/* Typical idiom for applets which exec *optional* PROG [ARGS] */ void FAST_FUNC exec_prog_or_SHELL(char **argv) { if (argv[0]) { BB_EXECVP_or_die(argv); } run_shell(getenv("SHELL"), /*login:*/ 1, NULL); }
void FAST_FUNC launch_helper(const char **argv) { // setup vanilla unidirectional pipes interchange int i; int pipes[4]; xpipe(pipes); xpipe(pipes + 2); // NB: handler must be installed before vfork bb_signals(0 + (1 << SIGCHLD) + (1 << SIGALRM) , signal_handler); G.helper_pid = xvfork(); i = (!G.helper_pid) * 2; // for parent:0, for child:2 close(pipes[i + 1]); // 1 or 3 - closing one write end close(pipes[2 - i]); // 2 or 0 - closing one read end xmove_fd(pipes[i], STDIN_FILENO); // 0 or 2 - using other read end xmove_fd(pipes[3 - i], STDOUT_FILENO); // 3 or 1 - other write end if (!G.helper_pid) { // child: try to execute connection helper // NB: SIGCHLD & SIGALRM revert to SIG_DFL on exec BB_EXECVP_or_die((char**)argv); } // parent // check whether child is alive //redundant:signal_handler(SIGCHLD); // child seems OK -> parent goes on atexit(kill_helper); }
int nohup_main(int argc UNUSED_PARAM, char **argv) { const char *nohupout; char *home; xfunc_error_retval = 127; if (!argv[1]) { bb_show_usage(); } /* If stdin is a tty, detach from it. */ if (isatty(STDIN_FILENO)) { /* bb_error_msg("ignoring input"); */ close(STDIN_FILENO); xopen(bb_dev_null, O_RDONLY); /* will be fd 0 (STDIN_FILENO) */ } nohupout = "nohup.out"; /* Redirect stdout to nohup.out, either in "." or in "$HOME". */ if (isatty(STDOUT_FILENO)) { close(STDOUT_FILENO); if (open(nohupout, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR) < 0) { home = getenv("HOME"); if (home) { nohupout = concat_path_file(home, nohupout); xopen3(nohupout, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR); } else { xopen(bb_dev_null, O_RDONLY); /* will be fd 1 */ } } bb_error_msg("appending output to %s", nohupout); } /* If we have a tty on stderr, redirect to stdout. */ if (isatty(STDERR_FILENO)) { /* if (stdout_wasnt_a_tty) bb_error_msg("redirecting stderr to stdout"); */ dup2(STDOUT_FILENO, STDERR_FILENO); } signal(SIGHUP, SIG_IGN); argv++; BB_EXECVP_or_die(argv); }
int chroot_main(int argc UNUSED_PARAM, char **argv) { ++argv; if (!*argv) bb_show_usage(); xchroot(*argv); ++argv; if (!*argv) { /* no 2nd param (PROG), use shell */ argv -= 2; argv[0] = (char *) get_shell_name(); argv[1] = (char *) "-i"; /* GNU coreutils 8.4 compat */ /*argv[2] = NULL; - already is */ } BB_EXECVP_or_die(argv); }
int runcon_main(int argc UNUSED_PARAM, char **argv) { char *role = NULL; char *range = NULL; char *user = NULL; char *type = NULL; char *context = NULL; unsigned opts; context_t con; selinux_or_die(); opts = getopt32long(argv, "^" "r:t:u:l:ch" "\0" "-1", runcon_longopts, &role, &type, &user, &range ); argv += optind; if (!(opts & OPTS_CONTEXT_COMPONENT)) { context = *argv++; if (!argv[0]) bb_error_msg_and_die("no command given"); } if (context) { con = context_new(context); if (!con) bb_error_msg_and_die("'%s' is not a valid context", context); } else { con = runcon_compute_new_context(user, role, type, range, argv[0], opts & OPTS_COMPUTE); } if (security_check_context(context_str(con))) bb_error_msg_and_die("'%s' is not a valid context", context_str(con)); if (setexeccon(context_str(con))) bb_error_msg_and_die("can't set up security context '%s'", context_str(con)); BB_EXECVP_or_die(argv); }
int setarch_main(int argc UNUSED_PARAM, char **argv) { unsigned opts; unsigned long pers; /* Figure out what personality we are supposed to switch to ... * we can be invoked as either: * argv[0],argv[1] == "setarch","personality" * argv[0] == "personality" */ if (ENABLE_SETARCH && applet_name[0] == 's' && argv[1] && is_prefixed_with(argv[1], "linux") ) { applet_name = argv[1]; argv++; } if (applet_name[5] == '6') /* linux64 */ pers = PER_LINUX; else if (applet_name[5] == '3') /* linux32 */ pers = PER_LINUX32; else bb_show_usage(); opts = getopt32(argv, "+R"); /* '+': stop at first non-option */ if (opts) pers |= ADDR_NO_RANDOMIZE; /* Try to set personality */ if (personality(pers) < 0) bb_perror_msg_and_die("personality(0x%lx)", pers); argv += optind; if (!argv[0]) (--argv)[0] = (char*)"/bin/sh"; /* Try to execute the program */ BB_EXECVP_or_die(argv); }
void FAST_FUNC launch_helper(const char **argv) { // setup vanilla unidirectional pipes interchange int i; int pipes[4]; xpipe(pipes); xpipe(pipes + 2); // NB: handler must be installed before vfork bb_signals(0 + (1 << SIGCHLD) + (1 << SIGALRM) , signal_handler); G.helper_pid = xvfork(); i = (!G.helper_pid) * 2; // for parent:0, for child:2 close(pipes[i + 1]); // 1 or 3 - closing one write end close(pipes[2 - i]); // 2 or 0 - closing one read end xmove_fd(pipes[i], STDIN_FILENO); // 0 or 2 - using other read end xmove_fd(pipes[3 - i], STDOUT_FILENO); // 3 or 1 - using other write end // End result: // parent stdout [3] -> child stdin [2] // child stdout [1] -> parent stdin [0] if (!G.helper_pid) { // child // if parent dies, get SIGTERM prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0); // try to execute connection helper // NB: SIGCHLD & SIGALRM revert to SIG_DFL on exec BB_EXECVP_or_die((char**)argv); } // parent goes on }
/* Run command CMD and return statistics on it. Put the statistics in *RESP. */ static void run_command(char *const *cmd, resource_t *resp) { pid_t pid; void (*interrupt_signal)(int); void (*quit_signal)(int); resp->elapsed_ms = monotonic_ms(); pid = xvfork(); if (pid == 0) { /* Child */ BB_EXECVP_or_die((char**)cmd); } /* Have signals kill the child but not self (if possible). */ //TODO: just block all sigs? and reenable them in the very end in main? interrupt_signal = signal(SIGINT, SIG_IGN); quit_signal = signal(SIGQUIT, SIG_IGN); resuse_end(pid, resp); /* Re-enable signals. */ signal(SIGINT, interrupt_signal); signal(SIGQUIT, quit_signal); }
static int parse(const char *boundary, char **argv) { char *line, *s, *p; const char *type; int boundary_len = strlen(boundary); const char *delims = " ;\"\t\r\n"; const char *uniq; int ntokens; const char *tokens[32]; // 32 is enough // prepare unique string pattern uniq = xasprintf("%%llu.%u.%s", (unsigned)getpid(), safe_gethostname()); //bb_info_msg("PARSE[%s]", uniq); while ((line = xmalloc_fgets_str(stdin, "\r\n\r\n")) != NULL) { // seek to start of MIME section // N.B. to avoid false positives let us seek to the _last_ occurance p = NULL; s = line; while ((s = strcasestr(s, "Content-Type:")) != NULL) p = s++; if (!p) goto next; //bb_info_msg("L[%s]", p); // split to tokens // TODO: strip of comments which are of form: (comment-text) ntokens = 0; tokens[ntokens] = NULL; for (s = strtok(p, delims); s; s = strtok(NULL, delims)) { tokens[ntokens] = s; if (ntokens < ARRAY_SIZE(tokens) - 1) ntokens++; //bb_info_msg("L[%d][%s]", ntokens, s); } tokens[ntokens] = NULL; //bb_info_msg("N[%d]", ntokens); // analyse tokens type = find_token(tokens, "Content-Type:", "text/plain"); //bb_info_msg("T[%s]", type); if (0 == strncasecmp(type, "multipart/", 10)) { if (0 == strcasecmp(type+10, "mixed")) { parse(xfind_token(tokens, "boundary="), argv); } else bb_error_msg_and_die("no support of content type '%s'", type); } else { pid_t pid = pid; int rc; FILE *fp; // fetch charset const char *charset = find_token(tokens, "charset=", CONFIG_FEATURE_MIME_CHARSET); // fetch encoding const char *encoding = find_token(tokens, "Content-Transfer-Encoding:", "7bit"); // compose target filename char *filename = (char *)find_token(tokens, "filename=", NULL); if (!filename) filename = xasprintf(uniq, monotonic_us()); else filename = bb_get_last_path_component_strip(xstrdup(filename)); // start external helper, if any if (opts & OPT_X) { int fd[2]; xpipe(fd); pid = vfork(); if (0 == pid) { // child reads from fd[0] close(fd[1]); xmove_fd(fd[0], STDIN_FILENO); xsetenv("CONTENT_TYPE", type); xsetenv("CHARSET", charset); xsetenv("ENCODING", encoding); xsetenv("FILENAME", filename); BB_EXECVP_or_die(argv); } // parent dumps to fd[1] close(fd[0]); fp = xfdopen_for_write(fd[1]); signal(SIGPIPE, SIG_IGN); // ignore EPIPE // or create a file for dump } else { char *fname = xasprintf("%s%s", *argv, filename); fp = xfopen_for_write(fname); free(fname); } // housekeeping free(filename); // dump to fp if (0 == strcasecmp(encoding, "base64")) { read_base64(stdin, fp, '-'); } else if (0 != strcasecmp(encoding, "7bit") && 0 != strcasecmp(encoding, "8bit") ) { // quoted-printable, binary, user-defined are unsupported so far bb_error_msg_and_die("no support of encoding '%s'", encoding); } else { // N.B. we have written redundant \n. so truncate the file // The following weird 2-tacts reading technique is due to // we have to not write extra \n at the end of the file // In case of -x option we could truncate the resulting file as // fseek(fp, -1, SEEK_END); // if (ftruncate(fileno(fp), ftell(fp))) // bb_perror_msg("ftruncate"); // But in case of -X we have to be much more careful. There is // no means to truncate what we already have sent to the helper. p = xmalloc_fgets_str(stdin, "\r\n"); while (p) { s = xmalloc_fgets_str(stdin, "\r\n"); if (s == NULL) break; if ('-' == s[0] && '-' == s[1] && 0 == strncmp(s+2, boundary, boundary_len) ) { break; } fputs(p, fp); p = s; } /* while ((s = xmalloc_fgetline_str(stdin, "\r\n")) != NULL) { if ('-' == s[0] && '-' == s[1] && 0 == strncmp(s+2, boundary, boundary_len)) break; fprintf(fp, "%s\n", s); } // N.B. we have written redundant \n. so truncate the file fseek(fp, -1, SEEK_END); if (ftruncate(fileno(fp), ftell(fp))) bb_perror_msg("ftruncate"); */ } fclose(fp); // finalize helper if (opts & OPT_X) { signal(SIGPIPE, SIG_DFL); // exit if helper exited >0 rc = (wait4pid(pid) & 0xff); if (rc) return rc+20; } // check multipart finalized if (s && '-' == s[2+boundary_len] && '-' == s[2+boundary_len+1]) { free(line); break; } } next: free(line); } //bb_info_msg("ENDPARSE[%s]", boundary); return EXIT_SUCCESS; }
int lpd_main(int argc UNUSED_PARAM, char *argv[]) { int spooling = spooling; // for compiler char *s, *queue; char *filenames[2]; // goto spool directory if (*++argv) xchdir(*argv++); // error messages of xfuncs will be sent over network xdup2(STDOUT_FILENO, STDERR_FILENO); // nullify ctrl/data filenames memset(filenames, 0, sizeof(filenames)); // read command s = queue = xmalloc_read_stdin(); // we understand only "receive job" command if (2 != *queue) { unsupported_cmd: printf("Command %02x %s\n", (unsigned char)s[0], "is not supported"); goto err_exit; } // parse command: "2 | QUEUE_NAME | '\n'" queue++; // protect against "/../" attacks // *strchrnul(queue, '\n') = '\0'; - redundant, sane() will do if (!*sane(queue)) return EXIT_FAILURE; // queue is a directory -> chdir to it and enter spooling mode spooling = chdir(queue) + 1; // 0: cannot chdir, 1: done // we don't free(s), we might need "queue" var later while (1) { char *fname; int fd; // int is easier than ssize_t: can use xatoi_positive, // and can correctly display error returns (-1) int expected_len, real_len; // signal OK safe_write(STDOUT_FILENO, "", 1); // get subcommand // valid s must be of form: "SUBCMD | LEN | space | FNAME" // N.B. we bail out on any error s = xmalloc_read_stdin(); if (!s) { // (probably) EOF char *p, *q, var[2]; // non-spooling mode or no spool helper specified if (!spooling || !*argv) return EXIT_SUCCESS; // the only non-error exit // spooling mode but we didn't see both ctrlfile & datafile if (spooling != 7) goto err_exit; // reject job // spooling mode and spool helper specified -> exec spool helper // (we exit 127 if helper cannot be executed) var[1] = '\0'; // read and delete ctrlfile q = xmalloc_xopen_read_close(filenames[0], NULL); unlink(filenames[0]); // provide datafile name // we can use leaky setenv since we are about to exec or exit xsetenv("DATAFILE", filenames[1]); // parse control file by "\n" while ((p = strchr(q, '\n')) != NULL && isalpha(*q)) { *p++ = '\0'; // q is a line of <SYM><VALUE>, // we are setting environment string <SYM>=<VALUE>. // Ignoring "l<datafile>", exporting others: if (*q != 'l') { var[0] = *q++; xsetenv(var, q); } q = p; // next line } // helper should not talk over network. // this call reopens stdio fds to "/dev/null" // (no daemonization is done) bb_daemonize_or_rexec(DAEMON_DEVNULL_STDIO | DAEMON_ONLY_SANITIZE, NULL); BB_EXECVP_or_die(argv); } // validate input. // we understand only "control file" or "data file" cmds if (2 != s[0] && 3 != s[0]) goto unsupported_cmd; if (spooling & (1 << (s[0]-1))) { puts("Duplicated subcommand"); goto err_exit; } // get filename chomp(s); fname = strchr(s, ' '); if (!fname) { // bad_fname: puts("No or bad filename"); goto err_exit; } *fname++ = '\0'; // // s[0]==2: ctrlfile, must start with 'c' // // s[0]==3: datafile, must start with 'd' // if (fname[0] != s[0] + ('c'-2)) // goto bad_fname; // get length expected_len = bb_strtou(s + 1, NULL, 10); if (errno || expected_len < 0) { puts("Bad length"); goto err_exit; } if (2 == s[0] && expected_len > 16 * 1024) { // SECURITY: // ctrlfile can't be big (we want to read it back later!) puts("File is too big"); goto err_exit; } // open the file if (spooling) { // spooling mode: dump both files // job in flight has mode 0200 "only writable" sane(fname); fd = open3_or_warn(fname, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0200); if (fd < 0) goto err_exit; filenames[s[0] - 2] = xstrdup(fname); } else { // non-spooling mode: // 2: control file (ignoring), 3: data file fd = -1; if (3 == s[0]) fd = xopen(queue, O_RDWR | O_APPEND); } // signal OK safe_write(STDOUT_FILENO, "", 1); // copy the file real_len = bb_copyfd_size(STDIN_FILENO, fd, expected_len); if (real_len != expected_len) { printf("Expected %d but got %d bytes\n", expected_len, real_len); goto err_exit; } // get EOF indicator, see whether it is NUL (ok) // (and don't trash s[0]!) if (safe_read(STDIN_FILENO, &s[1], 1) != 1 || s[1] != 0) { // don't send error msg to peer - it obviously // doesn't follow the protocol, so probably // it can't understand us either goto err_exit; } if (spooling) { // chmod completely downloaded file as "readable+writable" fchmod(fd, 0600); // accumulate dump state // N.B. after all files are dumped spooling should be 1+2+4==7 spooling |= (1 << (s[0]-1)); // bit 1: ctrlfile; bit 2: datafile } free(s); close(fd); // NB: can do close(-1). Who cares? // NB: don't do "signal OK" write here, it will be done // at the top of the loop } // while (1) err_exit: // don't keep corrupted files if (spooling) { #define i spooling for (i = 2; --i >= 0; ) if (filenames[i]) unlink(filenames[i]); } return EXIT_FAILURE; }
int timeout_main(int argc UNUSED_PARAM, char **argv) { int signo; int status; int parent = 0; int timeout = 10; pid_t pid; #if !BB_MMU char *sv1, *sv2; #endif const char *opt_s = "TERM"; /* -p option is not documented, it is needed to support NOMMU. */ /* -t SECONDS; -p PARENT_PID */ opt_complementary = "t+" USE_FOR_NOMMU(":p+"); /* '+': stop at first non-option */ getopt32(argv, "+s:t:" USE_FOR_NOMMU("p:"), &opt_s, &timeout, &parent); /*argv += optind; - no, wait for bb_daemonize_or_rexec! */ signo = get_signum(opt_s); if (signo < 0) bb_error_msg_and_die("unknown signal '%s'", opt_s); /* We want to create a grandchild which will watch * and kill the grandparent. Other methods: * making parent watch child disrupts parent<->child link * (example: "tcpsvd 0.0.0.0 1234 timeout service_prog" - * it's better if service_prog is a child of tcpsvd!), * making child watch parent results in programs having * unexpected children. */ if (parent) /* we were re-execed, already grandchild */ goto grandchild; if (!argv[optind]) /* no PROG? */ bb_show_usage(); #if !BB_MMU sv1 = argv[optind]; sv2 = argv[optind + 1]; #endif pid = xvfork(); if (pid == 0) { /* Child: spawn grandchild and exit */ parent = getppid(); #if !BB_MMU argv[optind] = xasprintf("-p%u", parent); argv[optind + 1] = NULL; #endif /* NB: exits with nonzero on error: */ bb_daemonize_or_rexec(0, argv); /* Here we are grandchild. Sleep, then kill grandparent */ grandchild: /* Just sleep(HUGE_NUM); kill(parent) may kill wrong process! */ while (1) { sleep(1); if (--timeout <= 0) break; if (kill(parent, 0)) { /* process is gone */ return EXIT_SUCCESS; } } kill(parent, signo); return EXIT_SUCCESS; } /* Parent */ wait(&status); /* wait for child to die */ /* Did intermediate [v]fork or exec fail? */ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) return EXIT_FAILURE; /* Ok, exec a program as requested */ argv += optind; #if !BB_MMU argv[0] = sv1; argv[1] = sv2; #endif BB_EXECVP_or_die(argv); }
int tcpudpsvd_main(int argc UNUSED_PARAM, char **argv) { char *str_C, *str_t; char *user; struct hcc *hccp; const char *instructs; char *msg_per_host = NULL; unsigned len_per_host = len_per_host; /* gcc */ #ifndef SSLSVD struct bb_uidgid_t ugid; #endif bool tcp; uint16_t local_port; char *preset_local_hostname = NULL; char *remote_hostname = remote_hostname; /* for compiler */ char *remote_addr = remote_addr; /* for compiler */ len_and_sockaddr *lsa; len_and_sockaddr local, remote; socklen_t sa_len; int pid; int sock; int conn; unsigned backlog = 20; unsigned opts; INIT_G(); tcp = (applet_name[0] == 't'); /* 3+ args, -i at most once, -p implies -h, -v is counter, -b N, -c N */ opt_complementary = "-3:i--i:ph:vv:b+:c+"; #ifdef SSLSVD opts = getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:vU:/:Z:K:", &cmax, &str_C, &instructs, &instructs, &user, &preset_local_hostname, &backlog, &str_t, &ssluser, &root, &cert, &key, &verbose ); #else /* "+": stop on first non-option */ opts = getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:v", &cmax, &str_C, &instructs, &instructs, &user, &preset_local_hostname, &backlog, &str_t, &verbose ); #endif if (opts & OPT_C) { /* -C n[:message] */ max_per_host = bb_strtou(str_C, &str_C, 10); if (str_C[0]) { if (str_C[0] != ':') bb_show_usage(); msg_per_host = str_C + 1; len_per_host = strlen(msg_per_host); } } if (max_per_host > cmax) max_per_host = cmax; if (opts & OPT_u) { xget_uidgid(&ugid, user); } #ifdef SSLSVD if (opts & OPT_U) ssluser = optarg; if (opts & OPT_slash) root = optarg; if (opts & OPT_Z) cert = optarg; if (opts & OPT_K) key = optarg; #endif argv += optind; if (!argv[0][0] || LONE_CHAR(argv[0], '0')) argv[0] = (char*)"0.0.0.0"; /* Per-IP flood protection is not thought-out for UDP */ if (!tcp) max_per_host = 0; bb_sanitize_stdio(); /* fd# 0,1,2 must be opened */ #ifdef SSLSVD sslser = user; client = 0; if ((getuid() == 0) && !(opts & OPT_u)) { xfunc_exitcode = 100; bb_error_msg_and_die(bb_msg_you_must_be_root); } if (opts & OPT_u) if (!uidgid_get(&sslugid, ssluser, 1)) { if (errno) { bb_perror_msg_and_die("can't get user/group: %s", ssluser); } bb_error_msg_and_die("unknown user/group %s", ssluser); } if (!cert) cert = "./cert.pem"; if (!key) key = cert; if (matrixSslOpen() < 0) fatal("can't initialize ssl"); if (matrixSslReadKeys(&keys, cert, key, 0, ca) < 0) { if (client) fatal("can't read cert, key, or ca file"); fatal("can't read cert or key file"); } if (matrixSslNewSession(&ssl, keys, 0, SSL_FLAGS_SERVER) < 0) fatal("can't create ssl session"); #endif sig_block(SIGCHLD); signal(SIGCHLD, sig_child_handler); bb_signals(BB_FATAL_SIGS, sig_term_handler); signal(SIGPIPE, SIG_IGN); if (max_per_host) ipsvd_perhost_init(cmax); local_port = bb_lookup_port(argv[1], tcp ? "tcp" : "udp", 0); lsa = xhost2sockaddr(argv[0], local_port); argv += 2; sock = xsocket(lsa->u.sa.sa_family, tcp ? SOCK_STREAM : SOCK_DGRAM, 0); setsockopt_reuseaddr(sock); sa_len = lsa->len; /* I presume sockaddr len stays the same */ xbind(sock, &lsa->u.sa, sa_len); if (tcp) { xlisten(sock, backlog); close_on_exec_on(sock); } else { /* udp: needed for recv_from_to to work: */ socket_want_pktinfo(sock); } /* ndelay_off(sock); - it is the default I think? */ #ifndef SSLSVD if (opts & OPT_u) { /* drop permissions */ xsetgid(ugid.gid); xsetuid(ugid.uid); } #endif if (verbose) { char *addr = xmalloc_sockaddr2dotted(&lsa->u.sa); if (opts & OPT_u) bb_error_msg("listening on %s, starting, uid %u, gid %u", addr, (unsigned)ugid.uid, (unsigned)ugid.gid); else bb_error_msg("listening on %s, starting", addr); free(addr); } /* Main accept() loop */ again: hccp = NULL; while (cnum >= cmax) wait_for_any_sig(); /* expecting SIGCHLD */ /* Accept a connection to fd #0 */ again1: close(0); again2: sig_unblock(SIGCHLD); local.len = remote.len = sa_len; if (tcp) { conn = accept(sock, &remote.u.sa, &remote.len); } else { /* In case recv_from_to won't be able to recover local addr. * Also sets port - recv_from_to is unable to do it. */ local = *lsa; conn = recv_from_to(sock, NULL, 0, MSG_PEEK, &remote.u.sa, &local.u.sa, sa_len); } sig_block(SIGCHLD); if (conn < 0) { if (errno != EINTR) bb_perror_msg(tcp ? "accept" : "recv"); goto again2; } xmove_fd(tcp ? conn : sock, 0); if (max_per_host) { /* Drop connection immediately if cur_per_host > max_per_host * (minimizing load under SYN flood) */ remote_addr = xmalloc_sockaddr2dotted_noport(&remote.u.sa); cur_per_host = ipsvd_perhost_add(remote_addr, max_per_host, &hccp); if (cur_per_host > max_per_host) { /* ipsvd_perhost_add detected that max is exceeded * (and did not store ip in connection table) */ free(remote_addr); if (msg_per_host) { /* don't block or test for errors */ send(0, msg_per_host, len_per_host, MSG_DONTWAIT); } goto again1; } /* NB: remote_addr is not leaked, it is stored in conn table */ } if (!tcp) { /* Voodoo magic: making udp sockets each receive its own * packets is not trivial, and I still not sure * I do it 100% right. * 1) we have to do it before fork() * 2) order is important - is it right now? */ /* Open new non-connected UDP socket for further clients... */ sock = xsocket(lsa->u.sa.sa_family, SOCK_DGRAM, 0); setsockopt_reuseaddr(sock); /* Make plain write/send work for old socket by supplying default * destination address. This also restricts incoming packets * to ones coming from this remote IP. */ xconnect(0, &remote.u.sa, sa_len); /* hole? at this point we have no wildcard udp socket... * can this cause clients to get "port unreachable" icmp? * Yup, time window is very small, but it exists (is it?) */ /* ..."open new socket", continued */ xbind(sock, &lsa->u.sa, sa_len); socket_want_pktinfo(sock); /* Doesn't work: * we cannot replace fd #0 - we will lose pending packet * which is already buffered for us! And we cannot use fd #1 * instead - it will "intercept" all following packets, but child * does not expect data coming *from fd #1*! */ #if 0 /* Make it so that local addr is fixed to localp->u.sa * and we don't accidentally accept packets to other local IPs. */ /* NB: we possibly bind to the _very_ same_ address & port as the one * already bound in parent! This seems to work in Linux. * (otherwise we can move socket to fd #0 only if bind succeeds) */ close(0); set_nport(localp, htons(local_port)); xmove_fd(xsocket(localp->u.sa.sa_family, SOCK_DGRAM, 0), 0); setsockopt_reuseaddr(0); /* crucial */ xbind(0, &localp->u.sa, localp->len); #endif } pid = vfork(); if (pid == -1) { bb_perror_msg("vfork"); goto again; } if (pid != 0) { /* Parent */ cnum++; if (verbose) connection_status(); if (hccp) hccp->pid = pid; /* clean up changes done by vforked child */ undo_xsetenv(); goto again; } /* Child: prepare env, log, and exec prog */ { /* vfork alert! every xmalloc in this block should be freed! */ char *local_hostname = local_hostname; /* for compiler */ char *local_addr = NULL; char *free_me0 = NULL; char *free_me1 = NULL; char *free_me2 = NULL; if (verbose || !(opts & OPT_E)) { if (!max_per_host) /* remote_addr is not yet known */ free_me0 = remote_addr = xmalloc_sockaddr2dotted(&remote.u.sa); if (opts & OPT_h) { free_me1 = remote_hostname = xmalloc_sockaddr2host_noport(&remote.u.sa); if (!remote_hostname) { bb_error_msg("can't look up hostname for %s", remote_addr); remote_hostname = remote_addr; } } /* Find out local IP peer connected to. * Errors ignored (I'm not paranoid enough to imagine kernel * which doesn't know local IP). */ if (tcp) getsockname(0, &local.u.sa, &local.len); /* else: for UDP it is done earlier by parent */ local_addr = xmalloc_sockaddr2dotted(&local.u.sa); if (opts & OPT_h) { local_hostname = preset_local_hostname; if (!local_hostname) { free_me2 = local_hostname = xmalloc_sockaddr2host_noport(&local.u.sa); if (!local_hostname) bb_error_msg_and_die("can't look up hostname for %s", local_addr); } /* else: local_hostname is not NULL, but is NOT malloced! */ } } if (verbose) { pid = getpid(); if (max_per_host) { bb_error_msg("concurrency %s %u/%u", remote_addr, cur_per_host, max_per_host); } bb_error_msg((opts & OPT_h) ? "start %u %s-%s (%s-%s)" : "start %u %s-%s", pid, local_addr, remote_addr, local_hostname, remote_hostname); } if (!(opts & OPT_E)) { /* setup ucspi env */ const char *proto = tcp ? "TCP" : "UDP"; #ifdef SO_ORIGINAL_DST /* Extract "original" destination addr:port * from Linux firewall. Useful when you redirect * an outbond connection to local handler, and it needs * to know where it originally tried to connect */ if (tcp && getsockopt(0, SOL_IP, SO_ORIGINAL_DST, &local.u.sa, &local.len) == 0) { char *addr = xmalloc_sockaddr2dotted(&local.u.sa); xsetenv_plain("TCPORIGDSTADDR", addr); free(addr); } #endif xsetenv_plain("PROTO", proto); xsetenv_proto(proto, "LOCALADDR", local_addr); xsetenv_proto(proto, "REMOTEADDR", remote_addr); if (opts & OPT_h) { xsetenv_proto(proto, "LOCALHOST", local_hostname); xsetenv_proto(proto, "REMOTEHOST", remote_hostname); } //compat? xsetenv_proto(proto, "REMOTEINFO", ""); /* additional */ if (cur_per_host > 0) /* can not be true for udp */ xsetenv_plain("TCPCONCURRENCY", utoa(cur_per_host)); } free(local_addr); free(free_me0); free(free_me1); free(free_me2); } xdup2(0, 1); signal(SIGPIPE, SIG_DFL); /* this one was SIG_IGNed */ /* Non-ignored signals revert to SIG_DFL on exec anyway */ /*signal(SIGCHLD, SIG_DFL);*/ sig_unblock(SIGCHLD); #ifdef SSLSVD strcpy(id, utoa(pid)); ssl_io(0, argv); bb_perror_msg_and_die("can't execute '%s'", argv[0]); #else BB_EXECVP_or_die(argv); #endif }