masqmail

view src/masqmail.c @ 276:1abc1faeb45d

for -t cmdline args are now added to the rcpt list instead of substracted Please read the diff and the section about -t in man/masqmail.8. Masqmail's behavior had been like the one of exim/smail, now it's similar to postfix. Masqmail does it now the most simple way, regarding the code. Also, addr args are always recipients, -t does not change their meaning. -t makes the addrs from rcpt hdrs, rcpt addrs too. It would have been logical too, to ignore the cmdline args, in the sense of ``headers *instead of* args'' but none of the popular MTAs does it that way and it would have been a bit more complicated in the code. Anyway, this is a corner-case that should better be avoided completely.
author markus schnalke <meillo@marmaro.de>
date Fri, 03 Dec 2010 21:05:34 -0300
parents 7b12d081b939
children c35c59a36a2a
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;
227 /* warn if -t option and cmdline addr args */
228 if (addr_cnt && (accept_flags & ACC_RCPT_FROM_HEAD)) {
229 logwrite(LOG_ALERT, "command line address arguments are now *added* to the mail header\\\n");
230 logwrite(LOG_ALERT, " recipient addresses (instead of substracted) when -t is given.\\\n");
231 logwrite(LOG_ALERT, " this changed with version 0.3.1\n");
232 }
234 for (i = 0; i < addr_cnt; i++) {
235 if (addresses[i][0] == '|') {
236 logwrite(LOG_ALERT, "no pipe allowed as recipient address: %s\n", addresses[i]);
237 /* should we better ignore this one addr? */
238 exit(1);
239 }
240 msg->rcpt_list = g_list_append(msg->rcpt_list, create_address_qualified(addresses[i], TRUE, conf.host_name));
241 }
243 /* -f option */
244 msg->return_path = return_path;
246 /* -F option */
247 msg->full_sender_name = full_sender_name;
249 err = accept_message(stdin, msg, accept_flags);
251 switch (err) {
252 case AERR_OK:
253 /* to continue; all other cases exit */
254 break;
255 case AERR_EOF:
256 fprintf(stderr, "unexpected EOF.\n");
257 exit(1);
258 case AERR_NORCPT:
259 fprintf(stderr, "no recipients.\n");
260 exit(1);
261 case AERR_SIZE:
262 fprintf(stderr, "max message size exceeded.\n");
263 exit(1);
264 default:
265 /* should never happen: */
266 fprintf(stderr, "Unknown error (%d)\r\n", err);
267 exit(1);
268 }
270 if (!spool_write(msg, TRUE)) {
271 fprintf(stderr, "Could not write spool file\n");
272 exit(1);
273 }
275 /* here the mail is queued and thus in our responsibility */
276 logwrite(LOG_NOTICE, "%s <= %s with %s\n", msg->uid, addr_string(msg->return_path), prot_names[PROT_LOCAL]);
278 if (conf.do_queue) {
279 /* we're finished as we only need to queue it */
280 return;
281 }
283 /* deliver at once */
284 if ((pid = fork()) < 0) {
285 logwrite(LOG_ALERT, "could not fork for delivery, id = %s\n", msg->uid);
286 } else if (pid == 0) {
287 conf.do_verbose = FALSE;
288 fclose(stdin);
289 fclose(stdout);
290 fclose(stderr);
291 if (deliver(msg)) {
292 exit(0);
293 } else {
294 /*
295 TODO:
296 Should we really fail here? Because the mail is queued
297 already. If we fail the client might submit it again.
298 If at-once-delivery is seen as an additional best-effort
299 service, then we should still exit successful here.
300 */
301 exit(1);
302 }
303 }
304 }
306 /*
307 if -Mrm is given
309 currently only the `rm' command is supported
310 until this changes, we don't need any facility for further commands
311 return success if at least one message had been deleted
312 */
313 static int
314 manipulate_queue(char* cmd, char* id[])
315 {
316 gboolean ok = FALSE;
318 if (strcmp(cmd, "rm") != 0) {
319 fprintf(stderr, "unknown command %s\n", cmd);
320 return FALSE;
321 }
323 set_euidgid(conf.mail_uid, conf.mail_gid, NULL, NULL);
325 /* privileged users may delete any mail */
326 if (is_privileged_user(conf.orig_uid)) {
327 for (; *id; id++) {
328 fprintf(stderr, "id: %s\n", *id);
329 if (queue_delete(*id)) {
330 ok = TRUE;
331 }
332 }
333 return ok;
334 }
336 struct passwd *pw = getpwuid(conf.orig_uid);
337 if (!pw) {
338 fprintf(stderr, "could not find a passwd entry for uid %d: %s\n",
339 conf.orig_uid, strerror(errno));
340 return FALSE;
341 }
343 /* non-privileged users may only delete their own messages */
344 for (; *id; id++) {
345 message *msg = msg_spool_read(*id, FALSE);
347 fprintf(stderr, "id: %s\n", *id);
349 if (!msg->ident) {
350 fprintf(stderr, "message %s does not have an ident\n", *id);
351 continue;
352 }
353 if (strcmp(pw->pw_name, msg->ident) != 0) {
354 fprintf(stderr, "you do not own message id %s\n", *id);
355 continue;
356 }
358 if ( (msg->received_host || (msg->received_prot != PROT_LOCAL))
359 #ifdef ENABLE_IDENT
360 && !is_in_netlist(msg->received_host, conf.ident_trusted_nets)
361 #endif
362 ) {
363 fprintf(stderr, "message %s was not received locally or from a trusted network\n", *id);
364 continue;
365 }
367 ok = queue_delete(*id);
368 }
369 return ok;
370 }
372 /* -qo, -q (without argument), or called as runq */
373 /* TODO: are -qo and -q exclusively or not?
374 And how is this related to being a daemon? */
375 static int
376 run_queue(gboolean do_runq, gboolean do_runq_online, char* route_name)
377 {
378 int ret;
380 /* queue runs */
381 set_identity(conf.orig_uid, "queue run");
383 if (do_runq) {
384 ret = queue_run();
385 }
387 if (do_runq_online) {
388 if (route_name) {
389 conf.online_detect = g_strdup("argument");
390 set_online_name(route_name);
391 }
392 ret = queue_run_online();
393 }
394 return ret;
395 }
397 /* -bV or default mode if neither addr arg nor -t */
398 static void
399 mode_version(void)
400 {
401 gchar *with_resolver = "";
402 gchar *with_auth = "";
403 gchar *with_ident = "";
405 #ifdef ENABLE_RESOLVER
406 with_resolver = " +resolver";
407 #endif
408 #ifdef ENABLE_AUTH
409 with_auth = " +auth";
410 #endif
411 #ifdef ENABLE_IDENT
412 with_ident = " +ident";
413 #endif
415 printf("%s %s%s%s%s\n", PACKAGE, VERSION, with_resolver, with_auth, with_ident);
416 }
418 int
419 main(int argc, char *argv[])
420 {
421 gchar *progname;
422 char* opt;
423 gint arg;
425 mta_mode mta_mode = MODE_NONE;
426 gboolean do_listen = FALSE;
427 gboolean do_runq = FALSE;
428 gboolean do_runq_online = FALSE;
429 gboolean do_queue = FALSE;
430 gint queue_interval = 0;
431 gchar *M_cmd = NULL;
432 gboolean opt_t = FALSE;
433 gboolean opt_i = FALSE;
434 gchar *conf_file = CONF_FILE;
435 gchar *route_name = NULL;
436 gchar *f_address = NULL;
437 address *return_path = NULL; /* may be changed by -f option */
438 gchar *full_sender_name = NULL;
439 gboolean do_verbose = FALSE;
440 gint debug_level = -1;
442 /* strip the path part */
443 progname = strrchr(argv[0], '/');
444 progname = (progname) ? progname+1 : argv[0];
446 if (strcmp(progname, "mailq") == 0) {
447 mta_mode = MODE_LIST;
448 } else if (strcmp(progname, "mailrm") == 0) {
449 mta_mode = MODE_MCMD;
450 M_cmd = "rm";
451 } else if (strcmp(progname, "runq") == 0) {
452 mta_mode = MODE_RUNQUEUE;
453 do_runq = TRUE;
454 } else if (strcmp(progname, "rmail") == 0) {
455 /* the `rmail' alias should probably be removed now
456 that we have the rmail script. But let's keep it
457 for some while for compatibility. 2010-06-19 */
458 mta_mode = MODE_ACCEPT;
459 opt_i = TRUE;
460 } else if (strcmp(progname, "smtpd") == 0 || strcmp(progname, "in.smtpd") == 0) {
461 mta_mode = MODE_SMTP;
462 }
464 /* parse cmd line */
465 for (arg=1; arg<argc && argv[arg][0]=='-'; arg++) {
466 opt = argv[arg] + 1; /* points to the char after the dash */
468 if (strcmp(opt, "-") == 0) {
469 /* everything after `--' are address arguments */
470 arg++;
471 break;
473 } else if (strcmp(opt, "bd") == 0) {
474 do_listen = TRUE;
475 mta_mode = MODE_DAEMON;
477 } else if (strcmp(opt, "bi") == 0) {
478 /* ignored */
479 mta_mode = MODE_BI;
481 } else if (strcmp(opt, "bs") == 0) {
482 mta_mode = MODE_SMTP;
484 } else if (strcmp(opt, "bp") == 0) {
485 mta_mode = MODE_LIST;
487 } else if (strcmp(opt, "bV") == 0) {
488 mta_mode = MODE_VERSION;
490 } else if (strncmp(opt, "B", 1) == 0) {
491 /* we ignore this and throw the argument away */
492 get_optarg(argv, &arg, opt+1);
494 } else if (strncmp(opt, "C", 1) == 0) {
495 conf_file = get_optarg(argv, &arg, opt+1);
496 if (!conf_file) {
497 fprintf(stderr, "-C requires a filename as argument.\n");
498 exit(1);
499 }
501 } else if (strncmp(opt, "d", 1) == 0) {
502 if (getuid() != 0) {
503 fprintf(stderr, "only root may set the debug level.\n");
504 exit(1);
505 }
506 char *lvl = get_optarg(argv, &arg, opt+1);
507 if (!lvl) {
508 fprintf(stderr, "-d requires a number argument.\n");
509 exit(1);
510 }
511 debug_level = atoi(lvl);
513 } else if (strncmp(opt, "f", 1) == 0) {
514 /* set return path */
515 gchar *address = get_optarg(argv, &arg, opt+1);
516 if (!address) {
517 fprintf(stderr, "-f requires an address argument\n");
518 exit(1);
519 }
520 f_address = g_strdup(address);
522 } else if (strncmp(opt, "F", 1) == 0) {
523 full_sender_name = get_optarg(argv, &arg, opt+1);
524 if (!full_sender_name) {
525 fprintf(stderr, "-F requires a name argument\n");
526 exit(1);
527 }
529 } else if (strcmp(opt, "i") == 0) {
530 opt_i = TRUE;
532 } else if (strcmp(opt, "m") == 0) {
533 /* ignore -m (me too) switch (see man page) */
535 } else if (strcmp(opt, "Mrm") == 0) {
536 mta_mode = MODE_MCMD;
537 M_cmd = "rm";
539 } else if (strcmp(opt, "odq") == 0) {
540 do_queue = TRUE;
542 } else if (strcmp(opt, "oi") == 0) {
543 opt_i = TRUE;
545 } else if (strncmp(opt, "o", 1) == 0) {
546 /* ignore all other -oXXX options */
548 } else if (strncmp(opt, "qo", 2) == 0) {
549 mta_mode = MODE_RUNQUEUE;
550 do_runq = FALSE;
551 do_runq_online = TRUE;
552 /* can be NULL, then we use online detection method */
553 route_name = get_optarg(argv, &arg, opt+2);
555 } else if (strncmp(opt, "q", 1) == 0) {
556 /* must be after the `qo' check */
557 gchar *optarg;
559 do_runq = TRUE;
560 mta_mode = MODE_RUNQUEUE;
561 optarg = get_optarg(argv, &arg, opt+1);
562 if (optarg) {
563 /* not just one single queue run but regular runs */
564 mta_mode = MODE_DAEMON;
565 queue_interval = time_interval(optarg);
566 }
568 } else if (strcmp(opt, "t") == 0) {
569 opt_t = TRUE;
571 } else if (strcmp(opt, "v") == 0) {
572 do_verbose = TRUE;
574 } else {
575 fprintf(stderr, "unrecognized option `-%s'\n", opt);
576 exit(1);
577 }
578 }
580 if (!mta_mode) {
581 mta_mode = (arg<argc || opt_t) ? MODE_ACCEPT : MODE_VERSION;
582 }
584 if (mta_mode == MODE_VERSION) {
585 mode_version();
586 exit(0);
587 }
589 /* initialize random generator */
590 srand(time(NULL));
591 /* ignore SIGPIPE signal */
592 signal(SIGPIPE, SIG_IGN);
594 /* close all possibly open file descriptors, except std{in,out,err} */
595 {
596 int i, max_fd = sysconf(_SC_OPEN_MAX);
598 if (max_fd <= 0) {
599 max_fd = 64;
600 }
601 for (i=3; i<max_fd; i++) {
602 close(i);
603 }
604 }
606 init_conf();
608 /* if we are not privileged, and the config file was changed we
609 implicetely set the the run_as_user flag and give up all
610 privileges.
612 So it is possible for a user to run his own daemon without
613 breaking security.
614 */
615 if ((strcmp(conf_file, CONF_FILE) != 0) && (conf.orig_uid != 0)) {
616 conf.run_as_user = TRUE;
617 seteuid(conf.orig_uid);
618 setegid(conf.orig_gid);
619 setuid(conf.orig_uid);
620 setgid(conf.orig_gid);
621 }
623 conf.log_dir = LOG_DIR;
624 logopen();
625 if (!read_conf(conf_file)) {
626 logwrite(LOG_ALERT, "SHUTTING DOWN due to problems reading config\n");
627 exit(5);
628 }
629 logclose();
631 if (do_queue) {
632 conf.do_queue = TRUE;
633 }
634 if (do_verbose) {
635 conf.do_verbose = TRUE;
636 }
637 if (debug_level >= 0) { /* if >= 0, it was given by argument */
638 conf.debug_level = debug_level;
639 }
641 /* It appears that changing to / ensures that we are never in
642 a directory which we cannot access. This situation could be
643 possible after changing identity.
644 Maybe we should only change to / if we not run as user, to
645 allow relative paths for log files in test setups for
646 instance.
647 */
648 chdir("/");
650 if (!conf.run_as_user) {
651 if (setgid(0) != 0) {
652 fprintf(stderr, "could not set gid to 0. Is the setuid bit set? : %s\n", strerror(errno));
653 exit(1);
654 }
655 if (setuid(0) != 0) {
656 fprintf(stderr, "could not gain root privileges. Is the setuid bit set? : %s\n", strerror(errno));
657 exit(1);
658 }
659 }
661 if (!logopen()) {
662 fprintf(stderr, "could not open log file\n");
663 exit(1);
664 }
666 DEBUG(1) debugf("masqmail %s starting\n", VERSION);
668 DEBUG(5) {
669 gchar **str = argv;
670 debugf("args: \n");
671 while (*str) {
672 debugf("%s \n", *str);
673 str++;
674 }
675 }
676 DEBUG(5) debugf("queue_interval = %d\n", queue_interval);
678 if (f_address) {
679 return_path = create_address_qualified(f_address, TRUE, conf.host_name);
680 g_free(f_address);
681 if (!return_path) {
682 fprintf(stderr, "invalid RFC821 address: %s\n", f_address);
683 exit(1);
684 }
685 }
687 switch (mta_mode) {
688 case MODE_DAEMON:
689 mode_daemon(do_listen, queue_interval, argv);
690 break;
692 case MODE_RUNQUEUE:
693 exit(run_queue(do_runq, do_runq_online, route_name) ? 0 : 1);
694 break;
696 case MODE_SMTP:
697 mode_smtp();
698 break;
700 case MODE_LIST:
701 queue_list();
702 break;
704 case MODE_BI:
705 exit(0);
706 break; /* well... */
708 case MODE_MCMD:
709 exit(manipulate_queue(M_cmd, &argv[arg]) ? 0 : 1);
710 break;
712 case MODE_ACCEPT:
713 {
714 guint accept_flags = (opt_t ? ACC_RCPT_FROM_HEAD : 0)
715 | (opt_i ? ACC_DOT_IGNORE : ACC_NODOT_RELAX);
716 mode_accept(return_path, full_sender_name, accept_flags, &(argv[arg]), argc - arg);
717 exit(0);
718 }
719 break;
721 default:
722 fprintf(stderr, "unknown mode: %d\n", mta_mode);
723 break;
724 }
726 logclose();
728 exit(0);
729 }