/* main() for udpxrec module */ int udpxrec_main( int argc, char* const argv[] ) { int rc = 0, ch = 0, custom_log = 0, no_daemon = 0; static const char OPTMASK[] = "vb:e:M:p:B:n:m:l:c:R:u:T"; time_t now = time(NULL); char now_buf[ 32 ] = {0}, sel_buf[ 32 ] = {0}, app_finfo[80] = {0}; extern int optind, optopt; extern const char IPv4_ALL[]; mk_app_info(g_udpxrec_app, g_app_info, sizeof(g_app_info) - 1); if( argc < 2 ) { usage( argv[0], stderr ); return ERR_PARAM; } rc = init_recopt( &g_recopt ); while( (0 == rc) && (-1 != (ch = getopt( argc, argv, OPTMASK ))) ) { switch(ch) { case 'T': no_daemon = 1; break; case 'v': set_verbose( &g_recopt.is_verbose ); break; case 'b': if( (time_t)0 != g_recopt.end_time ) { (void) fprintf( stderr, "Cannot specify start-recording " "time after end-recording time has been set\n" ); } rc = a2time( optarg, &g_recopt.bg_time, time(NULL) ); if( 0 != rc ) { (void) fprintf( stderr, "Invalid time: [%s]\n", optarg ); rc = ERR_PARAM; } else { if( g_recopt.bg_time < now ) { (void)strncpy( now_buf, Zasctime(localtime( &now )), sizeof(now_buf) ); (void)strncpy( sel_buf, Zasctime(localtime( &g_recopt.bg_time )), sizeof(sel_buf) ); (void) fprintf( stderr, "Selected %s time is in the past, " "now=[%s], selected=[%s]\n", "start", now_buf, sel_buf ); rc = ERR_PARAM; } } break; case 'e': if( (time_t)0 == g_recopt.bg_time ) { g_recopt.bg_time = time(NULL); (void)fprintf( stderr, "Start-recording time defaults to now [%s]\n", Zasctime( localtime( &g_recopt.bg_time ) ) ); } rc = a2time( optarg, &g_recopt.end_time, g_recopt.bg_time ); if( 0 != rc ) { (void) fprintf( stderr, "Invalid time: [%s]\n", optarg ); rc = ERR_PARAM; } else { if( g_recopt.end_time < now ) { (void)strncpy( now_buf, Zasctime(localtime( &now )), sizeof(now_buf) ); (void)strncpy( sel_buf, Zasctime(localtime( &g_recopt.end_time )), sizeof(sel_buf) ); (void) fprintf( stderr, "Selected %s time is in the past, " "now=[%s], selected=[%s]\n", "end", now_buf, sel_buf ); rc = ERR_PARAM; } } break; case 'M': rc = a2int64( optarg, &g_recopt.max_fsize ); if( 0 != rc ) { (void) fprintf( stderr, "Invalid file size: [%s]\n", optarg ); rc = ERR_PARAM; } break; case 'p': g_recopt.pidfile = strdup(optarg); break; case 'B': rc = a2size( optarg, &g_recopt.bufsize ); if( 0 != rc ) { (void) fprintf( stderr, "Invalid buffer size: [%s]\n", optarg ); rc = ERR_PARAM; } else if( (g_recopt.bufsize < MIN_MCACHE_LEN) || (g_recopt.bufsize > MAX_MCACHE_LEN)) { (void) fprintf( stderr, "Buffer size must be in [%ld-%ld] bytes range\n", (long)MIN_MCACHE_LEN, (long)MAX_MCACHE_LEN ); rc = ERR_PARAM; } break; case 'n': g_recopt.nice_incr = atoi( optarg ); if( 0 == g_recopt.nice_incr ) { (void) fprintf( stderr, "Invalid nice-value increment: [%s]\n", optarg ); rc = ERR_PARAM; } break; case 'm': rc = get_ipv4_address( optarg, g_recopt.mcast_addr, sizeof(g_recopt.mcast_addr) ); if( 0 != rc ) { (void) fprintf( stderr, "Invalid multicast address: [%s]\n", optarg ); rc = ERR_PARAM; } break; case 'l': g_flog = fopen( optarg, "a" ); if( NULL == g_flog ) { rc = errno; (void) fprintf( stderr, "Error opening logfile [%s]: %s\n", optarg, strerror(rc) ); rc = ERR_PARAM; break; } Setlinebuf( g_flog ); custom_log = 1; break; case 'c': rc = get_addrport( optarg, g_recopt.rec_channel, sizeof( g_recopt.rec_channel ), &g_recopt.rec_port ); if( 0 != rc ) rc = ERR_PARAM; break; case 'R': g_recopt.rbuf_msgs = atoi( optarg ); if( (g_recopt.rbuf_msgs <= 0) && (-1 != g_recopt.rbuf_msgs) ) { (void) fprintf( stderr, "Invalid rcache size: [%s]\n", optarg ); rc = ERR_PARAM; } break; case 'u': g_recopt.waitupd_sec = atoi(optarg); if( g_recopt.waitupd_sec <= 0 ) { (void) fprintf( stderr, "Invalid wait-update value [%s] " "(must be a number > 0)\n", optarg ); rc = ERR_PARAM; } break; case ':': (void) fprintf( stderr, "Option [-%c] requires an argument\n", optopt ); rc = ERR_PARAM; break; case '?': (void) fprintf( stderr, "Unrecognized option: [-%c]\n", optopt ); rc = ERR_PARAM; break; default: usage( argv[0], stderr ); rc = ERR_PARAM; break; } /* switch */ } /* while getopt */ if( 0 == rc ) { if( optind >= argc ) { (void) fputs( "Missing destination file parameter\n", stderr ); rc = ERR_PARAM; } else { g_recopt.dstfile = strdup( argv[optind] ); } if( !(g_recopt.max_fsize > 0 || g_recopt.end_time) ) { (void) fputs( "Must specify either max file [-M] size " "or end time [-e]\n", stderr ); rc = ERR_PARAM; } if( !g_recopt.rec_channel[0] || !g_recopt.rec_port ) { (void) fputs( "Must specify multicast channel to record from\n", stderr ); rc = ERR_PARAM; } } if( rc ) { free_recopt( &g_recopt ); return rc; } do { if( '\0' == g_recopt.mcast_addr[0] ) { (void) strncpy( g_recopt.mcast_addr, IPv4_ALL, sizeof(g_recopt.mcast_addr) - 1 ); } if( !custom_log ) { /* in debug mode output goes to stderr, otherwise to /dev/null */ g_flog = ((uf_TRUE == g_recopt.is_verbose) ? stderr : fopen( "/dev/null", "a" )); if( NULL == g_flog ) { perror("fopen"); rc = ERR_INTERNAL; break; } } if( 0 == geteuid() ) { if( !no_daemon ) { if( stderr == g_flog ) { (void) fprintf( stderr, "Logfile must be specified to run " "in verbose mode in background\n" ); rc = ERR_PARAM; break; } if( NULL == g_recopt.pidfile ) { (void) fprintf( stderr, "pidfile must be specified " "to run as daemon\n" ); rc = ERR_PARAM; break; } if( 0 != (rc = daemonize(0, g_flog)) ) { rc = ERR_INTERNAL; break; } } } /* 0 == geteuid() */ if( NULL != g_recopt.pidfile ) { rc = make_pidfile( g_recopt.pidfile, getpid(), g_flog ); if( 0 != rc ) break; } (void) set_nice( g_recopt.nice_incr, g_flog ); if( 0 != (rc = setup_signals()) ) break; TRACE( fprint_recopt( g_flog, &g_recopt ) ); TRACE( printcmdln( g_flog, g_app_info, argc, argv ) ); if( g_recopt.bg_time ) { if( 0 != (rc = verify_channel()) || g_quit ) break; rc = wait_till( g_recopt.bg_time, g_recopt.waitupd_sec ); if( rc || g_quit ) break; } rc = record(); if( NULL != g_recopt.pidfile ) { if( -1 == unlink(g_recopt.pidfile) ) { mperror( g_flog, errno, "unlink [%s]", g_recopt.pidfile ); } } } while(0); if( g_flog ) { (void)tmfprintf( g_flog, "%s is exiting with rc=[%d]\n", app_finfo, rc ); } if( g_flog && (stderr != g_flog) ) { (void) fclose(g_flog); } free_recopt( &g_recopt ); return rc; }
/* relay traffic from source to destination socket * */ static int relay_traffic( int ssockfd, int dsockfd, struct server_ctx* ctx, int dfilefd, const struct in_addr* mifaddr ) { volatile sig_atomic_t quit = 0; int rc = 0; ssize_t nmsgs = -1; ssize_t nrcv = 0, nsent = 0, nwr = 0, lrcv = 0, lsent = 0; char* data = NULL; size_t data_len = g_uopt.rbuf_len; struct rdata_opt ropt; time_t pause_time = 0, rfr_tm = time(NULL); sigset_t ubset; const int ALLOW_PAUSES = get_flagval( "UDPXY_ALLOW_PAUSES", 0 ); const ssize_t MAX_PAUSE_MSEC = get_sizeval( "UDPXY_PAUSE_MSEC", 1000); /* permissible variation in data-packet size */ static const ssize_t t_delta = 0x20; struct dstream_ctx ds; static const int SET_PID = 1; struct tps_data tps; assert( ctx && mifaddr && MAX_PAUSE_MSEC > 0 ); (void) sigemptyset (&ubset); sigaddset (&ubset, SIGINT); sigaddset (&ubset, SIGQUIT); sigaddset (&ubset, SIGTERM); /* restore the ability to receive *quit* signals */ rc = sigprocmask (SIG_UNBLOCK, &ubset, NULL); if (0 != rc) { mperror (g_flog, errno, "%s: sigprocmask", __func__); return -1; } /* NOPs to eliminate warnings in lean version */ (void)&lrcv; (void)&lsent; (void)&t_delta; check_fragments( NULL, 0, 0, 0, 0, g_flog ); /* INIT */ rc = calc_buf_settings( &nmsgs, NULL ); if (0 != rc) return -1; TRACE( (void)tmfprintf( g_flog, "Data buffer will hold up to " "[%d] messages\n", nmsgs ) ); rc = init_dstream_ctx( &ds, ctx->cmd, g_uopt.srcfile, nmsgs ); if( 0 != rc ) return -1; (void) set_nice( g_uopt.nice_incr, g_flog ); do { if( NULL == g_uopt.srcfile ) { rc = set_timeouts( ssockfd, dsockfd, ctx->rcv_tmout, 0, ctx->snd_tmout, 0 ); if( 0 != rc ) break; } if( dsockfd > 0 ) { rc = sync_dsockbuf_len( ssockfd, dsockfd ); if( 0 != rc ) break; rc = send_http_response( dsockfd, 200, "OK" ); if( 0 != rc ) break; /* timeshift: to detect PAUSE make destination * socket non-blocking, otherwise make it blocking * (since it might have been set unblocking earlier) */ rc = set_nblock( dsockfd, (ALLOW_PAUSES ? 1 : 0) ); if( 0 != rc ) break; } data = malloc(data_len); if( NULL == data ) { mperror( g_flog, errno, "%s: malloc", __func__ ); break; } if( g_uopt.cl_tpstat ) tpstat_init( &tps, SET_PID ); } while(0); TRACE( (void)tmfprintf( g_flog, "Relaying traffic from socket[%d] " "to socket[%d], buffer size=[%d], Rmsgs=[%d], pauses=[%d]\n", ssockfd, dsockfd, data_len, g_uopt.rbuf_msgs, ALLOW_PAUSES) ); /* RELAY LOOP */ ropt.max_frgs = g_uopt.rbuf_msgs; ropt.buf_tmout = g_uopt.dhold_tmout; pause_time = 0; while( (0 == rc) && !(quit = must_quit()) ) { if( g_uopt.mcast_refresh > 0 ) { check_mcast_refresh( ssockfd, &rfr_tm, mifaddr ); } nrcv = read_data( &ds, ssockfd, data, data_len, &ropt ); if( -1 == nrcv ) break; TRACE( check_fragments( "received new", data_len, lrcv, nrcv, t_delta, g_flog ) ); lrcv = nrcv; if( dsockfd && (nrcv > 0) ) { nsent = write_data( &ds, data, nrcv, dsockfd ); if( -1 == nsent ) break; if ( nsent < 0 ) { if ( !ALLOW_PAUSES ) break; if ( 0 != pause_detect( nsent, MAX_PAUSE_MSEC, &pause_time ) ) break; } TRACE( check_fragments("sent", nrcv, lsent, nsent, t_delta, g_flog) ); lsent = nsent; } if( (dfilefd > 0) && (nrcv > 0) ) { nwr = write_data( &ds, data, nrcv, dfilefd ); if( -1 == nwr ) break; TRACE( check_fragments( "wrote to file", nrcv, lsent, nwr, t_delta, g_flog ) ); lsent = nwr; } if( ds.flags & F_SCATTERED ) reset_pkt_registry( &ds ); if( uf_TRUE == g_uopt.cl_tpstat ) tpstat_update( ctx, &tps, nsent ); } /* end of RELAY LOOP */ /* CLEANUP */ TRACE( (void)tmfprintf( g_flog, "Exited relay loop: received=[%ld], " "sent=[%ld], quit=[%ld]\n", (long)nrcv, (long)nsent, (long)quit ) ); free_dstream_ctx( &ds ); if( NULL != data ) free( data ); if( 0 != (quit = must_quit()) ) { TRACE( (void)tmfprintf( g_flog, "Child process=[%d] must quit\n", getpid()) ); } return rc; }
/* record network stream as per spec in opt */ static int record() { int rsock = -1, destfd = -1, rc = 0, wtime_sec = 0; struct in_addr raddr; struct timeval rtv; struct dstream_ctx ds; ssize_t nmsgs = 0; ssize_t nrcv = -1, lrcv = -1, t_delta = 0; int64_t n_total = 0; ssize_t nwr = -1, lwr = -1; sig_atomic_t quit = 0; struct rdata_opt ropt; int oflags = 0; char* data = NULL; static const u_short RSOCK_TIMEOUT = 5; extern const char CMD_UDP[]; /* NOPs to eliminate warnings in lean version */ (void)&t_delta; (void)&lrcv; t_delta = lrcv = lwr = 0; quit=0; check_fragments( NULL, 0, 0, 0, 0, g_flog ); /* init */ do { data = malloc( g_recopt.bufsize ); if( NULL == data ) { mperror(g_flog, errno, "%s: cannot allocate [%ld] bytes", __func__, (long)g_recopt.bufsize ); rc = ERR_INTERNAL; break; } rc = subscribe( &rsock, &raddr ); if( 0 != rc ) break; rtv.tv_sec = RSOCK_TIMEOUT; rtv.tv_usec = 0; rc = setsockopt( rsock, SOL_SOCKET, SO_RCVTIMEO, &rtv, sizeof(rtv) ); if( -1 == rc ) { mperror(g_flog, errno, "%s: setsockopt - SO_RCVTIMEO", __func__); rc = ERR_INTERNAL; break; } oflags = O_CREAT | O_TRUNC | O_WRONLY | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; # if defined(O_LARGEFILE) /* O_LARGEFILE is not defined under FreeBSD ??-7.1 */ oflags |= O_LARGEFILE; # endif destfd = open( g_recopt.dstfile, oflags, (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)); if( -1 == destfd ) { mperror( g_flog, errno, "%s: cannot create destination file [%s]", __func__, g_recopt.dstfile ); rc = ERR_INTERNAL; break; } rc = calc_buf_settings( &nmsgs, NULL ); if (0 != rc) return -1; if( nmsgs < (ssize_t)1 ) { (void) tmfprintf( g_flog, "Buffer for inbound data is too small [%ld] bytes; " "the minimum size is [%ld] bytes\n", (long)g_recopt.bufsize, (long)ETHERNET_MTU ); rc = ERR_PARAM; break; } TRACE( (void)tmfprintf( g_flog, "Inbound buffer set to " "[%d] messages\n", nmsgs ) ); rc = init_dstream_ctx( &ds, CMD_UDP, NULL, nmsgs ); if( 0 != rc ) return -1; (void) set_nice( g_recopt.nice_incr, g_flog ); /* set up alarm to break main loop */ if( 0 != g_recopt.end_time ) { wtime_sec = (int)difftime( g_recopt.end_time, time(NULL) ); assert( wtime_sec >= 0 ); (void) alarm( wtime_sec ); (void)tmfprintf( g_flog, "Recording will end in [%d] seconds\n", wtime_sec ); } } while(0); /* record loop */ ropt.max_frgs = g_recopt.rbuf_msgs; ropt.buf_tmout = -1; for( n_total = 0; (0 == rc) && !(quit = must_quit()); ) { nrcv = read_data( &ds, rsock, data, g_recopt.bufsize, &ropt ); if( -1 == nrcv ) { rc = ERR_INTERNAL; break; } if( 0 == n_total ) { (void) tmfprintf( g_flog, "Recording to file=[%s] started.\n", g_recopt.dstfile ); } TRACE( check_fragments( "received new", g_recopt.bufsize, lrcv, nrcv, t_delta, g_flog ) ); lrcv = nrcv; if( nrcv > 0 ) { if( g_recopt.max_fsize && ((n_total + nrcv) >= g_recopt.max_fsize) ) { break; } nwr = write_data( &ds, data, nrcv, destfd ); if( -1 == nwr ) { rc = ERR_INTERNAL; break; } n_total += (size_t)nwr; /* TRACE( tmfprintf( g_flog, "Wrote [%ld] to file, total=[%ld]\n", (long)nwr, (long)n_total ) ); */ TRACE( check_fragments( "wrote to file", nrcv, lwr, nwr, t_delta, g_flog ) ); lwr = nwr; } if( ds.flags & F_SCATTERED ) reset_pkt_registry( &ds ); } /* record loop */ (void) tmfprintf( g_flog, "Recording to file=[%s] stopped at filesize=[%lu] bytes\n", g_recopt.dstfile, (u_long)n_total ); /* CLEANUP */ (void) alarm(0); TRACE( (void)tmfprintf( g_flog, "Exited record loop: wrote [%lu] bytes to file [%s], " "rc=[%d], alarm=[%ld], quit=[%ld]\n", (u_long)n_total, g_recopt.dstfile, rc, g_alarm, (long)quit ) ); free_dstream_ctx( &ds ); if( data ) free( data ); close_mcast_listener( rsock, &raddr ); if( destfd >= 0 ) (void) close( destfd ); if( quit ) TRACE( (void)tmfprintf( g_flog, "%s process must quit\n", g_udpxrec_app ) ); return rc; }
void join(pid_t pid, int argc, char **argv, int index) { EUID_ASSERT(); pid_t parent = pid; // in case the pid is that of a firejail process, use the pid of the first child process pid = switch_to_child(pid); // now check if the pid belongs to a firejail sandbox if (invalid_sandbox(pid)) { fprintf(stderr, "Error: no valid sandbox\n"); exit(1); } // check privileges for non-root users uid_t uid = getuid(); if (uid != 0) { uid_t sandbox_uid = pid_get_uid(pid); if (uid != sandbox_uid) { fprintf(stderr, "Error: permission is denied to join a sandbox created by a different user.\n"); exit(1); } } extract_x11_display(parent); EUID_ROOT(); // in user mode set caps seccomp, cpu, cgroup, etc if (getuid() != 0) { extract_nonewprivs(pid); // redundant on Linux >= 4.10; duplicated in function extract_caps extract_caps(pid); extract_cpu(pid); extract_cgroup(pid); extract_nogroups(pid); extract_user_namespace(pid); } // set cgroup if (cfg.cgroup) // not available for uid 0 set_cgroup(cfg.cgroup); // set umask, also uid 0 extract_umask(pid); // join namespaces if (arg_join_network) { if (join_namespace(pid, "net")) exit(1); } else if (arg_join_filesystem) { if (join_namespace(pid, "mnt")) exit(1); } else { if (join_namespace(pid, "ipc") || join_namespace(pid, "net") || join_namespace(pid, "pid") || join_namespace(pid, "uts") || join_namespace(pid, "mnt")) exit(1); } pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { // drop discretionary access control capabilities for root sandboxes caps_drop_dac_override(); // chroot into /proc/PID/root directory char *rootdir; if (asprintf(&rootdir, "/proc/%d/root", pid) == -1) errExit("asprintf"); int rv; if (!arg_join_network) { rv = chroot(rootdir); // this will fail for processes in sandboxes not started with --chroot option if (rv == 0) printf("changing root to %s\n", rootdir); } EUID_USER(); if (chdir("/") < 0) errExit("chdir"); if (cfg.homedir) { struct stat s; if (stat(cfg.homedir, &s) == 0) { /* coverity[toctou] */ if (chdir(cfg.homedir) < 0) errExit("chdir"); } } // set caps filter EUID_ROOT(); if (apply_caps == 1) // not available for uid 0 caps_set(caps); #ifdef HAVE_SECCOMP if (getuid() != 0) seccomp_load_file_list(); #endif // mount user namespace or drop privileges if (arg_noroot) { // not available for uid 0 if (arg_debug) printf("Joining user namespace\n"); if (join_namespace(1, "user")) exit(1); // user namespace resets capabilities // set caps filter if (apply_caps == 1) // not available for uid 0 caps_set(caps); } // set nonewprivs if (arg_nonewprivs == 1) { // not available for uid 0 int rv = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); if (arg_debug && rv == 0) printf("NO_NEW_PRIVS set\n"); } EUID_USER(); int cwd = 0; if (cfg.cwd) { if (chdir(cfg.cwd) == 0) cwd = 1; } if (!cwd) { if (chdir("/") < 0) errExit("chdir"); if (cfg.homedir) { struct stat s; if (stat(cfg.homedir, &s) == 0) { /* coverity[toctou] */ if (chdir(cfg.homedir) < 0) errExit("chdir"); } } } // drop privileges drop_privs(arg_nogroups); // kill the child in case the parent died prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); extract_command(argc, argv, index); if (cfg.command_line == NULL) { assert(cfg.shell); cfg.command_line = cfg.shell; cfg.window_title = cfg.shell; } if (arg_debug) printf("Extracted command #%s#\n", cfg.command_line); // set cpu affinity if (cfg.cpus) // not available for uid 0 set_cpu_affinity(); // set nice value if (arg_nice) set_nice(cfg.nice); // add x11 display if (display) { char *display_str; if (asprintf(&display_str, ":%d", display) == -1) errExit("asprintf"); setenv("DISPLAY", display_str, 1); free(display_str); } start_application(0, NULL); // it will never get here!!! } int status = 0; //***************************** // following code is signal-safe install_handler(); // wait for the child to finish waitpid(child, &status, 0); // restore default signal action signal(SIGTERM, SIG_DFL); // end of signal-safe code //***************************** flush_stdin(); if (WIFEXITED(status)) { status = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { status = WTERMSIG(status); } else { status = 0; } exit(status); }