masqmail

view src/masqmail.c @ 248:018cfd163f5c

refactored processing of -oXXX options plus two minor things
author markus schnalke <meillo@marmaro.de>
date Mon, 01 Nov 2010 14:53:26 -0300
parents 3c40f86d50e4
children f9da5a7caeda
line source
1 /* MasqMail
2 Copyright (C) 1999-2001 Oliver Kurth
3 Copyright (C) 2010 markus schnalke <meillo@marmaro.de>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
20 #include <stdio.h>
21 #include <errno.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <sys/types.h>
26 #include <sys/socket.h>
27 #include <sys/time.h>
28 #include <netinet/in.h>
29 #include <netdb.h>
30 #include <syslog.h>
31 #include <signal.h>
33 #include <glib.h>
35 #include "masqmail.h"
37 /* mutually exclusive modes. Note that there is no 'queue daemon' mode.
38 It, as well as the distinction beween the two (non exclusive) daemon
39 (queue and listen) modes, is handled by flags.*/
40 typedef enum _mta_mode {
41 MODE_ACCEPT = 0, /* accept message on stdin */
42 MODE_DAEMON, /* run as daemon */
43 MODE_RUNQUEUE, /* single queue run, online or offline */
44 MODE_SMTP, /* accept SMTP on stdin */
45 MODE_LIST, /* list queue */
46 MODE_MCMD, /* do queue manipulation */
47 MODE_VERSION, /* show version */
48 MODE_BI, /* fake ;-) */
49 MODE_NONE /* to prevent default MODE_ACCEPT */
50 } mta_mode;
52 char *pidfile = NULL;
53 volatile int sigterm_in_progress = 0;
55 static void
56 sigterm_handler(int sig)
57 {
58 if (sigterm_in_progress)
59 raise(sig);
60 sigterm_in_progress = 1;
62 if (pidfile) {
63 uid_t uid;
64 uid = seteuid(0);
65 if (unlink(pidfile) != 0)
66 logwrite(LOG_WARNING, "could not delete pid file %s: %s\n", pidfile, strerror(errno));
67 seteuid(uid); /* we exit anyway after this, just to be sure */
68 }
70 signal(sig, SIG_DFL);
71 raise(sig);
72 }
74 #ifdef ENABLE_IDENT /* so far used for that only */
75 static gboolean
76 is_in_netlist(gchar * host, GList * netlist)
77 {
78 guint hostip = inet_addr(host);
79 struct in_addr addr;
81 addr.s_addr = hostip;
82 if (addr.s_addr != INADDR_NONE) {
83 GList *node;
84 foreach(netlist, node) {
85 struct in_addr *net = (struct in_addr *) (node->data);
86 if ((addr.s_addr & net->s_addr) == net->s_addr)
87 return TRUE;
88 }
89 }
90 return FALSE;
91 }
92 #endif
94 gchar*
95 get_optarg(char *argv[], gint argc, gint * argp, gint * pos)
96 {
97 if (argv[*argp][*pos])
98 return &(argv[*argp][*pos]);
99 else {
100 if (*argp + 1 < argc) {
101 if (argv[(*argp) + 1][0] != '-') {
102 (*argp)++;
103 *pos = 0;
104 return &(argv[*argp][*pos]);
105 }
106 }
107 }
108 return NULL;
109 }
111 gchar*
112 get_progname(gchar * arg0)
113 {
114 gchar *p = arg0 + strlen(arg0) - 1;
115 while (p > arg0) {
116 if (*p == '/')
117 return p + 1;
118 p--;
119 }
120 return p;
121 }
123 gboolean
124 write_pidfile(gchar * name)
125 {
126 FILE *fptr;
128 if ((fptr = fopen(name, "wt"))) {
129 fprintf(fptr, "%d\n", getpid());
130 fclose(fptr);
131 pidfile = strdup(name);
132 return TRUE;
133 }
134 logwrite(LOG_WARNING, "could not write pid file: %s\n", strerror(errno));
135 return FALSE;
136 }
138 static void
139 mode_daemon(gboolean do_listen, gint queue_interval, char *argv[])
140 {
141 guint pid;
143 /* daemon */
144 if (!conf.run_as_user) {
145 if ((conf.orig_uid != 0) && (conf.orig_uid != conf.mail_uid)) {
146 fprintf(stderr, "must be root or %s for daemon.\n", DEF_MAIL_USER);
147 exit(EXIT_FAILURE);
148 }
149 }
151 /* reparent to init only if init is not already the parent */
152 if (getppid() != 1) {
153 if ((pid = fork()) > 0) {
154 exit(EXIT_SUCCESS);
155 } else if (pid < 0) {
156 logwrite(LOG_ALERT, "could not fork!\n");
157 exit(EXIT_FAILURE);
158 }
159 }
161 signal(SIGTERM, sigterm_handler);
162 write_pidfile(PIDFILEDIR "/masqmail.pid");
164 conf.do_verbose = FALSE;
166 /* closing and reopening the log ensures that it is open afterwards
167 because it is possible that the log is assigned to fd 1 and gets
168 thus closes by fclose(stdout). Similar for the debugfile.
169 */
170 logclose();
171 fclose(stdin);
172 fclose(stdout);
173 fclose(stderr);
174 logopen();
176 logwrite(LOG_NOTICE, "%s %s daemon starting\n", PACKAGE, VERSION);
177 listen_port(do_listen ? conf.listen_addresses : NULL, queue_interval, argv);
178 }
180 static void
181 mode_smtp()
182 {
183 /* accept smtp message on stdin */
184 /* write responses to stderr. */
186 struct sockaddr_in saddr;
187 gchar *peername = NULL;
188 int dummy = sizeof(saddr);
190 conf.do_verbose = FALSE;
192 if (!conf.run_as_user) {
193 seteuid(conf.orig_uid);
194 setegid(conf.orig_gid);
195 }
197 DEBUG(5) debugf("accepting smtp message on stdin\n");
199 if (getpeername(0, (struct sockaddr *) (&saddr), &dummy) == 0) {
200 peername = g_strdup(inet_ntoa(saddr.sin_addr));
201 } else if (errno != ENOTSOCK)
202 exit(EXIT_FAILURE);
204 smtp_in(stdin, stderr, peername, NULL);
205 }
207 static void
208 mode_accept(address * return_path, gchar * full_sender_name, guint accept_flags, char **addresses, int addr_cnt)
209 {
210 /* accept message on stdin */
211 accept_error err;
212 message *msg = create_message();
213 gint i;
215 if (return_path && !is_privileged_user(conf.orig_uid)) {
216 fprintf(stderr, "must be root, %s or in group %s for setting return path.\n", DEF_MAIL_USER, DEF_MAIL_GROUP);
217 exit(EXIT_FAILURE);
218 }
220 if (!conf.run_as_user) {
221 seteuid(conf.orig_uid);
222 setegid(conf.orig_gid);
223 }
225 DEBUG(5) debugf("accepting message on stdin\n");
227 msg->received_prot = PROT_LOCAL;
228 for (i = 0; i < addr_cnt; i++) {
229 if (addresses[i][0] != '|')
230 msg->rcpt_list = g_list_append(msg->rcpt_list, create_address_qualified(addresses[i], TRUE, conf.host_name));
231 else {
232 logwrite(LOG_ALERT, "no pipe allowed as recipient address: %s\n", addresses[i]);
233 exit(EXIT_FAILURE);
234 }
235 }
237 /* -f option */
238 msg->return_path = return_path;
240 /* -F option */
241 msg->full_sender_name = full_sender_name;
243 if ((err = accept_message(stdin, msg, accept_flags)) == AERR_OK) {
244 if (spool_write(msg, TRUE)) {
245 pid_t pid;
246 logwrite(LOG_NOTICE, "%s <= %s with %s\n", msg->uid, addr_string(msg->return_path), prot_names[PROT_LOCAL]);
248 if (!conf.do_queue) {
249 if ((pid = fork()) == 0) {
250 conf.do_verbose = FALSE;
251 fclose(stdin);
252 fclose(stdout);
253 fclose(stderr);
254 if (deliver(msg)) {
255 exit(EXIT_SUCCESS);
256 } else
257 exit(EXIT_FAILURE);
258 } else if (pid < 0) {
259 logwrite(LOG_ALERT, "could not fork for delivery, id = %s\n", msg->uid);
260 }
261 }
262 } else {
263 fprintf(stderr, "Could not write spool file\n");
264 exit(EXIT_FAILURE);
265 }
266 } else {
267 switch (err) {
268 case AERR_EOF:
269 fprintf(stderr, "unexpected EOF.\n");
270 exit(EXIT_FAILURE);
271 case AERR_NORCPT:
272 fprintf(stderr, "no recipients.\n");
273 exit(EXIT_FAILURE);
274 case AERR_SIZE:
275 fprintf(stderr, "max message size exceeded.\n");
276 exit(EXIT_FAILURE);
277 default:
278 /* should never happen: */
279 fprintf(stderr, "Unknown error (%d)\r\n", err);
280 exit(EXIT_FAILURE);
281 }
282 exit(EXIT_FAILURE);
283 }
284 }
286 int
287 main(int argc, char *argv[])
288 {
289 /* cmd line flags */
290 gchar *conf_file = CONF_FILE;
291 gint arg = 1;
293 gboolean do_listen = FALSE;
294 gboolean do_runq = FALSE;
295 gboolean do_runq_online = FALSE;
297 gboolean do_queue = FALSE;
299 gboolean do_verbose = FALSE;
300 gint debug_level = -1;
302 mta_mode mta_mode = MODE_ACCEPT;
304 gint queue_interval = 0;
305 gboolean opt_t = FALSE;
306 gboolean opt_i = FALSE;
307 gboolean exit_failure = FALSE;
309 gchar *M_cmd = NULL;
311 gint exit_code = EXIT_SUCCESS;
312 gchar *route_name = NULL;
313 gchar *progname;
314 gchar *f_address = NULL;
315 gchar *full_sender_name = NULL;
316 address *return_path = NULL; /* may be changed by -f option */
318 progname = get_progname(argv[0]);
320 if (strcmp(progname, "mailq") == 0) {
321 mta_mode = MODE_LIST;
322 } else if (strcmp(progname, "mailrm") == 0) {
323 mta_mode = MODE_MCMD;
324 M_cmd = "rm";
325 } else if (strcmp(progname, "runq") == 0) {
326 mta_mode = MODE_RUNQUEUE;
327 do_runq = TRUE;
328 } else if (strcmp(progname, "rmail") == 0) {
329 /* the `rmail' alias should probably be removed now
330 that we have the rmail script. But let's keep it
331 for some while for compatibility. 2010-06-19 */
332 mta_mode = MODE_ACCEPT;
333 opt_i = TRUE;
334 } else if (strcmp(progname, "smtpd") == 0 || strcmp(progname, "in.smtpd") == 0) {
335 mta_mode = MODE_SMTP;
336 }
338 /* parse cmd line */
339 while (arg < argc) {
340 gint pos = 0;
341 if ((argv[arg][pos] == '-') && (argv[arg][pos + 1] != '-')) {
342 pos++;
343 switch (argv[arg][pos++]) {
344 case 'b':
345 switch (argv[arg][pos++]) {
346 case 'd':
347 do_listen = TRUE;
348 mta_mode = MODE_DAEMON;
349 break;
350 case 'i':
351 /* ignored */
352 mta_mode = MODE_BI;
353 break;
354 case 's':
355 mta_mode = MODE_SMTP;
356 break;
357 case 'p':
358 mta_mode = MODE_LIST;
359 break;
360 case 'V':
361 mta_mode = MODE_VERSION;
362 break;
363 default:
364 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
365 exit(EXIT_FAILURE);
366 }
367 break;
368 case 'B':
369 /* we ignore this and throw the argument away */
370 get_optarg(argv, argc, &arg, &pos);
371 break;
372 case 'C':
373 if (!(conf_file = get_optarg(argv, argc, &arg, &pos))) {
374 fprintf(stderr, "-C requires a filename as argument.\n");
375 exit(EXIT_FAILURE);
376 }
377 break;
378 case 'F':
379 {
380 full_sender_name = get_optarg(argv, argc, &arg, &pos);
381 if (!full_sender_name) {
382 fprintf(stderr, "-F requires a name as an argument\n");
383 exit(EXIT_FAILURE);
384 }
385 }
386 break;
387 case 'd':
388 if (getuid() == 0) {
389 char *lvl = get_optarg(argv, argc, &arg, &pos);
390 if (lvl)
391 debug_level = atoi(lvl);
392 else {
393 fprintf(stderr, "-d requires a number as an argument.\n");
394 exit(EXIT_FAILURE);
395 }
396 } else {
397 fprintf(stderr, "only root may set the debug level.\n");
398 exit(EXIT_FAILURE);
399 }
400 break;
401 case 'f':
402 /* set return path */
403 {
404 gchar *address;
405 address = get_optarg(argv, argc, &arg, &pos);
406 if (address) {
407 f_address = g_strdup(address);
408 } else {
409 fprintf(stderr, "-f requires an address as an argument\n");
410 exit(EXIT_FAILURE);
411 }
412 }
413 break;
414 case 'i':
415 if (argv[arg][pos] == 0) {
416 opt_i = TRUE;
417 exit_failure = FALSE; /* may override -oem */
418 } else {
419 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
420 exit(EXIT_FAILURE);
421 }
422 break;
423 case 'M':
424 {
425 mta_mode = MODE_MCMD;
426 M_cmd = g_strdup(&(argv[arg][pos]));
427 }
428 break;
429 case 'm':
430 /* ignore -m (me too) switch (see man page) */
431 break;
432 case 'o':
433 char* oarg = argv[arg][pos+1];
434 if (strcmp(oarg, "oem") == 0) {
435 if (!opt_i) {
436 /* FIXME: this check needs to be done after
437 option processing as -oi may come later */
438 exit_failure = TRUE;
439 }
440 } else if (strcmp(oarg, "odb") == 0) {
441 /* ignore ``deliver in background'' switch */
442 } else if (strcmp(oarg, "odq") == 0) {
443 do_queue = TRUE;
444 } else if (strcmp(oarg, "oi") == 0) {
445 exit_failure = FALSE; /* may override -oem */
446 } else if (strcmp(oarg, "om") == 0) {
447 /* ignore ``me too'' switch */
448 } else {
449 fprintf(stderr, "ignoring unrecognized option %s\n",
450 argv[arg]);
451 }
452 break;
454 case 'q':
455 {
456 gchar *optarg;
458 do_runq = TRUE;
459 mta_mode = MODE_RUNQUEUE;
460 if (argv[arg][pos] == 'o') {
461 pos++;
462 do_runq = FALSE;
463 do_runq_online = TRUE;
464 /* can be NULL, then we use online detection method */
465 route_name = get_optarg(argv, argc, &arg, &pos);
466 } else
467 if ((optarg = get_optarg(argv, argc, &arg, &pos))) {
468 mta_mode = MODE_DAEMON;
469 queue_interval = time_interval(optarg, &pos);
470 }
471 }
472 break;
473 case 't':
474 if (argv[arg][pos] == '\0') {
475 opt_t = TRUE;
476 } else {
477 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
478 exit(EXIT_FAILURE);
479 }
480 break;
481 case 'v':
482 do_verbose = TRUE;
483 break;
484 default:
485 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
486 exit(EXIT_FAILURE);
487 }
488 } else {
489 if (argv[arg][pos + 1] == '-') {
490 if (argv[arg][pos + 2] != '\0') {
491 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
492 exit(EXIT_FAILURE);
493 }
494 arg++;
495 }
496 break;
497 }
498 arg++;
499 }
501 if (mta_mode == MODE_VERSION) {
502 gchar *with_resolver = "";
503 gchar *with_auth = "";
504 gchar *with_ident = "";
506 #ifdef ENABLE_RESOLVER
507 with_resolver = " +resolver";
508 #endif
509 #ifdef ENABLE_AUTH
510 with_auth = " +auth";
511 #endif
512 #ifdef ENABLE_IDENT
513 with_ident = " +ident";
514 #endif
516 printf("%s %s%s%s%s\n", PACKAGE, VERSION, with_resolver, with_auth, with_ident);
518 exit(EXIT_SUCCESS);
519 }
521 /* initialize random generator */
522 srand(time(NULL));
523 /* ignore SIGPIPE signal */
524 signal(SIGPIPE, SIG_IGN);
526 /* close all possibly open file descriptors, except std{in,out,err} */
527 {
528 int i, max_fd = sysconf(_SC_OPEN_MAX);
530 if (max_fd <= 0)
531 max_fd = 64;
532 for (i = 3; i < max_fd; i++)
533 close(i);
534 }
536 init_conf();
538 /* if we are not privileged, and the config file was changed we
539 implicetely set the the run_as_user flag and give up all
540 privileges.
542 So it is possible for a user to run his own daemon without
543 breaking security.
544 */
545 if (strcmp(conf_file, CONF_FILE) != 0) {
546 if (conf.orig_uid != 0) {
547 conf.run_as_user = TRUE;
548 seteuid(conf.orig_uid);
549 setegid(conf.orig_gid);
550 setuid(conf.orig_uid);
551 setgid(conf.orig_gid);
552 }
553 }
555 conf.log_dir = LOG_DIR;
556 logopen();
557 if (!read_conf(conf_file)) {
558 logwrite(LOG_ALERT, "SHUTTING DOWN due to problems reading config\n");
559 exit(5);
560 }
561 logclose();
563 if (do_queue)
564 conf.do_queue = TRUE;
565 if (do_verbose)
566 conf.do_verbose = TRUE;
567 if (debug_level >= 0) /* if >= 0, it was given by argument */
568 conf.debug_level = debug_level;
570 /* It appears that changing to / ensures that we are never in
571 a directory which we cannot access. This situation could be
572 possible after changing identity.
573 Maybe we should only change to / if we not run as user, to
574 allow relative paths for log files in test setups for
575 instance.
576 */
577 chdir("/");
579 if (!conf.run_as_user) {
580 if (setgid(0) != 0) {
581 fprintf(stderr, "could not set gid to 0. Is the setuid bit set? : %s\n", strerror(errno));
582 exit(EXIT_FAILURE);
583 }
584 if (setuid(0) != 0) {
585 fprintf(stderr, "could not gain root privileges. Is the setuid bit set? : %s\n", strerror(errno));
586 exit(EXIT_FAILURE);
587 }
588 }
590 if (!logopen()) {
591 fprintf(stderr, "could not open log file\n");
592 exit(EXIT_FAILURE);
593 }
595 DEBUG(1) debugf("masqmail %s starting\n", VERSION);
597 DEBUG(5) {
598 gchar **str = argv;
599 debugf("args: \n");
600 while (*str) {
601 debugf("%s \n", *str);
602 str++;
603 }
604 }
605 DEBUG(5) debugf("queue_interval = %d\n", queue_interval);
607 if (f_address) {
608 return_path = create_address_qualified(f_address, TRUE, conf.host_name);
609 g_free(f_address);
610 if (!return_path) {
611 fprintf(stderr, "invalid RFC821 address: %s\n", f_address);
612 exit(EXIT_FAILURE);
613 }
614 }
616 switch (mta_mode) {
617 case MODE_DAEMON:
618 mode_daemon(do_listen, queue_interval, argv);
619 break;
620 case MODE_RUNQUEUE:
621 {
622 /* queue runs */
623 set_identity(conf.orig_uid, "queue run");
625 if (do_runq)
626 exit_code = queue_run() ? EXIT_SUCCESS : EXIT_FAILURE;
628 if (do_runq_online) {
629 if (route_name != NULL) {
630 conf.online_detect = g_strdup("argument");
631 set_online_name(route_name);
632 }
633 exit_code =
634 queue_run_online() ? EXIT_SUCCESS : EXIT_FAILURE;
635 }
636 }
637 break;
639 case MODE_SMTP:
640 mode_smtp();
641 break;
643 case MODE_LIST:
644 queue_list();
645 break;
647 case MODE_BI:
648 exit(EXIT_SUCCESS);
649 break; /* well... */
651 case MODE_MCMD:
652 if (strcmp(M_cmd, "rm") == 0) {
653 gboolean ok = FALSE;
655 set_euidgid(conf.mail_uid, conf.mail_gid, NULL, NULL);
657 if (is_privileged_user(conf.orig_uid)) {
658 for (; arg < argc; arg++) {
659 if (queue_delete(argv[arg]))
660 ok = TRUE;
661 }
662 } else {
663 struct passwd *pw = getpwuid(conf.orig_uid);
664 if (pw) {
665 for (; arg < argc; arg++) {
666 message *msg = msg_spool_read(argv[arg], FALSE);
667 #ifdef ENABLE_IDENT
668 if (((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL))
669 || is_in_netlist(msg->received_host, conf.ident_trusted_nets))
670 #else
671 if ((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL))
672 #endif
673 {
674 if (msg->ident) {
675 if (strcmp(pw->pw_name, msg->ident) == 0) {
676 if (queue_delete(argv[arg]))
677 ok = TRUE;
678 } else {
679 fprintf(stderr, "you do not own message id %s\n", argv[arg]);
680 }
681 } else
682 fprintf(stderr, "message %s does not have an ident.\n", argv[arg]);
683 } else {
684 fprintf(stderr, "message %s was not received locally or from a trusted network.\n", argv[arg]);
685 }
686 }
687 } else {
688 fprintf(stderr, "could not find a passwd entry for uid %d: %s\n", conf.orig_uid, strerror(errno));
689 }
690 }
691 exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
692 } else {
693 fprintf(stderr, "unknown command %s\n", M_cmd);
694 exit(EXIT_FAILURE);
695 }
696 break;
698 case MODE_ACCEPT:
699 {
700 guint accept_flags = (opt_t ? ACC_DEL_RCPTS | ACC_RCPT_FROM_HEAD : 0)
701 | (opt_i ? ACC_DOT_IGNORE : ACC_NODOT_RELAX);
702 mode_accept(return_path, full_sender_name, accept_flags, &(argv[arg]), argc - arg);
703 exit(exit_failure ? EXIT_FAILURE : EXIT_SUCCESS);
704 }
705 break;
706 case MODE_NONE:
707 break;
708 default:
709 fprintf(stderr, "unknown mode: %d\n", mta_mode);
710 break;
711 }
713 logclose();
715 exit(exit_code);
716 }