/* * Copyright (c) 1996-2007, OpenFWTK Development Group * All rights reserved. See LICENSE. */ /* cmd-gw.c */ /* Copyright 1997, 1998 by Eberhard Mattes */ #include #include #include #include #include #include #include #include #include #include #include #include #include "firewall.h" #include "auth.h" #include "libemfw.h" #include "libemtn.h" #include "firewall2.h" /* firewall.h lacks some declarations. */ int str_to_port (char *); int do_daemon (int); int peername (int, char *, char *, int); int enargv (char *, char **, int, char *, int); static int authenticated = 0; static int authneeded = 0; static int extendperm = 0; #if defined(linux) || defined (SOLARIS) extern void initsetproctitle(int,char**); #endif /* Prototypes. */ static int cmd_auth (); static int cmd_help (); static int cmd_once (); static int cmd_quit (); /* Important: The compiler should put this array into read-only memory (.text). Therefore we make it `const'. */ static Cmd const cmds[] = { {"auth", 0, " auth ", cmd_auth}, {"dig", 1, " dig ", 0}, {"exit", 0, " exit", cmd_quit}, {"finger", 2, " finger [@]", 0}, {"help", 0, " help/?", cmd_help}, {"login", 0, " login ", cmd_auth}, {"once", 0, " once [...]", cmd_once}, {"ping", 3, " ping ", 0}, {"quit", 0, " quit", cmd_quit}, {"traceroute", 4, " traceroute ", 0}, {"?", 0, 0, cmd_help}, {0, 0, 0, 0} }; static char** cmdlist = (char**) 0; static char** acmdlist = (char**) 0; static fwparm options[] = { {FWPARM_BOOL, "-extnd", (char*) &extendperm }, {FWPARM_BOOL, "-authall", (char*) &authneeded }, {FWPARM_STRING, "-authuser", proxy_stats.authuser}, {FWPARM_LIST , "-commands", (char*) &cmdlist }, {FWPARM_LIST , "-auth", (char*) &acmdlist }, {0, 0, 0 } }; /* Important: The compiler should put this array into read-only memory (.text). Therefore we make it `const'. */ static const char * const pgms[] = { #ifdef OpenBSD "/usr/sbin/dig", /* 1 */ #else "/usr/bin/dig", /* 1 */ #endif "/usr/bin/finger", /* 2 */ #ifdef SOLARIS "/usr/sbin/ping", /* 3 */ #else "/sbin/ping", /* 3 */ #endif "/usr/sbin/traceroute" /* 4 */ }; #define PGMS (sizeof (pgms) / sizeof (pgms[0])) static int child_pid; static char pgmena[PGMS]; static char apgmena[PGMS]; static char *tokv[MAX_ARG]; static char input[MAX_STR]; static char tokbuf[MAX_STR]; static tnconn *t; static void cleanup (void); static void exec_command (char **v, int n); static void run_prog (int argc, char *argv[], const char *pgm); static void config () { Cfg *cf; int j; memset (pgmena, 0, PGMS * sizeof(pgmena[0])); memset (pgmena, 0, PGMS * sizeof(pgmena[0])); if ((cf=proxy_conf_hosts(proxy_confp,proxy_stats.rladdr, proxy_stats.riaddr))) proxy_parse_options(cf,options); else proxy_exit(); if(cmdlist != (char **)0) { char **xp; for(xp = cmdlist; *xp != (char *)0; xp++) { if(!strcmp(*xp,"*")) { memset (pgmena, 1, PGMS * sizeof(pgmena[0])); continue; } for(j=0; cmds[j].cnam != NULL; j++) { if (cmds[j].flg > PGMS) abort (); if(!strcmp(*xp,cmds[j].cnam)) { pgmena[cmds[j].flg - 1] = 1; break; } } if(cmds[j].cnam == NULL) { syslog (LLEV, "fwtkcfgerr: unknown command, line %d", cf->ln); exit (1); } } } memcpy(apgmena, pgmena, PGMS * sizeof(pgmena[0])); if(acmdlist != (char **)0) { char **xp; for(xp = acmdlist; *xp != (char *)0; xp++) { if(!strcmp(*xp,"*")) { memset (apgmena, 1, PGMS * sizeof(pgmena[0])); continue; } for(j=0; cmds[j].cnam != NULL; j++) { if (cmds[j].flg > PGMS) abort (); if(!strcmp(*xp,cmds[j].cnam)) { apgmena[cmds[j].flg - 1] = 1; break; } } if(cmds[j].cnam == NULL) { syslog (LLEV, "fwtkcfgerr: unknown command, line %d", cf->ln); exit (1); } } } } int main (int argc, char *argv[]) { int tokc; if (argc >= 2 && strcmp (argv[1], "-check") == 0) { check_snprintf (0); exit (0); } proxy_init(argc,argv); proxy_chroot_setugid(); config(); atexit (cleanup); t = tn_init (0, TN_INIT_TELNET); if (authneeded && cmd_auth(0, NULL, NULL)) proxy_exit(); tn_puts (t, "Welcome to cmd-gw.\r\n"); for (;;) { tn_puts (t, "Command: "); *proxy_stats.dst = '\0'; proxy_update_operation("IDLE"); tn_gets (t, input, sizeof (input), PROXY_TIMEOUT, TN_GETS_ECHO); syslog (LLEV, "command: %.512s", input); proxy_stats.outbytes += strlen(input); proxy_update_operation(input); tokc = enargv (input, tokv, sizeof (tokv) / sizeof (tokv[0]), tokbuf, sizeof (tokbuf)); if (tokc <= 0) continue; exec_command (tokv, tokc); } } static int cmd_help (int argc, char *argv[], char *raw) { const Cmd *c; tn_puts (t, "Command list:\r\n"); for (c = cmds; c->cnam != NULL; ++c) if (c->help != NULL) { if (c->flg < 0 || c->flg > PGMS) abort (); if (c->flg == 0 || pgmena[c->flg-1]) tn_printf (t, "%.79s\r\n", c->help); } return 0; } static int cmd_once (int argc, char *argv[], char *raw) { if (argc < 2) tn_puts (t, "Missing argument.\r\n"); else { exec_command (argv + 1, argc - 1); proxy_exit (); } return 0; } static int cmd_quit (int argc, char *argv[], char *raw) { tn_puts (t, "Good bye!\r\n"); proxy_exit (); return 0; } static int cmd_auth (int argc, char *argv[], char *raw) { char lbuf[MAX_STR]; char rbuf[MAX_STR]; char username[AUTH_USIZ+1]; static int authopened = 0; authenticated = 0; if (argc == 0 || argc == 1) { tn_puts(t,"Username: "); tn_gets (t, username, sizeof (username), PROXY_TIMEOUT, TN_GETS_ECHO); } else if (argc == 2) strlcpy(username,argv[1],sizeof(username)); else { tn_puts (t, "Syntax error\r\n"); return 1; } if(strlen(username) > AUTH_USIZ - 1) { tn_puts(t,"User name too long\r\n"); return 1; } if(!authopened) { if(auth_open(proxy_confp)) { tn_puts(t,"Cannot connect to authentication server\r\n"); return 1; } if(auth_recv(rbuf,sizeof(rbuf))) goto lostconn; if(strncmp(rbuf,"Authsrv ready",13)) { tn_puts(t, rbuf); auth_close(); return 1; } authopened++; } /* send username */ if(strlen(proxy_stats.rladdr) + strlen(proxy_stats.riaddr) + strlen(username) + 100 > MAX_STR) { tn_puts(t,"Buffer overflow on authentication stage"); return 1; } snprintf(rbuf, sizeof(rbuf), "authorize %s 'cmd-gw %s/%s'",username,proxy_stats.rladdr,proxy_stats.riaddr); if(auth_send(rbuf)) goto lostconn; while (1) { if(auth_recv(rbuf,sizeof(rbuf))) goto lostconn; if(!strncmp(rbuf,"ok",2)) break; if(!strncmp(rbuf,"display ",8)) { tn_puts(t,&rbuf[8]); if (auth_send("response dummy")) goto lostconn; continue; } if(!strncmp(rbuf,"challenge ",10)) { tn_puts(t,&rbuf[10]); tn_gets(t, rbuf, sizeof (rbuf), PROXY_TIMEOUT, TN_GETS_ECHO); snprintf(lbuf, sizeof (lbuf), "response '%s'", rbuf); if (auth_send(lbuf)) goto lostconn; continue; } if(!strncmp(rbuf,"chalnecho ",10)) { tn_puts(t,&rbuf[10]); tn_gets(t, rbuf, sizeof (rbuf), PROXY_TIMEOUT, 0); snprintf(lbuf, sizeof (lbuf), "response '%s'", rbuf); if (auth_send(lbuf)) goto lostconn; continue; } if(!strncmp(rbuf,"password",8)) { tn_puts(t,"Password:"); tn_gets(t, rbuf, sizeof (rbuf), PROXY_TIMEOUT, 0); snprintf(lbuf, sizeof (lbuf), "response '%s'", rbuf); if (auth_send(lbuf)) goto lostconn; continue; } tn_puts(t,rbuf); tn_puts(t,"\r\n"); return 1; } tn_puts(t,"Authenticated ("); tn_puts(t,rbuf); tn_puts(t,")\r\n"); memcpy(pgmena, apgmena, PGMS * sizeof(pgmena[0])); return 0; lostconn: auth_close(); authopened = 0; tn_puts(t, "Cannot connect to authentication server"); return 1; } static void exec_command (char **v, int n) { Cmd *cp; int rc; cp = find_command ((Cmd *)cmds, v[0]); if (cp == (Cmd *)0) tn_puts (t, "Ambiguous command.\r\n"); else if (cp->cnam == (char *)0) tn_puts (t, "Unrecognized command.\r\n"); else if (cp->flg == 0) cp->cfun (n, v, input); else if (cp->flg < 1 || cp->flg > PGMS) abort (); else if (pgmena[cp->flg-1] == 0) tn_puts (t, "Unrecognized command.\r\n"); /* Same message as above! */ else if((extendperm) && (((rc = auth_perm(proxy_confp,proxy_stats.authuser,"cmd-gw", proxy_stats.riaddr,v)) == 1) || (rc == -1))) { syslog(LLEV,"deny host=%.512s/%.20s operation %.100s - extended permissions",proxy_stats.rladdr,proxy_stats.riaddr,input); tn_puts (t, "Operation denied\r\n"); return; } else run_prog (n, v, pgms[cp->flg-1]); } /* This function is called by exit(). */ static void cleanup (void) { if (child_pid > 0) { kill (child_pid, SIGINT); child_pid = -1; } } static sig_atomic_t child_died; static void sigchld (int signo) { int status; /* As we have only one child process, handling SIGCHLD is simple. */ wait (&status); child_died = 1; child_pid = -1; } static void run_prog (int argc, char *argv[], const char *pgm) { int fd, pgm_output[2]; /* Feed the program's output through a pipe to replace \n with \r\n. */ if (pipe (pgm_output) != 0) { tn_printf (t, "pipe(): %s\r\n", strerror (errno)); return; } if (pgm_output[0] >= FD_SETSIZE) { tn_printf (t, "file descriptor out of range for select()\r\n"); close (pgm_output[0]); close (pgm_output[1]); return; } /* Ensure that pgm_output[1] is > 2. This simplifies the code in the child process. */ if (pgm_output[1] <= 2) { errno = 0; fd = fcntl (pgm_output[1], F_DUPFD, 3); if (fd < 3) { tn_printf (t, "fcntl(F_DUPFD): %s\r\n", strerror (errno)); return; } close (pgm_output[1]); pgm_output[1] = fd; } /* Set up SIGCHLD. */ child_died = 0; signal (SIGCHLD, sigchld); /* Create a child process. */ child_pid = fork (); if (child_pid == -1) { tn_printf (t, "fork(): %s\r\n", strerror (errno)); close (pgm_output[0]); close (pgm_output[1]); } else if (child_pid == 0) { /* Child process. */ int e; char *s; signal (SIGCHLD, SIG_DFL); close (0); close (1); close (2); if (pgm_output[0] > 2) close (pgm_output[0]); errno = 0; if (open ("/dev/null", O_RDWR) != 0) /* This may fail in jail */ syslog (LLEV, "fwtksyserr: /dev/null: %s", strerror(errno)); errno = 0; if (dup2 (pgm_output[1], 1) != 1) { syslog (LLEV, "fwtksyserr: dup2(): %s", strerror(errno)); exit (2); } errno = 0; if (dup2 (pgm_output[1], 2) != 2) { syslog (LLEV, "fwtksyserr: dup2(): %s", strerror(errno)); exit (2); } close (pgm_output[1]); /* TODO: Close all other file descriptors (authdb!) */ argv[0] = (char *)pgm; argv[argc] = 0; /* Don't trust enargv() */ execv (pgm, argv); e = errno; write (1, pgm, strlen (pgm)); write (1, ": ", 2); s = strerror (e); write (1, s, strlen (s)); write (1, "\n", 1); exit (2); } else { /* Parent process. */ fd_set rfds; struct timeval timeout; int n; tn_reset_dfa (t); close (pgm_output[1]); for (;;) { FD_ZERO (&rfds); FD_SET (pgm_output[0], &rfds); FD_SET (0, &rfds); timeout.tv_sec = child_died ? 0 : PROXY_TIMEOUT; timeout.tv_usec = 0; n = select (FD_SETSIZE, &rfds, (fd_set *)0, (fd_set *)0, &timeout); if (n == -1) { if (errno == EINTR) continue; syslog (LLEV, "fwtksyserr: select(): %s", strerror(errno)); exit (1); } if (n == 0) { syslog (LLEV, "Timeout"); tn_puts (t, "Timeout\r\n"); proxy_exit (); } if (FD_ISSET (0, &rfds)) { int c = tn_getc (t); tndfa dfa = tn_dfa (t, c); if (dfa == TN_IP || (dfa == TN_CHAR && c == 0x03)) kill (child_pid, SIGINT); proxy_stats.outbytes++; } if (FD_ISSET (pgm_output[0], &rfds)) { char c; /* TODO: use bigger buffer, non-blocking */ n = read (pgm_output[0], &c, 1); if (n == -1 && errno == EINTR) continue; if (n <= 0) break; proxy_stats.inbytes++; if (c == '\n') tn_putnl (t); else tn_putc (t, c); } } close (pgm_output[0]); signal (SIGCHLD, SIG_DFL); child_pid = -1; } }