/* * console.c * * Written by Archie Cobbs * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved. * See ``COPYRIGHT.whistle'' */ #include "ppp.h" #include "console.h" #include "util.h" #include /* * DEFINITIONS */ #ifndef CTRL #define CTRL(c) (c - '@') #endif #define EchoOff() cs->write(cs, "\xFF\xFB\x01\xFF\xFD\x01"); #define EchoOn() cs->write(cs, "\xFF\xFC\x01\xFF\xFE\x01"); enum { STATE_USERNAME = 0, STATE_PASSWORD, STATE_AUTHENTIC }; /* Set menu options */ enum { SET_OPEN, SET_CLOSE, SET_SELF, SET_DISABLE, SET_ENABLE }; /* * INTERNAL FUNCTIONS */ static void ConsoleConnect(int type, void *cookie); static void ConsoleSessionClose(ConsoleSession cs); static void ConsoleSessionReadEvent(int type, void *cookie); static void ConsoleSessionWrite(ConsoleSession cs, const char *fmt, ...); static void ConsoleSessionWriteV(ConsoleSession cs, const char *fmt, va_list vl); static void ConsoleSessionShowPrompt(ConsoleSession cs); static void StdConsoleSessionClose(ConsoleSession cs); static void StdConsoleSessionWrite(ConsoleSession cs, const char *fmt, ...); static void StdConsoleSessionWriteV(ConsoleSession cs, const char *fmt, va_list vl); static int ConsoleUserHashEqual(struct ghash *g, const void *item1, const void *item2); static u_int32_t ConsoleUserHash(struct ghash *g, const void *item); static int ConsoleSetCommand(Context ctx, int ac, const char *const av[], const void *arg); /* * GLOBAL VARIABLES */ const struct cmdtab ConsoleSetCmds[] = { { "open", "Open the console" , ConsoleSetCommand, NULL, 2, (void *) SET_OPEN }, { "close", "Close the console" , ConsoleSetCommand, NULL, 2, (void *) SET_CLOSE }, { "self {ip} [{port}]", "Set console ip and port" , ConsoleSetCommand, NULL, 2, (void *) SET_SELF }, { "enable [opt ...]", "Enable this console option" , ConsoleSetCommand, NULL, 0, (void *) SET_ENABLE }, { "disable [opt ...]", "Disable this console option" , ConsoleSetCommand, NULL, 0, (void *) SET_DISABLE }, { NULL, NULL, NULL, NULL, 0, NULL }, }; struct ghash *gUsers; /* allowed users */ pthread_rwlock_t gUsersLock; /* * INTERNAL VARIABLES */ static const struct confinfo gConfList[] = { { 0, CONSOLE_AUTH, "auth" }, { 0, 0, NULL }, }; static const struct confinfo sConfList[] = { { 0, CONSOLE_LOGGING, "logging" }, { 0, 0, NULL }, }; static struct termios gOrigTermiosAttrs; static int gOrigFlags; /* * ConsoleInit() */ int ConsoleInit(Console c) { int ret; /* setup console-defaults */ memset(&gConsole, 0, sizeof(gConsole)); ParseAddr(DEFAULT_CONSOLE_IP, &c->addr, ALLOW_IPV4|ALLOW_IPV6); c->port = DEFAULT_CONSOLE_PORT; Enable(&c->options, CONSOLE_AUTH); SLIST_INIT(&c->sessions); if ((ret = pthread_rwlock_init (&c->lock, NULL)) != 0) { Log(LG_ERR, ("Could not create rwlock %d", ret)); return (-1); } gUsers = ghash_create(c, 0, 0, MB_CONS, ConsoleUserHash, ConsoleUserHashEqual, NULL, NULL); if ((ret = pthread_rwlock_init (&gUsersLock, NULL)) != 0) { Log(LG_ERR, ("Could not create rwlock %d", ret)); return (-1); } return (0); } /* * ConsoleOpen() */ int ConsoleOpen(Console c) { char addrstr[INET6_ADDRSTRLEN]; if (c->fd) { Log(LG_ERR, ("CONSOLE: Console already running")); return -1; } u_addrtoa(&c->addr, addrstr, sizeof(addrstr)); if ((c->fd = TcpGetListenPort(&c->addr, c->port, FALSE)) < 0) { Log(LG_ERR, ("CONSOLE: Can't listen for connections on %s %d", addrstr, c->port)); return -1; } if (EventRegister(&c->event, EVENT_READ, c->fd, EVENT_RECURRING, ConsoleConnect, c) < 0) return -1; Log(LG_CONSOLE, ("CONSOLE: listening on %s %d", addrstr, c->port)); return 0; } /* * ConsoleClose() */ int ConsoleClose(Console c) { if (!c->fd) return 0; EventUnRegister(&c->event); close(c->fd); c->fd = 0; return 0; } void ConsoleCancelCleanup(void *rwlock) NO_THREAD_SAFETY_ANALYSIS { pthread_rwlock_t p = (pthread_rwlock_t)rwlock; RWLOCK_UNLOCK(p); } /* * ConsoleStat() */ int ConsoleStat(Context ctx, int ac, const char *const av[], const void *arg) NO_THREAD_SAFETY_ANALYSIS { Console c = &gConsole; ConsoleSession s; ConsoleSession cs = ctx->cs; char addrstr[INET6_ADDRSTRLEN]; (void)ac; (void)av; (void)arg; Printf("Configuration:\r\n"); Printf("\tState : %s\r\n", c->fd ? "OPENED" : "CLOSED"); Printf("\tIP-Address : %s\r\n", u_addrtoa(&c->addr,addrstr,sizeof(addrstr))); Printf("\tPort : %d\r\n", c->port); pthread_cleanup_push(ConsoleCancelCleanup, c->lock); RWLOCK_RDLOCK(c->lock); Printf("Active sessions:\r\n"); SLIST_FOREACH(s, &c->sessions, next) { Printf("\tUsername: %s\tFrom: %s\r\n", s->user.username, u_addrtoa(&s->peer_addr,addrstr,sizeof(addrstr))); } pthread_cleanup_pop(1); Printf("Global options:\r\n"); OptStat(ctx, &c->options, gConfList); if (cs) { Printf("This session options:\r\n"); OptStat(ctx, &cs->options, sConfList); } return 0; } /* * ConsoleConnect() */ static void ConsoleConnect(int type, void *cookie) { Console c = cookie; ConsoleSession cs; const char *options = "\xFF\xFB\x03" /* WILL Suppress Go-Ahead */ "\xFF\xFD\x03" /* DO Suppress Go-Ahead */ "\xFF\xFB\x01" /* WILL echo */ "\xFF\xFD\x01"; /* DO echo */ char addrstr[INET6_ADDRSTRLEN]; struct sockaddr_storage ss; (void)type; Log(LG_CONSOLE, ("CONSOLE: Connect")); cs = Malloc(MB_CONS, sizeof(*cs)); if ((cs->fd = TcpAcceptConnection(c->fd, &ss, FALSE)) < 0) goto cleanup; sockaddrtou_addr(&ss, &cs->peer_addr, &cs->peer_port); if (EventRegister(&cs->readEvent, EVENT_READ, cs->fd, EVENT_RECURRING, ConsoleSessionReadEvent, cs) < 0) goto fail; cs->console = c; cs->close = ConsoleSessionClose; cs->write = ConsoleSessionWrite; cs->writev = ConsoleSessionWriteV; cs->prompt = ConsoleSessionShowPrompt; if (!Enabled(&c->options, CONSOLE_AUTH)) { cs->state = STATE_AUTHENTIC; strcpy(cs->user.username, "root"); cs->context.priv = 2; } else { cs->state = STATE_USERNAME; } cs->context.cs = cs; RWLOCK_WRLOCK(c->lock); SLIST_INSERT_HEAD(&c->sessions, cs, next); RWLOCK_UNLOCK(c->lock); Log(LG_CONSOLE, ("CONSOLE: Allocated new console session %p from %s", cs, u_addrtoa(&cs->peer_addr,addrstr,sizeof(addrstr)))); cs->write(cs, "Multi-link PPP daemon for FreeBSD\r\n\r\n"); cs->write(cs, options); cs->prompt(cs); return; fail: close(cs->fd); cleanup: Freee(cs); return; } /* * StdConsoleConnect() */ Context StdConsoleConnect(Console c) { ConsoleSession cs; struct termios settings; cs = Malloc(MB_CONS, sizeof(*cs)); /* Init stdin */ cs->fd = 0; /* Save original STDxxx flags */ gOrigFlags = fcntl(0, F_GETFL, 0); tcgetattr(cs->fd, &settings); gOrigTermiosAttrs = settings; settings.c_lflag &= (~ICANON); settings.c_lflag &= (~ECHO); // don't echo the character settings.c_cc[VTIME] = 0; // timeout (tenths of a second) settings.c_cc[VMIN] = 0; // minimum number of characters tcsetattr(cs->fd, TCSANOW, &settings); if (fcntl(cs->fd, F_SETFL, O_NONBLOCK) < 0) { Perror("%s: fcntl", __FUNCTION__); Freee(cs); return(NULL); } /* Init stdout */ if (fcntl(1, F_SETFL, O_NONBLOCK) < 0) { Perror("%s: fcntl", __FUNCTION__); Freee(cs); return(NULL); } Enable(&cs->options, CONSOLE_LOGGING); cs->console = c; cs->close = StdConsoleSessionClose; cs->write = StdConsoleSessionWrite; cs->writev = StdConsoleSessionWriteV; cs->prompt = ConsoleSessionShowPrompt; cs->state = STATE_AUTHENTIC; cs->context.cs = cs; strcpy(cs->user.username, "root"); cs->context.priv = 2; RWLOCK_WRLOCK(c->lock); SLIST_INSERT_HEAD(&c->sessions, cs, next); RWLOCK_UNLOCK(c->lock); EventRegister(&cs->readEvent, EVENT_READ, cs->fd, EVENT_RECURRING, ConsoleSessionReadEvent, cs); return (&cs->context); } /* * ConsoleSessionClose() * This function is potentially thread unsafe. * To avois this locks should be used. * The only unlocked place is ->readEvent, * but it is actually the only place where ->close called. */ static void ConsoleSessionClose(ConsoleSession cs) { cs->write(cs, "Console closed.\r\n"); RWLOCK_WRLOCK(cs->console->lock); SLIST_REMOVE(&cs->console->sessions, cs, console_session, next); RWLOCK_UNLOCK(cs->console->lock); EventUnRegister(&cs->readEvent); close(cs->fd); Freee(cs); return; } /* * StdConsoleSessionClose() */ static void StdConsoleSessionClose(ConsoleSession cs) { cs->write(cs, "Console closed.\r\n"); EventUnRegister(&cs->readEvent); /* Restore original attrs */ tcsetattr(cs->fd, TCSANOW, &gOrigTermiosAttrs); /* Restore original STDxxx flags. */ if (gOrigFlags>=0) fcntl(cs->fd, F_SETFL, gOrigFlags); cs->close = NULL; return; } /* * ConsoleSessionReadEvent() */ static void ConsoleSessionReadEvent(int type, void *cookie) { ConsoleSession cs = cookie; CmdTab cmd, tab; int n, ac, ac2, i; u_char c; char compl[MAX_CONSOLE_LINE], line[MAX_CONSOLE_LINE]; char *av[MAX_CONSOLE_ARGS], *av2[MAX_CONSOLE_ARGS]; char *av_copy[MAX_CONSOLE_ARGS]; char addrstr[INET6_ADDRSTRLEN]; (void)type; while(1) { if ((n = read(cs->fd, &c, 1)) <= 0) { if (n < 0) { if (errno == EAGAIN) goto out; Perror("CONSOLE: Error while reading"); } else { if (cs->fd == 0 && isatty(cs->fd)) goto out; Log(LG_ERR, ("CONSOLE: Connection closed by peer")); } goto abort; } if (cs->context.lnk && cs->context.lnk->dead) { UNREF(cs->context.lnk); cs->context.lnk = NULL; } if (cs->context.bund && cs->context.bund->dead) { UNREF(cs->context.bund); cs->context.bund = NULL; } if (cs->context.rep && cs->context.rep->dead) { UNREF(cs->context.rep); cs->context.rep = NULL; } /* deal with escapes, map cursors */ if (cs->escaped) { if (cs->escaped == '[') { switch(c) { case 'A': /* up */ c = CTRL('P'); break; case 'B': /* down */ c = CTRL('N'); break; case 'C': /* right */ c = CTRL('F'); break; case 'D': /* left */ c = CTRL('B'); break; default: c = 0; } cs->escaped = FALSE; } else { cs->escaped = c == '[' ? c : 0; continue; } } switch(c) { case 0: break; case 27: /* escape */ cs->escaped = TRUE; break; case 255: /* telnet option follows */ cs->telnet = TRUE; break; case 253: /* telnet option-code DO */ break; case 9: /* TAB */ if (cs->state != STATE_AUTHENTIC) break; strcpy(line, cs->cmd); ac = ParseLine(line, av, sizeof(av) / sizeof(*av), 1); memset(compl, 0, sizeof(compl)); tab = gCommands; for (i = 0; i < ac; i++) { if (FindCommand(&cs->context, tab, av[i], &cmd)) { cs->write(cs, "\a"); goto notfound; } strcpy(line, cmd->name); ac2 = ParseLine(line, av2, sizeof(av2) / sizeof(*av2), 1); snprintf(&compl[strlen(compl)], sizeof(compl) - strlen(compl), "%s ", av2[0]); FreeArgs(ac2, av2); if (cmd->func != CMD_SUBMENU && (i != 0 || strcmp(compl, "help ") !=0)) { i++; if (i < ac) { cs->write(cs, "\a"); for (; i < ac; i++) { strcat(compl, av[i]); if ((i+1) < ac) strcat(compl, " "); } } break; } else if (cmd->func == CMD_SUBMENU) { tab = cmd->arg; } } for (i = 0; i < cs->cmd_len; i++) cs->write(cs, "\b \b"); strcpy(cs->cmd, compl); cs->cmd_len = strlen(cs->cmd); cs->write(cs, cs->cmd); notfound: FreeArgs(ac, av); break; case CTRL('C'): if (cs->telnet) break; cs->write(cs, "\r\n"); memset(cs->cmd, 0, MAX_CONSOLE_LINE); cs->cmd_len = 0; cs->prompt(cs); cs->telnet = FALSE; break; case CTRL('P'): /* page up */ if ((cs->currhist < MAX_CONSOLE_HIST) && (cs->history[cs->currhist][0])) { for (i = 0; i < cs->cmd_len; i++) cs->write(cs, "\b \b"); memcpy(cs->cmd, cs->history[cs->currhist], MAX_CONSOLE_LINE); cs->currhist++; cs->cmd_len = strlen(cs->cmd); cs->write(cs, cs->cmd); } break; case CTRL('N'): /* page down */ if (cs->currhist > 1) { for (i = 0; i < cs->cmd_len; i++) cs->write(cs, "\b \b"); cs->currhist--; memcpy(cs->cmd, cs->history[cs->currhist-1], MAX_CONSOLE_LINE); cs->cmd_len = strlen(cs->cmd); cs->write(cs, cs->cmd); } else { if (cs->cmd_len>0) { if (strncmp(cs->cmd, cs->history[0], MAX_CONSOLE_LINE) != 0) { for (i = MAX_CONSOLE_HIST - 1; i > 0; i--) memcpy(cs->history[i], cs->history[i - 1], MAX_CONSOLE_LINE); memcpy(cs->history[0], cs->cmd, MAX_CONSOLE_LINE); cs->write(cs, "\r\n"); cs->prompt(cs); } else { for (i = 0; i < cs->cmd_len; i++) cs->write(cs, "\b \b"); } } cs->currhist = 0; memset(cs->cmd, 0, MAX_CONSOLE_LINE); cs->cmd_len = 0; } break; case CTRL('F'): case CTRL('B'): break; case CTRL('H'): case 127: /* BS */ if (cs->cmd_len > 0) { cs->cmd_len -= 1; cs->cmd[cs->cmd_len] = 0; if (cs->state != STATE_PASSWORD) cs->write(cs, "\b \b"); } break; case '\r': case '\n': case '?': { if (c == '?') cs->write(cs, "?"); cs->write(cs, "\r\n"); cs->telnet = FALSE; if (cs->state == STATE_USERNAME) { strlcpy(cs->user.username, cs->cmd, sizeof(cs->user.username)); memset(cs->cmd, 0, MAX_CONSOLE_LINE); cs->cmd_len = 0; cs->state = STATE_PASSWORD; cs->prompt(cs); break; } else if (cs->state == STATE_PASSWORD) { ConsoleUser u; RWLOCK_RDLOCK(gUsersLock); u = ghash_get(gUsers, &cs->user); RWLOCK_UNLOCK(gUsersLock); if (!u) goto failed; if (strcmp(u->password, cs->cmd)) goto failed; cs->state = STATE_AUTHENTIC; cs->user.priv = u->priv; cs->context.priv = u->priv; cs->write(cs, "\r\nWelcome!\r\nMpd pid %lu, version %s\r\n", (u_long) gPid, gVersion); goto success; failed: cs->write(cs, "Login failed\r\n"); Log(LG_CONSOLE, ("CONSOLE: Failed login attempt from %s", u_addrtoa(&cs->peer_addr,addrstr,sizeof(addrstr)))); cs->user.username[0] = 0; cs->state = STATE_USERNAME; success: memset(cs->cmd, 0, MAX_CONSOLE_LINE); cs->cmd_len = 0; cs->prompt(cs); break; } memcpy(line, cs->cmd, sizeof(line)); ac = ParseLine(line, av, sizeof(av) / sizeof(*av), 1); memcpy(av_copy, av, sizeof(av)); if (c != '?') { Log2(LG_CONSOLE, ("[%s] CONSOLE: %s: %s", cs->context.lnk ? cs->context.lnk->name : (cs->context.bund? cs->context.bund->name : ""), cs->user.username, cs->cmd)); DoCommand(&cs->context, ac, (const char *const *)av, NULL, 0); } else { HelpCommand(&cs->context, ac, (const char *const *)av, NULL); } FreeArgs(ac, av_copy); if (cs->exit) goto abort; cs->prompt(cs); if (c != '?') { if (cs->cmd_len > 0 && strncmp(cs->cmd, cs->history[0], MAX_CONSOLE_LINE) != 0) { for (i = MAX_CONSOLE_HIST - 1; i > 0; i--) memcpy(cs->history[i], cs->history[i - 1], MAX_CONSOLE_LINE); memcpy(cs->history[0], cs->cmd, MAX_CONSOLE_LINE); } cs->currhist = 0; memset(cs->cmd, 0, MAX_CONSOLE_LINE); cs->cmd_len = 0; } else { cs->write(cs, cs->cmd); } break; } /* "normal" char entered */ default: if (cs->telnet) { if (c < 251) cs->telnet = FALSE; break; } if ((cs->cmd_len + 2) >= MAX_CONSOLE_LINE) { cs->write(cs, "\a"); break; } if (c < 32) break; if (cs->state != STATE_PASSWORD) cs->write(cs, "%c", c); cs->cmd[cs->cmd_len++] = c; } } abort: if (cs->close) { RESETREF(cs->context.lnk, NULL); RESETREF(cs->context.bund, NULL); RESETREF(cs->context.rep, NULL); cs->close(cs); } out: return; } /* * ConsoleSessionWrite() */ static void ConsoleSessionWrite(ConsoleSession cs, const char *fmt, ...) { va_list vl; va_start(vl, fmt); ConsoleSessionWriteV(cs, fmt, vl); va_end(vl); } /* * ConsoleSessionWriteV() */ static void ConsoleSessionWriteV(ConsoleSession cs, const char *fmt, va_list vl) { char *buf; size_t len; len = vasprintf(&buf, fmt, vl); write(cs->fd, buf, len); free(buf); } /* * StdConsoleSessionWrite() */ static void StdConsoleSessionWrite(ConsoleSession cs, const char *fmt, ...) { va_list vl; va_start(vl, fmt); StdConsoleSessionWriteV(cs, fmt, vl); va_end(vl); } /* * StdConsoleSessionWriteV() */ static void StdConsoleSessionWriteV(ConsoleSession cs, const char *fmt, va_list vl) { (void)cs; vprintf(fmt, vl); fflush(stdout); } /* * ConsoleShowPrompt() */ static void ConsoleSessionShowPrompt(ConsoleSession cs) { switch(cs->state) { case STATE_USERNAME: cs->write(cs, "Username: "); break; case STATE_PASSWORD: cs->write(cs, "Password: "); break; case STATE_AUTHENTIC: if (cs->context.lnk) cs->write(cs, "[%s] ", cs->context.lnk->name); else if (cs->context.bund) cs->write(cs, "[%s] ", cs->context.bund->name); else if (cs->context.rep) cs->write(cs, "[%s] ", cs->context.rep->name); else cs->write(cs, "[] "); break; } } /* * ConsoleUserHash * * Fowler/Noll/Vo- hash * see http://www.isthe.com/chongo/tech/comp/fnv/index.html * * By: * chongo /\oo/\ * http://www.isthe.com/chongo/ */ static u_int32_t ConsoleUserHash(struct ghash *g, const void *item) { const struct console_user *u = (const struct console_user *) item; const u_char *s = (const u_char *) u->username; u_int32_t hash = 0x811c9dc5; (void)g; while (*s) { hash += (hash<<1) + (hash<<4) + (hash<<7) + (hash<<8) + (hash<<24); /* xor the bottom with the current octet */ hash ^= (u_int32_t)*s++; } return hash; } /* * ConsoleUserHashEqual */ static int ConsoleUserHashEqual(struct ghash *g, const void *item1, const void *item2) { const struct console_user *u1 = (const struct console_user *) item1; const struct console_user *u2 = (const struct console_user *) item2; (void)g; if (u1 && u2) return (strcmp(u1->username, u2->username) == 0); else return 0; } /* * ConsoleSetCommand() */ static int ConsoleSetCommand(Context ctx, int ac, const char *const av[], const void *arg) { Console c = &gConsole; ConsoleSession cs = ctx->cs; int port; switch ((intptr_t)arg) { case SET_OPEN: ConsoleOpen(c); break; case SET_CLOSE: ConsoleClose(c); break; case SET_ENABLE: if (cs) { if (strcmp(av[0], "auth") != 0) EnableCommand(ac, av, &cs->options, sConfList); } else EnableCommand(ac, av, &c->options, gConfList); break; case SET_DISABLE: if (cs) { if (strcmp(av[0], "auth") != 0) DisableCommand(ac, av, &cs->options, sConfList); } else DisableCommand(ac, av, &c->options, gConfList); break; case SET_SELF: if (ac < 1 || ac > 2) return(-1); if (!ParseAddr(av[0], &c->addr, ALLOW_IPV4|ALLOW_IPV6)) Error("CONSOLE: Bogus IP address given %s", av[0]); if (ac == 2) { port = strtol(av[1], NULL, 10); if (port < 1 || port > 65535) Error("CONSOLE: Bogus port given %s", av[1]); c->port=port; } break; default: return(-1); } return 0; } /* * ConsoleShutdown() */ void ConsoleShutdown(Console c) { ConsoleSession s, tmp; SLIST_FOREACH_SAFE(s, &c->sessions, next, tmp) { if (s->close != NULL) s->close(s); } } /* * UserCommand() */ int UserCommand(Context ctx, int ac, const char *const av[], const void *arg) { ConsoleUser u; (void)arg; (void)ctx; if (ac < 2 || ac > 3) return(-1); u = Malloc(MB_CONS, sizeof(*u)); strlcpy(u->username, av[0], sizeof(u->username)); strlcpy(u->password, av[1], sizeof(u->password)); if (ac == 3) { if (!strcmp(av[2],"admin")) u->priv = 2; else if (!strcmp(av[2],"operator")) u->priv = 1; else if (!strcmp(av[2],"user")) u->priv = 0; else { Freee(u); return (-1); } } RWLOCK_WRLOCK(gUsersLock); ghash_put(gUsers, u); RWLOCK_UNLOCK(gUsersLock); return (0); } /* * UserStat() */ int UserStat(Context ctx, int ac, const char *const av[], const void *arg) NO_THREAD_SAFETY_ANALYSIS { struct ghash_walk walk; ConsoleUser u; (void)ac; (void)av; (void)arg; Printf("Configured users:\r\n"); pthread_cleanup_push(ConsoleCancelCleanup, gUsersLock); RWLOCK_RDLOCK(gUsersLock); ghash_walk_init(gUsers, &walk); while ((u = ghash_walk_next(gUsers, &walk)) != NULL) { Printf("\tUsername: %-15s Priv: %s\r\n", u->username, ((u->priv == 2)?"admin":((u->priv == 1)?"operator":"user"))); } pthread_cleanup_pop(1); return 0; }