masqmail
view src/masqmail.c @ 227:cab46cefa4ce
renamed contrib/ to admin/
because the contents are for system admins
and possibly for advanced users too
author | meillo@marmaro.de |
---|---|
date | Fri, 23 Jul 2010 11:49:44 +0200 |
parents | 4fd237550525 |
children | 3c40f86d50e4 |
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 'o':
432 switch (argv[arg][pos++]) {
433 case 'e':
434 if (argv[arg][pos++] == 'm') /* -oem */
435 if (!opt_i)
436 exit_failure = TRUE;
437 opt_oem = TRUE;
438 break;
439 case 'd':
440 if (argv[arg][pos] == 'b') /* -odb */
441 opt_odb = TRUE;
442 else if (argv[arg][pos] == 'q') /* -odq */
443 do_queue = TRUE;
444 break;
445 case 'i':
446 opt_i = TRUE;
447 exit_failure = FALSE; /* may override -oem */
448 break;
449 }
450 break;
452 case 'q':
453 {
454 gchar *optarg;
456 do_runq = TRUE;
457 mta_mode = MODE_RUNQUEUE;
458 if (argv[arg][pos] == 'o') {
459 pos++;
460 do_runq = FALSE;
461 do_runq_online = TRUE;
462 /* can be NULL, then we use online detection method */
463 route_name = get_optarg(argv, argc, &arg, &pos);
464 } else
465 if ((optarg = get_optarg(argv, argc, &arg, &pos))) {
466 mta_mode = MODE_DAEMON;
467 queue_interval = time_interval(optarg, &pos);
468 }
469 }
470 break;
471 case 't':
472 if (argv[arg][pos] == 0) {
473 opt_t = TRUE;
474 } else {
475 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
476 exit(EXIT_FAILURE);
477 }
478 break;
479 case 'v':
480 do_verbose = TRUE;
481 break;
482 default:
483 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
484 exit(EXIT_FAILURE);
485 }
486 } else {
487 if (argv[arg][pos + 1] == '-') {
488 if (argv[arg][pos + 2] != '\0') {
489 fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
490 exit(EXIT_FAILURE);
491 }
492 arg++;
493 }
494 break;
495 }
496 arg++;
497 }
499 if (mta_mode == MODE_VERSION) {
500 gchar *with_resolver = "";
501 gchar *with_auth = "";
502 gchar *with_ident = "";
504 #ifdef ENABLE_RESOLVER
505 with_resolver = " +resolver";
506 #endif
507 #ifdef ENABLE_AUTH
508 with_auth = " +auth";
509 #endif
510 #ifdef ENABLE_IDENT
511 with_ident = " +ident";
512 #endif
514 printf("%s %s%s%s%s\n", PACKAGE, VERSION, with_resolver, with_auth, with_ident);
516 exit(EXIT_SUCCESS);
517 }
519 /* initialize random generator */
520 srand(time(NULL));
521 /* ignore SIGPIPE signal */
522 signal(SIGPIPE, SIG_IGN);
524 /* close all possibly open file descriptors, except std{in,out,err} */
525 {
526 int i, max_fd = sysconf(_SC_OPEN_MAX);
528 if (max_fd <= 0)
529 max_fd = 64;
530 for (i = 3; i < max_fd; i++)
531 close(i);
532 }
534 init_conf();
536 /* if we are not privileged, and the config file was changed we
537 implicetely set the the run_as_user flag and give up all
538 privileges.
540 So it is possible for a user to run his own daemon without
541 breaking security.
542 */
543 if (strcmp(conf_file, CONF_FILE) != 0) {
544 if (conf.orig_uid != 0) {
545 conf.run_as_user = TRUE;
546 seteuid(conf.orig_uid);
547 setegid(conf.orig_gid);
548 setuid(conf.orig_uid);
549 setgid(conf.orig_gid);
550 }
551 }
553 conf.log_dir = LOG_DIR;
554 logopen();
555 if (!read_conf(conf_file)) {
556 logwrite(LOG_ALERT, "SHUTTING DOWN due to problems reading config\n");
557 exit(5);
558 }
559 logclose();
561 if (do_queue)
562 conf.do_queue = TRUE;
563 if (do_verbose)
564 conf.do_verbose = TRUE;
565 if (debug_level >= 0) /* if >= 0, it was given by argument */
566 conf.debug_level = debug_level;
568 /* It appears that changing to / ensures that we are never in
569 a directory which we cannot access. This situation could be
570 possible after changing identity.
571 Maybe we should only change to / if we not run as user, to
572 allow relative paths for log files in test setups for
573 instance.
574 */
575 chdir("/");
577 if (!conf.run_as_user) {
578 if (setgid(0) != 0) {
579 fprintf(stderr, "could not set gid to 0. Is the setuid bit set? : %s\n", strerror(errno));
580 exit(EXIT_FAILURE);
581 }
582 if (setuid(0) != 0) {
583 fprintf(stderr, "could not gain root privileges. Is the setuid bit set? : %s\n", strerror(errno));
584 exit(EXIT_FAILURE);
585 }
586 }
588 if (!logopen()) {
589 fprintf(stderr, "could not open log file\n");
590 exit(EXIT_FAILURE);
591 }
593 DEBUG(1) debugf("masqmail %s starting\n", VERSION);
595 DEBUG(5) {
596 gchar **str = argv;
597 debugf("args: \n");
598 while (*str) {
599 debugf("%s \n", *str);
600 str++;
601 }
602 }
603 DEBUG(5) debugf("queue_interval = %d\n", queue_interval);
605 if (f_address) {
606 return_path = create_address_qualified(f_address, TRUE, conf.host_name);
607 g_free(f_address);
608 if (!return_path) {
609 fprintf(stderr, "invalid RFC821 address: %s\n", f_address);
610 exit(EXIT_FAILURE);
611 }
612 }
614 switch (mta_mode) {
615 case MODE_DAEMON:
616 mode_daemon(do_listen, queue_interval, argv);
617 break;
618 case MODE_RUNQUEUE:
619 {
620 /* queue runs */
621 set_identity(conf.orig_uid, "queue run");
623 if (do_runq)
624 exit_code = queue_run() ? EXIT_SUCCESS : EXIT_FAILURE;
626 if (do_runq_online) {
627 if (route_name != NULL) {
628 conf.online_detect = g_strdup("argument");
629 set_online_name(route_name);
630 }
631 exit_code =
632 queue_run_online() ? EXIT_SUCCESS : EXIT_FAILURE;
633 }
634 }
635 break;
637 case MODE_SMTP:
638 mode_smtp();
639 break;
641 case MODE_LIST:
642 queue_list();
643 break;
645 case MODE_BI:
646 exit(EXIT_SUCCESS);
647 break; /* well... */
649 case MODE_MCMD:
650 if (strcmp(M_cmd, "rm") == 0) {
651 gboolean ok = FALSE;
653 set_euidgid(conf.mail_uid, conf.mail_gid, NULL, NULL);
655 if (is_privileged_user(conf.orig_uid)) {
656 for (; arg < argc; arg++) {
657 if (queue_delete(argv[arg]))
658 ok = TRUE;
659 }
660 } else {
661 struct passwd *pw = getpwuid(conf.orig_uid);
662 if (pw) {
663 for (; arg < argc; arg++) {
664 message *msg = msg_spool_read(argv[arg], FALSE);
665 #ifdef ENABLE_IDENT
666 if (((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL))
667 || is_in_netlist(msg->received_host, conf.ident_trusted_nets)) {
668 #else
669 if ((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL)) {
670 #endif
671 if (msg->ident) {
672 if (strcmp(pw->pw_name, msg->ident) == 0) {
673 if (queue_delete(argv[arg]))
674 ok = TRUE;
675 } else {
676 fprintf(stderr, "you do not own message id %s\n", argv[arg]);
677 }
678 } else
679 fprintf(stderr, "message %s does not have an ident.\n", argv[arg]);
680 } else {
681 fprintf(stderr, "message %s was not received locally or from a trusted network.\n", argv[arg]);
682 }
683 }
684 } else {
685 fprintf(stderr, "could not find a passwd entry for uid %d: %s\n", conf.orig_uid, strerror(errno));
686 }
687 }
688 exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
689 } else {
690 fprintf(stderr, "unknown command %s\n", M_cmd);
691 exit(EXIT_FAILURE);
692 }
693 break;
695 case MODE_ACCEPT:
696 {
697 guint accept_flags = (opt_t ? ACC_DEL_RCPTS | ACC_RCPT_FROM_HEAD : 0)
698 | (opt_i ? ACC_DOT_IGNORE : ACC_NODOT_RELAX);
699 mode_accept(return_path, full_sender_name, accept_flags, &(argv[arg]), argc - arg);
700 exit(exit_failure ? EXIT_FAILURE : EXIT_SUCCESS);
701 }
702 break;
703 case MODE_NONE:
704 break;
705 default:
706 fprintf(stderr, "unknown mode: %d\n", mta_mode);
707 break;
708 }
710 logclose();
712 exit(exit_code);
713 }