masqmail
view src/masqmail.c @ 305:794071925a22
refactored complex conditions
author | meillo@marmaro.de |
---|---|
date | Sun, 24 Apr 2011 15:10:11 +0200 |
parents | 84ea0b1fc8f8 |
children | f10a56dc7481 |
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 enum mta_mode {
41 MODE_NONE = 0, /* to check if a mode was set */
42 MODE_ACCEPT, /* accept message on stdin (fallback mode) */
43 MODE_DAEMON, /* run as daemon */
44 MODE_RUNQUEUE, /* single queue run, online or offline */
45 MODE_SMTP, /* accept SMTP on stdin */
46 MODE_LIST, /* list queue */
47 MODE_MCMD, /* do queue manipulation */
48 MODE_VERSION, /* show version */
49 MODE_BI, /* fake ;-) */
50 };
51 enum mta_mode mta_mode = MODE_NONE;
53 char *pidfile = NULL;
54 volatile int sigterm_in_progress = 0;
56 static void
57 sigterm_handler(int sig)
58 {
59 if (sigterm_in_progress)
60 raise(sig);
61 sigterm_in_progress = 1;
63 if (pidfile) {
64 uid_t uid;
65 uid = seteuid(0);
66 if (unlink(pidfile) != 0)
67 logwrite(LOG_WARNING, "could not delete pid file %s: %s\n", pidfile, strerror(errno));
68 seteuid(uid); /* we exit anyway after this, just to be sure */
69 }
71 signal(sig, SIG_DFL);
72 raise(sig);
73 }
75 #ifdef ENABLE_IDENT /* so far used for that only */
76 static gboolean
77 is_in_netlist(gchar * host, GList * netlist)
78 {
79 guint hostip = inet_addr(host);
80 struct in_addr addr;
82 addr.s_addr = hostip;
83 if (addr.s_addr != INADDR_NONE) {
84 GList *node;
85 foreach(netlist, node) {
86 struct in_addr *net = (struct in_addr *) (node->data);
87 if ((addr.s_addr & net->s_addr) == net->s_addr)
88 return TRUE;
89 }
90 }
91 return FALSE;
92 }
93 #endif
95 /*
96 argv: the original argv
97 argp: number of arg (may get modified!)
98 cp: pointing to the char after the option
99 e.g. `-d 6' `-d6'
100 ^ ^
101 */
102 gchar*
103 get_optarg(char* argv[], gint* argp, char* cp)
104 {
105 if (*cp) {
106 /* this kind: -xval */
107 return cp;
108 }
109 cp = argv[*argp+1];
110 if (cp && (*cp != '-')) {
111 /* this kind: -x val */
112 (*argp)++;
113 return cp;
114 }
115 return NULL;
116 }
118 gboolean
119 write_pidfile(gchar * name)
120 {
121 FILE *fptr;
123 if ((fptr = fopen(name, "wt"))) {
124 fprintf(fptr, "%d\n", getpid());
125 fclose(fptr);
126 pidfile = strdup(name);
127 return TRUE;
128 }
129 logwrite(LOG_WARNING, "could not write pid file: %s\n", strerror(errno));
130 return FALSE;
131 }
133 /* on -bd or if -q has an argument */
134 static void
135 mode_daemon(gboolean do_listen, gint queue_interval, char *argv[])
136 {
137 guint pid;
139 /* daemon */
140 if (!conf.run_as_user) {
141 if ((conf.orig_uid != 0) && (conf.orig_uid != conf.mail_uid)) {
142 fprintf(stderr, "must be root or %s for daemon.\n", DEF_MAIL_USER);
143 exit(1);
144 }
145 }
147 /* reparent to init only if init is not already the parent */
148 if (getppid() != 1) {
149 if ((pid = fork()) > 0) {
150 exit(0);
151 } else if (pid < 0) {
152 logwrite(LOG_ALERT, "could not fork!\n");
153 exit(1);
154 }
155 }
157 signal(SIGTERM, sigterm_handler);
158 write_pidfile(PIDFILEDIR "/masqmail.pid");
160 conf.do_verbose = FALSE;
162 /* closing and reopening the log ensures that it is open afterwards
163 because it is possible that the log is assigned to fd 1 and gets
164 thus closes by fclose(stdout). Similar for the debugfile.
165 */
166 logclose();
167 fclose(stdin);
168 fclose(stdout);
169 fclose(stderr);
170 logopen();
172 logwrite(LOG_NOTICE, "%s %s daemon starting\n", PACKAGE, VERSION);
173 listen_port(do_listen ? conf.listen_addresses : NULL, queue_interval, argv);
174 }
176 /* -bs or called as smtpd or in.smtpd */
177 static void
178 mode_smtp()
179 {
180 /* accept smtp message on stdin */
181 /* write responses to stderr. */
183 struct sockaddr_in saddr;
184 gchar *peername = NULL;
185 int dummy = sizeof(saddr);
187 conf.do_verbose = FALSE;
189 if (!conf.run_as_user) {
190 seteuid(conf.orig_uid);
191 setegid(conf.orig_gid);
192 }
194 DEBUG(5) debugf("accepting smtp message on stdin\n");
196 if (getpeername(0, (struct sockaddr *) (&saddr), &dummy) == 0) {
197 peername = g_strdup(inet_ntoa(saddr.sin_addr));
198 } else if (errno != ENOTSOCK)
199 exit(1);
201 smtp_in(stdin, stderr, peername, NULL);
202 }
204 /* default mode if address args or -t is specified, or called as rmail */
205 static void
206 mode_accept(address * return_path, gchar * full_sender_name, guint accept_flags, char **addresses, int addr_cnt)
207 {
208 /* accept message on stdin */
209 accept_error err;
210 message *msg = create_message();
211 gint i;
212 pid_t pid;
214 if (return_path && !is_privileged_user(conf.orig_uid)) {
215 fprintf(stderr, "must be root, %s or in group %s for setting return path.\n", DEF_MAIL_USER, DEF_MAIL_GROUP);
216 exit(1);
217 }
219 if (!conf.run_as_user) {
220 seteuid(conf.orig_uid);
221 setegid(conf.orig_gid);
222 }
224 DEBUG(5) debugf("accepting message on stdin\n");
226 msg->received_prot = PROT_LOCAL;
228 /* warn if -t option and cmdline addr args */
229 if (addr_cnt && (accept_flags & ACC_RCPT_FROM_HEAD)) {
230 logwrite(LOG_ALERT, "command line address arguments are now *added* to the mail header\\\n");
231 logwrite(LOG_ALERT, " recipient addresses (instead of substracted) when -t is given.\\\n");
232 logwrite(LOG_ALERT, " this changed with version 0.3.1\n");
233 }
235 for (i = 0; i < addr_cnt; i++) {
236 if (addresses[i][0] == '|') {
237 logwrite(LOG_ALERT, "no pipe allowed as recipient address: %s\n", addresses[i]);
238 /* should we better ignore this one addr? */
239 exit(1);
240 }
241 msg->rcpt_list = g_list_append(msg->rcpt_list, create_address_qualified(addresses[i], TRUE, conf.host_name));
242 }
244 /* -f option */
245 msg->return_path = return_path;
247 /* -F option */
248 msg->full_sender_name = full_sender_name;
250 err = accept_message(stdin, msg, accept_flags);
252 switch (err) {
253 case AERR_OK:
254 /* to continue; all other cases exit */
255 break;
256 case AERR_EOF:
257 fprintf(stderr, "unexpected EOF.\n");
258 exit(1);
259 case AERR_NORCPT:
260 fprintf(stderr, "no recipients.\n");
261 exit(1);
262 case AERR_SIZE:
263 fprintf(stderr, "max message size exceeded.\n");
264 exit(1);
265 default:
266 /* should never happen: */
267 fprintf(stderr, "Unknown error (%d)\r\n", err);
268 exit(1);
269 }
271 if (!spool_write(msg, TRUE)) {
272 fprintf(stderr, "Could not write spool file\n");
273 exit(1);
274 }
276 /* here the mail is queued and thus in our responsibility */
277 logwrite(LOG_NOTICE, "%s <= %s with %s\n", msg->uid, addr_string(msg->return_path), prot_names[PROT_LOCAL]);
279 if (conf.do_queue) {
280 /* we're finished as we only need to queue it */
281 return;
282 }
284 /* deliver at once */
285 if ((pid = fork()) < 0) {
286 logwrite(LOG_ALERT, "could not fork for delivery, id = %s\n", msg->uid);
287 } else if (pid == 0) {
288 conf.do_verbose = FALSE;
289 fclose(stdin);
290 fclose(stdout);
291 fclose(stderr);
292 if (deliver(msg)) {
293 exit(0);
294 } else {
295 /*
296 TODO:
297 Should we really fail here? Because the mail is queued
298 already. If we fail the client might submit it again.
299 If at-once-delivery is seen as an additional best-effort
300 service, then we should still exit successful here.
301 */
302 exit(1);
303 }
304 }
305 }
307 /*
308 if -Mrm is given
310 currently only the `rm' command is supported
311 until this changes, we don't need any facility for further commands
312 return success if at least one message had been deleted
313 */
314 static int
315 manipulate_queue(char* cmd, char* id[])
316 {
317 gboolean ok = FALSE;
319 if (strcmp(cmd, "rm") != 0) {
320 fprintf(stderr, "unknown command %s\n", cmd);
321 return FALSE;
322 }
324 set_euidgid(conf.mail_uid, conf.mail_gid, NULL, NULL);
326 /* privileged users may delete any mail */
327 if (is_privileged_user(conf.orig_uid)) {
328 for (; *id; id++) {
329 fprintf(stderr, "id: %s\n", *id);
330 if (queue_delete(*id)) {
331 ok = TRUE;
332 }
333 }
334 return ok;
335 }
337 struct passwd *pw = getpwuid(conf.orig_uid);
338 if (!pw) {
339 fprintf(stderr, "could not find a passwd entry for uid %d: %s\n",
340 conf.orig_uid, strerror(errno));
341 return FALSE;
342 }
344 /* non-privileged users may only delete their own messages */
345 for (; *id; id++) {
346 message *msg = msg_spool_read(*id, FALSE);
348 fprintf(stderr, "id: %s\n", *id);
350 if (!msg->ident) {
351 fprintf(stderr, "message %s does not have an ident\n", *id);
352 continue;
353 }
354 if (strcmp(pw->pw_name, msg->ident) != 0) {
355 fprintf(stderr, "you do not own message id %s\n", *id);
356 continue;
357 }
359 if ( (msg->received_host || (msg->received_prot != PROT_LOCAL))
360 #ifdef ENABLE_IDENT
361 && !is_in_netlist(msg->received_host, conf.ident_trusted_nets)
362 #endif
363 ) {
364 fprintf(stderr, "message %s was not received locally or from a trusted network\n", *id);
365 continue;
366 }
368 ok = queue_delete(*id);
369 }
370 return ok;
371 }
373 /* -qo, -q (without argument), or called as runq */
374 static int
375 run_queue(gboolean do_runq, gboolean do_runq_online, char* route_name)
376 {
377 int ret;
379 /* queue runs */
380 set_identity(conf.orig_uid, "queue run");
382 if (do_runq) {
383 ret = queue_run();
384 }
386 if (do_runq_online) {
387 if (route_name) {
388 conf.online_detect = g_strdup("argument");
389 set_online_name(route_name);
390 }
391 /* TODO: change behavior of `-qo without argument'?
392 Because that behavior is included in -q. */
393 ret = queue_run_online();
394 }
395 return ret;
396 }
398 /* -bV or default mode if neither addr arg nor -t */
399 static void
400 mode_version(void)
401 {
402 gchar *with_resolver = "";
403 gchar *with_auth = "";
404 gchar *with_ident = "";
406 #ifdef ENABLE_RESOLVER
407 with_resolver = " +resolver";
408 #endif
409 #ifdef ENABLE_AUTH
410 with_auth = " +auth";
411 #endif
412 #ifdef ENABLE_IDENT
413 with_ident = " +ident";
414 #endif
416 printf("%s %s%s%s%s\n", PACKAGE, VERSION, with_resolver, with_auth, with_ident);
417 }
419 void
420 set_mode(enum mta_mode mode)
421 {
422 if (mta_mode && mta_mode!=mode) {
423 fprintf(stderr, "operation mode was already specified (%d vs. %d)\n", mta_mode, mode);
424 exit(1);
425 }
427 mta_mode = mode;
428 return;
429 }
431 int
432 main(int argc, char *argv[])
433 {
434 gchar *progname;
435 char* opt;
436 gint arg;
438 gboolean do_listen = FALSE;
439 gboolean do_runq = FALSE;
440 gboolean do_runq_online = FALSE;
441 gboolean do_queue = FALSE;
442 gint queue_interval = 0;
443 gchar *M_cmd = NULL;
444 gboolean opt_t = FALSE;
445 gboolean opt_i = FALSE;
446 gchar *conf_file = CONF_FILE;
447 gchar *route_name = NULL;
448 gchar *f_address = NULL;
449 address *return_path = NULL; /* may be changed by -f option */
450 gchar *full_sender_name = NULL;
451 gboolean do_verbose = FALSE;
452 gint debug_level = -1;
454 /* strip the path part */
455 progname = strrchr(argv[0], '/');
456 progname = (progname) ? progname+1 : argv[0];
458 if (strcmp(progname, "mailq") == 0) {
459 mta_mode = MODE_LIST;
460 } else if (strcmp(progname, "mailrm") == 0) {
461 mta_mode = MODE_MCMD;
462 M_cmd = "rm";
463 } else if (strcmp(progname, "newaliases") == 0) {
464 mta_mode = MODE_BI;
465 } else if (strcmp(progname, "rmail") == 0) {
466 /* the `rmail' alias should probably be removed now
467 that we have the rmail script. But let's keep it
468 for some while for compatibility. 2010-06-19 */
469 mta_mode = MODE_ACCEPT;
470 opt_i = TRUE;
471 } else if (strcmp(progname, "runq") == 0) {
472 mta_mode = MODE_RUNQUEUE;
473 do_runq = TRUE;
474 } else if (strcmp(progname, "smtpd") == 0
475 || strcmp(progname, "in.smtpd") == 0) {
476 mta_mode = MODE_SMTP;
477 }
479 /* parse cmd line */
480 for (arg=1; arg<argc && argv[arg][0]=='-'; arg++) {
481 opt = argv[arg] + 1; /* points to the char after the dash */
483 if (strcmp(opt, "-") == 0) {
484 /* everything after `--' are address arguments */
485 arg++;
486 break;
488 } else if (strcmp(opt, "bm") == 0) {
489 set_mode(MODE_ACCEPT);
491 } else if (strcmp(opt, "bd") == 0) {
492 set_mode(MODE_DAEMON);
493 do_listen = TRUE;
495 } else if (strcmp(opt, "bi") == 0) {
496 set_mode(MODE_BI);
498 } else if (strcmp(opt, "bs") == 0) {
499 set_mode(MODE_SMTP);
501 } else if (strcmp(opt, "bp") == 0) {
502 set_mode(MODE_LIST);
504 } else if (strcmp(opt, "bV") == 0) {
505 set_mode(MODE_VERSION);
507 } else if (strncmp(opt, "B", 1) == 0) {
508 /* we ignore this and throw the argument away */
509 get_optarg(argv, &arg, opt+1);
511 } else if (strncmp(opt, "C", 1) == 0) {
512 conf_file = get_optarg(argv, &arg, opt+1);
513 if (!conf_file) {
514 fprintf(stderr, "-C requires a filename as argument.\n");
515 exit(1);
516 }
518 } else if (strncmp(opt, "d", 1) == 0) {
519 if (getuid() != 0) {
520 fprintf(stderr, "only root may set the debug level.\n");
521 exit(1);
522 }
523 char *lvl = get_optarg(argv, &arg, opt+1);
524 if (!lvl) {
525 fprintf(stderr, "-d requires a number argument.\n");
526 exit(1);
527 }
528 debug_level = atoi(lvl);
530 } else if (strncmp(opt, "f", 1) == 0) {
531 /* set return path */
532 gchar *address = get_optarg(argv, &arg, opt+1);
533 if (!address) {
534 fprintf(stderr, "-f requires an address argument\n");
535 exit(1);
536 }
537 f_address = g_strdup(address);
539 } else if (strncmp(opt, "F", 1) == 0) {
540 full_sender_name = get_optarg(argv, &arg, opt+1);
541 if (!full_sender_name) {
542 fprintf(stderr, "-F requires a name argument\n");
543 exit(1);
544 }
546 } else if (strcmp(opt, "i") == 0) {
547 opt_i = TRUE;
549 } else if (strcmp(opt, "m") == 0) {
550 /* ignore -m (me too) switch (see man page) */
552 } else if (strcmp(opt, "Mrm") == 0) {
553 set_mode(MODE_MCMD);
554 M_cmd = "rm";
556 } else if (strcmp(opt, "odq") == 0) {
557 do_queue = TRUE;
559 } else if (strcmp(opt, "oi") == 0) {
560 opt_i = TRUE;
562 } else if (strncmp(opt, "o", 1) == 0) {
563 /* ignore all other -oXXX options */
565 } else if (strncmp(opt, "qo", 2) == 0) {
566 /* must be before the `q' check */
567 set_mode(MODE_RUNQUEUE);
568 do_runq_online = TRUE;
569 /* can be NULL, then we use online detection method */
570 /* TODO: behavior might change if it is NULL */
571 route_name = get_optarg(argv, &arg, opt+2);
572 if (!route_name) {
573 fprintf(stderr, "Please do not use -qo without argument anymore; use -q instead.\n");
574 fprintf(stderr, "The behavior for -qo without argument is likely to change.\n");
575 }
577 } else if (strncmp(opt, "q", 1) == 0) {
578 /* must be after the `qo' check */
579 gchar *optarg;
581 optarg = get_optarg(argv, &arg, opt+1);
582 if (optarg) {
583 /* not just one single queue run but regular runs */
584 set_mode(MODE_DAEMON);
585 queue_interval = time_interval(optarg);
586 } else {
587 set_mode(MODE_RUNQUEUE);
588 do_runq = TRUE;
589 }
591 } else if (strcmp(opt, "t") == 0) {
592 opt_t = TRUE;
594 } else if (strcmp(opt, "v") == 0) {
595 do_verbose = TRUE;
597 } else {
598 fprintf(stderr, "unrecognized option `-%s'\n", opt);
599 exit(1);
600 }
601 }
603 if (!mta_mode && arg==argc && !opt_t) {
604 /*
605 In this case no rcpts can be found, thus no mail
606 can be sent, thus masqmail will always fail. We
607 rather do something better instead. This covers
608 also the case of calling masqmail without args.
609 */
610 mode_version();
611 exit(0);
612 }
614 if (mta_mode == MODE_VERSION) {
615 mode_version();
616 exit(0);
617 }
619 if (!mta_mode) {
620 mta_mode = MODE_ACCEPT;
621 }
623 /* initialize random generator */
624 srand(time(NULL));
625 /* ignore SIGPIPE signal */
626 signal(SIGPIPE, SIG_IGN);
628 /* close all possibly open file descriptors, except std{in,out,err} */
629 {
630 int i, max_fd = sysconf(_SC_OPEN_MAX);
632 if (max_fd <= 0) {
633 max_fd = 64;
634 }
635 for (i=3; i<max_fd; i++) {
636 close(i);
637 }
638 }
640 init_conf();
642 /* if we are not privileged, and the config file was changed we
643 implicetely set the the run_as_user flag and give up all
644 privileges.
646 So it is possible for a user to run his own daemon without
647 breaking security.
648 */
649 if ((strcmp(conf_file, CONF_FILE) != 0) && (conf.orig_uid != 0)) {
650 conf.run_as_user = TRUE;
651 seteuid(conf.orig_uid);
652 setegid(conf.orig_gid);
653 setuid(conf.orig_uid);
654 setgid(conf.orig_gid);
655 }
657 conf.log_dir = LOG_DIR;
658 /* FIXME: fails if we run as user */
659 logopen();
660 if (!read_conf(conf_file)) {
661 logwrite(LOG_ALERT, "SHUTTING DOWN due to problems reading config\n");
662 exit(5);
663 }
664 logclose();
666 if (do_queue) {
667 conf.do_queue = TRUE;
668 }
669 if (do_verbose) {
670 conf.do_verbose = TRUE;
671 }
672 if (debug_level >= 0) { /* if >= 0, it was given by argument */
673 conf.debug_level = debug_level;
674 }
676 /* It appears that changing to / ensures that we are never in
677 a directory which we cannot access. This situation could be
678 possible after changing identity.
679 Maybe we should only change to / if we not run as user, to
680 allow relative paths for log files in test setups for
681 instance.
682 */
683 chdir("/");
685 if (!conf.run_as_user) {
686 if (setgid(0) != 0) {
687 fprintf(stderr, "could not set gid to 0. Is the setuid bit set? : %s\n", strerror(errno));
688 exit(1);
689 }
690 if (setuid(0) != 0) {
691 fprintf(stderr, "could not gain root privileges. Is the setuid bit set? : %s\n", strerror(errno));
692 exit(1);
693 }
694 }
696 if (!logopen()) {
697 fprintf(stderr, "could not open log file\n");
698 exit(1);
699 }
701 DEBUG(1) debugf("masqmail %s starting\n", VERSION);
703 DEBUG(5) {
704 gchar **str = argv;
705 debugf("args: \n");
706 while (*str) {
707 debugf("%s \n", *str);
708 str++;
709 }
710 }
711 DEBUG(5) debugf("queue_interval = %d\n", queue_interval);
713 if (f_address) {
714 return_path = create_address_qualified(f_address, TRUE, conf.host_name);
715 g_free(f_address);
716 if (!return_path) {
717 fprintf(stderr, "invalid RFC821 address: %s\n", f_address);
718 exit(1);
719 }
720 }
722 switch (mta_mode) {
723 case MODE_DAEMON:
724 mode_daemon(do_listen, queue_interval, argv);
725 break;
727 case MODE_RUNQUEUE:
728 exit(run_queue(do_runq, do_runq_online, route_name) ? 0 : 1);
729 break;
731 case MODE_SMTP:
732 mode_smtp();
733 break;
735 case MODE_LIST:
736 queue_list();
737 break;
739 case MODE_BI:
740 exit(0);
741 break; /* well... */
743 case MODE_MCMD:
744 exit(manipulate_queue(M_cmd, &argv[arg]) ? 0 : 1);
745 break;
747 case MODE_ACCEPT:
748 {
749 guint accept_flags = (opt_t ? ACC_RCPT_FROM_HEAD : 0)
750 | (opt_i ? ACC_DOT_IGNORE : ACC_NODOT_RELAX);
751 mode_accept(return_path, full_sender_name, accept_flags, &(argv[arg]), argc - arg);
752 exit(0);
753 }
754 break;
756 default:
757 fprintf(stderr, "unknown mode: %d\n", mta_mode);
758 break;
759 }
761 logclose();
763 exit(0);
764 }