masqmail

view src/masqmail.c @ 247:3c40f86d50e4

ignoring -m switch now Some software (e.g. spost of nmh) calls sendmail with -m. Until now, masqmail failed on this unrecognized option. From now on it is ignored. See man/masqmail.8 for the meaning of -m.
author markus schnalke <meillo@marmaro.de>
date Mon, 01 Nov 2010 13:30:02 -0300
parents 3708b655a371
children 018cfd163f5c
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 opt_odb = FALSE;
308 gboolean opt_oem = FALSE;
309 gboolean exit_failure = FALSE;
311 gchar *M_cmd = NULL;
313 gint exit_code = EXIT_SUCCESS;
314 gchar *route_name = NULL;
315 gchar *progname;
316 gchar *f_address = NULL;
317 gchar *full_sender_name = NULL;
318 address *return_path = NULL; /* may be changed by -f option */
320 progname = get_progname(argv[0]);
322 if (strcmp(progname, "mailq") == 0) {
323 mta_mode = MODE_LIST;
324 } else if (strcmp(progname, "mailrm") == 0) {
325 mta_mode = MODE_MCMD;
326 M_cmd = "rm";
327 } else if (strcmp(progname, "runq") == 0) {
328 mta_mode = MODE_RUNQUEUE;
329 do_runq = TRUE;
330 } else if (strcmp(progname, "rmail") == 0) {
331 /* the `rmail' alias should probably be removed now
332 that we have the rmail script. But let's keep it
333 for some while for compatibility. 2010-06-19 */
334 mta_mode = MODE_ACCEPT;
335 opt_i = TRUE;
336 } else if (strcmp(progname, "smtpd") == 0 || strcmp(progname, "in.smtpd") == 0) {
337 mta_mode = MODE_SMTP;
338 }
340 /* parse cmd line */
341 while (arg < argc) {
342 gint pos = 0;
343 if ((argv[arg][pos] == '-') && (argv[arg][pos + 1] != '-')) {
344 pos++;
345 switch (argv[arg][pos++]) {
346 case 'b':
347 switch (argv[arg][pos++]) {
348 case 'd':
349 do_listen = TRUE;
350 mta_mode = MODE_DAEMON;
351 break;
352 case 'i':
353 /* ignored */
354 mta_mode = MODE_BI;
355 break;
356 case 's':
357 mta_mode = MODE_SMTP;
358 break;
359 case 'p':
360 mta_mode = MODE_LIST;
361 break;
362 case 'V':
363 mta_mode = MODE_VERSION;
364 break;
365 default:
366 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
367 exit(EXIT_FAILURE);
368 }
369 break;
370 case 'B':
371 /* we ignore this and throw the argument away */
372 get_optarg(argv, argc, &arg, &pos);
373 break;
374 case 'C':
375 if (!(conf_file = get_optarg(argv, argc, &arg, &pos))) {
376 fprintf(stderr, "-C requires a filename as argument.\n");
377 exit(EXIT_FAILURE);
378 }
379 break;
380 case 'F':
381 {
382 full_sender_name = get_optarg(argv, argc, &arg, &pos);
383 if (!full_sender_name) {
384 fprintf(stderr, "-F requires a name as an argument\n");
385 exit(EXIT_FAILURE);
386 }
387 }
388 break;
389 case 'd':
390 if (getuid() == 0) {
391 char *lvl = get_optarg(argv, argc, &arg, &pos);
392 if (lvl)
393 debug_level = atoi(lvl);
394 else {
395 fprintf(stderr, "-d requires a number as an argument.\n");
396 exit(EXIT_FAILURE);
397 }
398 } else {
399 fprintf(stderr, "only root may set the debug level.\n");
400 exit(EXIT_FAILURE);
401 }
402 break;
403 case 'f':
404 /* set return path */
405 {
406 gchar *address;
407 address = get_optarg(argv, argc, &arg, &pos);
408 if (address) {
409 f_address = g_strdup(address);
410 } else {
411 fprintf(stderr, "-f requires an address as an argument\n");
412 exit(EXIT_FAILURE);
413 }
414 }
415 break;
416 case 'i':
417 if (argv[arg][pos] == 0) {
418 opt_i = TRUE;
419 exit_failure = FALSE; /* may override -oem */
420 } else {
421 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
422 exit(EXIT_FAILURE);
423 }
424 break;
425 case 'M':
426 {
427 mta_mode = MODE_MCMD;
428 M_cmd = g_strdup(&(argv[arg][pos]));
429 }
430 break;
431 case 'm':
432 /* ignore -m (me too) switch (see man page) */
433 break;
434 case 'o':
435 switch (argv[arg][pos++]) {
436 case 'e':
437 if (argv[arg][pos++] == 'm') /* -oem */
438 if (!opt_i)
439 exit_failure = TRUE;
440 opt_oem = TRUE;
441 break;
442 case 'd':
443 if (argv[arg][pos] == 'b') /* -odb */
444 opt_odb = TRUE;
445 else if (argv[arg][pos] == 'q') /* -odq */
446 do_queue = TRUE;
447 break;
448 case 'i':
449 opt_i = TRUE;
450 exit_failure = FALSE; /* may override -oem */
451 break;
452 case 'm':
453 /* ignore -m (me too) switch (see man page) */
454 break;
455 }
456 break;
458 case 'q':
459 {
460 gchar *optarg;
462 do_runq = TRUE;
463 mta_mode = MODE_RUNQUEUE;
464 if (argv[arg][pos] == 'o') {
465 pos++;
466 do_runq = FALSE;
467 do_runq_online = TRUE;
468 /* can be NULL, then we use online detection method */
469 route_name = get_optarg(argv, argc, &arg, &pos);
470 } else
471 if ((optarg = get_optarg(argv, argc, &arg, &pos))) {
472 mta_mode = MODE_DAEMON;
473 queue_interval = time_interval(optarg, &pos);
474 }
475 }
476 break;
477 case 't':
478 if (argv[arg][pos] == 0) {
479 opt_t = TRUE;
480 } else {
481 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
482 exit(EXIT_FAILURE);
483 }
484 break;
485 case 'v':
486 do_verbose = TRUE;
487 break;
488 default:
489 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
490 exit(EXIT_FAILURE);
491 }
492 } else {
493 if (argv[arg][pos + 1] == '-') {
494 if (argv[arg][pos + 2] != '\0') {
495 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
496 exit(EXIT_FAILURE);
497 }
498 arg++;
499 }
500 break;
501 }
502 arg++;
503 }
505 if (mta_mode == MODE_VERSION) {
506 gchar *with_resolver = "";
507 gchar *with_auth = "";
508 gchar *with_ident = "";
510 #ifdef ENABLE_RESOLVER
511 with_resolver = " +resolver";
512 #endif
513 #ifdef ENABLE_AUTH
514 with_auth = " +auth";
515 #endif
516 #ifdef ENABLE_IDENT
517 with_ident = " +ident";
518 #endif
520 printf("%s %s%s%s%s\n", PACKAGE, VERSION, with_resolver, with_auth, with_ident);
522 exit(EXIT_SUCCESS);
523 }
525 /* initialize random generator */
526 srand(time(NULL));
527 /* ignore SIGPIPE signal */
528 signal(SIGPIPE, SIG_IGN);
530 /* close all possibly open file descriptors, except std{in,out,err} */
531 {
532 int i, max_fd = sysconf(_SC_OPEN_MAX);
534 if (max_fd <= 0)
535 max_fd = 64;
536 for (i = 3; i < max_fd; i++)
537 close(i);
538 }
540 init_conf();
542 /* if we are not privileged, and the config file was changed we
543 implicetely set the the run_as_user flag and give up all
544 privileges.
546 So it is possible for a user to run his own daemon without
547 breaking security.
548 */
549 if (strcmp(conf_file, CONF_FILE) != 0) {
550 if (conf.orig_uid != 0) {
551 conf.run_as_user = TRUE;
552 seteuid(conf.orig_uid);
553 setegid(conf.orig_gid);
554 setuid(conf.orig_uid);
555 setgid(conf.orig_gid);
556 }
557 }
559 conf.log_dir = LOG_DIR;
560 logopen();
561 if (!read_conf(conf_file)) {
562 logwrite(LOG_ALERT, "SHUTTING DOWN due to problems reading config\n");
563 exit(5);
564 }
565 logclose();
567 if (do_queue)
568 conf.do_queue = TRUE;
569 if (do_verbose)
570 conf.do_verbose = TRUE;
571 if (debug_level >= 0) /* if >= 0, it was given by argument */
572 conf.debug_level = debug_level;
574 /* It appears that changing to / ensures that we are never in
575 a directory which we cannot access. This situation could be
576 possible after changing identity.
577 Maybe we should only change to / if we not run as user, to
578 allow relative paths for log files in test setups for
579 instance.
580 */
581 chdir("/");
583 if (!conf.run_as_user) {
584 if (setgid(0) != 0) {
585 fprintf(stderr, "could not set gid to 0. Is the setuid bit set? : %s\n", strerror(errno));
586 exit(EXIT_FAILURE);
587 }
588 if (setuid(0) != 0) {
589 fprintf(stderr, "could not gain root privileges. Is the setuid bit set? : %s\n", strerror(errno));
590 exit(EXIT_FAILURE);
591 }
592 }
594 if (!logopen()) {
595 fprintf(stderr, "could not open log file\n");
596 exit(EXIT_FAILURE);
597 }
599 DEBUG(1) debugf("masqmail %s starting\n", VERSION);
601 DEBUG(5) {
602 gchar **str = argv;
603 debugf("args: \n");
604 while (*str) {
605 debugf("%s \n", *str);
606 str++;
607 }
608 }
609 DEBUG(5) debugf("queue_interval = %d\n", queue_interval);
611 if (f_address) {
612 return_path = create_address_qualified(f_address, TRUE, conf.host_name);
613 g_free(f_address);
614 if (!return_path) {
615 fprintf(stderr, "invalid RFC821 address: %s\n", f_address);
616 exit(EXIT_FAILURE);
617 }
618 }
620 switch (mta_mode) {
621 case MODE_DAEMON:
622 mode_daemon(do_listen, queue_interval, argv);
623 break;
624 case MODE_RUNQUEUE:
625 {
626 /* queue runs */
627 set_identity(conf.orig_uid, "queue run");
629 if (do_runq)
630 exit_code = queue_run() ? EXIT_SUCCESS : EXIT_FAILURE;
632 if (do_runq_online) {
633 if (route_name != NULL) {
634 conf.online_detect = g_strdup("argument");
635 set_online_name(route_name);
636 }
637 exit_code =
638 queue_run_online() ? EXIT_SUCCESS : EXIT_FAILURE;
639 }
640 }
641 break;
643 case MODE_SMTP:
644 mode_smtp();
645 break;
647 case MODE_LIST:
648 queue_list();
649 break;
651 case MODE_BI:
652 exit(EXIT_SUCCESS);
653 break; /* well... */
655 case MODE_MCMD:
656 if (strcmp(M_cmd, "rm") == 0) {
657 gboolean ok = FALSE;
659 set_euidgid(conf.mail_uid, conf.mail_gid, NULL, NULL);
661 if (is_privileged_user(conf.orig_uid)) {
662 for (; arg < argc; arg++) {
663 if (queue_delete(argv[arg]))
664 ok = TRUE;
665 }
666 } else {
667 struct passwd *pw = getpwuid(conf.orig_uid);
668 if (pw) {
669 for (; arg < argc; arg++) {
670 message *msg = msg_spool_read(argv[arg], FALSE);
671 #ifdef ENABLE_IDENT
672 if (((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL))
673 || is_in_netlist(msg->received_host, conf.ident_trusted_nets)) {
674 #else
675 if ((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL)) {
676 #endif
677 if (msg->ident) {
678 if (strcmp(pw->pw_name, msg->ident) == 0) {
679 if (queue_delete(argv[arg]))
680 ok = TRUE;
681 } else {
682 fprintf(stderr, "you do not own message id %s\n", argv[arg]);
683 }
684 } else
685 fprintf(stderr, "message %s does not have an ident.\n", argv[arg]);
686 } else {
687 fprintf(stderr, "message %s was not received locally or from a trusted network.\n", argv[arg]);
688 }
689 }
690 } else {
691 fprintf(stderr, "could not find a passwd entry for uid %d: %s\n", conf.orig_uid, strerror(errno));
692 }
693 }
694 exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
695 } else {
696 fprintf(stderr, "unknown command %s\n", M_cmd);
697 exit(EXIT_FAILURE);
698 }
699 break;
701 case MODE_ACCEPT:
702 {
703 guint accept_flags = (opt_t ? ACC_DEL_RCPTS | ACC_RCPT_FROM_HEAD : 0)
704 | (opt_i ? ACC_DOT_IGNORE : ACC_NODOT_RELAX);
705 mode_accept(return_path, full_sender_name, accept_flags, &(argv[arg]), argc - arg);
706 exit(exit_failure ? EXIT_FAILURE : EXIT_SUCCESS);
707 }
708 break;
709 case MODE_NONE:
710 break;
711 default:
712 fprintf(stderr, "unknown mode: %d\n", mta_mode);
713 break;
714 }
716 logclose();
718 exit(exit_code);
719 }