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
8USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh",TOYFLAG_SBIN))
9
10config 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
32GLOBALS(
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
49typedef void (*sighandler_t)(int);
50struct speed_mapper {
51  long speed;
52  speed_t code;
53};
54
55struct 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
84static 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
94static 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
107static 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
120static 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
147static 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>
177static 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
204void 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
219void 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.
242static 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
263static 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
298void 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