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     -h    Enable hardware RTS/CTS flow control
17     -L    Set CLOCAL (ignore Carrier Detect state)
18     -m    Get baud rate from modem's CONNECT status message
19     -n    Don't prompt for login name
20     -w    Wait for CR or LF before sending /etc/issue
21     -i    Don't display /etc/issue
22     -f ISSUE_FILE  Display ISSUE_FILE instead of /etc/issue
23     -l LOGIN  Invoke LOGIN instead of /bin/login
24     -t SEC    Terminate after SEC if no login name is read
25     -I INITSTR  Send INITSTR before anything else
26     -H HOST    Log HOST into the utmp file as the hostname
27 */
28 #define FOR_getty
29 #include "toys.h"
30 #include <utmp.h>
31 
32 GLOBALS(
33   char *issue_str;
34   char *login_str;
35   char *init_str;
36   char *host_str;
37   long timeout;
38 
39   char *tty_name;
40   int  speeds[20];
41   int  sc;
42   struct termios termios;
43   char buff[128];
44 )
45 
46 #define CTL(x)        ((x) ^ 0100)
47 #define HOSTNAME_SIZE 32
48 
49 typedef void (*sighandler_t)(int);
50 struct speed_mapper {
51   long speed;
52   speed_t code;
53 };
54 
55 struct speed_mapper speedtab[] = {
56   {50, B50}, {75, B75}, {110, B110}, {134, B134}, {150, B150}, {200, B200},
57   {300, B300}, {600, B600}, {1200, B1200}, {1800, B1800}, {2400, B2400},
58   {4800, B4800}, {9600, B9600},
59 #ifdef  B19200
60   {19200, B19200},
61 #endif
62 #ifdef  B38400
63   {38400, B38400},
64 #endif
65 #ifdef  EXTA
66   {19200, EXTA},
67 #endif
68 #ifdef  EXTB
69   {38400, B38400},
70 #endif
71 #ifdef B57600
72   {57600, B57600},
73 #endif
74 #ifdef B115200
75   {115200, B115200},
76 #endif
77 #ifdef B230400
78   {230400, B230400},
79 #endif
80   {0, 0},
81 };
82 
83 // Find speed from mapper array
encode(char * s)84 static speed_t encode(char *s)
85 {
86   struct speed_mapper *sp;
87   long speed = atolx(s);
88 
89   if (!speed) return 0;
90   for (sp = speedtab; sp->speed; sp++) if (sp->speed == speed) return sp->code;
91   return (speed_t) -1;
92 }
93 
get_speed(char * sp)94 static void get_speed(char *sp)
95 {
96   char *ptr;
97 
98   TT.sc = 0;
99   while ((ptr = strsep(&sp, ","))) {
100     TT.speeds[TT.sc] = encode(ptr);
101     if (TT.speeds[TT.sc] < 0) perror_exit("bad speed");
102     if (++TT.sc > 10) perror_exit("too many speeds, max is 10");
103   }
104 }
105 
106 // Parse args and set TERM env. variable
parse_arguments(void)107 static void parse_arguments(void)
108 {
109   if (isdigit(**toys.optargs)) {
110     get_speed(*toys.optargs);
111     if (*++toys.optargs) TT.tty_name = xmprintf("%s", *toys.optargs);
112   } else {
113     TT.tty_name = xmprintf("%s", *toys.optargs);
114     if (*++toys.optargs) get_speed(*toys.optargs);
115   }
116   if (*++toys.optargs) setenv("TERM", *toys.optargs, 1);
117 }
118 
119 // Get controlling terminal and redirect stdio
open_tty(void)120 static void open_tty(void)
121 {
122   if (strcmp(TT.tty_name, "-")) {
123     if (*(TT.tty_name) != '/') TT.tty_name = xmprintf("/dev/%s", TT.tty_name);
124     // Sends SIGHUP to all foreground process if Session leader don't die,Ignore
125     sighandler_t sig = signal(SIGHUP, SIG_IGN);
126     ioctl(0, TIOCNOTTY, 0); // Giveup if there is any controlling terminal
127     signal(SIGHUP, sig);
128     if ((setsid() < 0) && (getpid() != getsid(0)))
129       perror_exit("setsid");
130     xclose(0);
131     xopen(TT.tty_name, O_RDWR|O_NDELAY|O_CLOEXEC);
132     fcntl(0, F_SETFL, fcntl(0, F_GETFL) & ~O_NONBLOCK); // Block read
133     dup2(0, 1);
134     dup2(0, 2);
135     if (ioctl(0, TIOCSCTTY, 1) < 0) perror_msg("ioctl(TIOCSCTTY)");
136     if (!isatty(0)) perror_exit("/dev/%s: not a tty", TT.tty_name);
137     chown(TT.tty_name, 0, 0); // change ownership, Hope login will change this
138     chmod(TT.tty_name, 0620);
139   } else { // We already have opened TTY
140     if (setsid() < 0) perror_msg("setsid failed");
141     if ((fcntl(0, F_GETFL) & (O_RDWR|O_RDONLY|O_WRONLY)) != O_RDWR)
142       perror_exit("no read/write permission");
143   }
144 }
145 
146 // Intialise terminal settings
termios_init(void)147 static void termios_init(void)
148 {
149   if (tcgetattr(STDIN_FILENO, &TT.termios) < 0) perror_exit("tcgetattr");
150   // Flush input and output queues, important for modems!
151   tcflush(STDIN_FILENO, TCIOFLUSH);
152   TT.termios.c_cflag &= (0|CSTOPB|PARENB|PARODD);
153 #ifdef CRTSCTS
154   if (toys.optflags & FLAG_h) TT.termios.c_cflag |= CRTSCTS;
155 #endif
156   if (toys.optflags & FLAG_L) TT.termios.c_cflag |= CLOCAL;
157   TT.termios.c_cc[VTIME] = 0;
158   TT.termios.c_cc[VMIN] = 1;
159   TT.termios.c_oflag = OPOST|ONLCR;
160   TT.termios.c_cflag |= CS8|CREAD|HUPCL|CBAUDEX;
161   // login will disable echo for passwd.
162   TT.termios.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOKE;
163   TT.termios.c_cc[VINTR] = CTL('C');
164   TT.termios.c_cc[VQUIT] = CTL('\\');
165   TT.termios.c_cc[VEOF] = CTL('D');
166   TT.termios.c_cc[VEOL] = '\n';
167   TT.termios.c_cc[VKILL] = CTL('U');
168   TT.termios.c_cc[VERASE] = 127; // CERASE
169   TT.termios.c_iflag = ICRNL|IXON|IXOFF;
170   // set non-zero baud rate. Zero baud rate left it unchanged.
171   if (TT.speeds[0] != B0) cfsetspeed(&TT.termios, TT.speeds[0]);
172   if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0)
173     perror_exit("tcsetattr");
174 }
175 
176 // Get the baud rate from modems CONNECT mesage, Its of form <junk><BAUD><Junk>
sense_baud(void)177 static void sense_baud(void)
178 {
179   int vmin;
180   ssize_t size;
181   char *ptr;
182   speed_t speed;
183 
184   vmin = TT.termios.c_cc[VMIN]; // Store old
185   TT.termios.c_cc[VMIN] = 0; // No block even queue is empty.
186   if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0)
187     perror_exit("tcsetattr");
188   size = readall(STDIN_FILENO, TT.buff, sizeof(TT.buff)-1);
189   if (size > 0) {
190     for (ptr = TT.buff; ptr < TT.buff+size; ptr++) {
191       if (isdigit(*ptr)) {
192         speed = encode(ptr);
193         if (speed > 0) cfsetspeed(&TT.termios,speed);
194         break;
195       }
196     }
197   }
198   TT.termios.c_cc[VMIN] = vmin; //restore old value
199   if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0)
200     perror_exit("tcsetattr");
201 }
202 
203 // Just prompt for login name
print_prompt(void)204 void print_prompt(void)
205 {
206   char *hostname;
207   struct utsname uts;
208 
209   uname(&uts);
210   hostname = xstrdup(uts.nodename);
211   fputs(hostname, stdout);
212   fputs(" login: ", stdout);
213   fflush(NULL);
214   free(hostname);
215   hostname = NULL;
216 }
217 
218 // Print /etc/isuue with taking care of each escape sequence
write_issue(char * file)219 void write_issue(char *file)
220 {
221   char buff[20] = {0,};
222   struct utsname u;
223   uname(&u);
224   int size, fd = open(TT.issue_str, O_RDONLY);
225 
226   if (fd < 0) return;
227   while ((size = readall(fd, buff, 1)) > 0) {
228     char *ch = buff;
229 
230     if (*ch == '\\' || *ch == '%') {
231       if (readall(fd, buff, 1) <= 0) perror_exit("readall");
232       if (*ch == 's') fputs(u.sysname, stdout);
233       if (*ch == 'n'|| *ch == 'h') fputs(u.nodename, stdout);
234       if (*ch == 'r') fputs(u.release, stdout);
235       if (*ch == 'm') fputs(u.machine, stdout);
236       if (*ch == 'l') fputs(TT.tty_name, stdout);
237     } else xputc(*ch);
238   }
239 }
240 
241 // Read login name and print prompt and Issue file.
read_login_name(void)242 static int read_login_name(void)
243 {
244   tcflush(STDIN_FILENO, TCIFLUSH); // Flush pending speed switches
245   int i = 0;
246 
247   while (1) { // Option -i will overide -f
248     if (!(toys.optflags & FLAG_i)) write_issue(TT.issue_str);
249     print_prompt();
250     TT.buff[0] = getchar();
251     if (!TT.buff[0] && TT.sc > 1) return 0; // Switch speed
252     if (TT.buff[0] == '\n') continue;
253     if (TT.buff[0] != '\n')
254       if (!fgets(&TT.buff[1], HOSTNAME_SIZE-1, stdin)) _exit(1);
255     while (i < HOSTNAME_SIZE-1 && isgraph(TT.buff[i])) i++;
256     TT.buff[i] = 0;
257     break;
258   }
259   return 1;
260 }
261 
262 // Put hostname entry in utmp file
utmp_entry(void)263 static void utmp_entry(void)
264 {
265   struct utmp entry;
266   struct utmp *utp_ptr;
267   pid_t pid = getpid();
268   char *utmperr = "can't make utmp entry, host length greater than UT_HOSTSIZE(256)";
269 
270   utmpname(_PATH_UTMP);
271   setutent(); // Starts from start
272   while ((utp_ptr = getutent()))
273     if (utp_ptr->ut_pid == pid && utp_ptr->ut_type >= INIT_PROCESS) break;
274   if (!utp_ptr) {
275     entry.ut_type = LOGIN_PROCESS;
276     entry.ut_pid = getpid();
277     xstrncpy(entry.ut_line, ttyname(STDIN_FILENO) +
278         strlen("/dev/"), UT_LINESIZE);
279     time((time_t *)&entry.ut_time);
280     xstrncpy(entry.ut_user, "LOGIN", UT_NAMESIZE);
281     if (strlen(TT.host_str) > UT_HOSTSIZE)
282       perror_msg(utmperr);
283     else xstrncpy(entry.ut_host, TT.host_str, UT_HOSTSIZE);
284     setutent();
285     pututline(&entry);
286     return;
287   }
288   xstrncpy(entry.ut_line, ttyname(STDIN_FILENO) + strlen("/dev/"), UT_LINESIZE);
289   xstrncpy(entry.ut_user, "LOGIN", UT_NAMESIZE);
290   if (strlen(TT.host_str) > UT_HOSTSIZE)
291     perror_msg(utmperr);
292   else xstrncpy(entry.ut_host, TT.host_str, UT_HOSTSIZE);
293   time((time_t *)&entry.ut_time);
294   setutent();
295   pututline(&entry);
296 }
297 
getty_main(void)298 void getty_main(void)
299 {
300   pid_t pid = getpid();
301   char *ptr[3] = {"/bin/login", NULL, NULL}; //2 NULLs so we can add username
302 
303   if (!(toys.optflags & FLAG_f)) TT.issue_str = "/etc/issue";
304   if (toys.optflags & FLAG_l) ptr[0] = TT.login_str;
305   parse_arguments();
306   open_tty();
307   termios_init();
308   tcsetpgrp(STDIN_FILENO, pid);
309   if (toys.optflags & FLAG_H) utmp_entry();
310   if (toys.optflags & FLAG_I)
311     writeall(STDOUT_FILENO,TT.init_str,strlen(TT.init_str));
312   if (toys.optflags & FLAG_m) sense_baud();
313   if (toys.optflags & FLAG_t) alarm(TT.timeout);
314   if (toys.optflags & FLAG_w) {
315     char ch;
316 
317     while (readall(STDIN_FILENO, &ch, 1) != 1)
318       if (ch == '\n' || ch == '\r') break;
319   }
320   if (!(toys.optflags & FLAG_n)) {
321     int index = 1; // 0th we already set.
322 
323     while (1) {
324       int l = read_login_name();
325 
326       if (l) break;
327       index = index % TT.sc;
328       cfsetspeed(&TT.termios, TT.speeds[index]); // Select from multiple speeds
329       //Necessary after cfsetspeed
330       if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0)
331         perror_exit("tcsetattr");
332     }
333     ptr[1]=TT.buff; //put the username in the login command line
334   }
335   xexec(ptr);
336 }
337