1 /* getty.c - A getty program to get controlling terminal.
2 *
3 * Copyright 2012 Sandeep Sharma <sandeep.jack2756@gamil.com>
4 * Copyright 2013 Kyungwan Han <asura321@gmail.com>
5 *
6 * No Standard.
7
8 USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh", TOYFLAG_SBIN))
9
10 config GETTY
11 bool "getty"
12 default n
13 help
14 usage: getty [OPTIONS] BAUD_RATE[,BAUD_RATE]... TTY [TERMTYPE]
15
16 Wait for a modem to dial into serial port, adjust baud rate, call login.
17
18 -h Enable hardware RTS/CTS flow control
19 -L Set CLOCAL (ignore Carrier Detect state)
20 -m Get baud rate from modem's CONNECT status message
21 -n Don't prompt for login name
22 -w Wait for CR or LF before sending /etc/issue
23 -i Don't display /etc/issue
24 -f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue
25 -l LOGIN Invoke LOGIN instead of /bin/login
26 -t SEC Terminate after SEC if no login name is read
27 -I INITSTR Send INITSTR before anything else
28 -H HOST Log HOST into the utmp file as the hostname
29 */
30
31 #define FOR_getty
32 #include "toys.h"
33
34 GLOBALS(
35 char *f, *l, *I, *H;
36 long t;
37
38 char *tty_name, buff[128];
39 int speeds[20], sc;
40 struct termios termios;
41 )
42
43 #define CTL(x) ((x) ^ 0100)
44 #define HOSTNAME_SIZE 32
45
parse_speeds(char * sp)46 static void parse_speeds(char *sp)
47 {
48 char *ptr;
49
50 TT.sc = 0;
51 while ((ptr = strsep(&sp, ","))) {
52 TT.speeds[TT.sc] = atolx_range(ptr, 0, INT_MAX);
53 if (TT.speeds[TT.sc] < 0) perror_exit("bad speed %s", ptr);
54 if (++TT.sc > 10) perror_exit("too many speeds, max is 10");
55 }
56 }
57
58 // Get controlling terminal and redirect stdio
open_tty(void)59 static void open_tty(void)
60 {
61 if (strcmp(TT.tty_name, "-")) {
62 if (*(TT.tty_name) != '/') TT.tty_name = xmprintf("/dev/%s", TT.tty_name);
63 // Sends SIGHUP to all foreground process if Session leader don't die,Ignore
64 void* handler = signal(SIGHUP, SIG_IGN);
65 ioctl(0, TIOCNOTTY, 0); // Giveup if there is any controlling terminal
66 signal(SIGHUP, handler);
67 if ((setsid() < 0) && (getpid() != getsid(0))) perror_exit("setsid");
68 xclose(0);
69 xopen_stdio(TT.tty_name, O_RDWR|O_NDELAY|O_CLOEXEC);
70 fcntl(0, F_SETFL, fcntl(0, F_GETFL) & ~O_NONBLOCK); // Block read
71 dup2(0, 1);
72 dup2(0, 2);
73 if (ioctl(0, TIOCSCTTY, 1) < 0) perror_msg("ioctl(TIOCSCTTY)");
74 if (!isatty(0)) perror_exit("/dev/%s: not a tty", TT.tty_name);
75 chown(TT.tty_name, 0, 0); // change ownership, Hope login will change this
76 chmod(TT.tty_name, 0620);
77 } else { // We already have opened TTY
78 if (setsid() < 0) perror_msg("setsid failed");
79 if ((fcntl(0, F_GETFL) & (O_RDWR|O_RDONLY|O_WRONLY)) != O_RDWR)
80 perror_exit("no read/write permission");
81 }
82 }
83
termios_init(void)84 static void termios_init(void)
85 {
86 if (tcgetattr(0, &TT.termios) < 0) perror_exit("tcgetattr");
87 // Flush input and output queues, important for modems!
88 tcflush(0, TCIOFLUSH);
89 TT.termios.c_cflag &= (0|CSTOPB|PARENB|PARODD);
90 #ifdef CRTSCTS
91 if (FLAG(h)) TT.termios.c_cflag |= CRTSCTS;
92 #endif
93 if (FLAG(L)) TT.termios.c_cflag |= CLOCAL;
94 TT.termios.c_cc[VTIME] = 0;
95 TT.termios.c_cc[VMIN] = 1;
96 TT.termios.c_oflag = OPOST|ONLCR;
97 TT.termios.c_cflag |= CS8|CREAD|HUPCL|CBAUDEX;
98 // login will disable echo for passwd.
99 TT.termios.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOKE;
100 TT.termios.c_cc[VINTR] = CTL('C');
101 TT.termios.c_cc[VQUIT] = CTL('\\');
102 TT.termios.c_cc[VEOF] = CTL('D');
103 TT.termios.c_cc[VEOL] = '\n';
104 TT.termios.c_cc[VKILL] = CTL('U');
105 TT.termios.c_cc[VERASE] = 127; // CERASE
106 TT.termios.c_iflag = ICRNL|IXON|IXOFF;
107 // Set non-zero baud rate. Zero baud rate left it unchanged.
108 if (TT.speeds[0] != 0) xsetspeed(&TT.termios, TT.speeds[0]);
109 if (tcsetattr(0, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr");
110 }
111
112 // Get the baud rate from modems CONNECT mesage, Its of form <junk><BAUD><Junk>
sense_baud(void)113 static void sense_baud(void)
114 {
115 int vmin, speed;
116 ssize_t size;
117 char *ptr;
118
119 vmin = TT.termios.c_cc[VMIN]; // Store old
120 TT.termios.c_cc[VMIN] = 0; // No block even queue is empty.
121 if (tcsetattr(0, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr");
122 size = readall(0, TT.buff, sizeof(TT.buff)-1);
123 if (size > 0) {
124 for (ptr = TT.buff; ptr < TT.buff+size; ptr++) {
125 if (isdigit(*ptr)) {
126 speed = atolx_range(ptr, 0, INT_MAX);
127 if (speed > 0) xsetspeed(&TT.termios, speed);
128 break;
129 }
130 }
131 }
132 TT.termios.c_cc[VMIN] = vmin; //restore old value
133 if (tcsetattr(0, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr");
134 }
135
136 // Print /etc/isuue with taking care of each escape sequence
write_issue(char * file,struct utsname * uts)137 void write_issue(char *file, struct utsname *uts)
138 {
139 char buff[20] = {0,};
140 int fd = open(TT.f, O_RDONLY), size;
141
142 if (fd < 0) return;
143 while ((size = readall(fd, buff, 1)) > 0) {
144 char *ch = buff;
145
146 if (*ch == '\\' || *ch == '%') {
147 if (readall(fd, buff, 1) <= 0) perror_exit("readall");
148 if (*ch == 's') fputs(uts->sysname, stdout);
149 if (*ch == 'n'|| *ch == 'h') fputs(uts->nodename, stdout);
150 if (*ch == 'r') fputs(uts->release, stdout);
151 if (*ch == 'm') fputs(uts->machine, stdout);
152 if (*ch == 'l') fputs(TT.tty_name, stdout);
153 } else xputc(*ch);
154 }
155 }
156
157 // Read login name and print prompt and Issue file.
read_login_name(void)158 static int read_login_name(void)
159 {
160 tcflush(0, TCIFLUSH); // Flush pending speed switches
161 while (1) {
162 struct utsname uts;
163 int i = 0;
164
165 uname(&uts);
166
167 if (!FLAG(i)) write_issue(TT.f, &uts);
168
169 dprintf(1, "%s login: ", uts.nodename);
170
171 TT.buff[0] = getchar();
172 if (!TT.buff[0] && TT.sc > 1) return 0; // Switch speed
173 if (TT.buff[0] == '\n') continue;
174 if (TT.buff[0] != '\n')
175 if (!fgets(&TT.buff[1], HOSTNAME_SIZE-1, stdin)) _exit(1);
176 while (i < HOSTNAME_SIZE-1 && isgraph(TT.buff[i])) i++;
177 TT.buff[i] = 0;
178 break;
179 }
180 return 1;
181 }
182
utmp_entry(void)183 static void utmp_entry(void)
184 {
185 struct utmpx entry = {.ut_pid = getpid()}, *ep;
186 int fd;
187
188 // We're responsible for ensuring that the utmp file exists.
189 if (access(_PATH_UTMP, F_OK) && (fd = open(_PATH_UTMP, O_CREAT, 0664)) != -1)
190 close(fd);
191
192 // Find any existing entry.
193 setutxent();
194 while ((ep = getutxent()))
195 if (ep->ut_pid == entry.ut_pid && ep->ut_type >= INIT_PROCESS) break;
196 if (ep) entry = *ep;
197 else entry.ut_type = LOGIN_PROCESS;
198
199 // Modify.
200 entry.ut_tv.tv_sec = time(0);
201 xstrncpy(entry.ut_user, "LOGIN", sizeof(entry.ut_user));
202 xstrncpy(entry.ut_line, ttyname(0) + strlen("/dev/"), sizeof(entry.ut_line));
203 if (FLAG(H)) xstrncpy(entry.ut_host, TT.H, sizeof(entry.ut_host));
204
205 // Write.
206 pututxline(&entry);
207 endutxent();
208 }
209
getty_main(void)210 void getty_main(void)
211 {
212 char ch, *cmd[3] = {TT.l ? : "/bin/login", 0, 0}; // space to add username
213
214 if (!FLAG(f)) TT.f = "/etc/issue";
215
216 // parse arguments and set $TERM
217 if (isdigit(**toys.optargs)) {
218 parse_speeds(*toys.optargs);
219 if (*++toys.optargs) TT.tty_name = xmprintf("%s", *toys.optargs);
220 } else {
221 TT.tty_name = xmprintf("%s", *toys.optargs);
222 if (*++toys.optargs) parse_speeds(*toys.optargs);
223 }
224 if (*++toys.optargs) setenv("TERM", *toys.optargs, 1);
225
226 open_tty();
227 termios_init();
228 tcsetpgrp(0, getpid());
229 utmp_entry();
230 if (FLAG(I)) xputsn(TT.I);
231 if (FLAG(m)) sense_baud();
232 if (FLAG(t)) alarm(TT.t);
233 if (FLAG(w)) while (readall(0, &ch, 1) != 1) if (ch=='\n' || ch=='\r') break;
234 if (!FLAG(n)) {
235 int index = 1; // 0th we already set.
236
237 for (;;) {
238 if (read_login_name()) break;
239 index %= TT.sc;
240 xsetspeed(&TT.termios, TT.speeds[index]);
241 //Necessary after cfsetspeed
242 if (tcsetattr(0, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr");
243 }
244 cmd[1] = TT.buff; //put the username in the login command line
245 }
246 xexec(cmd);
247 }
248