/* ** Copyright 1998 - 2020 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif /* ** OK - the poop is that if we include socks.h after stdio.h, SOCKSfwrite ** does not get prototyped. ** If we include socks.h before stdio.h, gcc will complain about getc being ** redefined. The easiest solution is to simply undef getc, because we ** don't use it here. */ #include "soxwrap/soxwrap.h" #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_SYS_STAT_H #include #endif #if HAVE_FCNTL_H #include #endif #if HAVE_SYS_IOCTL_H #include #endif #if HAVE_UNISTD_H #include #endif #include "waitlib/waitlib.h" #include "rfc1035/rfc1035.h" #include "liblock/config.h" #include "liblock/liblock.h" #include "tcpremoteinfo.h" #include "numlib/numlib.h" #include "argparse.h" #include static const char *accessarg=0; static const char *accesslocal=0; static const char *denymsgarg=0; static const char *listenarg=0; static const char *ipaddrarg=0; static const char *userarg=0; static const char *grouparg=0; static const char *maxprocsarg=0; static const char *warnarg=0; static const char *maxperiparg=0; static const char *maxpercarg=0; static const char *droparg=0; static const char *nodnslookup=0; static const char *noidentlookup=0; static const char *stderrarg=0; static const char *stderrloggerarg=0; static const char *pidarg=0; static const char *proxyarg=0; static const char *restartarg=0; static const char *stoparg=0; static const char *stderrloggername=0; static char *lockfilename; static void setup_block(const char *); static void setup_allow(const char *); static struct args arginfo[]={ {"access", &accessarg}, {"accesslocal", &accesslocal}, {"denymsg", &denymsgarg}, {"drop", &droparg}, {"address", &ipaddrarg}, {"block", 0, setup_block}, {"allow", 0, setup_allow}, {"group", &grouparg}, {"listen", &listenarg}, {"maxperc", &maxpercarg}, {"maxperip", &maxperiparg}, {"maxprocs", &maxprocsarg}, {"warn", &warnarg}, {"nodnslookup", &nodnslookup}, {"noidentlookup", &noidentlookup}, {"pid", &pidarg}, {"restart", &restartarg}, {"stderr", &stderrarg}, {"stderrlogger", &stderrloggerarg}, {"stderrloggername", &stderrloggername}, {"stop", &stoparg}, {"user", &userarg}, {"proxy", &proxyarg}, {0} } ; /* Ports we're listening on: */ static struct portinfo { struct portinfo *next; const char *ipaddr; /* Specific IP addr, or 0 */ const char *servname; /* Service name/port */ int fd1, fd2; /* BSD may need both IPv4 and IPv6 sockets */ } *fdlist=0; static int maxfd; static int nprocs, maxperc, maxperip, nwarn; static pid_t *pids; static time_t last_alert=0, last_warn=0; static RFC1035_ADDR *addrs; static int sighup_received=0; struct blocklist_s { struct blocklist_s *next; char *zone; /* zone to lookup */ char *display_zone; /* zone for var_ZONE */ char *var; struct in_addr ia; /* 0, anything */ char *msg; /* NULL, query for TXT record */ int allow; /* This is an -allow entry */ } *blocklist=0; extern int openaccess(const char *); extern void closeaccess(); extern char *chkaccess(const char *); /* ** Process -block and -allow parameters */ static void setup_block_allow(const char *blockinfo, int isallow) { struct blocklist_s *newbl=(struct blocklist_s *)malloc(sizeof(*blocklist)); char *p; struct blocklist_s **blptr; const char *ip; for (blptr= &blocklist; *blptr; blptr=&(*blptr)->next) ; if (!newbl || (newbl->zone=malloc(strlen(blockinfo)+1)) == 0) { perror("malloc"); exit(1); } *blptr=newbl; newbl->next=0; newbl->allow=isallow; strcpy(newbl->zone, blockinfo); newbl->var=0; newbl->msg=0; newbl->display_zone=0; newbl->ia.s_addr=INADDR_ANY; /* Look for var or IP address */ for (p=newbl->zone; *p; ++p) { if (*p == '=') { *p++=0; newbl->display_zone=p; continue; } if (*p == '/') break; if (*p == ',') { *p++=0; newbl->var=p; break; } } if (newbl->display_zone == 0) newbl->display_zone = newbl->zone; ip=0; for (; *p; p++) { if (*p == ',') break; if (*p == '/') { *p++=0; ip=p; break; } } for (; *p; p++) { if (*p == ',') { *p++=0; newbl->msg=p; break; } } if (ip) { rfc1035_aton_ipv4(ip, &newbl->ia); } } static void setup_block(const char *blockinfo) { setup_block_allow(blockinfo, 0); } static void setup_allow(const char *blockinfo) { setup_block_allow(blockinfo, 1); } static int isid(const char *p) { while (*p) { if (*p < '0' || *p > '9') return (0); ++p; } return (1); } static RETSIGTYPE sigexit(int n) { kill( -getpid(), SIGTERM); _exit(0); #if RETSIGTYPE != void return (0) #endif } static RETSIGTYPE sighup(int n) { sighup_received=1; signal(SIGHUP, sighup); #if RETSIGTYPE != void return (0) #endif } /* ** Initialize a single listening socket */ static struct portinfo *createport(const char *a, const char *s) { struct portinfo *p=(struct portinfo *)malloc(sizeof(struct portinfo)); if (!p) { perror("malloc"); return (NULL); } p->next=fdlist; fdlist=p; p->ipaddr=a; p->servname=s; p->fd1=p->fd2= -1; return (p); } static int parseaddr(const char *p) { char *buf=strdup(p); char *q, *a, *s; if (!buf) { perror("malloc"); return (-1); } for (q=buf; (q=strtok(q, ",")) != NULL; q=0) { if ((s=strrchr(q, '.')) != 0) { *s++=0; a=q; } else { a=0; s=q; } if (createport(a, s) == NULL) return (-1); } if (ipaddrarg) { struct portinfo *p; for (p=fdlist; p; p=p->next) { if (p->ipaddr && strcmp(p->ipaddr, "0")) continue; p->ipaddr=ipaddrarg; } } return (0); } /* ** Create one socket, bound to a specific host/port */ static int mksocket(const char *ipaddrarg, /* Host/IP address */ const char *servname, /* Service/port */ int flags) #define MKS_USEAFINET4 1 #define MKS_ERROK 2 { struct servent *servptr; int port; int fd; RFC1035_ADDR addr; RFC1035_NETADDR netaddr; const struct sockaddr *sinaddr; int sinaddrlen; #if RFC1035_IPV6 struct sockaddr_in6 sin6; #endif struct sockaddr_in sin4; int af; servptr=getservbyname(servname, "tcp"); if (servptr) port=servptr->s_port; else { port=atoi(servname); if (port <= 0 || port > 65535) { fprintf(stderr, "Invalid port: %s\n", servname); return (-1); } port=htons(port); } /* Create an IPv6 or an IPv4 socket */ #if RFC1035_IPV6 if (flags & MKS_USEAFINET4) { fd=socket(PF_INET, SOCK_STREAM, 0); af=AF_INET; } else #endif fd=rfc1035_mksocket(SOCK_STREAM, 0, &af); if (fd < 0) { perror("socket"); return (-1); } /* Figure out what to bind based on what socket we created */ if (ipaddrarg && strcmp(ipaddrarg, "0")) { if (rfc1035_aton(ipaddrarg, &addr) < 0) { fprintf(stderr,"Invalid IP address: %s\n", ipaddrarg); close(fd); return (-1); } if (rfc1035_mkaddress(af, &netaddr, &addr, port, &sinaddr, &sinaddrlen)) { fprintf(stderr,"Unable to bind IP address: %s\n", ipaddrarg); close(fd); return (-1); } } else /* Bind default address */ { #if RFC1035_IPV6 if (af == AF_INET6) { memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family=AF_INET6; sin6.sin6_addr=in6addr_any; sin6.sin6_port=port; sinaddr=(const struct sockaddr *)&sin6; sinaddrlen=sizeof(sin6); } else #endif if (af == AF_INET) { sin4.sin_family=AF_INET; sin4.sin_addr.s_addr=INADDR_ANY; sin4.sin_port=port; sinaddr=(const struct sockaddr *)&sin4; sinaddrlen=sizeof(sin4); } else { errno=EAFNOSUPPORT; perror("socket"); close(fd); return (-1); } } { int dummy=1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&dummy, sizeof(dummy)) < 0) { perror("setsockopt"); } } if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { perror("fcntl"); close(fd); return (-1); } if (fcntl(fd, F_SETFL, O_NONBLOCK)) { perror("fcntl"); close(fd); return (-1); } if (sox_bind(fd, (struct sockaddr *)sinaddr, sinaddrlen) < 0) { if (flags & MKS_ERROK) { close(fd); return (-2); } perror("bind"); close(fd); return (-1); } if (sox_listen(fd, #ifdef SOMAXCONN SOMAXCONN #else 5 #endif )) { if (flags && MKS_ERROK) { close(fd); return (-2); } perror("listen"); close(fd); return (-1); } return (fd); } static int mksockets() { struct portinfo *p; maxfd= -1; for (p=fdlist; p; p=p->next) { int fd; struct in_addr addr; int fd_flag=0; if (p->ipaddr && strcmp(p->ipaddr, "0")) { /* FreeBSD needs AF_INET binds for IPv4 addys */ if (rfc1035_aton_ipv4(p->ipaddr, &addr) == 0) { fd_flag=MKS_USEAFINET4; } } fd=mksocket(p->ipaddr, p->servname, fd_flag); if (fd < 0) break; p->fd1=fd; if (fd > maxfd) maxfd=fd; /* BSD requires both an IPv6 and an IPv4 socket */ #if RFC1035_IPV6 if (p->ipaddr == 0 || strcmp(p->ipaddr, "0") == 0) { fd=mksocket(p->ipaddr, p->servname, (MKS_USEAFINET4|MKS_ERROK)); if (fd == -2) continue; /* Ok if bind failed */ if (fd < 0) break; if (fd > maxfd) maxfd=fd; p->fd2=fd; } #endif } if (p) /* Clean up after ourselves, after an error */ { for (p=fdlist; p; p=p->next) { if (p->fd1 >= 0) close(p->fd1); if (p->fd2 >= 0) close(p->fd2); } return (-1); } return (0); } static int dup_and_check(int orig) { int fd=sox_dup(orig); if (fd < 0) { perror("dup"); exit(1); } return fd; } static int init(int argc, char **argv) { int argn; struct group *gr; int i; gid_t gid=0; const char *servname; int forced=0; int lockfd=-1; argn=argparse(argc, argv, arginfo); if ((stoparg || restartarg) && pidarg == 0) { fprintf(stderr, "%s: -pid argument is required.\n", argv[0]); return (-1); } if (pidarg) { lockfilename=malloc(strlen(pidarg)+sizeof(".lock")); if (!lockfilename) { perror("malloc"); return (-1); } strcat(strcpy(lockfilename, pidarg), ".lock"); } if (stoparg) { ll_daemon_stop(lockfilename, pidarg); exit(0); } if (restartarg) { ll_daemon_restart(lockfilename, pidarg); exit(0); } if (argc - argn < 2) { fprintf(stderr, "Usage: %s [options] port prog arg1 arg2...\n", argv[0]); return (-1); } if (pidarg) { /* -start implied for backwards compatibility */ lockfd=ll_daemon_start(lockfilename); if (lockfd < 0) { perror("ll_daemon_start"); return (-1); } } servname=argv[argn++]; if (parseaddr(servname)) { close(lockfd); return (-1); } if (mksockets()) { close(lockfd); return (-1); } signal(SIGINT, sigexit); signal(SIGHUP, sighup); signal(SIGTERM, sigexit); #if 0 { int fd2; int dummy; perror("bind"); if (!forcebindarg || errno != EADDRINUSE) { sox_close(fd); return (-1); } /* Poke around */ if ((fd2=rfc1035_mksocket(SOCK_STREAM, 0, &dummy)) < 0) /* Better get same socket as fd */ { perror("socket"); sox_close(fd); return (-1); } if (sox_connect(fd2, (struct sockaddr *)sinaddr, sinaddrlen) == 0) { sox_close(fd2); sox_close(fd); return (-1); } sox_close(fd2); savepid(); sleep(60); forced=1; } #endif if (pidarg) ll_daemon_started(pidarg, lockfd); if (grouparg) { if (isid(grouparg)) gid=atoi(grouparg); else if ((gr=getgrnam(grouparg)) == 0) { fprintf(stderr, "Group not found: %s\n", grouparg); close(lockfd); return (-1); } else gid=gr->gr_gid; libmail_changegroup(gid); } if (userarg) { uid_t uid; if (isid(userarg)) { uid=atoi(userarg); libmail_changeuidgid(uid, getgid()); } else { gid_t g=getgid(), *gp=0; if (grouparg) gp= &g; libmail_changeusername(userarg, gp); } } if (pidarg && ll_daemon_resetio()) { perror("ll_daemon_resetio"); close(lockfd); return (-1); } if (stderrloggerarg) { pid_t p; int waitstat; int pipefd[2]; const char *progname=argv[argn]; if (pipe(pipefd) < 0) { perror("pipe"); return (-1); } signal(SIGCHLD, SIG_DFL); while ((p=fork()) == -1) { sleep(5); } if (p == 0) { signal(SIGHUP, SIG_IGN); sox_close(0); dup_and_check(pipefd[0]); sox_close(pipefd[0]); sox_close(pipefd[1]); sox_close(1); open("/dev/null", O_WRONLY); sox_close(2); dup_and_check(1); closeaccess(); while ((p=fork()) == -1) { perror("fork"); sleep(5); } if (p == 0) { const char *p=strrchr(progname, '/'); if (p) ++p; else p=progname; if (stderrloggername && *stderrloggername) p=stderrloggername; execl(stderrloggerarg, stderrloggerarg, p, (char *)0); perror(stderrloggerarg); _exit(5); } _exit(0); } sox_close(2); dup_and_check(pipefd[1]); sox_close(pipefd[0]); sox_close(pipefd[1]); while (wait(&waitstat) != p) ; } else if (stderrarg) { int fd=open(stderrarg, O_WRONLY|O_APPEND|O_CREAT, 0660); if (!fd) { perror(stderrarg); return (-1); } sox_close(2); dup_and_check(fd); sox_close(fd); } nprocs=40; if (maxprocsarg) { nprocs=atoi(maxprocsarg); if (nprocs <= 0) { fprintf(stderr, "Invalid -maxprocsarg option.\n"); return (-1); } } nwarn= nprocs - (nprocs / 10 + 1); if (warnarg) { int c=atoi(warnarg); if (c >= 0 && c <= nprocs) nwarn=c; } if ((pids=malloc(sizeof(*pids)*nprocs)) == 0) { perror("malloc"); return (-1); } if ((addrs=malloc(sizeof(*addrs)*nprocs)) == 0) { free(pids); perror("malloc"); return (-1); } for (i=0; irrname, buf); if (rfc1035_hostnamecmp(buf, wanted_hostname)) return 0; if (rrptr->rrtype != RFC1035_TYPE_A) return 0; return 1; } /* ** Process TXT records in DNSBL lookup response. */ static int search_txt_records(struct rfc1035_res *res, int allow, const char *varname, struct rfc1035_reply *replyp, const char *wanted_hostname) { char buf[RFC1035_MAXNAMESIZE+1]; int j; if ((j=rfc1035_replysearch_all(res, replyp, wanted_hostname, RFC1035_TYPE_TXT, RFC1035_CLASS_IN, 0)) >= 0) { rfc1035_rr_gettxt(replyp->allrrs[j], 0, buf); if (buf[0]) /* is this necessary? */ { if (!allow) mysetenv(varname, buf); set_txt_response(varname, buf); return 1; } } return 0; } /* ** check_blocklist is called once for each blocklist query to process. */ static void docheckblocklist(struct blocklist_s *p, const char *nameptr) { const char *q; const char *varname=p->var; char hostname[RFC1035_MAXNAMESIZE+1]; int wanttxt; struct rfc1035_reply *replyp; struct rfc1035_res res; unsigned int i; int found; unsigned char query_A[] = {RFC1035_TYPE_A, 0}, query_A_and_TXT[] = {RFC1035_TYPE_A, RFC1035_TYPE_TXT, 0}; hostname[0]=0; strncat(hostname, nameptr, RFC1035_MAXNAMESIZE); if (!varname) varname="BLOCK"; if ((q=getenv(varname)) != 0) return; /* Env var already set */ rfc1035_init_resolv(&res); /* ** The third parameter has opposite meanings. For -block, the last ** component specifies a custom message that overrides any TXT record ** in the DNS access list. For -allow, it simply asks for TXT records ** to be fetched, for use by external software. */ if (p->allow) wanttxt = p->msg != 0; else wanttxt = p->msg && strcmp(p->msg, "*") == 0; (void)rfc1035_resolve_cname_multiple(&res, hostname, wanttxt ? query_A_and_TXT:query_A, RFC1035_CLASS_IN, &replyp, 0); if (!replyp) { rfc1035_destroy_resolv(&res); return; } found=0; for (i=0; iancount+replyp->nscount+replyp->arcount; i++) { if (!is_a_rr(replyp, replyp->allrrs[i], hostname)) continue; if (p->ia.s_addr != INADDR_ANY && p->ia.s_addr != replyp->allrrs[i]->rr.inaddr.s_addr) continue; set_zone(varname, p->display_zone); set_a_response(varname, &replyp->allrrs[i]->rr.inaddr); /* ** The -block option was kind enough to supply the ** error message. */ if (!p->allow && p->msg && *p->msg) { mkmymsg(varname, p->msg); continue; } /* ** search_txt_records takes care of setting varname for ** -blocks, and we must set it for -allows. */ if (p->allow) set_allow_variable(varname, p->msg); if (replyp->next && !search_txt_records(&res, p->allow, varname, replyp->next, hostname) && !p->allow) { size_t l=strlen(p->zone)+40; char *buf=malloc(l+1); if (!buf) { perror("malloc"); _exit(1); } buf[snprintf(buf, l, "Blacklisted by %s", p->zone)]=0; /* ** Even though we did not find a TXT record, we're here ** because of an A record, so for -blocks, we must ** set varname to something. */ mkmymsg(varname, buf); free(buf); } found=1; break; } /* ** Last chance: if all we got is a TXT record, and we were not looking ** for a specific IP address, then take what we've got. */ if (p->ia.s_addr == INADDR_ANY && !found && replyp->next) { if (search_txt_records(&res, p->allow, varname, replyp->next, hostname)) { /* ** search_txt_record takes care of setting varname ** for -blocks, and we must do it for -allows */ if (p->allow) mysetenv(varname, ""); /* Whitelist */ set_zone(varname, p->display_zone); } } rfc1035_replyfree(replyp); rfc1035_destroy_resolv(&res); } static void check_blocklist_ipv4(struct blocklist_s *p, const struct in_addr *ia) { unsigned a,b,c,d; char hostname[RFC1035_MAXNAMESIZE+1]; const unsigned char *q=(const unsigned char *)ia; /* Calculate DNS query hostname */ a=q[0]; b=q[1]; c=q[2]; d=q[3]; /* Silently ignore exceedingly long zones */ if (snprintf(hostname, sizeof hostname, "%u.%u.%u.%u.%s", d, c, b, a, p->zone) <= RFC1035_MAXNAMESIZE) docheckblocklist(p, hostname); } #if RFC1035_IPV6 static void check_blocklist(struct blocklist_s *p, const RFC1035_ADDR *ia) { char hostname[RFC1035_MAXNAMESIZE+1]; /* ** 16 byte IPv6 address. 32 nybbles. Each nybble followed by a dot: ** 64 characters. */ char decimal_address[65]; char bytebuf[5]; int i; if (IN6_IS_ADDR_V4MAPPED(ia)) { struct in_addr ia4; memcpy(&ia4, (const char *)ia + 12, 4); check_blocklist_ipv4(p, &ia4); } decimal_address[0]=0; for (i=0; i<16; ++i) { unsigned char byte=((struct in6_addr *)ia)->s6_addr[15-i]; sprintf(bytebuf, "%x.%x.",(byte & 0x0F), ((byte >> 4) & 0x0F)); strcat(decimal_address, bytebuf); } /* Silently ignore exceedingly long zones */ if (snprintf(hostname, sizeof hostname, "%s%s", decimal_address, p->zone) <= RFC1035_MAXNAMESIZE) docheckblocklist(p, hostname); } #else static void check_blocklist(struct blocklist_s *p, const RFC1035_ADDR *ia) { check_blocklist_ipv4(p, ia); } #endif static void check_drop(int sockfd) { const char *p, *q; char *r; p=droparg; if (p && !*p) p="BLOCK"; for (; p && *p; q=p) { if (*p == ',') { q= ++p; continue; } for (q=p; *q; ++q) if (*q == ',') break; r=malloc(q-p+1); if (!r) { perror("malloc"); _exit(1); } memcpy(r, p, q-p); r[q-p]=0; p=getenv(r); free(r); if (p && *p) { fprintf(stderr, "WARN: dropped blocked connection from %s\n", getenv("TCPREMOTEIP")); denied(sockfd); } } } static void proxy(); static void run(int fd, const RFC1035_ADDR *addr, int addrport, const char *prog, char **argv) { RFC1035_NETADDR lsin; RFC1035_ADDR laddr; int lport; socklen_t i; int ipcnt, ccnt; char buf[RFC1035_MAXNAMESIZE+128]; struct blocklist_s *bl; const char *remoteinfo; const char *p; i=sizeof(lsin); if (sox_getsockname(fd, (struct sockaddr *)&lsin, &i) || rfc1035_sockaddrip(&lsin, i, &laddr) || rfc1035_sockaddrport(&lsin, i, &lport)) { fprintf(stderr, "getsockname failed.\n"); exit(1); } if (!noidentlookup && (remoteinfo=tcpremoteinfo( &laddr, lport, addr, addrport, 0)) != 0) { char *q=malloc(sizeof("TCPREMOTEINFO=")+strlen(remoteinfo)); if (!q) { perror("malloc"); _exit(1); } strcat(strcpy(q, "TCPREMOTEINFO="), remoteinfo); putenv(q); } /* check if it's an exception to the global ip limit */ if( (p=getenv("MAXCPERIP")) != NULL ) { int j = atoi(p); if( j > 0 ) maxperip = j; } for (i=0, ipcnt=ccnt=0; i= sizeof(*addr) && ++ipcnt >= maxperip) { rfc1035_ntoa(addr, buf); fprintf(stderr,"ALERT: Maximum connection limit reached for %s\n",buf); _exit(0); /* Too many from same IP address */ } if ( j >= sizeof(*addr)-1 && ++ccnt >= maxperc) _exit(0); /* Too many from same netblock */ } rfc1035_ntoa(addr, buf); mysetenv("TCPREMOTEIP", buf); sprintf(buf, "%d", ntohs(addrport)); mysetenv("TCPREMOTEPORT", buf); ip2host(addr, "TCPREMOTEHOST"); rfc1035_ntoa(&laddr, buf); mysetenv("TCPLOCALIP", buf); sprintf(buf, "%d", ntohs(lport)); mysetenv("TCPLOCALPORT", buf); ip2host(&laddr, "TCPLOCALHOST"); for (bl=blocklist; bl; bl=bl->next) check_blocklist(bl, addr); check_drop(fd); sox_close(0); sox_close(1); dup_and_check(fd); dup_and_check(fd); sox_close(fd); if (stderrarg && strcmp(stderrarg, "socket") == 0) { sox_close(2); dup_and_check(1); } proxy(); signal(SIGPIPE, SIG_DFL); execv(prog, argv); perror(prog); exit(1); } int main(int argc, char **argv) { int argn=init(argc, argv); int rc; if (argn < 0) { exit(1); } if (accessarg && openaccess(accessarg)) perror(accessarg); if (accesslocal && !accessarg) fprintf(stderr,"-accesslocal requires -access\n"); rc=doit(argn, argc, argv); kill( -getpid(), SIGTERM); exit(rc); return (0); } #if 1 static void proxy() { } #else /* ** SOCKSv5 does not support wildcards binds, for now, so there's no ** reason to manually proxy anything, yet. */ /*************************************************************************** Manual proxy, to support SOCKS encryption. Because encrypted connection to the SOCKS server is supported transparently in libsocks5, we can't just run the app, because we'll lose libsocks5's intercept of read/write, et al ***************************************************************************/ struct proxybuf { char buffer[BUFSIZ]; char *p; int buffered; int rfd, wfd; } ; static void proxy_init(struct proxybuf *b, int r, int w) { b->buffered=0; b->rfd=r; b->wfd=w; } static int proxy_setfd(struct proxybuf *b, fd_set *r, fd_set *w, int *max) { /* If we have something buffered, write it out */ if (b->buffered) { FD_SET(b->wfd, w); if (b->wfd > *max) *max=b->wfd; return (1); } if (b->rfd < 0) { if (b->wfd >= 0) { sox_close(b->wfd); b->wfd= -1; } return (0); /* Nothing else to do */ } if (b->rfd > *max) *max=b->rfd; FD_SET(b->rfd, r); return (1); } static void proxy_dofd(struct proxybuf *b, fd_set *r, fd_set *w) { if (b->buffered) { if (FD_ISSET(b->wfd, w)) { int n=sox_write(b->wfd, b->p, b->buffered); if (n <= 0) { sox_close(b->rfd); sox_close(b->wfd); b->rfd=b->wfd= -1; b->buffered=0; return; } b->p += n; b->buffered -= n; } return; } if (b->rfd >= 0 && FD_ISSET(b->rfd, r)) { int n=sox_read(b->rfd, b->buffer, sizeof(b->buffer)); if (n <= 0) { sox_close(b->rfd); sox_close(b->wfd); b->rfd=b->wfd= -1; b->buffered=0; return; } b->p = b->buffer; b->buffered=n; } } static void proxy_do(struct proxybuf *); static void proxy() { int pipefd0[2], pipefd1[2], pipefd2[2]; pid_t p, p2; int waitstat; struct proxybuf proxy_[3]; if (!proxyarg) return; if (pipe(pipefd0) || pipe(pipefd1) || pipe(pipefd2)) { perror("pipe"); exit(1); } p=fork(); if (p == -1) { perror("fork"); exit(1); } /* ** The parent goes on its merry way, but first makes sure that the ** child process is OK. */ if (p) { while ((p2=wait(&waitstat)) != p) { if (p2 == -1 && errno != EINTR) { perror("wait"); exit(1); } } if (waitstat) exit(0); sox_close(0); sox_close(1); sox_close(2); errno=EINVAL; if (dup_and_check(pipefd0[0]) != 0 || dup_and_check(pipefd1[1]) != 1 || dup_and_check(pipefd2[1]) != 2) { perror("dup(app)"); exit(1); } sox_close(pipefd0[0]); sox_close(pipefd0[1]); sox_close(pipefd1[0]); sox_close(pipefd1[1]); sox_close(pipefd2[0]); sox_close(pipefd2[1]); return; } p=fork(); if (p == -1) exit(1); if (p) exit(0); sox_close(pipefd0[0]); sox_close(pipefd1[1]); sox_close(pipefd2[1]); proxy_init(&proxy_[0], 0, pipefd0[1]); proxy_init(&proxy_[1], pipefd1[0], 1); proxy_init(&proxy_[2], pipefd2[0], 2); proxy_do(proxy_); exit(0); } static void proxy_do(struct proxybuf *p) { fd_set r, w; for (;;) { int m=0; int rc0, rc1, rc2; FD_ZERO(&r); FD_ZERO(&w); rc0=proxy_setfd(p, &r, &w, &m); rc1=proxy_setfd(p+1, &r, &w, &m); rc2=proxy_setfd(p+2, &r, &w, &m); if (rc0 == 0 && rc1 == 0 && rc2 == 0) break; if (rc0 == 0 || rc1 == 0 || rc2 == 0) alarm(10); if (select(m+1, &r, &w, 0, 0) < 0) { perror("select"); break; } proxy_dofd(p, &r, &w); proxy_dofd(p+1, &r, &w); proxy_dofd(p+2, &r, &w); } } #endif