/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 1999, 2000, 2001, 2002, 2005 Free Software Foundation, Inc. GNU Mailutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Mailutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Mailutils; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "comsat.h" #ifndef PATH_DEV # define PATH_DEV "/dev" #endif #ifndef PATH_TTY_PFX # define PATH_TTY_PFX PATH_DEV #endif #ifdef HAVE_UTMP_H # include <utmp.h> #endif #ifndef HAVE_GETUTENT_CALLS extern void setutent (void); extern struct utmp *getutent (void); #endif #ifdef UTMPX # ifdef HAVE_UTMPX_H # include <utmpx.h> # endif typedef struct utmpx UTMP; # define SETUTENT() setutxent() # define GETUTENT() getutxent() # define ENDUTENT() endutxent() #else typedef struct utmp UTMP; # define SETUTENT() setutent() # define GETUTENT() getutent() # define ENDUTENT() endutent() #endif #define MAX_TTY_SIZE (sizeof (PATH_TTY_PFX) + sizeof (((UTMP*)0)->ut_line)) const char *program_version = "comsatd (" PACKAGE_STRING ")"; static char doc[] = "GNU comsatd"; static struct argp_option options[] = { {"config", 'c', N_("FILE"), 0, N_("Read configuration from FILE"), 0}, { NULL, 0, NULL, 0, NULL, 0 } }; static error_t comsatd_parse_opt (int key, char *arg, struct argp_state *state); static struct argp argp = { options, comsatd_parse_opt, NULL, doc, NULL, NULL, NULL }; static const char *comsat_argp_capa[] = { "daemon", "common", "logging", "mailbox", "license", NULL }; #define SUCCESS 0 #define NOT_HERE 1 #define PERMISSION_DENIED 2 #ifndef MAXHOSTNAMELEN # define MAXHOSTNAMELEN 64 #endif struct daemon_param daemon_param = { MODE_INTERACTIVE, /* Start in interactive (inetd) mode */ 20, /* Default maximum number of children. Currently unused */ 512, /* Default biff port */ 0, /* Default timeout */ }; int maxlines = 5; char hostname[MAXHOSTNAMELEN]; const char *username; static void comsat_init (void); static void comsat_daemon_init (void); static void comsat_daemon (int _port); static int comsat_main (int fd); static void notify_user (const char *user, const char *device, const char *path, off_t offset); static int find_user (const char *name, char *tty); static char *mailbox_path (const char *user); static void change_user (const char *user); static int xargc; static char **xargv; char *config_file = NULL; static error_t comsatd_parse_opt (int key, char *arg, struct argp_state *state) { switch (key) { case ARGP_KEY_INIT: state->child_inputs[0] = state->input; break; case 'c': config_file = arg; break; default: return ARGP_ERR_UNKNOWN; } return 0; } int main(int argc, char **argv) { int c; /* Native Language Support */ mu_init_nls (); mu_argp_init (program_version, NULL); mu_argp_parse (&argp, &argc, &argv, 0, comsat_argp_capa, NULL, &daemon_param); if (daemon_param.timeout > 0 && daemon_param.mode == MODE_DAEMON) { fprintf (stderr, _("--timeout and --daemon are incompatible\n")); exit (EXIT_FAILURE); } comsat_init (); if (daemon_param.mode == MODE_DAEMON) { /* Preserve invocation arguments */ xargc = argc; xargv = argv; comsat_daemon_init (); } /* Set up error messaging */ openlog ("gnu-comsat", LOG_PID, log_facility); mu_error_set_print (mu_syslog_error_printer); if (config_file) read_config (config_file); chdir ("/"); if (daemon_param.mode == MODE_DAEMON) comsat_daemon (daemon_param.port); else c = comsat_main (0); return c != 0; } static RETSIGTYPE sig_hup (int sig) { syslog (LOG_NOTICE, _("Restarting")); if (xargv[0][0] != '/') syslog (LOG_ERR, _("Cannot restart: program must be invoked using absolute pathname")); else execvp (xargv[0], xargv); signal (sig, sig_hup); } void comsat_init () { mu_registrar_record (mu_path_record); gethostname (hostname, sizeof hostname); /* Set signal handlers */ signal (SIGTTOU, SIG_IGN); signal (SIGCHLD, SIG_IGN); signal (SIGHUP, SIG_IGN); /* Ignore SIGHUP. */ } /* Set up for daemon mode. */ static void comsat_daemon_init (void) { extern int daemon (int, int); /* Become a daemon. Take care to close inherited fds and to hold first three ones, in, out, err. Do not do the chdir("/"). */ if (daemon (1, 0) < 0) { perror (_("Failed to become a daemon")); exit (EXIT_FAILURE); } } int allow_biffrc = 1; /* Allow per-user biffrc files */ unsigned maxrequests = 16; /* Maximum number of request allowed per control interval */ time_t request_control_interval = 10; /* Request control interval */ time_t overflow_control_interval = 10; /* Overflow control interval */ time_t overflow_delay_time = 5; void comsat_daemon (int port) { int fd; struct sockaddr_in local_sin; time_t last_request_time; /* Timestamp of the last received request */ unsigned reqcount = 0; /* Number of request received in the current control interval */ time_t last_overflow_time; /* Timestamp of last overflow */ unsigned overflow_count = 0; /* Number of overflows achieved during the current interval */ time_t now; fd = socket (PF_INET, SOCK_DGRAM, 0); if (fd == -1) { syslog (LOG_CRIT, "socket: %m"); exit (1); } memset (&local_sin, 0, sizeof local_sin); local_sin.sin_family = AF_INET; local_sin.sin_addr.s_addr = INADDR_ANY; /*FIXME*/ local_sin.sin_port = htons (port); if (bind (fd, (struct sockaddr *) &local_sin, sizeof local_sin) < 0) { syslog (LOG_CRIT, "bind: %m"); exit (1); } syslog (LOG_NOTICE, _("GNU comsat started")); last_request_time = last_overflow_time = time (NULL); while (1) { fd_set fdset; int rc; FD_ZERO (&fdset); FD_SET (fd, &fdset); rc = select (fd+1, &fdset, NULL, NULL, NULL); if (rc == -1) { if (errno != EINTR) syslog (LOG_ERR, "select: %m"); continue; } /* Control the request flow */ if (maxrequests != 0) { now = time (NULL); if (reqcount > maxrequests) { unsigned delay; delay = overflow_delay_time << (overflow_count + 1); syslog (LOG_NOTICE, ngettext ("Too many requests: pausing for %u second", "Too many requests: pausing for %u seconds", delay), delay); sleep (delay); reqcount = 0; if (now - last_overflow_time <= overflow_control_interval) { if ((overflow_delay_time << (overflow_count + 2)) > overflow_delay_time) ++overflow_count; } else overflow_count = 0; last_overflow_time = time (NULL); } if (now - last_request_time <= request_control_interval) reqcount++; else { last_request_time = now; reqcount = 1; } } comsat_main (fd); } } int comsat_main (int fd) { int rdlen; int len; struct sockaddr_in sin_from; char buffer[216]; /*FIXME: Arbitrary size */ pid_t pid; char tty[MAX_TTY_SIZE]; char *p, *endp; size_t offset; char *path = NULL; len = sizeof sin_from; rdlen = recvfrom (fd, buffer, sizeof buffer, 0, (struct sockaddr*)&sin_from, &len); if (rdlen <= 0) { if (errno == EINTR) return 0; syslog (LOG_ERR, "recvfrom: %m"); return 1; } if (acl_match (&sin_from)) { syslog (LOG_ALERT, _("DENIED attempt to connect from %s"), inet_ntoa (sin_from.sin_addr)); return 1; } syslog (LOG_INFO, ngettext ("Received %d byte from %s", "Received %d bytes from %s", rdlen), rdlen, inet_ntoa (sin_from.sin_addr)); buffer[rdlen] = 0; /* Parse the buffer */ p = strchr (buffer, '@'); if (!p) { syslog (LOG_ERR, _("Malformed input: %s"), buffer); return 1; } *p++ = 0; offset = strtoul (p, &endp, 0); switch (*endp) { case 0: break; case ':': path = endp+1; break; default: if (!isspace (*endp)) syslog (LOG_ERR, _("Malformed input: %s@%s (near %s)"), buffer, p, endp); } if (find_user (buffer, tty) != SUCCESS) return 0; /* All I/O is done by child process. This is to avoid various blocking problems. */ pid = fork (); if (pid == -1) { syslog (LOG_ERR, "fork: %m"); return 1; } if (pid > 0) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 100000; select (0, NULL, NULL, NULL, &tv); kill (pid, SIGKILL); /* Just in case the child is hung */ return 0; } /* Child: do actual I/O */ notify_user (buffer, tty, path, offset); exit (0); } static const char * get_newline_str (FILE *fp) { #if defined(OPOST) && defined(ONLCR) struct termios tbuf; tcgetattr (fileno (fp), &tbuf); if ((tbuf.c_oflag & OPOST) && (tbuf.c_oflag & ONLCR)) return "\n"; else return "\r\n"; #else return "\r\n"; /* Just in case */ #endif } /* NOTE: Do not bother to free allocated memory, as the program exits immediately after executing this */ static void notify_user (const char *user, const char *device, const char *path, off_t offset) { FILE *fp; const char *cr; char *blurb; mu_mailbox_t mbox = NULL, tmp = NULL; mu_message_t msg; mu_stream_t stream = NULL; int status; off_t size; size_t count, n; change_user (user); if ((fp = fopen (device, "w")) == NULL) { syslog (LOG_ERR, _("Cannot open device %s: %m"), device); exit (0); } cr = get_newline_str (fp); if (!path) { path = mailbox_path (user); if (!path) return; } if ((status = mu_mailbox_create (&mbox, path)) != 0 || (status = mu_mailbox_open (mbox, MU_STREAM_READ)) != 0) { syslog (LOG_ERR, _("Cannot open mailbox %s: %s"), path, mu_strerror (status)); return; } if ((status = mu_mailbox_get_stream (mbox, &stream))) { syslog (LOG_ERR, _("Cannot get stream for mailbox %s: %s"), path, mu_strerror (status)); return; } if ((status = mu_stream_size (stream, &size))) { syslog (LOG_ERR, _("Cannot get stream size (mailbox %s): %s"), path, mu_strerror (status)); return; } /* Read headers */ size -= offset; blurb = malloc (size + 1); if (!blurb) return; mu_stream_read (stream, blurb, size, offset, &n); blurb[size] = 0; if ((status = mu_mailbox_create (&tmp, "/dev/null")) != 0 || (status = mu_mailbox_open (tmp, MU_STREAM_READ)) != 0) { syslog (LOG_ERR, _("Cannot create temporary mailbox: %s"), mu_strerror (status)); return; } if ((status = mu_memory_stream_create (&stream, 0, 0))) { syslog (LOG_ERR, _("Cannot create temporary stream: %s"), mu_strerror (status)); return; } mu_stream_write (stream, blurb, size, 0, &count); mu_mailbox_set_stream (tmp, stream); mu_mailbox_messages_count (tmp, &count); mu_mailbox_get_message (tmp, 1, &msg); run_user_action (fp, cr, msg); fclose (fp); } /* Search utmp for the local user */ static int find_user (const char *name, char *tty) { UTMP *uptr; int status; struct stat statb; char ftty[MAX_TTY_SIZE]; time_t last_time = 0; status = NOT_HERE; sprintf (ftty, "%s/", PATH_TTY_PFX); SETUTENT (); while ((uptr = GETUTENT ()) != NULL) { #ifdef USER_PROCESS if (uptr->ut_type != USER_PROCESS) continue; #endif if (!strncmp (uptr->ut_name, name, sizeof(uptr->ut_name))) { /* no particular tty was requested */ strncpy (ftty + sizeof(PATH_DEV), uptr->ut_line, sizeof (ftty) - sizeof (PATH_DEV) - 2); ftty[sizeof (ftty) - 1] = 0; mu_normalize_path (ftty, "/"); if (strncmp (ftty, PATH_TTY_PFX, strlen (PATH_TTY_PFX))) { /* An attempt to break security... */ syslog (LOG_ALERT, _("Bad line name in utmp record: %s"), ftty); return NOT_HERE; } if (stat (ftty, &statb) == 0) { if (!S_ISCHR (statb.st_mode)) { syslog (LOG_ALERT, _("Not a character device: %s"), ftty); return NOT_HERE; } if (!(statb.st_mode & S_IEXEC)) { if (status != SUCCESS) status = PERMISSION_DENIED; continue; } if (statb.st_atime > last_time) { last_time = statb.st_atime; strcpy(tty, ftty); status = SUCCESS; } continue; } } } ENDUTENT (); return status; } void change_user (const char *user) { struct passwd *pw; pw = getpwnam (user); if (!pw) { syslog (LOG_CRIT, _("No such user: %s"), user); exit (1); } setgid (pw->pw_gid); setuid (pw->pw_uid); chdir (pw->pw_dir); username = user; } char * mailbox_path (const char *user) { struct mu_auth_data *auth; char *mailbox_name; auth = mu_get_auth_by_name (user); if (!auth) { syslog (LOG_ALERT, _("User nonexistent: %s"), user); return NULL; } mailbox_name = strdup (auth->mailbox); mu_auth_data_free (auth); return mailbox_name; }