/* ttysrv - serve a tty to stdin/stdout, a named pipe, or a network socket * vix 28may91 [written] */ #ifndef LINT static char RCSid[] = "$Id: ttysrv.c,v 1.17 2001/03/24 21:14:32 vixie Exp $"; #endif /* Copyright (c) 1996 by Internet Software Consortium. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(DEBUG) && !defined(dprintf) #define dprintf if (Debug) lprintf #else #define dprintf (void) #endif #include "rtty.h" #include "misc.h" #include "ttyprot.h" #ifdef WANT_TCPIP # include "locbrok.h" #endif struct whoson { char *who, *host, *auth; time_t lastInput; enum {local, remote} type; enum {wlogin, wpasswd, auth} state; int aux; }; #define MAX_AUTH_ATTEMPTS 3 #define USAGE_STR "{-o option} [-s LServ|-r RServ] [-l Log]\n\ -t Tty [-b Baud] [-p Parity] [-w Wordsize] [-i Pidfile]" #ifdef DEBUG int Debug = 0; #endif extern int rconnect(char *host, char *service, FILE *verbose, FILE *errors, int timeout); extern char Version[]; static char *ProgName = "amnesia", *LServSpec = NULL, *RServSpec = NULL, *TtySpec = NULL, *LogSpec = NULL, *Parity = "none", ParityBuf[TP_MAXVAR], *PidFile = NULL; static int LServ = -1, RServ = -1, Tty = -1, Ttyios_set = 0, LogDirty = FALSE, Baud = 9600, Wordsize = 8, highest_fd = -1, #ifdef WANT_TCPIP LocBrok = -1, #endif Sigpiped = 0; #ifdef WANT_TCPIP static u_int Port; #endif static struct termios Ttyios, Ttyios_orig; static FILE *LogF = NULL; static fd_set Clients; static ttyprot T; static time_t Now; static char Hostname[MAXHOSTNAMELEN]; static struct timeval TOinput = {0, 3000}; /* 3ms: >1byte @9600baud */ static struct timeval TOflush = {1, 0}; /* 1 second */ static struct whoson **WhosOn; static char *handle_option(char *); static void main_loop(void), tty_input(int, int), broadcast(u_char *, int, u_int), sigpipe(int), sighup(int), open_log(void), quit(int), serv_input(int), client_input(int), close_client(int), set_parity(u_int), set_wordsize(u_int), auth_needed(int), auth_ok(int), lprintf(FILE *, const char *, ...); static int set_baud(int), find_parity(char *), find_wordsize(int); int main(int argc, char *argv[]) { int i; char ch, *msg; gethostname(Hostname, sizeof Hostname); ProgName = argv[0]; while ((ch = getopt(argc, argv, "o:s:r:t:l:b:p:w:x:i:")) != EOF) { switch (ch) { case 'o': msg = handle_option(optarg); if (msg) { USAGE((stderr, "%s: bad option (%s): %s\n", ProgName, optarg, msg)); } case 's': LServSpec = optarg; break; case 'r': #ifdef WANT_TCPIP RServSpec = optarg; #else USAGE((stderr, "%s: -r not supported on this system\n", ProgName)); #endif break; case 't': TtySpec = optarg; break; case 'l': LogSpec = optarg; break; case 'b': Baud = atoi(optarg); break; case 'p': Parity = optarg; break; case 'w': Wordsize = atoi(optarg); break; #ifdef DEBUG case 'x': Debug = atoi(optarg); break; #endif case 'i': PidFile = optarg; break; default: USAGE((stderr, "%s: getopt=%c ?\n", ProgName, ch)); } } if (!TtySpec) { USAGE((stderr, "%s: must specify -t ttyspec ?\n", ProgName)); } if (0 > (Tty = open(TtySpec, O_NONBLOCK|O_RDWR))) { fprintf(stderr, "%s: can't open tty ", ProgName); perror(TtySpec); exit(2); } dprintf(stderr, "main: tty open on fd%d\n", Tty); tcgetattr(Tty, &Ttyios); Ttyios_orig = Ttyios; prepare_term(&Ttyios, 0); set_baud(Baud); if ((i = find_parity(Parity)) == -1) { USAGE((stderr, "%s: parity %s ?\n", ProgName, Parity)); } set_parity(i); if ((i = find_wordsize(Wordsize)) == -1) { USAGE((stderr, "%s: wordsize %d ?\n", ProgName, Wordsize)); } set_wordsize(i); signal(SIGINT, quit); signal(SIGQUIT, quit); install_ttyios(Tty, &Ttyios); Ttyios_set++; if (!LServSpec && !RServSpec) { USAGE((stderr, "%s: must specify either -s or -r\n", ProgName)); } if (LServSpec) { struct sockaddr_un n; struct stat statbuf; if (LServSpec[0] != '/') { USAGE((stderr, "%s: -s must specify local pathname\n", ProgName)); } LServ = socket(PF_UNIX, SOCK_STREAM, 0); ASSERT(LServ>=0, "socket"); n.sun_family = AF_UNIX; (void) strcpy(n.sun_path, LServSpec); if (stat(LServSpec, &statbuf) >= 0) { fprintf(stderr, "warning: removing \"%s\"\n", LServSpec); if ((statbuf.st_mode & S_IFMT) == S_IFSOCK) { if (unlink(LServSpec) < 0) perror("unlink"); } } ASSERT(0<=bind(LServ, (struct sockaddr *)&n, sizeof n), n.sun_path); } #ifdef WANT_TCPIP if (RServSpec) { struct sockaddr_in n; int nlen = sizeof n; RServ = socket(PF_INET, SOCK_STREAM, 0); ASSERT(RServ>=0, "socket"); n.sin_family = AF_INET; #ifndef NO_SOCKADDR_LEN n.sin_len = sizeof(struct sockaddr_in); #endif n.sin_port = 0; /* "any" */ n.sin_addr.s_addr = INADDR_ANY; ASSERT(0<=bind(RServ, (struct sockaddr *)&n, sizeof n), "bind"); ASSERT(0<=getsockname(RServ, (struct sockaddr *)&n, &nlen), "getsockname"); Port = ntohs(n.sin_port); fprintf(stderr, "serving internet port %d\n", Port); /* register with the location broker, or die */ { int len = min(LB_MAXNAMELEN, strlen(RServSpec)); locbrok lb; LocBrok = rconnect("127.1", "locbrok", NULL,stderr,0); ASSERT(LocBrok>0, "rconnect locbrok"); lb.lb_port = htons(Port); lb.lb_nlen = htons(len); strncpy(lb.lb_name, RServSpec, len); ASSERT(0 2) lprintf(stderr, "main_loop: select(%d,%08x) LD=%d\n", highest_fd+1, readfds.fds_bits[0], LogDirty); #endif nfound = select(highest_fd+1, &readfds, NULL, NULL, (LogDirty ?&TOflush :NULL)); if (nfound < 0 && errno == EINTR) continue; if (nfound == 0 && LogDirty && LogF) { fflush(LogF); LogDirty = FALSE; } Now = time(0); #ifdef DEBUG if (Debug > 2) lprintf(stderr, "main_loop: select->%d\n", nfound); #endif for (fd = 0; fd <= highest_fd; fd++) { if (!FD_ISSET(fd, &readfds)) { continue; } dprintf(stderr, "main_loop: fd%d readable\n", fd); if (fd == Tty) { tty_input(fd, FALSE); tty_input(fd, TRUE); } else if ((fd == LServ) || (fd == RServ)) { serv_input(fd); } else { client_input(fd); } } } /*NOTREACHED*/ } static void tty_input(int fd, int aggregate) { u_char buf[TP_MAXVAR]; int nchars, x, nloops; nchars = 0; nloops = 0; do { nloops++; x = read(fd, buf+nchars, TP_MAXVAR-nchars); } while ((x > 0) && ((nchars += x) < TP_MAXVAR) && (aggregate && !select(0, NULL, NULL, NULL, &TOinput)) ) ; #ifdef DEBUG if (Debug) { lprintf(stderr, "tty_input: %d bytes read on fd%d (nl %d)", nchars, fd, nloops); if (Debug > 1) { fputs(": \"", stderr); cat_v(stderr, buf, nchars); fputs("\"", stderr); } fputc('\n', stderr); } #endif if (nchars == 0) return; if (LogF) { if (nchars != fwrite(buf, sizeof(char), nchars, LogF)) { perror("fwrite(LogF)"); } else { LogDirty = TRUE; } } broadcast(buf, nchars, TP_DATA); } static void broadcast(u_char *buf, int nchars, u_int typ) { int fd, x; for (fd = 0; fd <= highest_fd; fd++) { if (!FD_ISSET(fd, &Clients)) { continue; } Sigpiped = 0; x = tp_senddata(fd, buf, nchars, typ); dprintf(stderr, "tty_input: %d bytes sent to client on fd%d\n", x, fd); if (Sigpiped) { dprintf(stderr, "tty_input: sigpipe on fd%d\n", fd); close_client(fd); } } } static void serv_input(int fd) { struct sockaddr_un un; struct sockaddr_in in; struct sockaddr *sa; int fromlen; if (fd == LServ) { sa = (struct sockaddr *) &un; fromlen = sizeof(un); } else if (fd == RServ) { sa = (struct sockaddr *) ∈ fromlen = sizeof(in); } else { fprintf(stderr, "%s: panic - serv_input(%d)\n", ProgName, fd); abort(); } dprintf(stderr, "serv_input: accepting on fd%d\n", fd); if ((fd = accept(fd, sa, &fromlen)) == -1) { perror("accept"); return; } dprintf(stderr, "serv_input: accepted fd%d\n", fd); FD_SET(fd, &Clients); if (fd > highest_fd) { highest_fd = fd; } if (!WhosOn[fd]) { WhosOn[fd] = (struct whoson *) malloc(sizeof(struct whoson)); } WhosOn[fd]->who = NULL; WhosOn[fd]->lastInput = Now; WhosOn[fd]->auth = NULL; if (sa == (struct sockaddr *) &un) { WhosOn[fd]->host = safe_strdup(Hostname); WhosOn[fd]->type = local; auth_ok(fd); } else if (sa == (struct sockaddr *) &in) { struct hostent *hp, *gethostbyaddr(); hp = gethostbyaddr((char *)&in.sin_addr, sizeof(in.sin_addr), in.sin_family); WhosOn[fd]->host = safe_strdup(hp ? hp->h_name : inet_ntoa(in.sin_addr)); WhosOn[fd]->type = remote; auth_needed(fd); } else { fprintf(stderr, "%s: panic - serv_input #2\n", ProgName); abort(); } } static void client_input(int fd) { int nchars, i, new, query; struct passwd *pw; u_short salt; char s[3]; u_int f; if (!WhosOn[fd]) return; WhosOn[fd]->lastInput = Now; /* read the fixed part of the ttyprot (everything but the array) */ if (TP_FIXED != (nchars = read(fd, &T, TP_FIXED))) { dprintf(stderr, "client_input: read=%d on fd%d: ", nchars, fd); #ifdef DEBUG if (Debug) perror("read"); #endif close_client(fd); return; } #ifdef DEBUG_not cat_v(stderr, &T, nchars); #endif i = ntohs(T.i); query = ntohs(T.f) & TP_QUERY; f = ntohs(T.f) & TP_TYPEMASK; #ifdef DEBUG if (Debug) { lprintf(stderr, "client_input: nchars=%d fd%d i=0x%x o='%c' f=0x%x\n", nchars, fd, i, query?'Q':' ', f); } #endif switch (f) { case TP_DATA: if (!(nchars = tp_getdata(fd, &T))) { close_client(fd); break; } dprintf(stderr, "client_input: TP_DATA, nchars=%d\n", nchars); if (WhosOn[fd]->state != auth) break; # if WANT_CLIENT_LOGGING if (LogF) { if (nchars != fwrite(T.c, sizeof(char), nchars, LogF)){ perror("fwrite(LogF)"); } else { LogDirty = TRUE; } } # endif /*WANT_CLIENT_LOGGING*/ nchars = write(Tty, T.c, nchars); #ifdef DEBUG if (Debug > 2) { lprintf(stderr, "client_input: wrote to tty: \""); cat_v(stderr, (u_char *)&T.c, nchars); fputs("\"\n", stderr); } #endif break; case TP_BREAK: if (WhosOn[fd]->state != auth) break; dprintf(stderr, "client_input: sending break\n"); tcsendbreak(Tty, 0); tp_senddata(fd, (u_char *)"BREAK", 5, TP_NOTICE); if (LogF) fputs("[BREAK]", LogF); dprintf(stderr, "client_input: done sending break\n"); break; case TP_BAUD: if (WhosOn[fd]->state != auth) break; if (query) { tp_sendctl(fd, TP_BAUD|TP_QUERY, Baud, NULL); break; } if ((set_baud(i) >= 0) && (install_ttyios(Tty, &Ttyios) >= 0)) { Baud = i; if (LogF) fprintf(LogF, "[baud now %d]", i); tp_sendctl(fd, TP_BAUD, 1, NULL); } else { tp_sendctl(fd, TP_BAUD, 0, NULL); } break; case TP_PARITY: if (!query) { if (!(nchars = tp_getdata(fd, &T))) { close_client(fd); break; } T.c[i] = '\0'; /* XXX */ } if (WhosOn[fd]->state != auth) break; if (query) { tp_sendctl(fd, TP_PARITY|TP_QUERY, strlen(Parity), (u_char *)Parity); break; } if (-1 == (new = find_parity((char *)T.c))) { tp_sendctl(fd, TP_PARITY, 0, NULL); } else { strcpy(ParityBuf, (char *)T.c); Parity = ParityBuf; set_parity(new); install_ttyios(Tty, &Ttyios); if (LogF) { fprintf(LogF, "[parity now %s]", (char*) T.c); } tp_sendctl(fd, TP_PARITY, 1, NULL); } break; case TP_WORDSIZE: if (WhosOn[fd]->state != auth) break; if (query) { tp_sendctl(fd, TP_WORDSIZE|TP_QUERY, Wordsize, NULL); break; } if (-1 == (new = find_wordsize(i))) { tp_sendctl(fd, TP_WORDSIZE, 0, NULL); } else { Wordsize = i; set_wordsize(new); install_ttyios(Tty, &Ttyios); if (LogF) { fprintf(LogF, "[wordsize now %d]", i); } tp_sendctl(fd, TP_WORDSIZE, 1, NULL); } break; case TP_WHOSON: if (!query) { if (!(nchars = tp_getdata(fd, &T))) { close_client(fd); break; } T.c[i] = '\0'; /* XXX */ } if (WhosOn[fd]->state != auth) break; if (query) { int iwho; for (iwho = getdtablesize()-1; iwho >= 0; iwho--) { struct whoson *who = WhosOn[iwho]; char data[TP_MAXVAR]; int idle; if (!who) continue; idle = Now - who->lastInput; sprintf(data, "%s [%s] (idle %d sec%s)", who->who ?who->who :"undeclared", who->host ?who->host :"?", idle, (idle==1) ?"" :"s"); tp_senddata(fd, (u_char *)data, strlen(data), TP_NOTICE); } break; } if (WhosOn[fd]) { if (WhosOn[fd]->who) free(WhosOn[fd]->who); WhosOn[fd]->who = safe_strdup((char *)T.c); } { /*local*/ char buf[TP_MAXVAR]; sprintf(buf, "%-*.*s connected\07", i, i, T.c); broadcast((u_char *)buf, strlen(buf), TP_NOTICE); } break; case TP_TAIL: if (WhosOn[fd]->state != auth) break; if (!query) break; if (!LogF) break; fflush(LogF); LogDirty = FALSE; if (ftell(LogF) < 1024L) { if (0 > fseek(LogF, 0, SEEK_SET)) break; } else { if (0 > fseek(LogF, -1024, SEEK_END)) break; } { /*local*/ char buf[TP_MAXVAR]; int len, something = FALSE; while (0 < (len = fread(buf, sizeof(char), sizeof buf, LogF))) { if (!something) { tp_senddata(fd, (u_char*)"tail+", 5, TP_NOTICE); something = TRUE; } tp_senddata(fd, (u_char*)buf, len, TP_DATA); } if (something) { tp_senddata(fd, (u_char*)"tail-", 5, TP_NOTICE); } } break; case TP_VERSION: if (!query) break; tp_senddata(fd, (u_char*)Version, strlen(Version), TP_NOTICE); break; case TP_LOGIN: if (!(nchars = tp_getdata(fd, &T))) { close_client(fd); break; } T.c[i] = '\0'; /* XXX */ if (query) break; if (WhosOn[fd]->state != wlogin) break; pw = getpwnam((char*)T.c); if (!pw) { char data[TP_MAXVAR]; sprintf(data, "%s - no such user", T.c); tp_senddata(fd, (u_char*)data, strlen(data), TP_NOTICE); } else if (!pw->pw_passwd[0]) { auth_ok(fd); } else { WhosOn[fd]->state = wpasswd; WhosOn[fd]->auth = safe_strdup(pw->pw_passwd); salt = WhosOn[fd]->auth[0]<<8 | WhosOn[fd]->auth[1]; tp_sendctl(fd, TP_PASSWD|TP_QUERY, salt, NULL); } break; case TP_PASSWD: if (!(nchars = tp_getdata(fd, &T))) { close_client(fd); break; } T.c[i] = '\0'; /* XXX */ if (query) break; if (WhosOn[fd]->state != wpasswd) break; strncpy(s, WhosOn[fd]->auth, 2); if (!strcmp((char*)T.c, WhosOn[fd]->auth)) { auth_ok(fd); } else { char data[TP_MAXVAR]; sprintf(data, "login incorrect"); if (++WhosOn[fd]->aux > MAX_AUTH_ATTEMPTS) { close_client(fd); } else { tp_senddata(fd, (u_char*)data, strlen(data), TP_NOTICE); auth_needed(fd); } } break; default: dprintf(stderr, "client_input: bad T: f=0x%x\n", ntohs(T.f)); break; } } static void close_client(int fd) { dprintf(stderr, "close_client: fd%d\n", fd); close(fd); FD_CLR(fd, &Clients); if (WhosOn[fd]) { if (WhosOn[fd]->who) { char buf[TP_MAXVAR]; sprintf(buf, "%s disconnected\07", WhosOn[fd]->who); broadcast((u_char*)buf, strlen(buf), TP_NOTICE); free(WhosOn[fd]->who); } free(WhosOn[fd]->host); if (WhosOn[fd]->auth) free(WhosOn[fd]->auth); free((char *) WhosOn[fd]); WhosOn[fd] = (struct whoson *) NULL; } } struct partab { char *parity; int sysparity; } partab[] = { { "even", PARENB }, { "odd", PARENB|PARODD }, { "none", 0 }, { NULL, -1 } }; struct cstab { int wordsize, syswordsize; } cstab[] = { { 5, CS5 }, { 6, CS6 }, { 7, CS7 }, { 8, CS8 }, { 0, -1 } }; static int set_baud(int baud) { if (cfsetispeed(&Ttyios, baud) < 0) return -1; if (cfsetospeed(&Ttyios, baud) < 0) return -1; return (0); } static int find_parity(char *parity) { struct partab *parp; int sysparity = -1; for (parp = partab; parp->parity; parp++) { if (!strcmp(parp->parity, parity)) { sysparity = parp->sysparity; } } return (sysparity); } static void set_parity(u_int parity) { Ttyios.c_cflag &= ~(PARENB|PARODD); Ttyios.c_cflag |= parity; } static int find_wordsize(int wordsize) { struct cstab *csp; int syswordsize = -1; for (csp = cstab; csp->wordsize; csp++) { if (csp->wordsize == wordsize) syswordsize = csp->syswordsize; } return (syswordsize); } static void set_wordsize(u_int wordsize) { Ttyios.c_cflag &= ~CSIZE; Ttyios.c_cflag |= wordsize; } static char * handle_option(char *option) { if (!strcmp("nolocal", option)) { Ttyios.c_cflag &= ~CLOCAL; } else { return "unrecognized"; } return (NULL); } static void auth_needed(int fd) { char data[TP_MAXVAR]; sprintf(data, "authorization needed"); tp_senddata(fd, (u_char*)data, strlen(data), TP_NOTICE); WhosOn[fd]->state = wlogin; tp_sendctl(fd, TP_LOGIN|TP_QUERY, 0, NULL); } static void auth_ok(int fd) { char data[TP_MAXVAR]; sprintf(data, "authorized"); tp_senddata(fd, (u_char*)data, strlen(data), TP_NOTICE); WhosOn[fd]->state = auth; } static void sigpipe(int x) { Sigpiped++; } static void sighup(int x) { if (LogF) { fclose(LogF); LogF = NULL; LogDirty = FALSE; open_log(); } } static void open_log(void) { if (!(LogF = fopen(LogSpec, "a+"))) { perror(LogSpec); fprintf(stderr, "%s: can't open log file\n", ProgName); } } static void quit(int x) { dprintf(stderr, "\r\nttysrv exiting\r\n"); if (Ttyios_set && (Tty != -1)) (void) install_ttyios(Tty, &Ttyios_orig); exit(0); } #ifdef DEBUG static void lprintf(FILE *fp, const char *fmt, ...) { va_list ap; struct timeval tvnow; struct tm *tmnow; time_t now; va_start(ap, fmt); (void) gettimeofday(&tvnow, NULL); now = (time_t) tvnow.tv_sec; tmnow = localtime(&now); fprintf(fp, "%02d:%02d:%02d.%03d ", tmnow->tm_hour, tmnow->tm_min, tmnow->tm_sec, (int) (tvnow.tv_usec / 1000)); vfprintf(fp, fmt, ap); va_end(ap); } #endif