304 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| /*
 | |
| tcpd.c
 | |
| */
 | |
| 
 | |
| #include <sys/types.h>
 | |
| #include <errno.h>
 | |
| #include <fcntl.h>
 | |
| #include <limits.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <unistd.h>
 | |
| #include <signal.h>
 | |
| #include <minix/config.h>
 | |
| #include <sys/ioctl.h>
 | |
| #include <sys/wait.h>
 | |
| #include <net/hton.h>
 | |
| #include <net/netlib.h>
 | |
| #include <net/gen/in.h>
 | |
| #include <net/gen/inet.h>
 | |
| #include <net/gen/netdb.h>
 | |
| #include <net/gen/tcp.h>
 | |
| #include <net/gen/tcp_io.h>
 | |
| 
 | |
| /* This program can be compiled to be paranoid, i.e. check incoming connection
 | |
|  * according to an access file, or to trust anyone.  The much smaller "trust
 | |
|  * 'em" binary will call the paranoid version if the access file exists.
 | |
|  */
 | |
| 
 | |
| static char *arg0, *service;
 | |
| static unsigned nchildren;
 | |
| 
 | |
| static void report(const char *label)
 | |
| {
 | |
|     int err= errno;
 | |
| 
 | |
|     fprintf(stderr, "%s %s: %s: %s\n", arg0, service, label, strerror(err));
 | |
|     errno= err;
 | |
| }
 | |
| 
 | |
| static void sigchld(int sig)
 | |
| {
 | |
|     while (waitpid(0, NULL, WNOHANG) > 0) {
 | |
| 	if (nchildren > 0) nchildren--;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void release(int *fd)
 | |
| {
 | |
|     if (*fd != -1) {
 | |
| 	close(*fd);
 | |
| 	*fd= -1;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void usage(void)
 | |
| {
 | |
|     fprintf(stderr,
 | |
| 	"Usage: %s [-d] [-m maxclients] service program [arg ...]\n",
 | |
| 	arg0);
 | |
|     exit(1);
 | |
| }
 | |
| 
 | |
| int main(int argc, char **argv)
 | |
| {
 | |
|     tcpport_t port;
 | |
|     struct nwio_tcpcl tcplistenopt;
 | |
|     struct nwio_tcpconf tcpconf;
 | |
|     struct nwio_tcpopt tcpopt;
 | |
|     char *tcp_device;
 | |
|     struct servent *servent;
 | |
|     int tcp_fd, client_fd, count, r;
 | |
|     int pfd[2];
 | |
|     unsigned stall= 0;
 | |
|     struct sigaction sa;
 | |
|     sigset_t chldmask, chldunmask, oldmask;
 | |
|     char **progv;
 | |
| 
 | |
| #if !PARANOID
 | |
| #   define debug 0
 | |
| #   define max_children ((unsigned) -1)
 | |
|     arg0= argv[0];
 | |
| 
 | |
|     /* Switch to the paranoid version of me if there are flags, or if
 | |
|      * there is an access file.
 | |
|      */
 | |
|     if (argv[1][0] == '-' || access(_PATH_SERVACCES, F_OK) == 0) {
 | |
| 	execv("/usr/bin/tcpdp", argv);
 | |
| 	report("tcpdp");
 | |
| 	exit(1);
 | |
|     }
 | |
|     if (argc < 3) usage();
 | |
|     service= argv[1];
 | |
|     progv= argv+2;
 | |
| 
 | |
| #else /* PARANOID */
 | |
|     int debug, i;
 | |
|     unsigned max_children;
 | |
| 
 | |
|     arg0= argv[0];
 | |
|     debug= 0;
 | |
|     max_children= -1;
 | |
|     i= 1;
 | |
|     while (i < argc && argv[i][0] == '-') {
 | |
| 	char *opt= argv[i++] + 1;
 | |
| 	unsigned long m;
 | |
| 	char *end;
 | |
| 
 | |
| 	if (*opt == '-' && opt[1] == 0) break;	/* -- */
 | |
| 
 | |
| 	while (*opt != 0) switch (*opt++) {
 | |
| 	case 'd':
 | |
| 	    debug= 1;
 | |
| 	    break;
 | |
| 	case 'm':
 | |
| 	    if (*opt == 0) {
 | |
| 		if (i == argc) usage();
 | |
| 		opt= argv[i++];
 | |
| 	    }
 | |
| 	    m= strtoul(opt, &end, 10);
 | |
| 	    if (m <= 0 || m > UINT_MAX || *end != 0) usage();
 | |
| 	    max_children= m;
 | |
| 	    opt= "";
 | |
| 	    break;
 | |
| 	default:
 | |
| 	    usage();
 | |
| 	}
 | |
|     }
 | |
|     service= argv[i++];
 | |
|     progv= argv+i;
 | |
|     if (i >= argc) usage();
 | |
| #endif
 | |
| 
 | |
|     /* The interface to start the service on. */
 | |
|     if ((tcp_device= getenv("TCP_DEVICE")) == NULL) tcp_device= TCP_DEVICE;
 | |
| 
 | |
|     /* Let SIGCHLD interrupt whatever I'm doing. */
 | |
|     sigemptyset(&chldmask);
 | |
|     sigaddset(&chldmask, SIGCHLD);
 | |
|     sigprocmask(SIG_BLOCK, &chldmask, &oldmask);
 | |
|     chldunmask= oldmask;
 | |
|     sigdelset(&chldunmask, SIGCHLD);
 | |
|     sigemptyset(&sa.sa_mask);
 | |
|     sa.sa_flags = 0;
 | |
|     sa.sa_handler = sigchld;
 | |
|     sigaction(SIGCHLD, &sa, NULL);
 | |
| 
 | |
|     /* Open a socket to the service I'm to serve. */
 | |
|     if ((servent= getservbyname(service, "tcp")) == NULL) {
 | |
| 	unsigned long p;
 | |
| 	char *end;
 | |
| 
 | |
| 	p= strtoul(service, &end, 0);
 | |
| 	if (p <= 0 || p > 0xFFFF || *end != 0) {
 | |
| 	    fprintf(stderr, "%s: %s: Unknown service\n",
 | |
| 		arg0, service);
 | |
| 	    exit(1);
 | |
| 	}
 | |
| 	port= htons((tcpport_t) p);
 | |
|     } else {
 | |
| 	port= servent->s_port;
 | |
| 
 | |
| 	if (debug)
 | |
| 	{
 | |
| 	    fprintf(stderr, "%s %s: listening to port %u\n",
 | |
| 		arg0, service, ntohs(port));
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     /* No client yet. */
 | |
|     client_fd= -1;
 | |
| 
 | |
|     while (1) {
 | |
| 	if ((tcp_fd= open(tcp_device, O_RDWR)) < 0) {
 | |
| 	    report(tcp_device);
 | |
| 	    if (errno == ENOENT || errno == ENODEV
 | |
| 			    || errno == ENXIO) {
 | |
| 		exit(1);
 | |
| 	    }
 | |
| 	    goto bad;
 | |
| 	}
 | |
| 
 | |
| 	tcpconf.nwtc_flags= NWTC_LP_SET | NWTC_UNSET_RA | NWTC_UNSET_RP;
 | |
| 	tcpconf.nwtc_locport= port;
 | |
| 
 | |
| 	if (ioctl(tcp_fd, NWIOSTCPCONF, &tcpconf) < 0) {
 | |
| 	    report("Can't configure TCP channel");
 | |
| 	    exit(1);
 | |
| 	}
 | |
| 
 | |
| 	tcpopt.nwto_flags= NWTO_DEL_RST;
 | |
| 
 | |
| 	if (ioctl(tcp_fd, NWIOSTCPOPT, &tcpopt) < 0) {
 | |
| 	    report("Can't set TCP options");
 | |
| 	    exit(1);
 | |
| 	}
 | |
| 
 | |
| 	if (client_fd != -1) {
 | |
| 	    /* We have a client, so start a server for it. */
 | |
| 
 | |
| 	    tcpopt.nwto_flags= 0;
 | |
| 	    (void) ioctl(client_fd, NWIOSTCPOPT, &tcpopt);
 | |
| 
 | |
| 	    fflush(NULL);
 | |
| 
 | |
| 	    /* Create a pipe to serve as an error indicator. */
 | |
| 	    if (pipe(pfd) < 0) {
 | |
| 		report("pipe");
 | |
| 		goto bad;
 | |
| 	    }
 | |
| 	    (void) fcntl(pfd[1], F_SETFD,
 | |
| 		    fcntl(pfd[1], F_GETFD) | FD_CLOEXEC);
 | |
| 
 | |
| 	    /* Fork and exec. */
 | |
| 	    switch (fork()) {
 | |
| 	    case -1:
 | |
| 		report("fork");
 | |
| 		close(pfd[0]);
 | |
| 		close(pfd[1]);
 | |
| 		goto bad;
 | |
| 	    case 0:
 | |
| 		close(tcp_fd);
 | |
| 		close(pfd[0]);
 | |
| #if PARANOID
 | |
| 		/* Check if access to this service allowed. */
 | |
| 		if (ioctl(client_fd, NWIOGTCPCONF, &tcpconf) == 0
 | |
| 		    && tcpconf.nwtc_remaddr != tcpconf.nwtc_locaddr
 | |
| 		    && !servxcheck(tcpconf.nwtc_remaddr, argv[1], NULL)
 | |
| 		) {
 | |
| 		    exit(1);
 | |
| 		}
 | |
| #endif
 | |
| 		sigprocmask(SIG_SETMASK, &oldmask, NULL);
 | |
| 		dup2(client_fd, 0);
 | |
| 		dup2(client_fd, 1);
 | |
| 		close(client_fd);
 | |
| 		execvp(progv[0], progv);
 | |
| 		report(progv[0]);
 | |
| 		write(pfd[1], &errno, sizeof(errno));
 | |
| 		exit(1);
 | |
| 	    default:
 | |
| 		nchildren++;
 | |
| 		release(&client_fd);
 | |
| 		close(pfd[1]);
 | |
| 		r= read(pfd[0], &errno, sizeof(errno));
 | |
| 		close(pfd[0]);
 | |
| 		if (r != 0) goto bad;
 | |
| 		break;
 | |
| 	    }
 | |
| 	}
 | |
| 
 | |
| 	while (nchildren >= max_children) {
 | |
| 	    /* Too many clients, wait for one to die off. */
 | |
| 	    sigsuspend(&chldunmask);
 | |
| 	}
 | |
| 
 | |
| 	/* Wait for a new connection. */
 | |
| 	sigprocmask(SIG_UNBLOCK, &chldmask, NULL);
 | |
| 
 | |
| 	tcplistenopt.nwtcl_flags= 0;
 | |
| 	while (ioctl(tcp_fd, NWIOTCPLISTEN, &tcplistenopt) < 0) {
 | |
| 	    if (errno != EINTR) {
 | |
| 		if (errno != EAGAIN || debug) {
 | |
| 		    report("Unable to listen");
 | |
| 		}
 | |
| 		goto bad;
 | |
| 	    }
 | |
| 	}
 | |
| 	sigprocmask(SIG_BLOCK, &chldmask, NULL);
 | |
| 
 | |
| 	/* We got a connection. */
 | |
| 	client_fd= tcp_fd;
 | |
| 	tcp_fd= -1;
 | |
| 
 | |
| 	if (debug && ioctl(client_fd, NWIOGTCPCONF, &tcpconf) == 0) {
 | |
| 	    fprintf(stderr, "%s %s: Connection from %s:%u\n",
 | |
| 		arg0, service,
 | |
| 		inet_ntoa(tcpconf.nwtc_remaddr),
 | |
| 		ntohs(tcpconf.nwtc_remport));
 | |
| 	}
 | |
| 	/* All is well, no need to stall. */
 | |
| 	stall= 0;
 | |
| 	continue;
 | |
| 
 | |
|     bad:
 | |
| 	/* All is not well, release resources. */
 | |
| 	release(&tcp_fd);
 | |
| 	release(&client_fd);
 | |
| 
 | |
| 	/* Wait a bit if this happens more than once. */
 | |
| 	if (stall != 0) {
 | |
| 	    if (debug) {
 | |
| 		fprintf(stderr, "%s %s: stalling %u second%s\n",
 | |
| 		    arg0, service,
 | |
| 		    stall, stall == 1 ? "" : "s");
 | |
| 	    }
 | |
| 	    sleep(stall);
 | |
| 	    stall <<= 1;
 | |
| 	} else {
 | |
| 	    stall= 1;
 | |
| 	}
 | |
|     }
 | |
| }
 | 
