/* getty.c - A getty program to get controlling terminal. * * Copyright 2012 Sandeep Sharma <sandeep.jack2756@gamil.com> * Copyright 2013 Kyungwan Han <asura321@gmail.com> * * No Standard. USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh",TOYFLAG_SBIN)) config GETTY bool "getty" default n help usage: getty [OPTIONS] BAUD_RATE[,BAUD_RATE]... TTY [TERMTYPE] -h Enable hardware RTS/CTS flow control -L Set CLOCAL (ignore Carrier Detect state) -m Get baud rate from modem's CONNECT status message -n Don't prompt for login name -w Wait for CR or LF before sending /etc/issue -i Don't display /etc/issue -f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue -l LOGIN Invoke LOGIN instead of /bin/login -t SEC Terminate after SEC if no login name is read -I INITSTR Send INITSTR before anything else -H HOST Log HOST into the utmp file as the hostname */ #define FOR_getty #include "toys.h" #include <utmp.h> GLOBALS( char *issue_str; char *login_str; char *init_str; char *host_str; long timeout; char *tty_name; int speeds[20]; int sc; struct termios termios; char buff[128]; ) #define CTL(x) ((x) ^ 0100) #define HOSTNAME_SIZE 32 typedef void (*sighandler_t)(int); struct speed_mapper { long speed; speed_t code; }; struct speed_mapper speedtab[] = { {50, B50}, {75, B75}, {110, B110}, {134, B134}, {150, B150}, {200, B200}, {300, B300}, {600, B600}, {1200, B1200}, {1800, B1800}, {2400, B2400}, {4800, B4800}, {9600, B9600}, #ifdef B19200 {19200, B19200}, #endif #ifdef B38400 {38400, B38400}, #endif #ifdef EXTA {19200, EXTA}, #endif #ifdef EXTB {38400, B38400}, #endif #ifdef B57600 {57600, B57600}, #endif #ifdef B115200 {115200, B115200}, #endif #ifdef B230400 {230400, B230400}, #endif {0, 0}, }; // Find speed from mapper array static speed_t encode(char *s) { struct speed_mapper *sp; long speed = atolx(s); if (!speed) return 0; for (sp = speedtab; sp->speed; sp++) if (sp->speed == speed) return sp->code; return (speed_t) -1; } static void get_speed(char *sp) { char *ptr; TT.sc = 0; while ((ptr = strsep(&sp, ","))) { TT.speeds[TT.sc] = encode(ptr); if (TT.speeds[TT.sc] < 0) perror_exit("bad speed"); if (++TT.sc > 10) perror_exit("too many speeds, max is 10"); } } // Parse args and set TERM env. variable static void parse_arguments(void) { if (isdigit(**toys.optargs)) { get_speed(*toys.optargs); if (*++toys.optargs) TT.tty_name = xmprintf("%s", *toys.optargs); } else { TT.tty_name = xmprintf("%s", *toys.optargs); if (*++toys.optargs) get_speed(*toys.optargs); } if (*++toys.optargs) setenv("TERM", *toys.optargs, 1); } // Get controlling terminal and redirect stdio static void open_tty(void) { if (strcmp(TT.tty_name, "-")) { if (*(TT.tty_name) != '/') TT.tty_name = xmprintf("/dev/%s", TT.tty_name); // Sends SIGHUP to all foreground process if Session leader don't die,Ignore sighandler_t sig = signal(SIGHUP, SIG_IGN); ioctl(0, TIOCNOTTY, 0); // Giveup if there is any controlling terminal signal(SIGHUP, sig); if ((setsid() < 0) && (getpid() != getsid(0))) perror_exit("setsid"); xclose(0); xopen(TT.tty_name, O_RDWR|O_NDELAY|O_CLOEXEC); fcntl(0, F_SETFL, fcntl(0, F_GETFL) & ~O_NONBLOCK); // Block read dup2(0, 1); dup2(0, 2); if (ioctl(0, TIOCSCTTY, 1) < 0) perror_msg("ioctl(TIOCSCTTY)"); if (!isatty(0)) perror_exit("/dev/%s: not a tty", TT.tty_name); chown(TT.tty_name, 0, 0); // change ownership, Hope login will change this chmod(TT.tty_name, 0620); } else { // We already have opened TTY if (setsid() < 0) perror_msg("setsid failed"); if ((fcntl(0, F_GETFL) & (O_RDWR|O_RDONLY|O_WRONLY)) != O_RDWR) perror_exit("no read/write permission"); } } // Intialise terminal settings static void termios_init(void) { if (tcgetattr(STDIN_FILENO, &TT.termios) < 0) perror_exit("tcgetattr"); // Flush input and output queues, important for modems! tcflush(STDIN_FILENO, TCIOFLUSH); TT.termios.c_cflag &= (0|CSTOPB|PARENB|PARODD); #ifdef CRTSCTS if (toys.optflags & FLAG_h) TT.termios.c_cflag |= CRTSCTS; #endif if (toys.optflags & FLAG_L) TT.termios.c_cflag |= CLOCAL; TT.termios.c_cc[VTIME] = 0; TT.termios.c_cc[VMIN] = 1; TT.termios.c_oflag = OPOST|ONLCR; TT.termios.c_cflag |= CS8|CREAD|HUPCL|CBAUDEX; // login will disable echo for passwd. TT.termios.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOKE; TT.termios.c_cc[VINTR] = CTL('C'); TT.termios.c_cc[VQUIT] = CTL('\\'); TT.termios.c_cc[VEOF] = CTL('D'); TT.termios.c_cc[VEOL] = '\n'; TT.termios.c_cc[VKILL] = CTL('U'); TT.termios.c_cc[VERASE] = 127; // CERASE TT.termios.c_iflag = ICRNL|IXON|IXOFF; // set non-zero baud rate. Zero baud rate left it unchanged. if (TT.speeds[0] != B0) cfsetspeed(&TT.termios, TT.speeds[0]); if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr"); } // Get the baud rate from modems CONNECT mesage, Its of form <junk><BAUD><Junk> static void sense_baud(void) { int vmin; ssize_t size; char *ptr; speed_t speed; vmin = TT.termios.c_cc[VMIN]; // Store old TT.termios.c_cc[VMIN] = 0; // No block even queue is empty. if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr"); size = readall(STDIN_FILENO, TT.buff, sizeof(TT.buff)-1); if (size > 0) { for (ptr = TT.buff; ptr < TT.buff+size; ptr++) { if (isdigit(*ptr)) { speed = encode(ptr); if (speed > 0) cfsetspeed(&TT.termios,speed); break; } } } TT.termios.c_cc[VMIN] = vmin; //restore old value if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr"); } // Just prompt for login name void print_prompt(void) { char *hostname; struct utsname uts; uname(&uts); hostname = xstrdup(uts.nodename); fputs(hostname, stdout); fputs(" login: ", stdout); fflush(NULL); free(hostname); hostname = NULL; } // Print /etc/isuue with taking care of each escape sequence void write_issue(char *file) { char buff[20] = {0,}; struct utsname u; uname(&u); int size, fd = open(TT.issue_str, O_RDONLY); if (fd < 0) return; while ((size = readall(fd, buff, 1)) > 0) { char *ch = buff; if (*ch == '\\' || *ch == '%') { if (readall(fd, buff, 1) <= 0) perror_exit("readall"); if (*ch == 's') fputs(u.sysname, stdout); if (*ch == 'n'|| *ch == 'h') fputs(u.nodename, stdout); if (*ch == 'r') fputs(u.release, stdout); if (*ch == 'm') fputs(u.machine, stdout); if (*ch == 'l') fputs(TT.tty_name, stdout); } else xputc(*ch); } } // Read login name and print prompt and Issue file. static int read_login_name(void) { tcflush(STDIN_FILENO, TCIFLUSH); // Flush pending speed switches int i = 0; while (1) { // Option -i will overide -f if (!(toys.optflags & FLAG_i)) write_issue(TT.issue_str); print_prompt(); TT.buff[0] = getchar(); if (!TT.buff[0] && TT.sc > 1) return 0; // Switch speed if (TT.buff[0] == '\n') continue; if (TT.buff[0] != '\n') if (!fgets(&TT.buff[1], HOSTNAME_SIZE-1, stdin)) _exit(1); while (i < HOSTNAME_SIZE-1 && isgraph(TT.buff[i])) i++; TT.buff[i] = 0; break; } return 1; } // Put hostname entry in utmp file static void utmp_entry(void) { struct utmp entry; struct utmp *utp_ptr; pid_t pid = getpid(); char *utmperr = "can't make utmp entry, host length greater than UT_HOSTSIZE(256)"; utmpname(_PATH_UTMP); setutent(); // Starts from start while ((utp_ptr = getutent())) if (utp_ptr->ut_pid == pid && utp_ptr->ut_type >= INIT_PROCESS) break; if (!utp_ptr) { entry.ut_type = LOGIN_PROCESS; entry.ut_pid = getpid(); xstrncpy(entry.ut_line, ttyname(STDIN_FILENO) + strlen("/dev/"), UT_LINESIZE); time((time_t *)&entry.ut_time); xstrncpy(entry.ut_user, "LOGIN", UT_NAMESIZE); if (strlen(TT.host_str) > UT_HOSTSIZE) perror_msg(utmperr); else xstrncpy(entry.ut_host, TT.host_str, UT_HOSTSIZE); setutent(); pututline(&entry); return; } xstrncpy(entry.ut_line, ttyname(STDIN_FILENO) + strlen("/dev/"), UT_LINESIZE); xstrncpy(entry.ut_user, "LOGIN", UT_NAMESIZE); if (strlen(TT.host_str) > UT_HOSTSIZE) perror_msg(utmperr); else xstrncpy(entry.ut_host, TT.host_str, UT_HOSTSIZE); time((time_t *)&entry.ut_time); setutent(); pututline(&entry); } void getty_main(void) { pid_t pid = getpid(); char *ptr[3] = {"/bin/login", NULL, NULL}; //2 NULLs so we can add username if (!(toys.optflags & FLAG_f)) TT.issue_str = "/etc/issue"; if (toys.optflags & FLAG_l) ptr[0] = TT.login_str; parse_arguments(); open_tty(); termios_init(); tcsetpgrp(STDIN_FILENO, pid); if (toys.optflags & FLAG_H) utmp_entry(); if (toys.optflags & FLAG_I) writeall(STDOUT_FILENO,TT.init_str,strlen(TT.init_str)); if (toys.optflags & FLAG_m) sense_baud(); if (toys.optflags & FLAG_t) alarm(TT.timeout); if (toys.optflags & FLAG_w) { char ch; while (readall(STDIN_FILENO, &ch, 1) != 1) if (ch == '\n' || ch == '\r') break; } if (!(toys.optflags & FLAG_n)) { int index = 1; // 0th we already set. while (1) { int l = read_login_name(); if (l) break; index = index % TT.sc; cfsetspeed(&TT.termios, TT.speeds[index]); // Select from multiple speeds //Necessary after cfsetspeed if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0) perror_exit("tcsetattr"); } ptr[1]=TT.buff; //put the username in the login command line } xexec(ptr); }