#if 0 /* # # To Compile this program, enter: # # $ sh revsh.c [-DHAVE_RSH] [-DHAVE_FSH] [-s] [-O2] # TRG=`basename $0 .c` verdate=1.93`awk '/\\$[I]d:/ { print " (r"$4 ",", $5, $6")"; exit; }' $0` [ x"$1" = xclean ] && { set -x; exec rm -f $TRG; } [ x"$1" = x ] && set -- -s -O2 set -x exec ${CC:-gcc} -Wall "$@" -o $TRG $0 -lutil -DVEDA="\"$verdate\"" #*/ #endif /* * $Id: revsh.c 1143 2006-11-23 21:07:13Z too $ * * Author: Tomi Ollila * * Copyright (c) 2003 Tomi Ollila * All rights reserved * * Created: Wed Sep 24 18:07:15 EEST 2003 too * Last modified: Thu Nov 23 22:53:45 EET 2006 too * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * If you don't have a copy of the GNU General Public License, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 US * */ /* This was supposed to be quick hack -- that just never happens... */ /* Note: currently the 'forwarder' part works only in Linux machines, since other OSses (at least *BSD and Solaris) doesn't return POLLHUP in .revents when .events == 0 (if ever). This will be fixed when I add window size change support... */ /* Missing (todo for version 2.0) - Info remote program about window size change (when in shell mode). - Have escape character when in shell mode in revsh_client. - Reporting exit values of commands / shells. */ /* Possible problems: This program is supposed to be run in 'friendly' environment. However, I'd like to know if anyone sees any security problems. (I haven't done any security audit to this so far... Try: run 2 `revsh sh -i'. Then run `CVS_RSH=revsh cvs checkout' and during that intensive checkout, exit the 2 shells. Once, I think one of the sh revsh:s did not return. It exited then the revsh tunnel was closed. (let's see whether adding of one setsid() helped there ... -- add `-d' -option to ask revsh_front to provide some protocol information to revsh_client... */ #define PROTOCOL_IDENT "revsh\000\007" /* 7 chars, revsh */ #define PROTOCOL_VERSTR "7" /* make match above */ #if 0 #define DEBUG_READ 0 #define DEBUG_WRITE 0 #define DEBUG_POLL 1 #define DEBUG_CLOSE 1 #define DEBUG_SHUTDOWN 1 #define HAVE_TRACE_FUNCTIONS 1 #define TRACE(a, b) if (a) { trace0(__FILE__,__LINE__, a); trace b; } while (0) #define TRACEMODULE(c) G.tracemodule = c #define BACK_DBFIFO 1 #else #define TRACE(a, b) do {} while(0) #define TRACEMODULE(c) #endif #include #include #include #include #include #include #include /* #include */ #include #include #include #include #include #include #include #include #include #include #include #if linux #include #else #include #endif #include #if (__GNUC__ >= 3) #define GCCATTR_SENTINEL __attribute ((sentinel)) /* since gcc 3.x ??? */ #define GCCATTR_NORETURN __attribute ((noreturn)) #define GCCATTR_UNUSED __attribute ((unused)) #define GCCATTR_CONST __attribute ((const)) #define S2U(v, t, i, o) \ __builtin_choose_expr (__builtin_types_compatible_p (typeof (v), t i), \ ((unsigned t o)(v)), (void)0) #define U2S(v, t, i, o) \ __builtin_choose_expr (__builtin_types_compatible_p \ (typeof (v), unsigned t o), /**/ ((t o)(v)), (void)0) #else #define GCCATTR_SENTINEL #define GCCATTR_NORETURN #define GCCATTR_UNUSED #define GCCATTR_CONST #define S2U(v, t, i, o) #define U2S(v, t, i, o) #endif #define UU GCCATTR_UNUSED /* convenience macro */ /* CS == constant string */ #define WriteCS(fd, s) writeall(fd, s, sizeof s - 1) #define StrCpyCS(d, s) memcpy(d, s, sizeof s) #define StrCmpCS0(v, c) memcmp(v, c, sizeof c - 1) #define StrCmpCS(v, c) memcmp(v, c, sizeof c) /* */ typedef enum { false = 0, true = 1 } bool; /* */ typedef struct _PktRead PktRead; struct server_io { PktRead * pr; struct pollfd ufds[253]; char ufd_to_aid_map[125]; unsigned char aid_to_ufd_map[125]; short outfds[125]; pid_t pids[125]; char * shell; char ** ssenv; short nfds; char nullfd; char acnt; }; struct forwarder_io { PktRead * pr; struct pollfd ufds[127]; char ufd_to_aid_map[125]; char aid_to_ufd_map[125]; short outfds[250]; short ssfd; short pending_fds[2]; short nfds; char acnt; }; /* Global vars */ struct { char * prgname; char verbose; char gotsig; void (* exit_hook)(void *); void * exit_context; #if HAVE_TRACE_FUNCTIONS char tracemodule; #endif } G; /* command line options */ struct opts { char * l; char d; int k; }; /* part: static function prototypes */ /*** Static function prototypes (see gen code at the end of this file) ***/ /*** == GENPROTO == Autogenerated; Keep the == GENPROTO == part intact ***/ static bool servercmd(char * arg); static void usage0(void) GCCATTR_NORETURN; static char * get_next_arg(int * c, char *** v); static int parse_opts(struct opts * opts, char ** argv); static int probably_shell(void); static int revsh_server(int argc, char ** argv, struct opts * opts) GCCATTR_NORETURN; static void go_daemon(int nullfd); static void server_loop(int ofd, int iofd, int nullfd, int keepalive_timeout) GCCATTR_NORETURN; static void s_doenv_chdir_home(char * envp[7], char ** shell); static void s_my_msg(struct server_io * bio, int netout, unsigned char * ptr, int len); static void s_launch_prog(struct server_io * bio, int netout, unsigned char * ptr, int len); static void s_setup_shell(struct server_io * bio, int netout, unsigned char * ptr, int len); static int s_net_to_app(struct server_io * bio, int netout); static int s_app_to_net(struct server_io * bio, int ufdi, int netout); static void s_delete_app(struct server_io * bio, int aid, int netout); static void childexit(int sig UU); static void revsh_forwarder(char * sockfile) GCCATTR_NORETURN; static void f_unlink_sockfile(void * context); static void f_write_1_char_to_stdout(void * context) /* XXX */; static void forwarder_loop(int sfd); static void f_new_app(struct forwarder_io * fio); static void f_my_msg(struct forwarder_io * fio, unsigned char * ptr, int len); static int f_net_to_app(struct forwarder_io * fio); static int f_app_to_net(struct forwarder_io * fio, int ufdi); static void f_delete_app(struct forwarder_io * fio, int aid); static void netwrite(int fd, unsigned char * buf, int len); static void revsh_client(char ** argv, struct opts * opts) GCCATTR_NORETURN; static void client_loop(int iofd, int efd) GCCATTR_NORETURN; static int move_data(int ifd, int ofd, int xfd); static void raw_terminal(void); static void restore_terminal(void * context); static void my_exit(int ev) GCCATTR_NORETURN; static void sendversion(int fd); static int checkversion(unsigned char * buf); static void lowcase_str(char * str); static int sockfile_sane(char * file); static char * getusername(int buflen); static int unix_connect(char * path); static int make_unix_socket(struct sockaddr_un * addr, const char * path); static void sigact(int sig, void (*handler)(int)); static void movefd(int a, int b); static void set_close_on_exec(int fd); static void set_nonblocking(int fd); static int writeall(int fd, char * buf, int len); static int recv2fds(int ifd, int fds[2]); static int send2fds(int ofd, int fd1, int fd2); static void nnclose(int fd); static int ntopoll(struct pollfd * pfds, int nfds); static int ntopoll_server(struct pollfd * pfds, int nfds, int timeout); static void xpipe(int filedes[2]); static void xsocketpair(int sv[2]); static int xfork(void); static void * xcalloc(size_t nmemb, size_t size); static char * xstrdup(const char * s); static void xsnprintf(char * buf, int len, char * format, ...); static void xerrf(char * format, ...) GCCATTR_NORETURN; static void errf(char * format, ...); static void xusage(char * format, ...) GCCATTR_NORETURN; #if HAVE_TRACE_FUNCTIONS static void trace0(char * file, int line, int n); static void trace(char * format, ...); #endif #if DEBUG_READ static int debug_read(int fd, unsigned char * buf, int len); #endif #if DEBUG_WRITE static int debug_write(int fd, unsigned char * buf, int len); #endif #if DEBUG_POLL static int debug_poll(struct pollfd * pfds, unsigned int nfds, int timeout); #endif #if DEBUG_CLOSE static int debug_close(int fd); #endif #if DEBUG_SHUTDOWN static int debug_shutdown(int fd, int how); #endif static int pktRead(PktRead * pr, unsigned char ** ptr); static PktRead * new_PktRead(PktRead * pr, int fd, int size); static void delete_PktRead(PktRead * beer); /*** == END == End autogenerated block. Keep the == END == part intact ***/ #if DEBUG_READ #define read debug_read #endif #if DEBUG_WRITE #define write debug_write #endif #if DEBUG_POLL #define poll debug_poll #endif #if DEBUG_CLOSE #define close debug_close #endif #if DEBUG_SHUTDOWN #define shutdown debug_shutdown #endif /* part: main */ int main(int argc, char * argv[]) GCCATTR_NORETURN; int main(int argc, char * argv[]) { char * e; int i; struct opts opts = { 0 }; /* default opts */ opts.k = 300 * 1000; /* 5 minute timeout by default */ TRACEMODULE('m'); memset(&G, 0, sizeof G); G.prgname = argv[0]; G.verbose = 1; sigact(SIGPIPE, SIG_IGN); sigact(SIGIO, SIG_IGN); sigact(SIGCHLD, childexit); /* Now cleanup possible when these signals arrive... */ sigact(SIGTERM, my_exit); sigact(SIGHUP, my_exit); /* system("echo 1>&2; printenv 1>&2"); */ if ((e = getenv("REVSH_DAEMON_SOCKET_FILE")) != NULL) revsh_forwarder(e); if (argc < 2) usage0(); /* Am I started as shell? */ if (strcmp(argv[1], "-c") == 0 && probably_shell() && argv[2] != NULL) { if (StrCmpCS0(argv[2], "REVSH_DAEMON_SOCKET_FILE=") == 0 && (e = strchr(argv[2] + 25, ' ')) != NULL) { *e = '\0'; revsh_forwarder(argv[2] + 25); } } argv++; argc--; i = parse_opts(&opts, argv); argv += i; argc -= i; if (servercmd(argv[0])) revsh_server(argc, argv, &opts); revsh_client(argv, &opts); } static bool servercmd(char * arg) { char * p; if ((p = strrchr(arg, '/')) != NULL) arg = p + 1; if (strcmp(arg, "ssh2") == 0 || strcmp(arg, "ssh") == 0 || strcmp(arg, "lsh") == 0 || strcmp(arg, "dbclient") == 0 #if HAVE_FSH || strcmp(arg, "fsh") == 0 #endif #if HAVE_RSH || strcmp(arg, "rsh") == 0 #endif ) return true; /* else */ return false; } static void usage0() /* protoadd GCCATTR_NORETURN */ { xusage("[-d] [-k secs] [-l user] [user@]host command\n\n" " with command `shell' interactive shell session is started.\n" " if 'host' is ssh2, ssh, lsh or rsh, then usage is different.\n"); } static char * get_next_arg(int * c, char *** v) { if ((*v)[1] == NULL) usage0(); (*v)++; (*c)--; return **v; } static int parse_opts(struct opts * opts, char ** argv) { int argc = 0; char * s; int i; while (argv[0][0] == '-' && argv[0][1] != '\0' && argv[0][2] == '\0') { switch (argv[0][1]) { case 'd': opts->d = 1; break; case 'l': opts->l = get_next_arg(&argc, &argv); break; case 'k': s = get_next_arg(&argc, &argv); /* XXX add string checking */ i = atoi(s); opts->k = (i <= 0)? -1: ((i > 30)? i * 1000: 30 * 1000); break; default: #if 0 if (!probably_shell()) fprintf(stderr, "%s: unknown option\n", argv[0]); #endif usage0(); } argv++; argc--; if (argv[0] == NULL) usage0(); } return -argc; } static int probably_shell() { char * shell = getenv("SHELL"); if (shell && strstr(shell, "revsh") != NULL) return 1; return 0; } /*** part: recvmd-server -- protoline ****/ static int revsh_server(int argc, char ** argv, struct opts * opts) /* protoadd GCCATTR_NORETURN */ { int ipipe[2]; int opipe[2]; int nullfd; TRACEMODULE('s'); if (argv[1] == NULL) xusage("[-d] [-l user@host] ssh/rsh command line with options.\n" "\nExample: %s ssh joe@outside\n", G.prgname); if ((nullfd = open("/dev/null", O_RDWR)) < 0) xerrf("open(/dev/null):"); xpipe(ipipe); xpipe(opipe); if (xfork() == 0) { /* child */ char ** args; char nev[256]; int i; args = xcalloc(argc + 3, sizeof *args); for (i = 0; i < argc; i++) args[i] = argv[i]; strcpy(nev, "REVSH_DAEMON_SOCKET_FILE="); if (opts->l) strncpy(nev + 25, opts->l, sizeof nev - 26); else { char * username = getusername(20); int l = strlen(username); char * e = nev + 25 + l; memcpy(nev + 25, username, l); *e++ = '@'; gethostname(e, sizeof nev - l - 27); } nev[sizeof nev - 1] = '\0'; lowcase_str(nev + 25); #if 0 write(2, nev, strlen(nev)); write(2, "\n", 1); #endif args[i++] = nev; args[i++] = "revsh"; args[i] = NULL; close(ipipe[1]); close(opipe[0]); movefd(ipipe[0], 0); movefd(opipe[1], 1); execvp(args[0], args); xerrf("execvp(%s, ...):", args[0]); } close(ipipe[0]); close(opipe[1]); sendversion(ipipe[1]); { char buf[7]; int i = 0; do { int l = read(opipe[0], buf + i, 7 - i); if (l <= 0) { errf("EOF while creating forwarder connection:"); my_exit(1); } i += l; } while (i < 7); if (checkversion(S2U(buf, char, [], *)) >= 0) { errf("Version mismatch with forwarder; got %d (<> " PROTOCOL_VERSTR ").\n", checkversion(S2U(buf, char, [], *))); my_exit(1); } } set_nonblocking(0); set_nonblocking(1); set_nonblocking(2); /* don't set these (network) fd:s nonblocking */ set_close_on_exec(ipipe[1]); set_close_on_exec(opipe[0]); WriteCS(2, "RevSH tunnel with remote host created.\n"); if (opts->d) { WriteCS(2, "Going background.\n"); go_daemon(nullfd); } server_loop(ipipe[1], opipe[0], nullfd, opts->k); } static void go_daemon(int nullfd) { if (xfork() == 0) { /* child */ dup2(nullfd, 0); dup2(nullfd, 1); dup2(nullfd, 2); setsid(); return; } /* parent */ _exit(0); } static void server_loop(int ofd, int iofd, int nullfd, int keepalive_timeout) /* protoadd GCCATTR_NORETURN */ { struct server_io bio; int i; char * envp[7]; TRACE(0, ("server_loop(ofd = %d, iofd = %d, iefd = %d)", ofd, iofd, iefd)); bio.pr = new_PktRead(NULL, iofd, 4096); bio.nullfd = nullfd; s_doenv_chdir_home(envp, &bio.shell); bio.ssenv = envp; /* * Pollfd utilisation: * 0: input from stdin (could be useful sometimes.) * 1: network input * 2 - n, in 2 fd groups: stdout and stderr of application in apptable, * with index (pollfd - 2 / 2) */ for (i = 0; i < sizeof bio.ufds / sizeof bio.ufds[0]; i++) bio.ufds[i].events = POLLIN; bio.ufds[0].fd = 0; bio.ufds[1].fd = iofd; bio.nfds = 2; bio.acnt = 0; for (i = 0; i< sizeof bio.aid_to_ufd_map / sizeof bio.aid_to_ufd_map[0]; i++) bio.outfds[i] = -1; while (1) { int n = ntopoll_server(bio.ufds, bio.nfds, keepalive_timeout); if (n <= 0) { //printf("xxx %d\n", n); // FIXME if (n == 0) write(ofd, "\000", 2); /* keepalive */ continue; } for (i = 2; i < bio.nfds; ) { if (bio.ufds[i].revents) /* & POLLIN|POLLHUP|POLLERR|POLLNVAL */ if (! s_app_to_net(&bio, i, ofd)) { i = (i - 1) | 1; continue; } i++; } if (bio.ufds[0].revents) /* & POLLIN|POLLHUP|POLLERR|POLLNVAL */ { TRACE(1, ("server_loop poll() 0")); /* read from stdin... DO SOMETHING*/ /* this could also be control FIFO or something */ bio.ufds[0].fd = -1; /* disable further action */ } if (bio.ufds[1].revents) /* & POLLIN|POLLHUP|POLLERR|POLLNVAL */ { TRACE(1, ("server_loop poll() 1")); if (! s_net_to_app(&bio, ofd)) break; } } wait(NULL); delete_PktRead(bio.pr); my_exit(0); } static void s_doenv_chdir_home(char * envp[7], char ** shell) { struct passwd * pw = getpwuid(getuid()); char buf[1024]; if (pw == NULL) xerrf("Cannot find passwd entry for user %d\n", getuid()); /* envp[0] will point to TERM, if given -- else &envp[1] is used ... */ xsnprintf(buf, 1024, "USER=%s", pw->pw_name); envp[1] = xstrdup(buf); xsnprintf(buf, 1024, "LOGNAME=%s", pw->pw_name); envp[2] = xstrdup(buf); xsnprintf(buf, 1024, "HOME=%s", pw->pw_dir); envp[3] = xstrdup(buf); chdir(pw->pw_dir); xsnprintf(buf, 1024, "SHELL=%s", pw->pw_shell); envp[4] = xstrdup(buf); xsnprintf(buf, 1024, "-%s", pw->pw_shell); *shell = xstrdup(buf); envp[5] = "PATH=/bin:/usr/bin"; envp[6] = NULL; } static void s_my_msg(struct server_io * bio, int netout, unsigned char * ptr, int len) { TRACE(1, ("s_my_msg(type = %x)", ptr[0])); switch (ptr[0]) { case 0: /* new app */ ptr[len] = '\0'; s_launch_prog(bio, netout, ptr + 1, len - 1); break; case 1: /* new shell */ ptr[len] = '\0'; s_setup_shell(bio, netout, ptr + 1, len - 1); break; case 2: /* delete app */ s_delete_app(bio, ptr[1], netout); break; case 3: /* winsize chage */ /* fall through -- unimplemented */ default: xerrf("Unknown messge type %d\n", ptr[0]); } } static void s_launch_prog(struct server_io * bio, int netout, unsigned char * ptr, int len) { unsigned char buf[128]; int ipipe[2]; int opipe[2]; int epipe[2]; int aid; char * args[4]; TRACE(1, ("s_launch_prog(ptr = %s, len = %d)", ptr, len)); TRACE(1, (" acnt = %d", bio->acnt)); buf[2] = 0x00; /* msg to revsh on the other end (and not to any of apps) */ if (bio->acnt >= 125) { /*** reply out of apps */ buf[3] = 0x81; goto _msg2peer; } /* find free app id */ for (aid = 0; aid < 125; aid++) if (bio->outfds[aid] < 0) break; if (aid == 125) _exit(10); /* assert? */ xpipe(ipipe); xpipe(opipe); xpipe(epipe); TRACE(1, (" aid = %d, nfds = %d, new fd:s %d %d %d", aid, bio->nfds, ipipe[1], opipe[0], epipe[0])); switch (fork()) { case 0: /* child */ close(ipipe[1]); close(opipe[0]); close(epipe[0]); movefd(ipipe[0], 0); movefd(opipe[1], 1); movefd(epipe[1], 2); /* get rid of parent process's tty (no sigs back) + no pgrp signals ...*/ setsid(); /* setpgrp(); (is not enough) */ args[0] = bio->shell; args[1] = "-c"; args[2] = U2S(ptr, char, *, *); args[3] = NULL; TRACE(1, ("exec()ing %s", ptr)); execve(bio->shell + 1, args, &bio->ssenv[1]); xerrf("execve(%s -c %s):", bio->shell, ptr); case -1: close(ipipe[1]); close(opipe[0]); close(epipe[0]); buf[3] = 0x81; /* reply app creation failed */ break; default: set_close_on_exec(ipipe[1]); set_nonblocking(ipipe[1]); set_close_on_exec(opipe[0]); set_nonblocking(opipe[0]); set_close_on_exec(epipe[0]); set_nonblocking(epipe[0]); bio->outfds[aid] = ipipe[1]; bio->ufd_to_aid_map[(bio->nfds - 2) / 2 ] = aid; bio->aid_to_ufd_map[aid] = bio->nfds; bio->ufds[bio->nfds++].fd = opipe[0]; bio->ufds[bio->nfds++].fd = epipe[0]; bio->acnt++; buf[3] = 0x80; /* reply app created */ buf[4] = aid; /* app index */ } /* here in cases above: -1 and default */ close(ipipe[0]); close(opipe[1]); close(epipe[1]); _msg2peer: netwrite(netout, buf, 5); } static void s_setup_shell(struct server_io * bio, int netout, unsigned char * ptr, int len) { unsigned char buf[128]; int aid; int tty, pty; struct winsize ws; char * args[2]; TRACE(1, ("s_setup_shell(ptr = %s, len = %d)", ptr, len)); TRACE(1, (" acnt = %d", bio->acnt)); buf[2] = 0x00; /* msg to revsh on the other end (and not to any of apps) */ /* 00 rowh rowl colh coll T E R M = ... */ /* XXX should do sanity check */ if (len < 10 || ptr[5] != 'T' || ptr[6] != 'E' || ptr[7] != 'R' || ptr[8] != 'M' || ptr[9] != '=' || bio->acnt >= 125) { /*** reply out of apps (or error in new shell msg.) */ buf[3] = 0x81; goto _msg2peer; } /* find free app id */ for (aid = 0; aid < 125; aid++) if (bio->outfds[aid] < 0) break; if (aid == 125) /* assert */ _exit(10); ws.ws_row = ((ptr[1] & 0x7f) << 7) + (ptr[2] & 0x7f); ws.ws_col = ((ptr[3] & 0x7f) << 7) + (ptr[4] & 0x7f); ws.ws_xpixel = ws.ws_ypixel = 0; if (openpty(&pty, &tty, NULL, NULL, &ws) < 0) { /*** in future versions message is provided to server. */ buf[3] = 0x81; goto _msg2peer; } TRACE(1, (" aid = %d, nfds = %d, pty %d, tty = %d, rows = %d, cols = %d", aid, bio->nfds, pty, tty, ws.ws_row, ws.ws_col)); switch (fork()) { case 0: /* child */ close(pty); /* disconnect from controlling tty */ close(0); close(1); close(2); setsid(); /* make tty our controlling tty */ ioctl(tty, TIOCSCTTY, NULL); movefd(tty, 0); dup2(0, 1); dup2(0, 2); args[0] = bio->shell; args[1] = NULL; bio->ssenv[0] = U2S(ptr + 5, char, *, *); /* TERM=...*/ TRACE(1, ("exec()ing %s", bio->shell + 1)); execve(bio->shell + 1, args, bio->ssenv); xerrf("execvp(%s):", bio->shell); case -1: close(pty); buf[3] = 0x81; /* reply app creation failed */ break; default: set_close_on_exec(pty); set_nonblocking(pty); bio->outfds[aid] = pty; bio->ufd_to_aid_map[(bio->nfds - 2) / 2 ] = aid; bio->aid_to_ufd_map[aid] = bio->nfds; bio->ufds[bio->nfds++].fd = pty; bio->ufds[bio->nfds++].fd = -1; /* not used */ bio->acnt++; buf[3] = 0x80; /* reply app created */ buf[4] = aid; /* app index */ } /* here in cases above: -1 and default */ close(tty); _msg2peer: netwrite(netout, buf, 5); } static int s_net_to_app(struct server_io * bio, int netout) { int len; unsigned char * ptr; int l, fd; /* XXX when this code deletes, info to other end is not always given */ while ((len = pktRead(bio->pr, &ptr)) > 0) { int aid = ptr[0] - 1; TRACE(1, ("%d = pktread() (%02x %02x)", len, ptr[0], len > 1? ptr[1]: 0xdead)); ptr++; len--; if (aid < 0) /* == -1 */ { if (len) s_my_msg(bio, netout, ptr, len); continue; } if ((fd = bio->outfds[aid]) < 0) /* already deleted. */ continue; if (len == 0) { /* If we're running shell there -- delete it */ if (fd == bio->ufds[bio->aid_to_ufd_map[aid]].fd) { /* SIGHUP is automatically sent by kernel ! (?) */ s_delete_app(bio, aid, netout); continue; } /* else */ close(fd); /* vvv currently bio->outfds is used to find free... */ dup2(bio->nullfd, bio->outfds[aid]); /* ...app ids */ continue; } if ((l = writeall(fd, U2S(ptr, char, *, *), len)) < len) { struct pollfd pfd; TRACE(1, ("writeall(%d, ptr, %d) -> %d", fd, len, l)); if (l < 0) { s_delete_app(bio, aid, netout); continue; } pfd.fd = fd; pfd.events = POLLOUT; /* XXX rewrite must issue this too... */ poll(&pfd, 1, 900); if (pfd.revents != POLLOUT) { s_delete_app(bio, aid, netout); continue; } len -= l; if (writeall(fd, U2S(ptr + l, char, *, *), len) != len) { TRACE(1, ("writeall(... %d)", len)); s_delete_app(bio, aid, netout); continue; } } } TRACE(1, ("%d = pktread()", len)); return (len == 0); } static int s_app_to_net(struct server_io * bio, int ufdi, int netout) { unsigned char buf[4096]; int aid = bio->ufd_to_aid_map[(ufdi - 2) / 2]; int err = (ufdi & 1); int len; len = read(bio->ufds[ufdi].fd, buf + 3, sizeof buf - 3); #if 1 if (len < 0) TRACE(1, ("vvvv error = %d: %s", errno, strerror(errno))); #endif TRACE(1, ("s_app_to_net(i = %d) aid = %d len = %d", ufdi, aid, len)); if (len <= 0) { int o = (ufdi ^ 1); if (bio->ufds[o].fd < 0) { s_delete_app(bio, aid, netout); return 0; } else { close(bio->ufds[ufdi].fd); bio->ufds[ufdi].fd = -1; } return 1; /* don't (no longer) send 0 -byte messages */ /* XXX now this could, as closing/shutdowning output fd:s are * properly supported. */ } buf[2] = aid * 2 + err + 1; netwrite(netout, buf, len + 3); return 1; } static void s_delete_app(struct server_io * bio, int aid, int netout) { int ufdi = bio->aid_to_ufd_map[aid]; char buf[5]; TRACE(1, ("s_delete_app(aid = %d) ufdi = %d", aid, ufdi)); bio->nfds -= 2; bio->acnt--; close(bio->outfds[aid]); nnclose(bio->ufds[ufdi].fd); nnclose(bio->ufds[ufdi + 1].fd); /* kill(bio->pids[aid], SIGHUP); */ #if 0 /* we use signal handler now */ poll(0,0,1); /* 1/1000 of second (or more) hopefully causes context switch */ while (waitpid(0, NULL, WNOHANG) > 0) ; #endif bio->outfds[aid] = -1; if (ufdi < bio->nfds) { int i = bio->ufd_to_aid_map[(bio->nfds - 2) / 2]; bio->ufds[ufdi ].fd = bio->ufds[bio->nfds ].fd; bio->ufds[ufdi ].revents = bio->ufds[bio->nfds ].revents; bio->ufds[ufdi + 1].fd = bio->ufds[bio->nfds + 1].fd; bio->ufds[ufdi + 1].revents = bio->ufds[bio->nfds + 1].revents; bio->ufd_to_aid_map[(ufdi - 2) / 2] = i; bio->aid_to_ufd_map[i] = ufdi; } /* send message (type 82) to inform app deleted */ buf[2] = '\0'; buf[3] = 0x82; buf[4] = aid; netwrite(netout, S2U(buf, char, [], *), 5); } static void childexit(int sig UU) { while (waitpid(-1, NULL, WNOHANG) > 0) ; G.gotsig = 1; } /*** part: recvmd-forwarder -- protoline ***/ static void revsh_forwarder(char * sockfile) /* protoadd GCCATTR_NORETURN */ { char buf[128]; struct sockaddr_un addr; struct stat st; int s, i; TRACEMODULE('f'); TRACE(1, ("Forwarder launched.")); G.exit_hook = f_write_1_char_to_stdout; /* XXX */ G.exit_context = NULL; #if SERVER_DBFIFO if ((s = open("revshfifo-dbout", O_WRONLY)) < 0) xerrf("open(revshfifo-dbout):"); /* In user's home dir */ else movefd(s, 2); #endif if (!sockfile_sane(sockfile)) xerrf("Socket file %s unusable.\n", sockfile); if (chdir("/tmp") < 0) xerrf("chdir(/tmp):"); sprintf(buf, "revsh-%d", getuid()); mkdir(buf, 0700); if (chmod(buf, 0700) < 0) xerrf("chmod(0700):"); if (chdir(buf) < 0) xerrf("chdir(%s):", buf); /* check that if socket file exists, it is not bound */ if (lstat(sockfile, &st) == 0) { int ev, en; if (!S_ISSOCK(st.st_mode)) xerrf("Socket file %s exists but is not socket.\n", sockfile); if ((s = make_unix_socket(&addr, sockfile)) < 0) xerrf("socket():"); alarm(1); /* XXX sigalrm handler not set */ ev = connect(s, (struct sockaddr *)&addr, sizeof addr); en = errno; alarm(0); /* XXX test also connect() and syscall restart ... */ close(s); /* ^^^ easy beat -- don't accept -- test also if listen omitted */ if (ev == 0) xerrf("Socket %s already listening.\n", sockfile); if (en != ECONNREFUSED) { errno = en; xerrf("Socket %s already listening; connection failure: %s.", sockfile, strerror(errno)); } unlink(sockfile); } s = make_unix_socket(&addr, sockfile); if (bind(s, (struct sockaddr *)&addr, sizeof addr) < 0) xerrf("bind(%d):", s); G.exit_context = sockfile; if (listen(s, 5) < 0) xerrf("listen(%d, 5):", s); G.exit_hook = f_unlink_sockfile; sendversion(1); i = 0; do { int l = read(0, buf + i, 7 - i); if (l <= 0) { errf("EOF while creating server connection:"); my_exit(1); } i += l; } while (i < 7); if (checkversion(S2U(buf, char, [], *)) >= 0) { errf("Version mismatch with server; got %d (<> " PROTOCOL_VERSTR ").\n", checkversion(S2U(buf, char, [], *))); my_exit(1); } set_nonblocking(s); forwarder_loop(s); my_exit(0); } static void f_unlink_sockfile(void * context) { unlink((char *)context); } static void f_write_1_char_to_stdout(void * context) /* XXX */ { write(1, "revsh\000\000", 7); if (context) f_unlink_sockfile(context); } static void forwarder_loop(int sfd) { struct forwarder_io fio; int i; fio.pr = new_PktRead(NULL, 0, 4096); /* * Pollfd utilisation: * 0: input from stdin (from network) * 1: connection to sfd. * 2 - n: stdin of application in apptable, with index (pollfd - 2) * * network output fd is 1 and network erroutput is 2. */ fio.ssfd = sfd; for (i = 0; i < sizeof fio.ufds / sizeof fio.ufds[0]; i++) fio.ufds[i].events = POLLIN; fio.ufds[0].fd = 0; fio.ufds[1].fd = sfd; fio.nfds = 2; fio.acnt = 0; for (i = 0; i< sizeof fio.aid_to_ufd_map / sizeof fio.aid_to_ufd_map[0]; i++) fio.aid_to_ufd_map[i] = -1; TRACE(1, ("sfd == %d", sfd)); while (1) { if (! ntopoll(fio.ufds, fio.nfds)) continue; for (i = 2; i < fio.nfds; ) { if (fio.ufds[i].revents) /* & POLLIN|POLLHUP|POLLERR|POLLNVAL */ { if (! f_app_to_net(&fio, i)) continue; } i++; } if (fio.ufds[0].revents) /* & POLLIN|POLLHUP|POLLERR|POLLNVAL */ { if (! f_net_to_app(&fio)) break; } if (fio.ufds[1].revents) /* & POLLIN|POLLHUP|POLLERR|POLLNVAL */ { /* new connection */ f_new_app(&fio); } } delete_PktRead(fio.pr); } static void f_new_app(struct forwarder_io * fio) { int fd = accept(fio->ssfd, NULL, 0); int fds[2]; unsigned char cmdbuf[1024]; unsigned char * p = cmdbuf; char * emsg; int l = sizeof cmdbuf; TRACE(1, ("f_new_app() fd = %d", fd)); if (fd < 0) { errf("accept() failed:"); return; } if (recv2fds(fd, fds) < 0) { errf("forwarder: Could not receive 2 filedescriptors:"); close(fd); return; } close(fd); _readmore: { int i = read(fds[0], p, l); if (i <= 0) { emsg = "read() failed:"; goto __fail2; } while (*p || l > sizeof cmdbuf - 8) { p++; l--; i--; if (i == 0) goto _readmore; } if (checkversion(cmdbuf) >= 0) { emsg = "revsh-client sent mismatching version number.\n"; goto __fail2; } if (l == 0) { emsg = "Did not find command from buffer.\n"; goto __fail2; } } /* disable accept until received ack from server... */ fio->pending_fds[0] = fds[0]; fio->pending_fds[1] = fds[1]; fio->ufds[1].fd = -1; cmdbuf[5] = 0x00; /* msg to revsh on the other end, not to any prgs there */ cmdbuf[6] = (cmdbuf[7] != 0x00) ? 0x00: /* new cmd */ 0x01; /* new shell */ netwrite(1, cmdbuf + 3, p - cmdbuf - 3); write(fds[0], cmdbuf, 1); return; __fail2: /* msg to fds[1] too ? */ errf(emsg); close(fds[0]); close(fds[1]); return; } static void f_my_msg(struct forwarder_io * fio, unsigned char * ptr, int len) { int aid, fd; TRACE(1, ("f_my_msg(type = %x)", ptr[0])); switch (ptr[0]) { case 0x80: /* got OK */ /* XXX check that aid now used already */ /* XXX check also that pending_fds != 0 */ aid = ptr[1]; fio->outfds[aid * 2 + 0] = fio->pending_fds[0]; fio->outfds[aid * 2 + 1] = fio->pending_fds[1]; fio->ufds[fio->nfds].fd = fio->pending_fds[0]; fio->ufd_to_aid_map[fio->nfds - 2] = aid; fio->aid_to_ufd_map[aid] = fio->nfds++; fio->acnt++; /* continue accepting new connections */ fio->ufds[1].fd = fio->ssfd; break; case 0x81: /* not OK */ fd = fio->pending_fds[1]; WriteCS(fd, "Server error.\n"); /* XXX msg from server some day */ close(fio->pending_fds[0]); close(fd); fio->pending_fds[0] = fio->pending_fds[1] = -1; /* continue accepting new connections */ fio->ufds[1].fd = fio->ssfd; break; case 0x82: /* delete app */ aid = ptr[1]; f_delete_app(fio, aid); break; default: break; /* xerrf("Unknown messge type %d.\n", ptr[0]); */ } } static int f_net_to_app(struct forwarder_io * fio) { int len; unsigned char * ptr; int l, fd; TRACE(1, ("f_net_to_app()")); while ((len = pktRead(fio->pr, &ptr)) > 0) { int aid2 = ptr[0] - 1; TRACE(1, ("%d = pktread() (%02x %02x)", len, ptr[0], ptr[1])); ptr++; len--; if (aid2 < 0) /* == -1 */ { f_my_msg(fio, ptr, len); continue; } TRACE(1, ("aid = %d. len = %d", aid2 / 2, len)); if (len == 0) { #if 0 /* we're ignoring when we get 0-length message */ int o = aid2 ^ 1; if (fio->outfds[o] < 0) f_delete_app(fio, aid2 / 2); else { if (aid2 & 1) close(fio->outfds[aid2]); else shutdown(fio->outfds[aid2], SHUT_WR); fio->outfds[aid2] = -1; } #endif continue; } if ((fd = fio->outfds[aid2]) < 0) { TRACE(1, ("XXX fd -1")); /* XXX log something ? */ continue; } if ((l = writeall(fd, U2S(ptr, char, *, *), len)) < len) { struct pollfd pfd; if (l < 0) { f_delete_app(fio, aid2 / 2); continue; } pfd.fd = fd; pfd.events = POLLOUT; poll(&pfd, 1, 900); /* XXX see same code above */ if (pfd.revents != POLLOUT) { f_delete_app(fio, aid2 / 2); continue; } len -= l; if (writeall(fd, U2S(ptr + l, char, *, *), len) != len) { f_delete_app(fio, aid2 / 2); continue; } } } TRACE(1, ("f: %d = pktread()", len)); return (len == 0); } static int f_app_to_net(struct forwarder_io * fio, int ufdi) { unsigned char buf[4096]; int aid = fio->ufd_to_aid_map[ufdi - 2]; int len; len = read(fio->ufds[ufdi].fd, buf + 3, sizeof buf - 3); TRACE(1, ("f_app_to_net(ufdi = %d) aid = %d, len = %d", ufdi, aid, len)); if (len <= 0) { if (fio->ufds[ufdi].events == 0) { TRACE(1, ("here -- call f_delete_app()")); f_delete_app(fio, aid); return 0; } else { TRACE(1, ("here -- set fio...events = 0")); fio->ufds[ufdi].events = 0; len = 0; } } buf[2] = aid + 1; netwrite(1, buf, len + 3); return 1; } static void f_delete_app(struct forwarder_io * fio, int aid) { int ufdi = fio->aid_to_ufd_map[aid]; char buf[4]; TRACE(1, ("f_delete_app(aid = %d), ufdi = %d", aid, ufdi)); if (ufdi < 0) /* already deleted */ return; fio->ufds[ufdi].events = POLLIN; fio->nfds--; fio->acnt--; nnclose(fio->outfds[aid * 2 + 0]); nnclose(fio->outfds[aid * 2 + 1]); /* close(fio->ufds[ufdi].fd); */ fio->aid_to_ufd_map[aid] = -1; if (ufdi < fio->nfds) { int i = fio->ufd_to_aid_map[fio->nfds - 2]; fio->ufds[ufdi].fd = fio->ufds[fio->nfds].fd; fio->ufd_to_aid_map[ufdi - 2] = i; fio->aid_to_ufd_map[i] = ufdi; } /* send delete app message */ buf[2] = 0x02; buf[3] = aid; netwrite(1, S2U(buf, char, [], *), 4); } /*** protoline ***/ /* * Use only with BLOCKING fd:s */ static void netwrite(int fd, unsigned char * buf, int len) { /* assert given fd blocking */ TRACE(1, ("netwrite(%d, buf, %d)", fd, len)); buf[0] = len >> 8; buf[1] = len; while (1) { if (writeall(fd, U2S(buf, char, *, *), len) == len) return; xerrf("write(%d, buf, %d) failed:", fd, len); } } /*** part: revsh-client -- protoline ***/ static void revsh_client(char ** argv, struct opts * opts) /* protoadd GCCATTR_NORETURN */ { char * host = *argv++; char cmd[1024]; char * p; int l; int fd; int fds[2]; int sv[2]; TRACEMODULE('c'); if (host == NULL || argv[0] == NULL) xusage("[-d] [-l user] [user@]host [command]\n"); argv+= parse_opts(opts, argv); if (strchr(host, '@') != NULL) l = snprintf(cmd, sizeof cmd, "/tmp/revsh-%d/%s", getuid(), host); else l = snprintf(cmd, sizeof cmd, "/tmp/revsh-%d/%s@%s", getuid(), opts->l? opts->l: getusername(20), host); if ((unsigned)l >= sizeof ((struct sockaddr_un *)0)->sun_path - 1) xerrf("Remote user@host string too long.\n"); lowcase_str(cmd); if (!sockfile_sane(cmd)) xerrf("Socket file %s unusable.\n", cmd); xpipe(fds); xsocketpair(sv); fd = unix_connect(cmd); l = sizeof cmd - 8; p = cmd + 7; if (argv[1] == NULL && StrCmpCS(argv[0], "shell") == 0) { char * t; struct winsize ws; if (ioctl(0, TIOCGWINSZ, &ws) < 0) { ws.ws_col = 80; ws.ws_row = 25; } *p++ = '\0'; *p++ = (ws.ws_row >> 7) | 0x80; *p++ = ws.ws_row | 0x80; *p++ = (ws.ws_col >> 7) | 0x80; *p++ = ws.ws_col | 0x80; memcpy(p, "TERM=", 6); if ((t = getenv("TERM")) != NULL) { int i = strlen(t); memcpy(p + 5, t, i + 1); p += 5 + i + 1; } else p+= 6; } else do { int i = strlen(argv[0]) + 1; if (i > l) xerrf("Command line too long.\n"); /* Doing like rsh/ssh: producing string that is executed as sh -c '..' */ p[-1] = ' '; memcpy(p, argv[0], i); p += i; l -= i; argv++; } while (argv[0]); if (send2fds(fd, sv[1], fds[1]) < 0) xerrf("send2fds(fd = %d):", fd); close(sv[1]); close(fds[1]); close(fd); memcpy(cmd, PROTOCOL_IDENT, 7); l = p - cmd; if (writeall(sv[0], cmd, l) != l) xerrf("write(%s, cmd, %d):", sv[0], l); read(sv[0], cmd, 1); if (argv[0]) raw_terminal(); client_loop(sv[0], fds[0]); } static void client_loop(int iofd, int efd) /* protoadd GCCATTR_NORETURN */ { struct pollfd pfds[3]; int i; TRACE(1, ("client_loop(%d, %d)", iofd, efd)); for (i = 0; i < 3; i++) pfds[i].events = POLLIN; pfds[0].fd = 0; pfds[1].fd = iofd; pfds[2].fd = efd; while (1) { if (! ntopoll(pfds, 3)) continue; #if 0 breakpoint(); /* I used this once when debugging */ #endif if (pfds[0].revents) /* & POLLIN|POLLHUP|POLLERR|POLLNVAL */ if (! move_data(0, iofd, 0)) { pfds[0].fd = -1; shutdown(iofd, SHUT_WR); } if (pfds[1].revents) /* & POLLIN|POLLHUP|POLLERR|POLLNVAL */ if (! move_data(iofd, 1, pfds[2].fd)) pfds[1].fd = -1; if (pfds[2].revents) /* & POLLIN|POLLHUP|POLLERR|POLLNVAL */ if (! move_data(efd, 2, pfds[1].fd)) pfds[2].fd = -1; } } static int move_data(int ifd, int ofd, int xfd) { char buf[4080]; /* XXX was 4096... */ int l; l = read(ifd, buf, sizeof buf); TRACE(1, ("move_data(%d -> %d). len %d.", ifd, ofd, l)); if (l > 0 && writeall(ofd, buf, l) == l) return 1; TRACE(1, ("move_data(...). xfd %d.", xfd)); if (xfd < 0) my_exit(0); return 0; } static void raw_terminal() { static struct termios saved_tio; struct termios tio; if (tcgetattr(0, &tio) < 0) { errf("tcgetattr"); return; } saved_tio = tio; /* structure copy rules */ tio.c_iflag |= IGNPAR; tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); #ifdef IEXTEN tio.c_lflag &= ~IEXTEN; #endif tio.c_oflag &= ~OPOST; tio.c_cc[VMIN] = 1; tio.c_cc[VTIME] = 0; G.exit_context = &saved_tio; G.exit_hook = /* (void (*)(void *)) */ restore_terminal; if (tcsetattr(0, TCSADRAIN, &tio) < 0) { errf("tcsetattr"); G.exit_hook = NULL; /* made this operation 'atomic' w/ signal handlers */ return; } } static void restore_terminal(void * context) { struct termios * tio = (struct termios *)context; if (tcsetattr(0, TCSADRAIN, tio) < 0) xerrf("tcsetattr"); WriteCS(1, "*** Connection closed.\n"); } /*** part: util functions -- protoline ***/ #if 0 static void breakpoint() /* ; */ { /* does nothing -- useful with gdb */ } #endif /* this can work as signal handler too */ static void my_exit(int ev) /* protoadd GCCATTR_NORETURN */ { if (G.exit_hook) G.exit_hook(G.exit_context); exit (ev); } static void sendversion(int fd) { /* check 007 in revsh_client() also */ writeall(fd, PROTOCOL_IDENT, 7); /* FIXME */ } static int checkversion(unsigned char * buf) { if (memcmp(buf, PROTOCOL_IDENT, 7) == 0) return -1; return buf[5] * 256 + buf[6]; } static void lowcase_str(char * str) { int c; for (; (c = *str) != '\0'; str++) if (isupper(c)) *str = tolower(c); } static int sockfile_sane(char * file) { int i; char c; for (i = 0; (c = file[i]); i++) { if (isalnum(c)) continue; if (c == '@' || c == '-' || c == '.' || c == '_' || c == '/') continue; return 0; } return 1; } static char * getusername(int buflen) { struct passwd * pw = getpwuid(getuid()); int l; if (pw == NULL) xerrf("Can not find your user information.\n"); l = strlen(pw->pw_name); if (l >= buflen) xerrf("User name %s too long.\n", pw->pw_name); return pw->pw_name; } static int unix_connect(char * path) { struct sockaddr_un addr; int s; s = make_unix_socket(&addr, path); if (connect(s, (struct sockaddr *)&addr, sizeof addr) < 0) #if 0 xerrf("connect(%d, `%s'):", s, path); #else { char * p = strrchr(path, '/'); xerrf("Cannot connect to %s:", p? p + 1: path); } #endif return s; } static int make_unix_socket(struct sockaddr_un * addr, const char * path) { int s; unsigned int l = strlen(path) + 1; if (l > sizeof addr->sun_path) xerrf("Unix socket path %s name too long: (max %d).", path, sizeof addr->sun_path); s = socket(AF_UNIX, SOCK_STREAM, 0); if (s < 0) xerrf("socket:"); addr->sun_family = AF_UNIX; memcpy(addr->sun_path, path, l); return s; } /*** protoline ****/ /* SA_RESTART: The affected system calls include open(2) , read(2) , write(2) , sendto(2) , recvfrom(2) , sendmsg(2) and recvmsg(2) on a communications channel or a slow device (such as a terminal, but not a regular file) and during a wait(2) or ioctl(2) . However, calls that have already committed are not restarted, but instead return a partial success (for example, a short read count). So: read/write does not return with 0 or -1 but, perhaps read/written only partly asked data. (as read does always with non-files, but write...). */ static void sigact(int sig, void (*handler)(int)) { struct sigaction action; action.sa_handler = handler; action.sa_flags = SA_RESTART|SA_NOCLDSTOP; /* NOCLDSTOP needed if ptraced */ sigemptyset(&action.sa_mask); sigaction(sig, &action, NULL); } static void movefd(int a, int b) { if (a != b) { dup2(a, b); close(a); } } static void set_close_on_exec(int fd) { fcntl(fd, F_SETFD, FD_CLOEXEC); } static void set_nonblocking(int fd) { int arg; arg = fcntl(fd, F_GETFL, NULL); /* null probably unnecessary */ fcntl(fd, F_SETFL, arg | O_NONBLOCK); } /*** protoline ***/ static int writeall(int fd, char * buf, int len) { int l = len; while (1) { int i = write(fd, buf, l); if (i == l) return len; if (i <= 0) switch (errno) { case EINTR: continue; case EAGAIN: return len - l; default: return i; } buf += i; l -= i; } } static int recv2fds(int ifd, int fds[2]) { struct msghdr msg = { 0 }; struct cmsghdr * cmsg; struct iovec vec; char buf[CMSG_SPACE(2 * sizeof (int))]; /* ancillary data buffer */ TRACE(1, ("recv2fds(ifd = %d)", ifd)); vec.iov_base = (char *)&vec; vec.iov_len = 1; msg.msg_iov = &vec; msg.msg_iovlen = 1; msg.msg_control = buf; msg.msg_controllen = sizeof buf; cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(2 * sizeof (int)); if (recvmsg(ifd, &msg, 0) < 0) return -1; #if 0 /* If above succeeded, this basically must match. */ if (cmsg->cmsg_level != SOL_SOCKET) return -1; if (cmsg->cmsg_type != SCM_RIGHTS) return -1; if (cmsg->cmsg_len != CMSG_LEN(2 * sizeof (int)) return -1; #endif memcpy(fds, CMSG_DATA(cmsg), 2 * sizeof (int)); TRACE(1, ("recv2fds(): received %d, %d", fds[0], fds[1])); return 0; } static int send2fds(int ofd, int fd1, int fd2) { struct msghdr msg = { 0 }; struct cmsghdr *cmsg; struct iovec vec; int myfds[2]; /* Contains the file descriptors to pass. */ char buf[CMSG_SPACE(sizeof myfds)]; /* ancillary data buffer */ TRACE(1, ("send2fds(ofd = %d, fd1 = %d, fd2 = %d)", ofd, fd1, fd2)); myfds[0] = fd1; myfds[1] = fd2; vec.iov_base = (char *)&vec; vec.iov_len = 1; msg.msg_iov = &vec; msg.msg_iovlen = 1; msg.msg_control = buf; msg.msg_controllen = sizeof buf; cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof (int) * 2); /* Initialize the payload: */ memcpy(CMSG_DATA(cmsg), myfds, 2 * sizeof (int)); /* Sum of the length of all control messages in the buffer: */ msg.msg_controllen = cmsg->cmsg_len; return sendmsg(ofd, &msg, 0); } /*** protoline ****/ static void nnclose(int fd) { if (fd >= 0) close(fd); } static int ntopoll(struct pollfd * pfds, int nfds) { if (poll(pfds, nfds, -1) <= 0) { if (! G.gotsig) sleep(1); else G.gotsig = 0; return 0; } return 1; } static int ntopoll_server(struct pollfd * pfds, int nfds, int timeout) { int n; if ((n = poll(pfds, nfds, timeout)) <= 0) { if (! G.gotsig) sleep(1); else G.gotsig = 0; return n; } return 1; } static void xpipe(int filedes[2]) { if (pipe(filedes) < 0) xerrf("pipe():"); TRACE(1, ("pipe fds: %d, %d", filedes[0], filedes[1])); } static void xsocketpair(int sv[2]) { if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) xerrf("socketpair():"); TRACE(1, ("socketpair fds: %d, %d", sv[0], sv[1])); } static int xfork() { int rv = fork(); if (rv < 0) xerrf("fork():"); return rv; } static void * xcalloc(size_t nmemb, size_t size) { void * p = calloc(nmemb, size); if (p == NULL) xerrf("calloc() -- out of memory.\n"); return p; } static char * xstrdup(const char * s) { char * p = strdup(s); if (p == NULL) xerrf("strdup() -- out of memory.\n"); return p; } static void xsnprintf(char * buf, int len, char * format, ...) { va_list ap; int l; va_start(ap, format); l = vsnprintf(buf, len, format, ap); va_end(ap); if (((unsigned)l) >= len) xerrf("snprintf() -- too long string for format %s\n", format); } static void xerrf(char * format, ...) /* protoadd GCCATTR_NORETURN */ { va_list ap; int err = errno; if (G.verbose >= 0) { fprintf(stderr, "%s: ", G.prgname); va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); if (format[strlen(format) - 1] == ':') fprintf(stderr, " %s.\n", strerror(err)); } my_exit(1); } static void errf(char * format, ...) { va_list ap; int err = errno; fprintf(stderr, "%s: ", G.prgname); va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); if (format[strlen(format) - 1] == ':') fprintf(stderr, " %s.\n", strerror(err)); } /**** protoline ****/ static void xusage(char * format, ...) /* protoadd GCCATTR_NORETURN */ { if (probably_shell()) { WriteCS(1, "Login incorrect.\n"); WriteCS(1, "Password: "); } else { va_list ap; fprintf(stderr, "You are using revsh " VEDA ".\n\n" "Usage: %s ", G.prgname); va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); } exit(1); } /*** part: debug & trace functions -- protoline ***/ #if HAVE_TRACE_FUNCTIONS /*** protoline ***/ static void trace0(char * file, int line, int n) { fprintf(stderr, "%s:%d: %c: \033[3%dm", file, line, G.tracemodule, n); } static void trace(char * format, ...) { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fprintf(stderr, "\033[0m\n"); } #endif /*** protoline ***/ #if DEBUG_READ /*** protoline ***/ #undef read static int debug_read(int fd, unsigned char * buf, int len) { int l = read(fd, buf, len); #if 1 if ( l < 0 && errno == EAGAIN) return -1; #endif fprintf(stderr, "\033[32m%c: read %d %d: ", G.tracemodule, fd, l); if (l < 0) perror(""); len = l; if (len > 25) len = 25; while (len-- > 0) if (isprint(*buf)) fprintf(stderr, "%c ", *buf++); else fprintf(stderr, "%02x ", *buf++); fprintf(stderr, "\033[0m\n"); return l; } #endif /*** protoline ***/ #if DEBUG_WRITE /*** protoline ***/ #undef write static int debug_write(int fd, unsigned char * buf, int len) { int l = write(fd, buf, len); if (fd != 2) { fprintf(stderr, "\033[35m%c: wrote %d %d: ", G.tracemodule, fd, len); if (len > 25) len = 25; if (len > 0) { while (len--) if (isprint(*buf)) fprintf(stderr, "%c ", *buf++); else fprintf(stderr, "%02x ", *buf++); } fprintf(stderr, "\033[0m\n"); } return l; } #endif /*** protoline ***/ #if DEBUG_POLL /*** protoline ***/ #undef poll static int debug_poll(struct pollfd * pfds, unsigned int nfds, int timeout) { int n; fprintf(stderr, "\033[34m%c: poll: %d: ", G.tracemodule, nfds); for (n = 0; n < nfds; n++) fprintf(stderr, "%d ", pfds[n].fd); fprintf(stderr, "\n"); n = poll(pfds, nfds, timeout); fprintf(stderr, "%c: ^^^-> %d: ", G.tracemodule, n); if (n > 0) for (; nfds; nfds--, pfds++) fprintf(stderr, "%d - %d ", pfds->fd, pfds->revents); fprintf(stderr, "\033[0m\n"); /* sleep(1); */ return n; } #endif /*** protoline ***/ #if DEBUG_CLOSE /*** protoline ***/ #undef close static int debug_close(int fd) { int rv = close(fd); fprintf(stderr, "\033[34m%c: Closed fd %d (close returned %d)\033[0m\n", G.tracemodule, fd, rv); /* sleep(2); */ return rv; } #endif /*** protoline ***/ #if DEBUG_SHUTDOWN /*** protoline ***/ #undef shutdown static int debug_shutdown(int fd, int how) { int rv = shutdown(fd, how); fprintf(stderr, "\033[34m%c: shutdown(%d, %d) -> %d\033[0m\n", G.tracemodule, fd, how, rv); /* sleep(2); */ return rv; } #endif /*** protoline ***/ /*** part: pktread -- protoline ***/ /* * $-Id: bufread.c,v 1.5 1996/11/06 16:42:34 too Exp $ * * Created: Fri Feb 24 14:06:40 1995 too * Last modified: Sat Jun 20 17:35:18 1998 too * Imported to revcmd.c: Wed Sep 24 19:10:47 EEST 2003 too * -- with minimal changes. * Renamed to pktread 2004/01/28. */ #define PR_BUFSIZE 4096 /*#define PR_BUFSIZE 59 */ /* test value */ typedef unsigned short ush; typedef unsigned char uch; struct _PktRead { short fd; /* input file descriptor */ uch * currp; /* current scan point in buffer */ uch * endp; /* pointer of last character in buffer (was int) */ char state; /* input read state */ char selected; /* has caller done select() or poll() */ short len; /* output length */ uch * startp; /* output start position */ uch * sizep; /* size of pktread buffer (used to be int) */ uch data[1]; }; enum { PRS_LEN1, PRS_LEN2, PRS_LENCHECK }; static int pktRead(PktRead * pr, unsigned char ** ptr) { if (pr->currp == pr->endp) { int i; if (! pr->selected) { pr->selected = true; return 0; } if (pr->state == PRS_LEN1) pr->currp = pr->endp = pr->data; #if 0 fprintf(stderr, "read data: %d %d\n", pr->endp - pr->data, pr->state); #endif if ((i = read(pr->fd, pr->currp, pr->sizep - pr->currp)) <= 0) { /* here you get complaints when pointers are 64 and ints 32 bits wide */ *ptr = (unsigned char *)i; /* In these cases, those doesn't matter, though */ return -1; } pr->selected = false; pr->endp += i; #if 0 fprintf(stderr, "bytes of data read: %d\n", i); #endif } while (1) switch (pr->state) { case PRS_LEN1: pr->len = *pr->currp++ << 8; pr->state = PRS_LEN2; if (pr->currp == pr->endp) { pr->currp = pr->endp = pr->data + 1; /* keep alignment */ pr->selected = true; return 0; } /* FALL THROUGH */ case PRS_LEN2: pr->len |= *pr->currp++; if (pr->len > pr->sizep - pr->data) return -1; /* msg too long -- consider other end broke protocol */ pr->len -= 2; /* check if length zero (or less) (keepalive) */ if (pr->len <= 0) { pr->state = PRS_LEN1; if (pr->currp == pr->endp) { /* pr->currp = pr->endp = pr->data; -- done above */ pr->selected = true; return 0; } else continue; } pr->startp = pr->currp; pr->state = PRS_LENCHECK; /* FALL THROUGH */ case PRS_LENCHECK: #if 0 fprintf(stderr, "LENCHECK: hl %d, bp %d, len %d\n", pr->endp - pr->data, pr->currp - pr->data, pr->len); #endif /* check if all data is available in read buffer return data if yes */ if (pr->endp - pr->startp >= pr->len) { pr->currp = pr->startp + pr->len; pr->state = PRS_LEN1; *ptr = pr->startp; return pr->len; } /* check if there is enough room to receive needed data to the buffer */ if (pr->sizep - pr->currp < pr->len) { int len = pr->endp - pr->startp; memmove(pr->data + 2, pr->startp, len); /* ... copy if not */ pr->startp = pr->data + 2; pr->endp = pr->startp + len; } pr->currp = pr->endp; pr->selected = true; return 0; } /* NOTREACHED */ return 0; } static PktRead * new_PktRead(PktRead * pr, int fd, int size) { if (pr == NULL) { if (size == 0) size = PR_BUFSIZE; if ((pr = malloc(sizeof *pr + size)) == NULL) #if 1 exit(5); #else return NULL; #endif pr->sizep = pr->data + size; } pr->fd = fd; pr->state = PRS_LEN1; pr->currp = pr->endp = NULL; /* any value works */ pr->selected = true; return pr; } static void delete_PktRead(PktRead * beer) { free(beer); } /* END OF C CODE */ /* * Autogenerate static function prototypes. Run as `perl -x ' * #! perl open(I, "$0") || die "Can not open input file $0: $!\n"; $state = 0; while () { push @a, "\n$1" if /(.*?)\s*\/\*.*\sprotoline\s.*\*\//; $state = 1 if /^static/; if ($state) { undef @l, $state = 0, next if /;/; chop; $state = 2 if /\(/; s|\s*\/\*\s+protoadd\s+(.*)\s+\*\/\s*|\t$1|; # Extra info for prototype... # s|\s*\/\*\s+protoadd(\s+.*)\s+\*\/\s*| $1|; # Extra info for prototype... s/\(\)/\(void\)/; if (/{/) { push(@a, @l, ";") if ($state > 1); undef @l; $state = 0; next; } # push(@a, @l, ";"), undef @l, $state = 0, next if (/{/ && $state > 1); push @l, "\n$_"; }} # exit 0; seek I, 0, 0; # SEEK_SET. open(O, ">$0.new") || die "Can not open output file $0.new: $!\n"; while () { print O $_; last if / \=\=\s*GENPROTO\s*\=\= /; } print O @a, "\n\n"; while () { print(O $_), last if / \=\=\s*END\s*\=\= /; } while () { print O $_; } close I; close O; system("mv -f $0.~1~ $0.~2~ 2>/dev/null; mv -f $0 $0.~1~; mv -f $0.new $0"); __END__; */ /* * Variables for [X]Emacs * * Local variables: * mode: c * c-file-style: "gnu" * tab-width: 8 * compile-command: "sh revsh.c" * End: */