1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 1999-2021 Free Software Foundation, Inc.
3 
4    GNU Mailutils is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    GNU Mailutils is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include "comsat.h"
18 #include "mailutils/syslog.h"
19 #include "mailutils/cli.h"
20 #include "mailutils/sockaddr.h"
21 #include "mailutils/alloc.h"
22 
23 #ifndef PATH_DEV
24 # define PATH_DEV "/dev"
25 #endif
26 #ifndef PATH_TTY_PFX
27 # define PATH_TTY_PFX PATH_DEV
28 #endif
29 
30 #ifdef HAVE_UTMP_H
31 # include <utmp.h>
32 #endif
33 
34 #ifndef HAVE_GETUTENT_CALLS
35 extern void setutent (void);
36 extern struct utmp *getutent (void);
37 #endif
38 
39 #ifdef HAVE_UTMPX_H
40 # include <utmpx.h>
41 typedef struct utmpx UTMP;
42 # define SETUTENT() setutxent()
43 # define GETUTENT() getutxent()
44 # define ENDUTENT() endutxent()
45 #else
46 typedef struct utmp UTMP;
47 # define SETUTENT() setutent()
48 # define GETUTENT() getutent()
49 # define ENDUTENT() endutent()
50 # if !HAVE_STRUCT_UTMP_UT_USER
51 #  if HAVE_STRUCT_UTMP_UT_NAME
52 #   define ut_user ut_name
53 #  else
54 #   error "Neither ut_user nor ut_name found in struct utmp. Please report."
55 #  endif
56 # endif
57 #endif
58 
59 #define MAX_TTY_SIZE (sizeof (PATH_TTY_PFX) + sizeof (((UTMP*)0)->ut_line))
60 
61 const char *program_version = "comsatd (" PACKAGE_STRING ")";
62 
63 char *test_mode;
64 char *biffrc = BIFF_RC;
65 mu_m_server_t server;
66 
67 static void
set_inetd_mode(struct mu_parseopt * po,struct mu_option * opt,char const * arg)68 set_inetd_mode (struct mu_parseopt *po, struct mu_option *opt,
69 		char const *arg)
70 {
71   mu_m_server_set_mode (server, MODE_INTERACTIVE);
72 }
73 
74 static void
set_test_mode(struct mu_parseopt * po,struct mu_option * opt,char const * arg)75 set_test_mode (struct mu_parseopt *po, struct mu_option *opt,
76 	       char const *arg)
77 {
78   if (arg)
79     {
80       if (arg[0] != '/')
81 	{
82 	  test_mode = mu_make_file_name (mu_getcwd (), arg);
83 	  if (!test_mode)
84 	    mu_alloc_die ();
85 	}
86       else
87 	test_mode = mu_strdup (arg);
88     }
89   else
90     test_mode = mu_strdup ("/dev/tty");
91 }
92 
93 static void
set_daemon_mode(struct mu_parseopt * po,struct mu_option * opt,char const * arg)94 set_daemon_mode (struct mu_parseopt *po, struct mu_option *opt,
95 		 char const *arg)
96 {
97   mu_m_server_set_mode (server, MODE_DAEMON);
98   if (arg)
99     {
100       size_t max_children;
101       char *errmsg;
102       int rc = mu_str_to_c (arg, mu_c_size, &max_children, &errmsg);
103       if (rc)
104 	{
105 	  mu_parseopt_error (po, _("%s: bad argument"), arg);
106 	  exit (po->po_exit_error);
107 	}
108       mu_m_server_set_max_children (server, max_children);
109     }
110 }
111 
112 static void
set_foreground(struct mu_parseopt * po,struct mu_option * opt,char const * arg)113 set_foreground (struct mu_parseopt *po, struct mu_option *opt,
114 		char const *arg)
115 {
116   mu_m_server_set_foreground (server, 1);
117 }
118 
119 static struct mu_option comsat_options[] = {
120   { "test", 't', N_("FILE"), MU_OPTION_ARG_OPTIONAL,
121     N_("run in test mode; use FILE as tty (default: /dev/tty)"),
122     mu_c_string, &test_mode, set_test_mode },
123   { "foreground",  0, NULL, MU_OPTION_DEFAULT,
124     N_("remain in foreground"),
125     mu_c_bool, NULL, set_foreground },
126   { "inetd",  'i', NULL, MU_OPTION_DEFAULT,
127     N_("run in inetd mode"),
128     mu_c_bool, NULL, set_inetd_mode },
129   { "daemon", 'd', N_("NUMBER"), MU_OPTION_ARG_OPTIONAL,
130     N_("runs in daemon mode with a maximum of NUMBER children"),
131     mu_c_string, NULL, set_daemon_mode },
132   { "file", 'f', N_("FILE"), MU_OPTION_DEFAULT,
133     N_("read FILE instead of .biffrc"),
134     mu_c_string, &biffrc },
135   MU_OPTION_END
136 }, *options[] = { comsat_options, NULL };
137 
138 #define SUCCESS 0
139 #define NOT_HERE 1
140 #define PERMISSION_DENIED 2
141 
142 int maxlines = 5;
143 char *hostname;
144 const char *username;
145 int require_tty;
146 int biffrc_errors = BIFFRC_ERRORS_TO_TTY | BIFFRC_ERRORS_TO_ERR;
147 
148 static void comsat_init (void);
149 static int comsat_main (int fd);
150 static void notify_user (const char *user, const char *device,
151 			 const char *path, mu_message_qid_t qid);
152 static int find_user (const char *name, char *tty);
153 static char *mailbox_path (const char *user);
154 static int change_user (const char *user);
155 
156 static int reload = 0;
157 static int
biffrc_error_ctl(mu_config_value_t * val,int flag)158 biffrc_error_ctl (mu_config_value_t *val, int flag)
159 {
160   int res;
161 
162   if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
163     return 1;
164   if (mu_str_to_c (val->v.string, mu_c_bool, &res, NULL))
165     mu_diag_output (MU_LOG_ERROR, _("not a boolean"));
166   else if (res)
167     biffrc_errors |= flag;
168   else
169     biffrc_errors &= ~flag;
170   return 0;
171 }
172 
173 static int
cb_biffrc_errors_to_tty(void * data,mu_config_value_t * val)174 cb_biffrc_errors_to_tty (void *data, mu_config_value_t *val)
175 {
176   return biffrc_error_ctl (val, BIFFRC_ERRORS_TO_TTY);
177 }
178 
179 static int
cb_biffrc_errors_to_err(void * data,mu_config_value_t * val)180 cb_biffrc_errors_to_err (void *data, mu_config_value_t *val)
181 {
182   return biffrc_error_ctl (val, BIFFRC_ERRORS_TO_ERR);
183 }
184 
185 struct mu_cfg_param comsat_cfg_param[] = {
186   { "allow-biffrc", mu_c_bool, &allow_biffrc, 0, NULL,
187     N_("Read .biffrc file from the user home directory.") },
188   { "require-tty", mu_c_bool, &require_tty, 0, NULL,
189     N_("Notify only if the user is logged on one of the ttys.") },
190   { "biffrc-errors-to-tty", mu_cfg_callback, NULL, 0, cb_biffrc_errors_to_tty,
191     N_("Send biffrc errors to user's tty."),
192     N_("arg: bool") },
193   { "biffrc-errors-to-err", mu_cfg_callback, NULL, 0, cb_biffrc_errors_to_err,
194     N_("Send biffrc errors to Mailutils error output."),
195     N_("arg: bool") },
196   { "max-lines", mu_c_int, &maxlines, 0, NULL,
197     N_("Maximum number of message body lines to be output.") },
198   { "max-requests", mu_c_uint, &maxrequests, 0, NULL,
199     N_("Maximum number of incoming requests per request control interval.") },
200   { "request-control-interval", mu_c_time, &request_control_interval,
201     0, NULL,
202     N_("Set control interval.") },
203   { "overflow-control-interval", mu_c_time, &overflow_control_interval,
204     0, NULL,
205     N_("Set overflow control interval.") },
206   { "overflow-delay-time", mu_c_time, &overflow_delay_time,
207    0, NULL,
208     N_("Time to sleep after the first overflow occurs.") },
209   { ".server", mu_cfg_section, NULL, 0, NULL,
210     N_("Server configuration.") },
211   { NULL }
212 };
213 
214 static char const *alt_args[] = { N_("--test MBOX-URL MSG-QID"), NULL };
215 
216 static struct mu_cli_setup cli = {
217   .optv = options,
218   .cfg =comsat_cfg_param,
219   .prog_doc = N_("GNU comsatd -- notify users about incoming mail"),
220   .prog_alt_args = alt_args,
221   .server = 1
222 };
223 
224 static char *capa[] = {
225   "debug",
226   "logging",
227   "mailbox",
228   "locking",
229   NULL
230 };
231 
232 static RETSIGTYPE
sig_hup(int sig)233 sig_hup (int sig)
234 {
235   mu_m_server_stop (1);
236   reload = 1;
237 }
238 
239 void
comsat_init()240 comsat_init ()
241 {
242   int rc;
243 
244   /* Register mailbox formats */
245   mu_register_all_mbox_formats ();
246 
247   rc = mu_get_host_name (&hostname);
248   if (rc)
249     {
250       mu_diag_funcall (MU_DIAG_ERROR, "mu_get_host_name", NULL, rc);
251       exit (EXIT_FAILURE);
252     }
253   /* Set signal handlers */
254   signal (SIGTTOU, SIG_IGN);
255   signal (SIGCHLD, SIG_IGN);
256   signal (SIGHUP, SIG_IGN);	/* Ignore SIGHUP.  */
257 }
258 
259 int allow_biffrc = 1;            /* Allow per-user biffrc files */
260 unsigned maxrequests = 16;       /* Maximum number of request allowed per
261 			            control interval */
262 time_t request_control_interval = 10;  /* Request control interval */
263 time_t overflow_control_interval = 10; /* Overflow control interval */
264 time_t overflow_delay_time = 5;
265 
266 void
comsat_process(char * buffer,size_t rdlen)267 comsat_process (char *buffer, size_t rdlen)
268 {
269   char tty[MAX_TTY_SIZE];
270   char *p;
271   char *path = NULL;
272   mu_message_qid_t qid;
273 
274   /* Parse the buffer */
275   p = strchr (buffer, '@');
276   if (!p)
277     {
278       mu_diag_output (MU_DIAG_ERROR, _("malformed input: %s"), buffer);
279       return;
280     }
281   *p++ = 0;
282 
283   qid = p;
284   p = strchr (qid, ':');
285   if (p)
286     {
287       *p++ = 0;
288       path = p;
289     }
290 
291   if (find_user (buffer, tty) != SUCCESS)
292     {
293       if (require_tty)
294 	return;
295       tty[0] = 0;
296     }
297 
298   /* Child: do actual I/O */
299   notify_user (buffer, tty, path, qid);
300 }
301 
302 int
comsat_main(int fd)303 comsat_main (int fd)
304 {
305   int rdlen;
306   socklen_t len;
307   struct sockaddr fromaddr;
308   char buffer[216]; /*FIXME: Arbitrary size */
309 
310   len = sizeof fromaddr;
311   rdlen = recvfrom (fd, buffer, sizeof buffer, 0, &fromaddr, &len);
312   if (rdlen <= 0)
313     {
314       if (errno == EINTR)
315 	return 0;
316       mu_diag_output (MU_DIAG_ERROR, "recvfrom: %m");
317       return 1;
318     }
319   buffer[rdlen] = 0;
320 
321   if (mu_m_server_check_acl (server, &fromaddr, len))
322     return 0;
323 
324   comsat_process (buffer, rdlen);
325   return 0;
326 }
327 
328 static time_t last_request_time;    /* Timestamp of the last received
329 				       request */
330 static unsigned reqcount = 0;       /* Number of request received in the
331 				       current control interval */
332 static time_t last_overflow_time;   /* Timestamp of last overflow */
333 static unsigned overflow_count = 0; /* Number of overflows detected during
334 				       the current interval */
335 
336 int
comsat_prefork(int fd,struct sockaddr * s,int size,struct mu_srv_config * pconf,void * data)337 comsat_prefork (int fd, struct sockaddr *s, int size,
338 		struct mu_srv_config *pconf, void *data)
339 {
340   int retval = 0;
341   time_t now;
342 
343   /* Control the request flow */
344   if (maxrequests != 0)
345     {
346       now = time (NULL);
347       if (reqcount > maxrequests)
348 	{
349 	  unsigned delay;
350 
351 	  delay = overflow_delay_time << (overflow_count + 1);
352 	  mu_diag_output (MU_DIAG_NOTICE,
353 			 ngettext ("too many requests: pausing for %u second",
354 				   "too many requests: pausing for %u seconds",
355 				    delay),
356 			  delay);
357 	  /* FIXME: drain the socket? */
358 	  sleep (delay);
359 	  reqcount = 0;
360 	  if (now - last_overflow_time <= overflow_control_interval)
361 	    {
362 	      if ((overflow_delay_time << (overflow_count + 2)) >
363 		  overflow_delay_time)
364 		++overflow_count;
365 	    }
366 	  else
367 	    overflow_count = 0;
368 	  last_overflow_time = time (NULL);
369 	  retval = 1;
370 	}
371 
372       if (now - last_request_time <= request_control_interval)
373 	reqcount++;
374       else
375 	{
376 	  last_request_time = now;
377 	  reqcount = 1;
378 	}
379     }
380   return retval;
381 }
382 
383 int
comsat_connection(int fd,struct sockaddr * sa,int salen,struct mu_srv_config * pconf,void * data)384 comsat_connection (int fd, struct sockaddr *sa, int salen,
385 		   struct mu_srv_config *pconf, void *data)
386 {
387   char *buffer;
388   size_t rdlen, size;
389 
390   if (mu_udp_server_get_rdata (pconf->tcpsrv, &buffer, &rdlen))
391     return 0;
392   if (pconf->transcript)
393     {
394       char *p = mu_sys_sockaddr_to_astr (sa, salen);
395       mu_diag_output (MU_DIAG_INFO,
396 		      ngettext ("received %lu byte from %s",
397 				"received %lu bytes from %s",
398 				(unsigned long) rdlen),
399 		      (unsigned long) rdlen, p);
400       mu_diag_output (MU_DIAG_INFO, "string: %s", buffer);
401       free (p);
402     }
403   mu_udp_server_get_bufsize (pconf->tcpsrv, &size);
404   if (size < rdlen + 1)
405     {
406       int rc = mu_udp_server_set_bufsize (pconf->tcpsrv, rdlen + 1);
407       if (rc)
408 	{
409 	  mu_error (_("cannot resize buffer: %s"), mu_strerror (rc));
410 	  return 0;
411 	}
412     }
413   buffer[rdlen] = 0;
414   comsat_process (buffer, rdlen);
415   return 0;
416 }
417 
418 /* NOTE: Do not bother to free allocated memory, as the program exits
419    immediately after executing this */
420 static void
notify_user(const char * user,const char * device,const char * path,mu_message_qid_t qid)421 notify_user (const char *user, const char *device, const char *path,
422 	     mu_message_qid_t qid)
423 {
424   mu_mailbox_t mbox = NULL;
425   mu_message_t msg;
426   int status;
427 
428   if (change_user (user))
429     return;
430 
431   if (!path)
432     {
433       path = mailbox_path (user);
434       if (!path)
435 	return;
436     }
437 
438   if ((status = mu_mailbox_create (&mbox, path)) != 0 ||
439       (status = mu_mailbox_open (mbox,
440 				 MU_STREAM_READ|MU_STREAM_QACCESS)) != 0)
441     {
442       mu_error (_("cannot open mailbox %s: %s"),
443 	      path, mu_strerror (status));
444       return;
445     }
446 
447   status = mu_mailbox_quick_get_message (mbox, qid, &msg);
448   if (status)
449     {
450       mu_error (_("cannot get message (mailbox %s, qid %s): %s"),
451 		path, qid, mu_strerror (status));
452       return; /* FIXME: Notify the user, anyway */
453     }
454 
455   run_user_action (device, msg);
456 }
457 
458 /* Search utmp for the local user */
459 static int
find_user(const char * name,char * tty)460 find_user (const char *name, char *tty)
461 {
462   UTMP *uptr;
463   int status;
464   struct stat statb;
465   char ftty[MAX_TTY_SIZE];
466   time_t last_time = 0;
467 
468   status = NOT_HERE;
469   sprintf (ftty, "%s/", PATH_TTY_PFX);
470 
471   SETUTENT ();
472 
473   while ((uptr = GETUTENT ()) != NULL)
474     {
475 #ifdef USER_PROCESS
476       if (uptr->ut_type != USER_PROCESS)
477 	continue;
478 #endif
479       if (!strncmp (uptr->ut_user, name, sizeof(uptr->ut_user)))
480 	{
481 	  /* no particular tty was requested */
482 	  strncpy (ftty + sizeof(PATH_DEV),
483 		   uptr->ut_line,
484 		   sizeof (ftty) - sizeof (PATH_DEV) - 2);
485 	  ftty[sizeof (ftty) - 1] = 0;
486 
487 	  mu_normalize_path (ftty);
488 	  if (strncmp (ftty, PATH_TTY_PFX, strlen (PATH_TTY_PFX)))
489 	    {
490 	      /* An attempt to break security... */
491 	      mu_diag_output (MU_DIAG_ALERT,
492 			      _("bad line name in utmp record: %s"), ftty);
493 	      return NOT_HERE;
494 	    }
495 
496 	  if (stat (ftty, &statb) == 0)
497 	    {
498 	      if (!S_ISCHR (statb.st_mode))
499 		{
500 		  mu_diag_output (MU_DIAG_ALERT,
501 				  _("not a character device: %s"), ftty);
502 		  return NOT_HERE;
503 		}
504 
505 	      if (!(statb.st_mode & S_IEXEC))
506 		{
507 		  if (status != SUCCESS)
508 		    status = PERMISSION_DENIED;
509 		  continue;
510 		}
511 	      if (statb.st_atime > last_time)
512 		{
513 		  last_time = statb.st_atime;
514 		  strcpy(tty, ftty);
515 		  status = SUCCESS;
516 		}
517 	      continue;
518 	    }
519 	}
520     }
521 
522   ENDUTENT ();
523   return status;
524 }
525 
526 int
change_user(const char * user)527 change_user (const char *user)
528 {
529   struct passwd *pw;
530 
531   pw = getpwnam (user);
532   if (!pw)
533     {
534       mu_diag_output (MU_DIAG_CRIT, _("no such user: %s"), user);
535       return 1;
536     }
537 
538   setgid (pw->pw_gid);
539   setuid (pw->pw_uid);
540   chdir (pw->pw_dir);
541   username = user;
542   return 0;
543 }
544 
545 char *
mailbox_path(const char * user)546 mailbox_path (const char *user)
547 {
548   struct mu_auth_data *auth;
549   char *mailbox_name;
550 
551   auth = mu_get_auth_by_name (user);
552 
553   if (!auth)
554     {
555       mu_diag_output (MU_DIAG_ALERT, _("user nonexistent: %s"), user);
556       return NULL;
557     }
558 
559   mailbox_name = strdup (auth->mailbox);
560   mu_auth_data_free (auth);
561   return mailbox_name;
562 }
563 
564 
565 int
main(int argc,char ** argv)566 main (int argc, char **argv)
567 {
568   int c;
569   char **save_argv;
570 
571   /* Native Language Support */
572   MU_APP_INIT_NLS ();
573 
574   comsat_init ();
575   mu_acl_cfg_init ();
576   mu_m_server_create (&server, program_version);
577   mu_m_server_set_type (server, MU_IP_UDP);
578   mu_m_server_set_conn (server, comsat_connection);
579   mu_m_server_set_prefork (server, comsat_prefork);
580   mu_m_server_set_mode (server, MODE_INTERACTIVE);
581   mu_m_server_set_max_children (server, 20);
582   /* FIXME mu_m_server_set_pidfile (); */
583   mu_m_server_set_default_port (server, 512);
584   mu_m_server_cfg_init (server, NULL);
585 
586   /* FIXME: timeout is not needed. How to disable it? */
587   mu_log_syslog = 1;
588 
589   save_argv = argv;
590 
591   mu_cli (argc, argv, &cli, capa, server, &argc, &argv);
592 
593   if (test_mode)
594     {
595       struct passwd *pw;
596       char *user;
597 
598       mu_stdstream_strerr_setup (MU_STRERR_STDERR);
599       biffrc_errors = BIFFRC_ERRORS_TO_ERR;
600       if (argc < 2 || argc > 2)
601 	{
602 	  mu_error (_("mailbox URL and message QID are required in test mode"));
603 	  exit (EXIT_FAILURE);
604 	}
605 
606       pw = getpwuid (getuid ());
607       if (!pw)
608 	{
609 	  mu_error (_("cannot determine user name"));
610 	  exit (EXIT_FAILURE);
611 	}
612       user = pw->pw_name;
613 
614       if (biffrc[0] == '.' && (biffrc[1] == '/' ||
615 			       (biffrc[1] == '.' && biffrc[2] == '/')))
616 	{
617 	  char *cwd = mu_getcwd ();
618 	  biffrc = mu_make_file_name (cwd, biffrc);
619 	  if (!biffrc)
620 	    {
621 	      mu_error ("%s", mu_strerror (ENOMEM));
622 	      exit (1);
623 	    }
624 	  mu_normalize_path (biffrc);
625 	  free (cwd);
626 	}
627 
628       notify_user (user, test_mode, argv[0], argv[1]);
629       exit (0);
630     }
631 
632   mu_stdstream_strerr_setup (mu_log_syslog ?
633 			     MU_STRERR_SYSLOG : MU_STRERR_STDERR);
634 
635   if (mu_m_server_mode (server) == MODE_DAEMON)
636     {
637       if (save_argv[0][0] != '/')
638 	mu_diag_output (MU_DIAG_NOTICE,
639 			_("program name is not absolute; reloading will not "
640 			  "be possible"));
641       else
642 	{
643 	  sigset_t set;
644 
645 	  mu_m_server_get_sigset (server, &set);
646 	  sigdelset (&set, SIGHUP);
647 	  mu_m_server_set_sigset (server, &set);
648 	  signal (SIGHUP, sig_hup);
649 	}
650 
651       mu_m_server_begin (server);
652       c = mu_m_server_run (server);
653       mu_m_server_end (server);
654       mu_m_server_destroy (&server);
655       if (reload)
656 	{
657 	  mu_diag_output (MU_DIAG_NOTICE, _("restarting"));
658 	  execvp (save_argv[0], save_argv);
659 	}
660     }
661   else
662     {
663       chdir ("/");
664       c = comsat_main (0);
665     }
666 
667   return c != 0;
668 }
669