diff options
Diffstat (limited to 'src/daemon/gnuserv.c')
-rw-r--r-- | src/daemon/gnuserv.c | 643 |
1 files changed, 643 insertions, 0 deletions
diff --git a/src/daemon/gnuserv.c b/src/daemon/gnuserv.c new file mode 100644 index 00000000..2be6659e --- /dev/null +++ b/src/daemon/gnuserv.c @@ -0,0 +1,643 @@ +/* -*-C-*- + * Server code for handling requests from clients and forwarding them + * on to the GNU Emacs process. + * + * This file is part of GNU Emacs. + * + * Copying is permitted under those conditions described by the GNU + * General Public License. + * + * Copyright (C) 1989 Free Software Foundation, Inc. + * + * Author: Andy Norman (ange@hplb.hpl.hp.com), based on 'etc/server.c' + * from the 18.52 GNU Emacs distribution. + * + * Please mail bugs and suggestions to the author at the above address. + */ + +/* HISTORY + * 11-Nov-1990 bristor@simba + * Added EOT stuff. + */ + +/* + * This file incorporates new features added by Bob Weiner <weiner@mot.com>, + * Darrell Kindred <dkindred@cmu.edu> and Arup Mukherjee <arup@cmu.edu>. + * Please see the note at the end of the README file for details. + * + * (If gnuserv came bundled with your emacs, the README file is probably + * ../etc/gnuserv.README relative to the directory containing this file) + */ + +#include <glibtop.h> +#include <glibtop/open.h> +#include <glibtop/close.h> +#include <glibtop/command.h> +#include <glibtop/xmalloc.h> + +#include <glibtop/parameter.h> + +#include "server_config.h" + +#include <glibtop/gnuserv.h> + +#include <errno.h> +#include <popt-gnome.h> + +#include "daemon.h" + +#ifdef AIX +#include <sys/select.h> +#endif + +#ifdef NEED_DECLARATION_PROGRAM_INVOCATION_NAME +extern char *program_invocation_name, *program_invocation_short_name; +#endif + +#ifndef HAVE_PROGRAM_INVOCATION_SHORT_NAME +char *program_invocation_short_name; +#endif + +#ifndef HAVE_PROGRAM_INVOCATION_NAME +char *program_invocation_name; +#endif + +void handle_parent_connection (int s); +void handle_slave_connection (int input, int output); + +#if !defined(INTERNET_DOMAIN_SOCKETS) +#error "Internet Domain sockets are required" +#endif + +#ifdef AUTH_MAGIC_COOKIE +#include <X11/X.h> +#include <X11/Xauth.h> + +static Xauth *server_xauth = NULL; + +#endif /* AUTH_MAGIC_COOKIE */ + +int enable_debug = 0; +int verbose_output = 0; +static int no_daemon = 0; +static int invoked_from_inetd = 0; +static int changed_uid = 0; + +void +syslog_message (int priority, char *format, ...) +{ + va_list ap; + char buffer [BUFSIZ]; + + va_start (ap, format); + vsnprintf (buffer, BUFSIZ-1, format, ap); + va_end (ap); + + syslog (priority, "%s", buffer); +} + +void +syslog_io_message (int priority, char *format, ...) +{ + va_list ap; + char buffer [BUFSIZ]; + char buffer2 [BUFSIZ]; + + va_start (ap, format); + vsnprintf (buffer, BUFSIZ-1, format, ap); + va_end (ap); + + snprintf (buffer2, BUFSIZ-1, "%s: %s", buffer, strerror (errno)); + syslog (priority, "%s", buffer2); +} + +/* + * timed_read - Read with timeout. + */ + +static int +timed_read (int fd, char *buf, int max, int timeout, int one_line) +{ + fd_set rmask; + struct timeval tv; /* = {timeout, 0}; */ + char c = 0; + int nbytes = 0; + int r; + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + FD_ZERO (&rmask); + FD_SET (fd, &rmask); + + do { + r = select (fd + 1, &rmask, NULL, NULL, &tv); + + if (r > 0) { + if (read (fd, &c, 1) == 1) { + *buf++ = c; + ++nbytes; + } else { + syslog_io_message (LOG_WARNING, "read error on socket"); + return -1; + } + } else if (r == 0) { + syslog_io_message (LOG_WARNING, "read timed out"); + return -1; + } else { + syslog_io_message (LOG_WARNING, "error in select"); + return -1; + } + } while ((nbytes < max) && !(one_line && (c == '\n'))); + + --buf; + if (one_line && *buf == '\n') { + *buf = 0; + } + return nbytes; +} + + +/* + * permitted -- return whether a given host is allowed to connect to the server. + */ + +static int +permitted (u_long host_addr, int fd) +{ + int i; + + char auth_protocol[128]; + char buf[1024]; + int auth_data_len; + + /* Read auth protocol name */ + + if (timed_read (fd, auth_protocol, AUTH_NAMESZ, AUTH_TIMEOUT, 1) <= 0) + return FALSE; + + if (enable_debug) + syslog_message (LOG_DEBUG, + "Client sent authenticatin protocol '%s'.", + auth_protocol); + + if (strcmp (auth_protocol, DEFAUTH_NAME) && + strcmp (auth_protocol, MCOOKIE_NAME)) { + syslog_message (LOG_WARNING, + "Invalid authentication protocol " + "'%s' from client", + auth_protocol); + return FALSE; + } + + if (!strcmp (auth_protocol, MCOOKIE_NAME)) { + /* + * doing magic cookie auth + */ + + if (timed_read (fd, buf, 10, AUTH_TIMEOUT, 1) <= 0) + return FALSE; + + auth_data_len = atoi (buf); + + if (auth_data_len < 1 || auth_data_len > sizeof(buf)) { + syslog_message(LOG_WARNING, "Invalid data length supplied by client"); + return FALSE; + } + + if (timed_read (fd, buf, auth_data_len, AUTH_TIMEOUT, 0) != auth_data_len) + return FALSE; + +#ifdef AUTH_MAGIC_COOKIE + if (!invoked_from_inetd && server_xauth && server_xauth->data && + !memcmp (buf, server_xauth->data, auth_data_len)) { + return TRUE; + } +#else + syslog_message (LOG_WARNING, + "Client tried Xauth, but server is " + "not compiled with Xauth"); +#endif + + /* + * auth failed, but allow this to fall through to the + * GNU_SECURE protocol.... + */ + + if (verbose_output) { + if (changed_uid || invoked_from_inetd) + syslog_message (LOG_WARNING, + "Xauth authentication not allowed, " + "trying GNU_SECURE ..."); + else + syslog_message (LOG_WARNING, + "Xauth authentication failed, " + "trying GNU_SECURE auth..."); + } + } + + /* Other auth protocols go here, and should execute only if + * the * auth_protocol name matches. */ + + /* Now, try the old GNU_SECURE stuff... */ + + if (enable_debug) + syslog_message (LOG_DEBUG, "Doing GNU_SECURE auth ..."); + + /* Now check the chain for that hash key */ + for (i = 0; i < HOST_TABLE_ENTRIES; i++) { + if (enable_debug) + syslog_message (LOG_DEBUG, "Trying %lx - %lx", + host_addr, permitted_hosts [i]); + if (permitted_hosts [i] == 0L) + return (FALSE); + if (host_addr == permitted_hosts [i]) + return (TRUE); + } + + return (FALSE); +} + + +/* + * setup_table -- initialise the table of hosts allowed to contact the server, + * by reading from the file specified by the GNU_SECURE + * environment variable + * Put in the local machine, and, if a security file is specifed, + * add each host that is named in the file. + * Return the number of hosts added. + */ + +static int +setup_table (void) +{ + char hostname [HOSTNAMSZ], screen [BUFSIZ]; + long host_addr; + int i, hosts = 0; + + /* Make sure every entry is null */ + for (i = 0; i < HOST_TABLE_ENTRIES; i++) + permitted_hosts [i] = 0; + + gethostname (hostname, HOSTNAMSZ); + + if ((host_addr = glibtop_internet_addr (hostname)) == -1) { + syslog_io_message (LOG_ERR, "Can't resolve '%s'", hostname); + exit (1); + } + +#ifdef AUTH_MAGIC_COOKIE + + sprintf (screen, "%d", SERVER_PORT); + + server_xauth = XauGetAuthByAddr + (FamilyInternet, + sizeof (host_addr), (char *) &host_addr, + strlen (screen), screen, + strlen (MCOOKIE_X_NAME), MCOOKIE_X_NAME); + hosts++; + +#endif /* AUTH_MAGIC_COOKIE */ + + /* Resolv host names from permitted_host_names []. */ + + for (i = 0; i < HOST_TABLE_ENTRIES; i++) { + if (!permitted_host_names [i]) + continue; + if (enable_debug) + syslog_message (LOG_DEBUG, "Resolving %s ...", + permitted_host_names [i]); + permitted_hosts [i] = + glibtop_internet_addr (permitted_host_names [i]); + if ((long) permitted_hosts [i] == -1) { + syslog_io_message (LOG_ERR, "Can't resolve '%s'", + permitted_host_names [i]); + exit (1); + } + } + + if (enable_debug) + for (i = 0; i < HOST_TABLE_ENTRIES; i++) + syslog_message (LOG_DEBUG, "Host %s - %lx", + permitted_host_names [i], + permitted_hosts [i]); + + hosts += HOST_TABLE_ENTRIES; + + return hosts; +} /* setup_table */ + +/* + * internet_init -- initialize server, returning an internet socket that can + * be listened on. + */ + +static int +internet_init (void) +{ + int ls; /* socket descriptor */ + struct sockaddr_in server; /* for local socket address */ + + if (setup_table () == 0) + return -1; + + /* clear out address structure */ + memset ((char *) &server, 0, sizeof (struct sockaddr_in)); + + /* Set up address structure for the listen socket. */ + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + + /* We use a fixed port given in the config file. */ + server.sin_port = htons (SERVER_PORT); + + if (verbose_output) + syslog_message (LOG_INFO, "Using port %u.", SERVER_PORT); + + /* Create the listen socket. */ + if ((ls = socket (AF_INET, SOCK_STREAM, 0)) == -1) { + syslog_io_message (LOG_ERR, "unable to create socket"); + exit (1); + } + + /* Bind the listen address to the socket. */ + if (bind (ls, (struct sockaddr *) &server, + sizeof (struct sockaddr_in)) == -1) { + syslog_io_message (LOG_ERR, "bind"); + exit (1); + } + + /* Initiate the listen on the socket so remote users * can connect. */ + if (listen (ls, 20) == -1) { + syslog_io_message (LOG_ERR, "listen"); + exit (1); + } + + return (ls); +} /* internet_init */ + + +/* + * handle_internet_request -- accept a request from a client and send the + * information to stdout (the gnu process). + */ + +static void +handle_internet_request (int ls) +{ + int s; + size_t addrlen = sizeof (struct sockaddr_in); + struct sockaddr_in peer; /* for peer socket address */ + pid_t pid; + + memset ((char *) &peer, 0, sizeof (struct sockaddr_in)); + + if ((s = accept (ls, (struct sockaddr *) &peer, (void *) &addrlen)) == -1) { + syslog_io_message (LOG_ERR, "accept"); + exit (1); + } + + if (verbose_output) + syslog_message (LOG_INFO, "Connection was made from %s port %u.", + inet_ntoa (peer.sin_addr), ntohs (peer.sin_port)); + + /* Check that access is allowed - if not return crud to the client */ + if (!permitted (peer.sin_addr.s_addr, s)) { + close (s); + syslog_message (LOG_CRIT, "Refused connection from %s.", + inet_ntoa (peer.sin_addr)); + return; + } /* if */ + + if (verbose_output) + syslog_message (LOG_INFO, "Accepted connection from %s port %u.", + inet_ntoa (peer.sin_addr), ntohs (peer.sin_port)); + + pid = fork (); + + if (pid == -1) { + syslog_io_message (LOG_ERR, "fork failed"); + exit (1); + } + + if (pid) { + if (verbose_output) + syslog_message (LOG_INFO, "Child pid is %d.", pid); + return; + } + + handle_parent_connection (s); + + close (s); + + if (verbose_output) + syslog_message (LOG_INFO, "Closed connection to %s port %u.", + inet_ntoa (peer.sin_addr), ntohs (peer.sin_port)); + + _exit (0); +} /* handle_internet_request */ + +static void +handle_signal (int sig) +{ + if (sig == SIGCHLD) + return; + + syslog_message (LOG_ERR, "Catched signal %d.\n", sig); + exit (1); +} + +const struct poptOption popt_options [] = { + POPT_AUTOHELP + { "debug", 'd', POPT_ARG_NONE, &enable_debug, 0, + N_("Enable debugging"), N_("DEBUG") }, + { "verbose", 'v', POPT_ARG_NONE, &verbose_output, 0, + N_("Enable verbose output"), N_("VERBOSE") }, + { "no-daemon", 'f', POPT_ARG_NONE, &no_daemon, 0, + N_("Don't fork into background"), N_("NO-DAEMON") }, + { "inetd", 'i', POPT_ARG_NONE, &invoked_from_inetd, 0, + N_("Invoked from inetd"), N_("INETD") }, + { NULL, '\0', 0, NULL, 0 } +}; + +int +main (int argc, char *argv []) +{ + const unsigned method = GLIBTOP_METHOD_PIPE; + const unsigned long features = GLIBTOP_SYSDEPS_ALL; + glibtop *server = glibtop_global_server; + poptContext context; + int nextopt; + + int ils = -1; /* internet domain listen socket */ + + /* On non-glibc systems, this is not set up for us. */ + if (!program_invocation_name) { + char *arg; + + program_invocation_name = argv[0]; + arg = strrchr (argv[0], '/'); + program_invocation_short_name = + arg ? (arg + 1) : program_invocation_name; + } + + context = poptGetContext ("libgtop-daemon", argc, argv, + popt_options, 0); + + poptReadDefaultConfig (context, TRUE); + + while ((nextopt = poptGetNextOpt (context)) > 0) + /* do nothing */ ; + + if(nextopt != -1) { + printf (_("Error on option %s: %s.\n" + "Run '%s --help' to see a full list of " + "available command line options.\n"), + poptBadOption (context, 0), + poptStrerror (nextopt), + argv[0]); + exit(1); + } + + if (enable_debug) + verbose_output = 1; + + if (no_daemon) { + openlog ("libgtop-daemon", LOG_PERROR | LOG_PID, LOG_LOCAL0); + } else { + openlog ("libgtop-daemon", LOG_PID, LOG_LOCAL0); + } + + if (!no_daemon && !invoked_from_inetd) { + pid_t pid = fork (); + + if (pid == -1) { + syslog_io_message (LOG_ERR, "fork failed"); + exit (1); + } else if (pid) + exit (0); + + close (0); + + setsid (); + } + + glibtop_init_r (&glibtop_global_server, 0, GLIBTOP_INIT_NO_INIT); + + signal (SIGCHLD, handle_signal); + + /* If we are root, completely switch to SERVER_UID and + * SERVER_GID. Otherwise we completely drop any priviledges. + */ + + if (enable_debug) + syslog_message (LOG_DEBUG, "Parent ID: (%d, %d) - (%d, %d)", + getuid (), geteuid (), getgid (), getegid ()); + + if (geteuid () == 0) { + changed_uid = 1; + if (setregid (SERVER_GID, SERVER_GID)) { + syslog_io_message (LOG_ERR, "setregid (SERVER_GID)"); + exit (1); + } + if (setreuid (SERVER_UID, SERVER_UID)) { + syslog_io_message (LOG_ERR, "setreuid (SERVER_UID)"); + exit (1); + } + } else { + if (setreuid (geteuid (), geteuid ())) { + syslog_io_message (LOG_ERR, "setreuid (euid)"); + exit (1); + } + } + + if (enable_debug) + syslog_message (LOG_DEBUG, "Parent ID: (%d, %d) - (%d, %d)", + getuid (), geteuid (), getgid (), getegid ()); + + if (invoked_from_inetd) { + size_t addrlen = sizeof (struct sockaddr_in); + struct sockaddr_in peer; + + memset ((char *) &peer, 0, sizeof (struct sockaddr_in)); + + if (getpeername (0, (struct sockaddr *) &peer, (void *) &addrlen)) { + syslog_io_message (LOG_ERR, "getpeername"); + exit (1); + } + + if (verbose_output) + syslog_message (LOG_INFO, "Connection was made from %s port %u.", + inet_ntoa (peer.sin_addr), ntohs (peer.sin_port)); + + /* Check that access is allowed - if not return crud to the client */ + if (!permitted (peer.sin_addr.s_addr, 0)) { + close (0); + syslog_message (LOG_CRIT, "Refused connection from %s.", + inet_ntoa (peer.sin_addr)); + exit (1); + } + + handle_parent_connection (0); + exit (0); + } + + /* get a internet domain socket to listen on. */ + ils = internet_init (); + + if (ils <= 0) { + syslog_message (LOG_ERR, "Unable to get internet domain socket."); + exit (1); + } + + glibtop_set_parameter_l (server, GLIBTOP_PARAM_METHOD, + &method, sizeof (method)); + + server->features = features; + + glibtop_init_r (&server, 0, 0); + + while (1) { + fd_set rmask; + int status, ret; + + while ((ret = wait3 (&status, WNOHANG, NULL)) != 0) { + if ((ret == -1) && (errno == ECHILD)) + break; + + if ((ret == -1) && ((errno == EAGAIN))) + continue; + if (ret == 0) { + syslog_io_message (LOG_WARNING, "wait3"); + continue; + } + + if (verbose_output) + syslog_message (LOG_INFO, "Child %d exited.", ret); + } + + FD_ZERO (&rmask); + + /* Only the child accepts connections from standard + * input made by its parent. */ + + FD_SET (ils, &rmask); + + if (enable_debug) + syslog_message (LOG_DEBUG, + "Server ready and waiting for connections."); + + if (select (ils+1, &rmask, (fd_set *) NULL, (fd_set *) NULL, + (struct timeval *) NULL) < 0) { + if (errno == EINTR) + continue; + syslog_io_message (LOG_ERR, "select"); + exit (1); + } + + if (FD_ISSET (ils, &rmask)) + handle_internet_request (ils); + } + + return 0; +} |