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