/* $Id: newstartd.c,v 1.9 2006/05/08 08:50:21 quozl Exp $ */ #ifndef lint static char vcid[] = "$Id: newstartd.c,v 1.9 2006/05/08 08:50:21 quozl Exp $"; #endif /* lint */ /* * There's a really bad hack in newstartd.c that refuses more than 1 * connection from any hostname. It's bad because it uses /usr/ucb/netstat * to figure out who's connected. * * At this point, only the hostname of the caller is available. The * daemon in theory has access to both user name and (partial) hostname, * so that's where multiple logins should be denied. * * To bypass this check, create the file defined by NoCount_File in * newaccess.c. Usually .nocount in SYSCONFDIR. Set by default normally. * * 4/13/92 * * Derived from distribution startd.c and xtrekII.sock.c (as of ~1/14/91). * * Installation: update the Prog and LogFile variables below, and * set the default port appropriately (presently 2592). Set debug=1 * to run this under dbx. Link this with access.o and subnet.o. * * Summary of changes: * - subsumes xtrekII.sock into startd (this calls ntserv directly to * reduce OS overhead) * - some debugger support code added * - uses reaper() to keep process table clean (removes procs) * * 8/16/91 Terence Chang * */ #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include INC_FCNTL #include "struct.h" #include "data.h" #include "proto.h" #include "ip.h" #define MVERS #include "version.h" #include "patchlevel.h" int restart; /* global flag, set by SIGHUP, cleared by read */ int debug = 0; /* programmers' debugging flag */ static int get_connection(); static int read_portfile(char *); static void deny(char *ip); static void statistics(int, char *ip); static void process(int, char *ip); static void reaper_block(); static void reaper_unblock(); static void reaper(int); static void hangup(int); static void putpid(void); #define SP_BADVERSION 21 struct badversion_spacket { char type; char why; char pad2; char pad3; }; #define MAXPROG 16 /* maximum ports to listen to */ #define MAXPROCESSES 8+MAXPLAYER+TESTERS+MAXWAITING /* maximum processes to create */ static int active; /* number of processes running */ static struct progrecord { /* array of data read from file */ int type; /* SOCK_STREAM, or SOCK_DGRAM */ unsigned short port; /* port number to listen on */ int sock; /* socket created on port or -1 */ pid_t child; /* for UDP, active child */ int nargs; /* number of arguments to prog */ char prog[512]; /* program to start on connect */ char progname[512]; /* more stuff for exec() */ char arg[4][64]; /* arguments for program */ int internal; /* ports handled without fork() */ int accepts; /* count of accept() calls */ int denials; /* count of deny() calls */ int forks; /* count of fork() calls */ unsigned long addr; /* address to bind() to */ } prog[MAXPROG]; static int num_progs = 0; int fd; /* log file file descriptor */ /* sigaction() is a POSIX signal function. On some systems, it is incompatible with SysV/BSD signals. Some SysV signals provide sigaction(), others don't. If you get odd signal behavior, undef this at the expense of zombie processes. -da */ #undef REAPER_HANDLER /* prevent child zombie processes -da */ #ifdef REAPER_HANDLER #ifdef linux #define sa_sigaction sa_restorer #endif void handle_reaper(void) { struct sigaction action; action.sa_flags = SA_NOCLDWAIT; action.sa_handler = reaper; sigemptyset(&(action.sa_mask)); action.sa_sigaction = NULL; sigaction(SIGCHLD, &action, NULL); } #endif int main (int argc, char *argv[]) { char *portfile = N_PORTS; int port_idx = -1; int i; pid_t pid; FILE *file; active = 0; getpath(); setpath(); /* if someone tries to ask for help, give 'em it */ if (argc == 2 && argv[1][0] == '-') { fprintf (stderr, "Usage: %s [portfile] [debug]\n", argv[0] ); fprintf (stderr, "Usage: %s stop\n", argv[0] ); exit (1); } /* fetch our previous pid if available */ file = fopen (N_NETREKDPID, "r"); if (file != NULL) { fscanf (file, "%d", &pid); fclose (file); } else { /* only a total lack of the file is acceptable */ if (errno != ENOENT) { perror (N_NETREKDPID); exit(1); } } /* check for a request to stop the listener */ if (argc == 2) { if (!strcmp (argv[1], "stop")) { if (file != NULL) { if (kill (pid, SIGINT) == 0) { remove (N_NETREKDPID); fprintf (stderr, "netrekd: stopped pid %d\n", pid); exit (0); } fprintf (stderr, "netrekd: cannot stop, pid %d\n, may be already stopped\n", pid); perror ("kill"); exit (1); } fprintf (stderr, "netrekd: cannot stop, no %s file\n", N_NETREKDPID); exit (1); } if (!strcmp (argv[1], "reload")) { if (file != NULL) { if (kill (pid, SIGHUP) == 0) { fprintf (stderr, "netrekd: sent SIGHUP to pid %d\n", pid); exit (0); } fprintf (stderr, "netrekd: cannot reload, pid %d not present\n", pid); perror ("kill"); exit (1); } fprintf (stderr, "netrekd: cannot reload, no %s file\n", N_NETREKDPID); exit (1); } } /* check for duplicate start */ if (file != NULL) { /* there is a pid file, test the pid is valid */ if (kill (pid, 0) == 0) { fprintf (stderr, "netrekd: cannot start, already running as pid %d\n", pid); exit (1); } else { if (errno != ESRCH) { fprintf (stderr, "netrekd: failed on test of pid %d existence\n", pid); perror ("kill"); exit (1); } } /* pid was not valid but file was present, so remove it */ remove (N_NETREKDPID); } /* allow user to specify a port file to use */ if (argc == 2) { if (strcmp (argv[1], "start")) { portfile = argv[1]; } } /* allow developer to ask for verbose output */ if (argc == 3 && !strcmp(argv[2], "debug")) debug++; /* wander off to the place where we live */ if (chdir (LIBDIR) != 0) { perror (LIBDIR); exit(1); } /* check file access before forking */ if (access (portfile, R_OK) != 0) { perror (portfile); exit(1); } /* set up handlers for signals */ #ifdef REAPER_HANDLER handle_reaper(); #else SIGNAL (SIGCHLD, reaper); #endif SIGNAL (SIGHUP, hangup); SIGNAL (SIGUSR1, SIG_IGN); /* open the connection log file */ if ((fd = open (LogFile, O_CREAT | O_WRONLY | O_APPEND, 0600)) < 0) { perror (LogFile); } /* fork this as a daemon */ if (!debug) { pid = fork(); if (pid != 0) { if (pid < 0) { perror("fork"); exit(1); } fprintf (stderr, "netrekd: Vanilla Netrek Listener %s.%d started, pid %d,\n" "netrekd: logging to %s\n", mvers, PATCHLEVEL, pid, LogFile ); exit(0); } /* detach from terminal */ DETACH; } /* do not propogate the log to forked processes */ fcntl(fd, F_SETFD, FD_CLOEXEC); /* close our standard file descriptors and attach them to the log */ if (!debug) { (void) dup2 (fd, 0); (void) dup2 (fd, 1); (void) dup2 (fd, 2); } /* set the standard streams to be line buffered (ANSI C3.159-1989) */ setvbuf (stderr, NULL, _IOLBF, 0); setvbuf (stdout, NULL, _IOLBF, 0); /* record our process id */ putpid (); /* run forever */ for (;;) { /* [re-]read the port file */ num_progs = read_portfile (portfile); restart = 0; /* run until restart requested */ while (!restart) { pid_t pid; struct sockaddr_in addr; /* wait for one connection */ port_idx = get_connection (&addr); if (port_idx < 0) continue; prog[port_idx].accepts++; /* check this client against denied parties */ if (ip_deny(inet_ntoa(addr.sin_addr))) { prog[port_idx].denials++; deny (inet_ntoa(addr.sin_addr)); close (0); continue; } /* check for limit */ if (active > MAXPROCESSES) { fprintf(stderr, "netrekd: hit maximum processes, connection closed\n"); close (0); continue; } /* check for internals */ if (prog[port_idx].internal) { statistics (port_idx, inet_ntoa(addr.sin_addr)); continue; } reaper_block(); /* fork to execute the program specified by .ports file */ pid = fork (); if (pid == -1) { perror("fork"); close (0); reaper_unblock(); continue; } if (pid == 0) { process (port_idx, inet_ntoa(addr.sin_addr)); /* process() returns only on error */ exit (0); } /* record the pid of any UDP clients created */ if (prog[port_idx].type == SOCK_DGRAM) { prog[port_idx].child = pid; } prog[port_idx].forks++; active++; if (debug) fprintf (stderr, "active++: %d: pid %d port %d\n", active, pid, prog[port_idx].port); reaper_unblock(); /* main program continues after fork, close socket to client */ close (0); } /* while (!restart) */ /* SIGHUP restart requested, close all listening sockets */ fprintf (stderr, "netrekd: restarting on SIGHUP\n"); for (i = 0; i < num_progs; i++) { if (prog[i].sock >= 0) close(prog[i].sock); } /* between closing the listening sockets and commencing the new ones there is a time during which incoming client connections are refused */ } } static int get_connection(struct sockaddr_in *peer) { struct sockaddr_in addr; socklen_t addrlen; fd_set accept_fds; int i, st, newsock; int foo = 1; /* stage one; for each port not yet open, set up listening socket */ /* check all ports */ for (i = 0; i < num_progs; i++) { sock = prog[i].sock; if (sock < 0) { fprintf(stderr, "netrekd: port %d, %s, ", prog[i].port, prog[i].prog); if ((sock = socket(AF_INET, prog[i].type, 0)) < 0) { perror("socket"); sleep(1); return -1; } /* we can't cope with having socket zero for a listening socket */ if (sock == 0) { if (debug) fprintf(stderr, "gack: don't want socket zero\n"); if ((sock = socket(AF_INET, prog[i].type, 0)) < 0) { perror("socket"); sleep(1); close(0); return -1; } close(0); } /* set the listening socket to close on exec so that children don't hold open the file descriptor, thus preventing us from restarting netrekd. */ if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { perror("fcntl F_SETFD FD_CLOEXEC"); close(sock); sleep(1); return -1; } /* set the listening socket to allow re-use of the address (port) so that we don't have to pay the CLOSE_WAIT penalty if we need to close and re-open the socket on restart. */ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &foo, sizeof(int)) < 0) { perror("setsockopt SOL_SOCKET SO_REUSEADDR"); close(sock); sleep(1); return -1; } addr.sin_family = AF_INET; addr.sin_addr.s_addr = prog[i].addr; addr.sin_port = htons(prog[i].port); if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("bind"); close(sock); sleep(1); continue; } if (prog[i].type == SOCK_STREAM) { if (listen(sock, 1) < 0) { perror("listen"); close(sock); sleep(1); continue; } } prog[i].sock = sock; fprintf(stderr, "listening fd %d, to do \"%s\" %s %s %s %s\n", prog[i].sock, prog[i].progname, prog[i].arg[0], prog[i].arg[1], prog[i].arg[2], prog[i].arg[3]); fflush(stderr); } } /* stage two; wait for a connection on the listening sockets */ for (;;) { /* wait for a connection */ st = 0; FD_ZERO(&accept_fds); /* clear the file descriptor mask */ for (i = 0; i < num_progs; i++) { /* set bits in mask for each sock */ sock = prog[i].sock; if (sock < 0) continue; if (prog[i].type == SOCK_STREAM) FD_SET(sock, &accept_fds); if (prog[i].type == SOCK_DGRAM) if (prog[i].child == 0) FD_SET(sock, &accept_fds); st++; } /* paranoia, it could be possible that we have no sockets ready */ if (st == 0) { fprintf(stderr, "netrekd: paranoia, no sockets to listen for\n"); sleep(1); return -1; } /* block until select() indicates accept() will complete */ st = select(32, &accept_fds, 0, 0, 0); if (st > 0) break; if (restart) return -1; if (errno == EINTR) continue; perror("select"); sleep(1); } /* find one socket in mask that shows activity */ for (i = 0; i < num_progs; i++) { sock = prog[i].sock; if (sock < 0) continue; if (FD_ISSET(sock, &accept_fds)) break; } /* none of the sockets showed activity? erk */ if (i >= num_progs) return -1; if (prog[i].type == SOCK_STREAM) { /* accept stream connections as new socket */ for (;;) { addrlen = sizeof(addr); newsock = accept(sock, (struct sockaddr *) peer, &addrlen); if (newsock >= 0) break; if (errno == EINTR) continue; fprintf(stderr, "netrekd: port %d, %s, ", prog[i].port, prog[i].prog); perror("accept"); sleep(1); return -1; } if (newsock != 0) { if (dup2(newsock, 0) == -1) { perror("dup2"); } close(newsock); } } else { /* datagrams will be read by child on duplicated socket */ dup2(sock, 0); } return i; } static void deny(char *ip) { struct badversion_spacket packet; /* if log is open, write an entry to it */ if (fd != -1) { time_t curtime; char logname[BUFSIZ]; time (&curtime); sprintf (logname, "Denied %-32.32s %s", ip, ctime(&curtime)); write (fd, logname, strlen(logname)); } /* issue a bad version packet to the client */ memset(&packet, 0, sizeof(struct badversion_spacket)); packet.type = SP_BADVERSION; #define BADVERSION_DENIED 1 /* access denied by netrekd */ packet.why = BADVERSION_DENIED; write (0, (char *) &packet, sizeof(packet)); sleep (2); } static void statistics (int port_idx, char *ip) { FILE *client; int i; if (fork() == 0) { static struct linger linger = {1, 500}; setsockopt (0, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof (linger)); client = fdopen (0, "w"); if (client != NULL) { if (!strcmp (ip, "127.0.0.1")) { fprintf (client, "port\taccepts\tdenials\tforks\n" ); for (i=0; inargs) { case 0: execl (pr->prog, pr->progname, ip, (char *) NULL); break; case 1: execl (pr->prog, pr->progname, pr->arg[0], ip, (char *) NULL); break; case 2: execl (pr->prog, pr->progname, pr->arg[0], pr->arg[1], ip, (char *) NULL); break; case 3: execl (pr->prog, pr->progname, pr->arg[0], pr->arg[1], pr->arg[2], ip, (char *) NULL); break; case 4: execl (pr->prog, pr->progname, pr->arg[0], pr->arg[1], pr->arg[2], pr->arg[3], ip, (char *) NULL); break; default: ; } fprintf (stderr, "Error in execl! -- %s\n", pr->prog); perror ("execl"); close (0); exit (1); } /* set flag requesting restart, select() will get EINTR */ static void hangup (int sig) { restart++; HANDLE_SIG (SIGHUP, hangup); /* get rid of compiler warning */ if (sig) return; } static void reaper_block() { sigset_t set; sigemptyset(&set); sigaddset(&set, SIGCHLD); if (sigprocmask(SIG_BLOCK, &set, NULL) < 0) { perror("sigprocmask: SIG_BLOCK SIGCHLD"); } } static void reaper_unblock() { sigset_t set; sigemptyset(&set); sigaddset(&set, SIGCHLD); if (sigprocmask(SIG_UNBLOCK, &set, NULL) < 0) { perror("sigprocmask: SIG_UNBLOCK SIGCHLD"); } } /* accept termination of any child processes */ static void reaper (int sig) { int stat; pid_t pid; for (;;) { pid = WAIT3 (&stat, WNOHANG, 0); if (pid > 0) { active--; if (debug) fprintf (stderr, "active--: %d: pid %d terminated\n", active, pid); int i; for (i=0; i= 3) { /* port, host:port, portu, host:portu */ if (!(port = strchr(addrbuf, ':'))) { prog[i].addr = INADDR_ANY; port = addrbuf; } else { *port++ = '\0'; prog[i].addr = inet_addr(addrbuf); } prog[i].port = atoi(port); prog[i].type = strchr(port, 'u') == NULL ? SOCK_STREAM : SOCK_DGRAM; prog[i].nargs = n-3; prog[i].sock = -1; prog[i].internal = (!strcmp (prog[i].prog, "special")); prog[i].accepts = 0; prog[i].denials = 0; prog[i].forks = 0; /* check normal entries to make sure program mode is ok */ if ((!prog[i].internal) && (stat (prog[i].prog, &sbuf) < 0 || !(sbuf.st_mode & S_IRWXU))) { fprintf (stderr, "\"%s\" not accessible or not executable.\n", prog[i].prog); fflush (stderr); } else { i++; } } } fclose (fi); } else { perror (portfile); } /* if the .ports file is empty, at least open the standard port */ if (!i) { strcpy (prog[0].prog, Prog); strcpy (prog[0].progname, Prog); prog[0].port = PORT; prog[0].nargs = 0; prog[0].sock = -1; prog[0].accepts = 0; prog[0].denials = 0; prog[0].forks = 0; i = 1; } return i; }