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