/* mserver.c internet modem server with lockfile support v0.10 5-9-94 Carl Declerck v0.11 1-10-94 "" v0.20 3-10-94 "" v0.21 16-4-96 "" (added tcpconn.c) v0.22 15-1-98 "" (improved SIGCHLD handling, ipaddr ACL bugfix) v0.23 18-1-98 "" (added syslogging, added baudrates >38400) v0.23a 28-1-98 "" (fixed endless loop on wait4()) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "stty.h" #define VERSION "0.23a" #define BUFF_SIZE 4096 #ifndef PATH_MAX #define PATH_MAX 512 #endif #define DEV_BUSY "DEVICE BUSY" #define DEV_NOACCESS "DEVICE INACCESSIBLE" #define DEV_ERROR "DEVICE ERROR" #define max(x,y) ((x) > (y) ? (x) : (y)) typedef struct _netmodem { int chldpid; char *devname; char *params; char *clients; struct _netmodem *next; } NETMODEM; typedef struct _netport { int port; int servfd; NETMODEM *netmods; struct sockaddr_in addr; struct _netport *next; } NETPORT; NETPORT *netports = NULL; char tmppath[PATH_MAX]; void errorf (char *fmt, ...) { va_list argptr; va_start (argptr, fmt); fprintf (stderr, "mserver: "); vfprintf (stderr, fmt, argptr); exit (1); } /* read a single line from a stream, ignoring all irrelevant stuff */ int read_line (char *buff, FILE *f) { int b, i; *buff = 0; do { while ((b = fgetc(f)) != EOF && isspace(b)); if (b == EOF) return (0); for (i = 0; b != EOF && b != '\n' && b != '\r'; i++) { buff[i] = (b == '\t') ? ' ': b; b = fgetc(f); } buff[i] = 0; } while (*buff == '#'); return (1); } /* extract the first word from a string */ char *parse_arg (char *arg, char *buff) { int i = 0, j = 0, quote = 0; *arg = 0; if (buff == NULL) return (NULL); while (buff[i] && isspace(buff[i])) i++; while (buff[i] && (!isspace(buff[i]) || quote)) { switch (buff[i]) { case '\\' : arg[j++] = buff[++i]; break; case '"' : quote = !quote; break; default : arg[j++] = buff[i]; } i++; } while (buff[i] && isspace(buff[i])) i++; arg[j] = 0; return (j ? buff + i : NULL); } int match (const char *s, const char *wc) { while (*wc) { if (*s == *wc || *wc == '?') { wc++; s++; } else if (*wc == '*') { if (!*(++wc)) return (1); while (!match(s, wc)) if (!*(++s)) return (0); } else return (0); } return (!*s); } char *leafname (char *path) { int i = 0, j; for (j = 0; path[j]; j++) if (path[j] == '/') i = j + 1; return (path + i); } void read_config (void) { NETPORT **wport; NETMODEM *tmpmod; FILE *f; int p; char line[BUFF_SIZE], arg[BUFF_SIZE], *tail; if ((f = fopen(CONFIG, "r")) == NULL) errorf ("can't open config file '%s'\n", CONFIG); while (read_line(line, f)) { tail = parse_arg(arg, line); if ((p = atoi(arg)) < 0) errorf ("illegal port %d\n", p); for (wport = &netports; *wport && (*wport)->port != p; wport = &(*wport)->next); if (*wport == NULL) { if ((*wport = (NETPORT *) malloc(sizeof(NETPORT))) == NULL) errorf ("can't allocate space for netport\n"); (*wport)->netmods = NULL; (*wport)->port = p; (*wport)->servfd = -1; (*wport)->next = NULL; } tmpmod = (*wport)->netmods; if (((*wport)->netmods = (NETMODEM *) malloc(sizeof(NETMODEM))) == NULL) errorf ("can't allocate space for exported modem\n"); (*wport)->netmods->next = tmpmod; (*wport)->netmods->chldpid = -1; tail = parse_arg(arg, tail); if (access(arg, F_OK) != 0) errorf ("can't export non-existant modem (%s)\n", arg); strcpy ((*wport)->netmods->devname = malloc(strlen(arg) + 1), arg); tail = parse_arg(arg, tail); strcpy ((*wport)->netmods->params = malloc(strlen(arg) + 1), arg); tail = parse_arg(arg, tail); strcpy ((*wport)->netmods->clients = malloc(strlen(arg) + 1), arg); } fclose (f); } void bind_ports (void) { NETPORT *w; for (w = netports; w; w = w->next) { if ((w->servfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) errorf ("can't create server socket\n"); bzero ((char *) &w->addr, sizeof(w->addr)); w->addr.sin_family = AF_INET; w->addr.sin_addr.s_addr = htonl(INADDR_ANY); w->addr.sin_port = htons(w->port); if (bind(w->servfd, (struct sockaddr *) &w->addr, sizeof(w->addr)) < 0) errorf ("can't bind server socket\n"); if (listen(w->servfd, 5) < 0) errorf ("can't listen on server socket\n"); } } char lockpath[PATH_MAX]; int create_lock (char *devname) { FILE *f; int ok; if ((f = fopen(tmppath, "w")) == NULL) return (0); fprintf (f, "%10d", getpid()); fclose (f); sprintf (lockpath, "%s/LCK..%s", LOCK_DIR, leafname(devname)); ok = (link(tmppath, lockpath) == 0); unlink (tmppath); return (ok); } static struct { int ibaud, speed; } btable[] = { #if !defined(BSD) { 460800, B460800 }, #endif { 230400, B230400 }, { 115200, B115200 }, { 57600, B57600 }, { 38400, B38400 }, { 19200, B19200 }, { 9600, B9600 }, { 4800, B4800 }, { 4800, B4800 }, { 2400, B2400 }, { 1200, B1200 }, { 600, B600 }, { 300, B300 }, { 200, B200 }, { 150, B150 }, { 110, B110 }, { 75, B75 }, { 50, B50 }, { 0, 0 } }; static struct { int idata, cdata; } dtable[] = { { 8, CS8 }, { 7, CS7 }, { 6, CS6 }, { 5, CS5 }, { 0, 0 } }; void set_lineparams (int fd, char *par) { struct termios tio; speed_t speed = B38400; int i, baud, data = CS8, stop = 1, parity = 0; char *p; if ((p = strtok(par, ",")) != NULL) baud = atoi(p); if ((p = strtok(NULL, ",")) != NULL) data = atoi(p); if ((p = strtok(NULL, ",")) != NULL) parity = toupper(*p); if ((p = strtok(NULL, ",")) != NULL) stop = atoi(p); for (i = 0; btable[i].ibaud; i++) if (baud == btable[i].ibaud) { speed = btable[i].speed; break; } for (i = 0; dtable[i].idata; i++) if (data == dtable[i].idata) { data = dtable[i].cdata; break; } switch (parity) { case 'E' : parity = PARENB; break; case 'O' : parity = PARENB | PARODD; break; default : parity = IGNPAR; break; } tcgetattr (fd, &tio); tio.c_cflag &= ~CSIZE; tio.c_cflag |= data; tio.c_iflag = (parity == IGNPAR) ? (tio.c_iflag | IGNPAR) : (tio.c_iflag & ~IGNPAR); tio.c_cflag = (parity == IGNPAR) ? tio.c_cflag : (tio.c_cflag | parity); tio.c_cflag = (stop == 2) ? (tio.c_cflag | CSTOPB) : (tio.c_cflag & ~CSTOPB); cfsetispeed (&tio, speed); cfsetospeed (&tio, speed); tcsetattr (fd, TCSANOW, &tio); } void closedown_netmodem (void) { if (*lockpath) { unlink (lockpath); syslog (LOG_INFO, "device released"); } stty_orig (); } void connect_netmodem (NETPORT *port, int sockfd, struct sockaddr_in *peer) { FILE *f; NETMODEM *mod; struct hostent *phost; struct in_addr iaddr; fd_set readset; int n, devfd, ok, fdmax = 0, quit = 0, *pidp; char buff[BUFF_SIZE], *s, **ss; for (mod = port->netmods; mod; mod = mod->next) if (mod->chldpid == -1) if (create_lock(mod->devname)) break; if (mod == NULL) { pidp = &ok; *lockpath = 0; } else pidp = &mod->chldpid; *pidp = fork(); if (*pidp < 0) { if (*lockpath) unlink (lockpath); return; } else if (*pidp > 0) return; /* child */ atexit (closedown_netmodem); dup2 (sockfd, STDOUT_FILENO); openlog ("mserver", LOG_PID, LOG_DAEMON); phost = gethostbyaddr((char *) &peer->sin_addr, sizeof(peer->sin_addr), AF_INET); /* all modems on this port in use? */ if (mod == NULL) { printf ("%s\r\n", DEV_BUSY); syslog (LOG_INFO, "device busy for %s", phost->h_name); exit (1); } /* is the peer allowed to connect to this modem? */ ok = 0; s = *mod->clients ? strtok(mod->clients, ",") : mod->clients; do { if (match(inet_ntoa(peer->sin_addr), s)) ok = 1; else if (!*s || match(phost->h_name, s)) ok = 1; else for (ss = phost->h_aliases; !ok && *ss; ss++) if (match(*ss, s)) ok = 1; } while (!ok && (s = strtok(NULL, ",")) != NULL); if (!ok) { printf ("%s\r\n", DEV_NOACCESS); syslog (LOG_INFO, "rejected access to %s for %s", mod->devname, phost->h_name); exit (1); } /* we'll probably be around for a while so update pid in lockfile */ if ((f = fopen(lockpath, "w")) != NULL) { fprintf (f, "%10d", getpid()); fclose (f); } /* open device */ if ((devfd = open(mod->devname, O_RDWR)) < 0) { printf ("%s\r\n", DEV_ERROR); exit (1); } /* close stdout and set line discipline */ close (STDOUT_FILENO); stty_initstore (); stty_raw (devfd); stty_clocal (devfd, 1); set_lineparams (devfd, mod->params); fdmax = max(sockfd, devfd); syslog (LOG_INFO, "granted access to %s for %s", mod->devname, phost->h_name); /* main server loop */ while (!quit) { FD_ZERO (&readset); FD_SET (devfd, &readset); FD_SET (sockfd, &readset); select (fdmax + 1, &readset, NULL, NULL, NULL); if (FD_ISSET(devfd, &readset)) { if ((n = read(devfd, buff, BUFF_SIZE)) > 0) { if (write(sockfd, buff, n) < 0) exit (1); } } if (FD_ISSET(sockfd, &readset)) { if ((n = read(sockfd, buff, BUFF_SIZE)) > 0) { if (write(devfd, buff, n) < 0) exit (1); } else quit = 1; } } exit (0); } void sigchld (int sig) { NETPORT *wport; NETMODEM *wmod; int pid, status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { for (wport = netports; wport; wport = wport->next) for (wmod = wport->netmods; wmod; wmod = wmod->next) if (pid == wmod->chldpid) wmod->chldpid = -1; } } int main (int argc, char **argv) { NETPORT *wport; fd_set readset; struct sigaction sig; struct sockaddr_in peer; int len, sockfd, fdmax; /* setup */ read_config (); bind_ports (); sprintf (tmppath, "%s/tmp_lck.%d", LOCK_DIR, getpid()); /* daemonize */ close (STDIN_FILENO); close (STDOUT_FILENO); close (STDERR_FILENO); if (fork() != 0) exit (0); chdir ("/"); #if !defined(BSD) setpgrp (); #endif signal (SIGHUP, SIG_IGN); if (fork() != 0) exit (0); sigemptyset (&sig.sa_mask); sigaddset (&sig.sa_mask, SIGCHLD); sig.sa_handler = sigchld; sig.sa_flags = 0; /* sig.sa_flags = SA_RESTART; */ if (sigaction(SIGCHLD, &sig, NULL) < 0) exit (1); /* tell'em we appear to be up & ok */ openlog ("mserver", LOG_PID, LOG_DAEMON); syslog (LOG_INFO, "v%s starting ...", VERSION); closelog (); /* main daemon loop */ while (1) { do { fdmax = 0; FD_ZERO (&readset); for (wport = netports; wport; wport = wport->next) /* if (wport->chldpid == -1) */ { FD_SET (wport->servfd, &readset); if (wport->servfd > fdmax) fdmax = wport->servfd; } } while (select(fdmax + 1, &readset, NULL, NULL, NULL) == -1 && errno == EINTR); for (wport = netports; wport; wport = wport->next) if (FD_ISSET(wport->servfd, &readset)) { len = sizeof(peer); if ((sockfd = accept(wport->servfd, (struct sockaddr *) &peer, &len)) >= 0) { /* reap any stale children */ sigchld (SIGCHLD); connect_netmodem (wport, sockfd, &peer); close (sockfd); } } } return (0); }