masqmail

view src/masqmail.c @ 269:fd1d77e5a5da

comment: problems with `To: alice, bob' and -t
author markus schnalke <meillo@marmaro.de>
date Fri, 03 Dec 2010 12:55:44 -0300
parents 8be687c06c20
children 1abc1faeb45d
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_NONE = 0, /* for being able to check if a mode was defined */
42 MODE_ACCEPT, /* accept message on stdin */
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 } 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 /*
95 argv: the original argv
96 argp: number of arg (may get modified!)
97 cp: pointing to the char after the option
98 e.g. `-d 6' `-d6'
99 ^ ^
100 */
101 gchar*
102 get_optarg(char* argv[], gint* argp, char* cp)
103 {
104 if (*cp) {
105 /* this kind: -xval */
106 return cp;
107 }
108 cp = argv[*argp+1];
109 if (cp && (*cp != '-')) {
110 /* this kind: -x val */
111 (*argp)++;
112 return cp;
113 }
114 return NULL;
115 }
117 gboolean
118 write_pidfile(gchar * name)
119 {
120 FILE *fptr;
122 if ((fptr = fopen(name, "wt"))) {
123 fprintf(fptr, "%d\n", getpid());
124 fclose(fptr);
125 pidfile = strdup(name);
126 return TRUE;
127 }
128 logwrite(LOG_WARNING, "could not write pid file: %s\n", strerror(errno));
129 return FALSE;
130 }
132 /* on -bd or if -q has an argument */
133 static void
134 mode_daemon(gboolean do_listen, gint queue_interval, char *argv[])
135 {
136 guint pid;
138 /* daemon */
139 if (!conf.run_as_user) {
140 if ((conf.orig_uid != 0) && (conf.orig_uid != conf.mail_uid)) {
141 fprintf(stderr, "must be root or %s for daemon.\n", DEF_MAIL_USER);
142 exit(1);
143 }
144 }
146 /* reparent to init only if init is not already the parent */
147 if (getppid() != 1) {
148 if ((pid = fork()) > 0) {
149 exit(0);
150 } else if (pid < 0) {
151 logwrite(LOG_ALERT, "could not fork!\n");
152 exit(1);
153 }
154 }
156 signal(SIGTERM, sigterm_handler);
157 write_pidfile(PIDFILEDIR "/masqmail.pid");
159 conf.do_verbose = FALSE;
161 /* closing and reopening the log ensures that it is open afterwards
162 because it is possible that the log is assigned to fd 1 and gets
163 thus closes by fclose(stdout). Similar for the debugfile.
164 */
165 logclose();
166 fclose(stdin);
167 fclose(stdout);
168 fclose(stderr);
169 logopen();
171 logwrite(LOG_NOTICE, "%s %s daemon starting\n", PACKAGE, VERSION);
172 listen_port(do_listen ? conf.listen_addresses : NULL, queue_interval, argv);
173 }
175 /* -bs or called as smtpd or in.smtpd */
176 static void
177 mode_smtp()
178 {
179 /* accept smtp message on stdin */
180 /* write responses to stderr. */
182 struct sockaddr_in saddr;
183 gchar *peername = NULL;
184 int dummy = sizeof(saddr);
186 conf.do_verbose = FALSE;
188 if (!conf.run_as_user) {
189 seteuid(conf.orig_uid);
190 setegid(conf.orig_gid);
191 }
193 DEBUG(5) debugf("accepting smtp message on stdin\n");
195 if (getpeername(0, (struct sockaddr *) (&saddr), &dummy) == 0) {
196 peername = g_strdup(inet_ntoa(saddr.sin_addr));
197 } else if (errno != ENOTSOCK)
198 exit(1);
200 smtp_in(stdin, stderr, peername, NULL);
201 }
203 /* default mode if address args or -t is specified, or called as rmail */
204 static void
205 mode_accept(address * return_path, gchar * full_sender_name, guint accept_flags, char **addresses, int addr_cnt)
206 {
207 /* accept message on stdin */
208 accept_error err;
209 message *msg = create_message();
210 gint i;
211 pid_t pid;
213 if (return_path && !is_privileged_user(conf.orig_uid)) {
214 fprintf(stderr, "must be root, %s or in group %s for setting return path.\n", DEF_MAIL_USER, DEF_MAIL_GROUP);
215 exit(1);
216 }
218 if (!conf.run_as_user) {
219 seteuid(conf.orig_uid);
220 setegid(conf.orig_gid);
221 }
223 DEBUG(5) debugf("accepting message on stdin\n");
225 msg->received_prot = PROT_LOCAL;
226 for (i = 0; i < addr_cnt; i++) {
227 if (addresses[i][0] == '|') {
228 logwrite(LOG_ALERT, "no pipe allowed as recipient address: %s\n", addresses[i]);
229 exit(1);
230 }
231 msg->rcpt_list = g_list_append(msg->rcpt_list, create_address_qualified(addresses[i], TRUE, conf.host_name));
232 }
234 /* -f option */
235 msg->return_path = return_path;
237 /* -F option */
238 msg->full_sender_name = full_sender_name;
240 err = accept_message(stdin, msg, accept_flags);
242 switch (err) {
243 case AERR_OK:
244 /* to continue; all other cases exit */
245 break;
246 case AERR_EOF:
247 fprintf(stderr, "unexpected EOF.\n");
248 exit(1);
249 case AERR_NORCPT:
250 fprintf(stderr, "no recipients.\n");
251 exit(1);
252 case AERR_SIZE:
253 fprintf(stderr, "max message size exceeded.\n");
254 exit(1);
255 default:
256 /* should never happen: */
257 fprintf(stderr, "Unknown error (%d)\r\n", err);
258 exit(1);
259 }
261 if (!spool_write(msg, TRUE)) {
262 fprintf(stderr, "Could not write spool file\n");
263 exit(1);
264 }
266 /* here the mail is queued and thus in our responsibility */
267 logwrite(LOG_NOTICE, "%s <= %s with %s\n", msg->uid, addr_string(msg->return_path), prot_names[PROT_LOCAL]);
269 if (conf.do_queue) {
270 /* we're finished as we only need to queue it */
271 return;
272 }
274 /* deliver at once */
275 if ((pid = fork()) < 0) {
276 logwrite(LOG_ALERT, "could not fork for delivery, id = %s\n", msg->uid);
277 } else if (pid == 0) {
278 conf.do_verbose = FALSE;
279 fclose(stdin);
280 fclose(stdout);
281 fclose(stderr);
282 if (deliver(msg)) {
283 exit(0);
284 } else {
285 /*
286 TODO:
287 Should we really fail here? Because the mail is queued
288 already. If we fail the client might submit it again.
289 If at-once-delivery is seen as an additional best-effort
290 service, then we should still exit successful here.
291 */
292 exit(1);
293 }
294 }
295 }
297 /*
298 if -Mrm is given
300 currently only the `rm' command is supported
301 until this changes, we don't need any facility for further commands
302 return success if at least one message had been deleted
303 */
304 static int
305 manipulate_queue(char* cmd, char* id[])
306 {
307 gboolean ok = FALSE;
309 if (strcmp(cmd, "rm") != 0) {
310 fprintf(stderr, "unknown command %s\n", cmd);
311 return FALSE;
312 }
314 set_euidgid(conf.mail_uid, conf.mail_gid, NULL, NULL);
316 /* privileged users may delete any mail */
317 if (is_privileged_user(conf.orig_uid)) {
318 for (; *id; id++) {
319 fprintf(stderr, "id: %s\n", *id);
320 if (queue_delete(*id)) {
321 ok = TRUE;
322 }
323 }
324 return ok;
325 }
327 struct passwd *pw = getpwuid(conf.orig_uid);
328 if (!pw) {
329 fprintf(stderr, "could not find a passwd entry for uid %d: %s\n",
330 conf.orig_uid, strerror(errno));
331 return FALSE;
332 }
334 /* non-privileged users may only delete their own messages */
335 for (; *id; id++) {
336 message *msg = msg_spool_read(*id, FALSE);
338 fprintf(stderr, "id: %s\n", *id);
340 if (!msg->ident) {
341 fprintf(stderr, "message %s does not have an ident\n", *id);
342 continue;
343 }
344 if (strcmp(pw->pw_name, msg->ident) != 0) {
345 fprintf(stderr, "you do not own message id %s\n", *id);
346 continue;
347 }
349 if ( (msg->received_host || (msg->received_prot != PROT_LOCAL))
350 #ifdef ENABLE_IDENT
351 && !is_in_netlist(msg->received_host, conf.ident_trusted_nets)
352 #endif
353 ) {
354 fprintf(stderr, "message %s was not received locally or from a trusted network\n", *id);
355 continue;
356 }
358 ok = queue_delete(*id);
359 }
360 return ok;
361 }
363 /* -qo, -q (without argument), or called as runq */
364 /* TODO: are -qo and -q exclusively or not?
365 And how is this related to being a daemon? */
366 static int
367 run_queue(gboolean do_runq, gboolean do_runq_online, char* route_name)
368 {
369 int ret;
371 /* queue runs */
372 set_identity(conf.orig_uid, "queue run");
374 if (do_runq) {
375 ret = queue_run();
376 }
378 if (do_runq_online) {
379 if (route_name) {
380 conf.online_detect = g_strdup("argument");
381 set_online_name(route_name);
382 }
383 ret = queue_run_online();
384 }
385 return ret;
386 }
388 /* -bV or default mode if neither addr arg nor -t */
389 static void
390 mode_version(void)
391 {
392 gchar *with_resolver = "";
393 gchar *with_auth = "";
394 gchar *with_ident = "";
396 #ifdef ENABLE_RESOLVER
397 with_resolver = " +resolver";
398 #endif
399 #ifdef ENABLE_AUTH
400 with_auth = " +auth";
401 #endif
402 #ifdef ENABLE_IDENT
403 with_ident = " +ident";
404 #endif
406 printf("%s %s%s%s%s\n", PACKAGE, VERSION, with_resolver, with_auth, with_ident);
407 }
409 int
410 main(int argc, char *argv[])
411 {
412 gchar *progname;
413 char* opt;
414 gint arg;
416 mta_mode mta_mode = MODE_NONE;
417 gboolean do_listen = FALSE;
418 gboolean do_runq = FALSE;
419 gboolean do_runq_online = FALSE;
420 gboolean do_queue = FALSE;
421 gint queue_interval = 0;
422 gchar *M_cmd = NULL;
423 gboolean opt_t = FALSE;
424 gboolean opt_i = FALSE;
425 gchar *conf_file = CONF_FILE;
426 gchar *route_name = NULL;
427 gchar *f_address = NULL;
428 address *return_path = NULL; /* may be changed by -f option */
429 gchar *full_sender_name = NULL;
430 gboolean do_verbose = FALSE;
431 gint debug_level = -1;
433 /* strip the path part */
434 progname = strrchr(argv[0], '/');
435 progname = (progname) ? progname+1 : argv[0];
437 if (strcmp(progname, "mailq") == 0) {
438 mta_mode = MODE_LIST;
439 } else if (strcmp(progname, "mailrm") == 0) {
440 mta_mode = MODE_MCMD;
441 M_cmd = "rm";
442 } else if (strcmp(progname, "runq") == 0) {
443 mta_mode = MODE_RUNQUEUE;
444 do_runq = TRUE;
445 } else if (strcmp(progname, "rmail") == 0) {
446 /* the `rmail' alias should probably be removed now
447 that we have the rmail script. But let's keep it
448 for some while for compatibility. 2010-06-19 */
449 mta_mode = MODE_ACCEPT;
450 opt_i = TRUE;
451 } else if (strcmp(progname, "smtpd") == 0 || strcmp(progname, "in.smtpd") == 0) {
452 mta_mode = MODE_SMTP;
453 }
455 /* parse cmd line */
456 for (arg=1; arg<argc && argv[arg][0]=='-'; arg++) {
457 opt = argv[arg] + 1; /* points to the char after the dash */
459 if (strcmp(opt, "-") == 0) {
460 /* everything after `--' are address arguments */
461 arg++;
462 break;
464 } else if (strcmp(opt, "bd") == 0) {
465 do_listen = TRUE;
466 mta_mode = MODE_DAEMON;
468 } else if (strcmp(opt, "bi") == 0) {
469 /* ignored */
470 mta_mode = MODE_BI;
472 } else if (strcmp(opt, "bs") == 0) {
473 mta_mode = MODE_SMTP;
475 } else if (strcmp(opt, "bp") == 0) {
476 mta_mode = MODE_LIST;
478 } else if (strcmp(opt, "bV") == 0) {
479 mta_mode = MODE_VERSION;
481 } else if (strncmp(opt, "B", 1) == 0) {
482 /* we ignore this and throw the argument away */
483 get_optarg(argv, &arg, opt+1);
485 } else if (strncmp(opt, "C", 1) == 0) {
486 conf_file = get_optarg(argv, &arg, opt+1);
487 if (!conf_file) {
488 fprintf(stderr, "-C requires a filename as argument.\n");
489 exit(1);
490 }
492 } else if (strncmp(opt, "d", 1) == 0) {
493 if (getuid() != 0) {
494 fprintf(stderr, "only root may set the debug level.\n");
495 exit(1);
496 }
497 char *lvl = get_optarg(argv, &arg, opt+1);
498 if (!lvl) {
499 fprintf(stderr, "-d requires a number argument.\n");
500 exit(1);
501 }
502 debug_level = atoi(lvl);
504 } else if (strncmp(opt, "f", 1) == 0) {
505 /* set return path */
506 gchar *address = get_optarg(argv, &arg, opt+1);
507 if (!address) {
508 fprintf(stderr, "-f requires an address argument\n");
509 exit(1);
510 }
511 f_address = g_strdup(address);
513 } else if (strncmp(opt, "F", 1) == 0) {
514 full_sender_name = get_optarg(argv, &arg, opt+1);
515 if (!full_sender_name) {
516 fprintf(stderr, "-F requires a name argument\n");
517 exit(1);
518 }
520 } else if (strcmp(opt, "i") == 0) {
521 opt_i = TRUE;
523 } else if (strcmp(opt, "m") == 0) {
524 /* ignore -m (me too) switch (see man page) */
526 } else if (strcmp(opt, "Mrm") == 0) {
527 mta_mode = MODE_MCMD;
528 M_cmd = "rm";
530 } else if (strcmp(opt, "odq") == 0) {
531 do_queue = TRUE;
533 } else if (strcmp(opt, "oi") == 0) {
534 opt_i = TRUE;
536 } else if (strncmp(opt, "o", 1) == 0) {
537 /* ignore all other -oXXX options */
539 } else if (strncmp(opt, "qo", 2) == 0) {
540 mta_mode = MODE_RUNQUEUE;
541 do_runq = FALSE;
542 do_runq_online = TRUE;
543 /* can be NULL, then we use online detection method */
544 route_name = get_optarg(argv, &arg, opt+2);
546 } else if (strncmp(opt, "q", 1) == 0) {
547 /* must be after the `qo' check */
548 gchar *optarg;
550 do_runq = TRUE;
551 mta_mode = MODE_RUNQUEUE;
552 optarg = get_optarg(argv, &arg, opt+1);
553 if (optarg) {
554 /* not just one single queue run but regular runs */
555 mta_mode = MODE_DAEMON;
556 queue_interval = time_interval(optarg);
557 }
559 } else if (strcmp(opt, "t") == 0) {
560 opt_t = TRUE;
562 } else if (strcmp(opt, "v") == 0) {
563 do_verbose = TRUE;
565 } else {
566 fprintf(stderr, "unrecognized option `-%s'\n", opt);
567 exit(1);
568 }
569 }
571 if (!mta_mode) {
572 mta_mode = (arg<argc || opt_t) ? MODE_ACCEPT : MODE_VERSION;
573 }
575 if (mta_mode == MODE_VERSION) {
576 mode_version();
577 exit(0);
578 }
580 /* initialize random generator */
581 srand(time(NULL));
582 /* ignore SIGPIPE signal */
583 signal(SIGPIPE, SIG_IGN);
585 /* close all possibly open file descriptors, except std{in,out,err} */
586 {
587 int i, max_fd = sysconf(_SC_OPEN_MAX);
589 if (max_fd <= 0) {
590 max_fd = 64;
591 }
592 for (i=3; i<max_fd; i++) {
593 close(i);
594 }
595 }
597 init_conf();
599 /* if we are not privileged, and the config file was changed we
600 implicetely set the the run_as_user flag and give up all
601 privileges.
603 So it is possible for a user to run his own daemon without
604 breaking security.
605 */
606 if ((strcmp(conf_file, CONF_FILE) != 0) && (conf.orig_uid != 0)) {
607 conf.run_as_user = TRUE;
608 seteuid(conf.orig_uid);
609 setegid(conf.orig_gid);
610 setuid(conf.orig_uid);
611 setgid(conf.orig_gid);
612 }
614 conf.log_dir = LOG_DIR;
615 logopen();
616 if (!read_conf(conf_file)) {
617 logwrite(LOG_ALERT, "SHUTTING DOWN due to problems reading config\n");
618 exit(5);
619 }
620 logclose();
622 if (do_queue) {
623 conf.do_queue = TRUE;
624 }
625 if (do_verbose) {
626 conf.do_verbose = TRUE;
627 }
628 if (debug_level >= 0) { /* if >= 0, it was given by argument */
629 conf.debug_level = debug_level;
630 }
632 /* It appears that changing to / ensures that we are never in
633 a directory which we cannot access. This situation could be
634 possible after changing identity.
635 Maybe we should only change to / if we not run as user, to
636 allow relative paths for log files in test setups for
637 instance.
638 */
639 chdir("/");
641 if (!conf.run_as_user) {
642 if (setgid(0) != 0) {
643 fprintf(stderr, "could not set gid to 0. Is the setuid bit set? : %s\n", strerror(errno));
644 exit(1);
645 }
646 if (setuid(0) != 0) {
647 fprintf(stderr, "could not gain root privileges. Is the setuid bit set? : %s\n", strerror(errno));
648 exit(1);
649 }
650 }
652 if (!logopen()) {
653 fprintf(stderr, "could not open log file\n");
654 exit(1);
655 }
657 DEBUG(1) debugf("masqmail %s starting\n", VERSION);
659 DEBUG(5) {
660 gchar **str = argv;
661 debugf("args: \n");
662 while (*str) {
663 debugf("%s \n", *str);
664 str++;
665 }
666 }
667 DEBUG(5) debugf("queue_interval = %d\n", queue_interval);
669 if (f_address) {
670 return_path = create_address_qualified(f_address, TRUE, conf.host_name);
671 g_free(f_address);
672 if (!return_path) {
673 fprintf(stderr, "invalid RFC821 address: %s\n", f_address);
674 exit(1);
675 }
676 }
678 switch (mta_mode) {
679 case MODE_DAEMON:
680 mode_daemon(do_listen, queue_interval, argv);
681 break;
683 case MODE_RUNQUEUE:
684 exit(run_queue(do_runq, do_runq_online, route_name) ? 0 : 1);
685 break;
687 case MODE_SMTP:
688 mode_smtp();
689 break;
691 case MODE_LIST:
692 queue_list();
693 break;
695 case MODE_BI:
696 exit(0);
697 break; /* well... */
699 case MODE_MCMD:
700 exit(manipulate_queue(M_cmd, &argv[arg]) ? 0 : 1);
701 break;
703 case MODE_ACCEPT:
704 {
705 guint accept_flags = (opt_t ? ACC_RCPT_FROM_HEAD : 0)
706 | (opt_i ? ACC_DOT_IGNORE : ACC_NODOT_RELAX);
707 mode_accept(return_path, full_sender_name, accept_flags, &(argv[arg]), argc - arg);
708 exit(0);
709 }
710 break;
712 default:
713 fprintf(stderr, "unknown mode: %d\n", mta_mode);
714 break;
715 }
717 logclose();
719 exit(0);
720 }