masqmail
view src/masqmail.c @ 197:50011f65bd35
added comment: TODO file may not be up-to-date
there is some stuff in it that is not wanted any longer
there are goals not included in it
author | meillo@marmaro.de |
---|---|
date | Fri, 16 Jul 2010 10:06:32 +0200 |
parents | 8630e37ae445 |
children | 5745edd5b769 |
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!");
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", PACKAGE, VERSION);
177 listen_port(do_listen ? conf.listen_addresses : NULL, queue_interval, argv);
178 }
180 #ifdef ENABLE_SMTP_SERVER
181 static void
182 mode_smtp()
183 {
184 /* accept smtp message on stdin */
185 /* write responses to stderr. */
187 struct sockaddr_in saddr;
188 gchar *peername = NULL;
189 int dummy = sizeof(saddr);
191 conf.do_verbose = FALSE;
193 if (!conf.run_as_user) {
194 seteuid(conf.orig_uid);
195 setegid(conf.orig_gid);
196 }
198 DEBUG(5) debugf("accepting smtp message on stdin\n");
200 if (getpeername(0, (struct sockaddr *) (&saddr), &dummy) == 0) {
201 peername = g_strdup(inet_ntoa(saddr.sin_addr));
202 } else if (errno != ENOTSOCK)
203 exit(EXIT_FAILURE);
205 smtp_in(stdin, stderr, peername, NULL);
206 }
207 #endif
209 static void
210 mode_accept(address * return_path, gchar * full_sender_name, guint accept_flags, char **addresses, int addr_cnt)
211 {
212 /* accept message on stdin */
213 accept_error err;
214 message *msg = create_message();
215 gint i;
217 if (return_path && !is_privileged_user(conf.orig_uid)) {
218 fprintf(stderr, "must be root, %s or in group %s for setting return path.\n", DEF_MAIL_USER, DEF_MAIL_GROUP);
219 exit(EXIT_FAILURE);
220 }
222 if (!conf.run_as_user) {
223 seteuid(conf.orig_uid);
224 setegid(conf.orig_gid);
225 }
227 DEBUG(5) debugf("accepting message on stdin\n");
229 msg->received_prot = PROT_LOCAL;
230 for (i = 0; i < addr_cnt; i++) {
231 if (addresses[i][0] != '|')
232 msg->rcpt_list = g_list_append(msg->rcpt_list, create_address_qualified(addresses[i], TRUE, conf.host_name));
233 else {
234 logwrite(LOG_ALERT, "no pipe allowed as recipient address: %s\n", addresses[i]);
235 exit(EXIT_FAILURE);
236 }
237 }
239 /* -f option */
240 msg->return_path = return_path;
242 /* -F option */
243 msg->full_sender_name = full_sender_name;
245 if ((err = accept_message(stdin, msg, accept_flags)) == AERR_OK) {
246 if (spool_write(msg, TRUE)) {
247 pid_t pid;
248 logwrite(LOG_NOTICE, "%s <= %s with %s\n", msg->uid, addr_string(msg->return_path), prot_names[PROT_LOCAL]);
250 if (!conf.do_queue) {
251 if ((pid = fork()) == 0) {
252 conf.do_verbose = FALSE;
253 fclose(stdin);
254 fclose(stdout);
255 fclose(stderr);
256 if (deliver(msg)) {
257 exit(EXIT_SUCCESS);
258 } else
259 exit(EXIT_FAILURE);
260 } else if (pid < 0) {
261 logwrite(LOG_ALERT, "could not fork for delivery, id = %s", msg->uid);
262 }
263 }
264 } else {
265 fprintf(stderr, "Could not write spool file\n");
266 exit(EXIT_FAILURE);
267 }
268 } else {
269 switch (err) {
270 case AERR_EOF:
271 fprintf(stderr, "unexpected EOF.\n");
272 exit(EXIT_FAILURE);
273 case AERR_NORCPT:
274 fprintf(stderr, "no recipients.\n");
275 exit(EXIT_FAILURE);
276 case AERR_SIZE:
277 fprintf(stderr, "max message size exceeded.\n");
278 exit(EXIT_FAILURE);
279 default:
280 /* should never happen: */
281 fprintf(stderr, "Unknown error (%d)\r\n", err);
282 exit(EXIT_FAILURE);
283 }
284 exit(EXIT_FAILURE);
285 }
286 }
288 int
289 main(int argc, char *argv[])
290 {
291 /* cmd line flags */
292 gchar *conf_file = CONF_FILE;
293 gint arg = 1;
295 gboolean do_listen = FALSE;
296 gboolean do_runq = FALSE;
297 gboolean do_runq_online = FALSE;
299 gboolean do_queue = FALSE;
301 gboolean do_verbose = FALSE;
302 gint debug_level = -1;
304 mta_mode mta_mode = MODE_ACCEPT;
306 gint queue_interval = 0;
307 gboolean opt_t = FALSE;
308 gboolean opt_i = FALSE;
309 gboolean opt_odb = FALSE;
310 gboolean opt_oem = FALSE;
311 gboolean exit_failure = FALSE;
313 gchar *M_cmd = NULL;
315 gint exit_code = EXIT_SUCCESS;
316 gchar *route_name = NULL;
317 gchar *progname;
318 gchar *f_address = NULL;
319 gchar *full_sender_name = NULL;
320 address *return_path = NULL; /* may be changed by -f option */
322 progname = get_progname(argv[0]);
324 if (strcmp(progname, "mailq") == 0) {
325 mta_mode = MODE_LIST;
326 } else if (strcmp(progname, "mailrm") == 0) {
327 mta_mode = MODE_MCMD;
328 M_cmd = "rm";
329 } else if (strcmp(progname, "runq") == 0) {
330 mta_mode = MODE_RUNQUEUE;
331 do_runq = TRUE;
332 } else if (strcmp(progname, "rmail") == 0) {
333 /* the `rmail' alias should probably be removed now
334 that we have the rmail script. But let's keep it
335 for some while for compatibility. 2010-06-19 */
336 mta_mode = MODE_ACCEPT;
337 opt_i = TRUE;
338 } else if (strcmp(progname, "smtpd") == 0 || strcmp(progname, "in.smtpd") == 0) {
339 mta_mode = MODE_SMTP;
340 }
342 /* parse cmd line */
343 while (arg < argc) {
344 gint pos = 0;
345 if ((argv[arg][pos] == '-') && (argv[arg][pos + 1] != '-')) {
346 pos++;
347 switch (argv[arg][pos++]) {
348 case 'b':
349 switch (argv[arg][pos++]) {
350 case 'd':
351 do_listen = TRUE;
352 mta_mode = MODE_DAEMON;
353 break;
354 case 'i':
355 /* ignored */
356 mta_mode = MODE_BI;
357 break;
358 case 's':
359 mta_mode = MODE_SMTP;
360 break;
361 case 'p':
362 mta_mode = MODE_LIST;
363 break;
364 case 'V':
365 mta_mode = MODE_VERSION;
366 break;
367 default:
368 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
369 exit(EXIT_FAILURE);
370 }
371 break;
372 case 'B':
373 /* we ignore this and throw the argument away */
374 get_optarg(argv, argc, &arg, &pos);
375 break;
376 case 'C':
377 if (!(conf_file = get_optarg(argv, argc, &arg, &pos))) {
378 fprintf(stderr, "-C requires a filename as argument.\n");
379 exit(EXIT_FAILURE);
380 }
381 break;
382 case 'F':
383 {
384 full_sender_name = get_optarg(argv, argc, &arg, &pos);
385 if (!full_sender_name) {
386 fprintf(stderr, "-F requires a name as an argument\n");
387 exit(EXIT_FAILURE);
388 }
389 }
390 break;
391 case 'd':
392 if (getuid() == 0) {
393 char *lvl = get_optarg(argv, argc, &arg, &pos);
394 if (lvl)
395 debug_level = atoi(lvl);
396 else {
397 fprintf(stderr, "-d requires a number as an argument.\n");
398 exit(EXIT_FAILURE);
399 }
400 } else {
401 fprintf(stderr, "only root may set the debug level.\n");
402 exit(EXIT_FAILURE);
403 }
404 break;
405 case 'f':
406 /* set return path */
407 {
408 gchar *address;
409 address = get_optarg(argv, argc, &arg, &pos);
410 if (address) {
411 f_address = g_strdup(address);
412 } else {
413 fprintf(stderr, "-f requires an address as an argument\n");
414 exit(EXIT_FAILURE);
415 }
416 }
417 break;
418 case 'i':
419 if (argv[arg][pos] == 0) {
420 opt_i = TRUE;
421 exit_failure = FALSE; /* may override -oem */
422 } else {
423 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
424 exit(EXIT_FAILURE);
425 }
426 break;
427 case 'M':
428 {
429 mta_mode = MODE_MCMD;
430 M_cmd = g_strdup(&(argv[arg][pos]));
431 }
432 break;
433 case 'o':
434 switch (argv[arg][pos++]) {
435 case 'e':
436 if (argv[arg][pos++] == 'm') /* -oem */
437 if (!opt_i)
438 exit_failure = TRUE;
439 opt_oem = TRUE;
440 break;
441 case 'd':
442 if (argv[arg][pos] == 'b') /* -odb */
443 opt_odb = TRUE;
444 else if (argv[arg][pos] == 'q') /* -odq */
445 do_queue = TRUE;
446 break;
447 case 'i':
448 opt_i = TRUE;
449 exit_failure = FALSE; /* may override -oem */
450 break;
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_smtp_server = "";
504 gchar *with_auth = "";
505 gchar *with_maildir = "";
506 gchar *with_ident = "";
508 #ifdef ENABLE_RESOLVER
509 with_resolver = " +resolver";
510 #endif
511 #ifdef ENABLE_SMTP_SERVER
512 with_smtp_server = " +smtp-server";
513 #endif
514 #ifdef ENABLE_AUTH
515 with_auth = " +auth";
516 #endif
517 #ifdef ENABLE_MAILDIR
518 with_maildir = " +maildir";
519 #endif
520 #ifdef ENABLE_IDENT
521 with_ident = " +ident";
522 #endif
524 printf("%s %s%s%s%s%s%s\n", PACKAGE, VERSION, with_resolver, with_smtp_server,
525 with_auth, with_maildir, with_ident);
527 exit(EXIT_SUCCESS);
528 }
530 /* initialize random generator */
531 srand(time(NULL));
532 /* ignore SIGPIPE signal */
533 signal(SIGPIPE, SIG_IGN);
535 /* close all possibly open file descriptors, except std{in,out,err} */
536 {
537 int i, max_fd = sysconf(_SC_OPEN_MAX);
539 if (max_fd <= 0)
540 max_fd = 64;
541 for (i = 3; i < max_fd; i++)
542 close(i);
543 }
545 init_conf();
547 /* if we are not privileged, and the config file was changed we
548 implicetely set the the run_as_user flag and give up all
549 privileges.
551 So it is possible for a user to run his own daemon without
552 breaking security.
553 */
554 if (strcmp(conf_file, CONF_FILE) != 0) {
555 if (conf.orig_uid != 0) {
556 conf.run_as_user = TRUE;
557 seteuid(conf.orig_uid);
558 setegid(conf.orig_gid);
559 setuid(conf.orig_uid);
560 setgid(conf.orig_gid);
561 }
562 }
564 conf.log_dir = LOG_DIR;
565 logopen();
566 if (!read_conf(conf_file)) {
567 logwrite(LOG_ALERT, "SHUTTING DOWN due to problems reading config\n");
568 exit(5);
569 }
570 logclose();
572 if (do_queue)
573 conf.do_queue = TRUE;
574 if (do_verbose)
575 conf.do_verbose = TRUE;
576 if (debug_level >= 0) /* if >= 0, it was given by argument */
577 conf.debug_level = debug_level;
579 /* It appears that changing to / ensures that we are never in
580 a directory which we cannot access. This situation could be
581 possible after changing identity.
582 Maybe we should only change to / if we not run as user, to
583 allow relative paths for log files in test setups for
584 instance.
585 */
586 chdir("/");
588 if (!conf.run_as_user) {
589 if (setgid(0) != 0) {
590 fprintf(stderr, "could not set gid to 0. Is the setuid bit set? : %s\n", strerror(errno));
591 exit(EXIT_FAILURE);
592 }
593 if (setuid(0) != 0) {
594 fprintf(stderr, "could not gain root privileges. Is the setuid bit set? : %s\n", strerror(errno));
595 exit(EXIT_FAILURE);
596 }
597 }
599 if (!logopen()) {
600 fprintf(stderr, "could not open log file\n");
601 exit(EXIT_FAILURE);
602 }
604 DEBUG(1) debugf("masqmail %s starting\n", VERSION);
606 DEBUG(5) {
607 gchar **str = argv;
608 debugf("args: \n");
609 while (*str) {
610 debugf("%s \n", *str);
611 str++;
612 }
613 }
614 DEBUG(5) debugf("queue_interval = %d\n", queue_interval);
616 if (f_address) {
617 return_path = create_address_qualified(f_address, TRUE, conf.host_name);
618 g_free(f_address);
619 if (!return_path) {
620 fprintf(stderr, "invalid RFC821 address: %s\n", f_address);
621 exit(EXIT_FAILURE);
622 }
623 }
625 switch (mta_mode) {
626 case MODE_DAEMON:
627 mode_daemon(do_listen, queue_interval, argv);
628 break;
629 case MODE_RUNQUEUE:
630 {
631 /* queue runs */
632 set_identity(conf.orig_uid, "queue run");
634 if (do_runq)
635 exit_code = queue_run() ? EXIT_SUCCESS : EXIT_FAILURE;
637 if (do_runq_online) {
638 if (route_name != NULL) {
639 conf.online_detect = g_strdup("argument");
640 set_online_name(route_name);
641 }
642 exit_code =
643 queue_run_online() ? EXIT_SUCCESS : EXIT_FAILURE;
644 }
645 }
646 break;
648 case MODE_SMTP:
649 #ifdef ENABLE_SMTP_SERVER
650 mode_smtp();
651 #else
652 fprintf(stderr, "smtp server support not compiled in\n");
653 #endif
654 break;
656 case MODE_LIST:
657 queue_list();
658 break;
660 case MODE_BI:
661 exit(EXIT_SUCCESS);
662 break; /* well... */
664 case MODE_MCMD:
665 if (strcmp(M_cmd, "rm") == 0) {
666 gboolean ok = FALSE;
668 set_euidgid(conf.mail_uid, conf.mail_gid, NULL, NULL);
670 if (is_privileged_user(conf.orig_uid)) {
671 for (; arg < argc; arg++) {
672 if (queue_delete(argv[arg]))
673 ok = TRUE;
674 }
675 } else {
676 struct passwd *pw = getpwuid(conf.orig_uid);
677 if (pw) {
678 for (; arg < argc; arg++) {
679 message *msg = msg_spool_read(argv[arg], FALSE);
680 #ifdef ENABLE_IDENT
681 if (((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL))
682 || is_in_netlist(msg->received_host, conf.ident_trusted_nets)) {
683 #else
684 if ((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL)) {
685 #endif
686 if (msg->ident) {
687 if (strcmp(pw->pw_name, msg->ident) == 0) {
688 if (queue_delete(argv[arg]))
689 ok = TRUE;
690 } else {
691 fprintf(stderr, "you do not own message id %s\n", argv[arg]);
692 }
693 } else
694 fprintf(stderr, "message %s does not have an ident.\n", argv[arg]);
695 } else {
696 fprintf(stderr, "message %s was not received locally or from a trusted network.\n", argv[arg]);
697 }
698 }
699 } else {
700 fprintf(stderr, "could not find a passwd entry for uid %d: %s\n", conf.orig_uid, strerror(errno));
701 }
702 }
703 exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
704 } else {
705 fprintf(stderr, "unknown command %s\n", M_cmd);
706 exit(EXIT_FAILURE);
707 }
708 break;
710 case MODE_ACCEPT:
711 {
712 guint accept_flags = (opt_t ? ACC_DEL_RCPTS | ACC_RCPT_FROM_HEAD : 0)
713 | (opt_i ? ACC_DOT_IGNORE : ACC_NODOT_RELAX);
714 mode_accept(return_path, full_sender_name, accept_flags, &(argv[arg]), argc - arg);
715 exit(exit_failure ? EXIT_FAILURE : EXIT_SUCCESS);
716 }
717 break;
718 case MODE_NONE:
719 break;
720 default:
721 fprintf(stderr, "unknown mode: %d\n", mta_mode);
722 break;
723 }
725 logclose();
727 exit(exit_code);
728 }