masqmail
view src/masqmail.c @ 249:f9da5a7caeda
refactored the cmdline argument processing
I replaced the nested switch statements with one single
large else-if construct. Instead of char comparision now
str(n)cmp(3) is used. Although this is slower it is much
more readable and covers corner-cases which were uncovered
before (e.g. -bdxxx).
As always: Readability and simplicity matter, not performance.
author | markus schnalke <meillo@marmaro.de> |
---|---|
date | Thu, 04 Nov 2010 11:02:42 -0300 |
parents | 018cfd163f5c |
children | a41c013c8458 |
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 /*
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 gchar*
118 get_progname(gchar * arg0)
119 {
120 gchar *p = arg0 + strlen(arg0) - 1;
121 while (p > arg0) {
122 if (*p == '/')
123 return p + 1;
124 p--;
125 }
126 return p;
127 }
129 gboolean
130 write_pidfile(gchar * name)
131 {
132 FILE *fptr;
134 if ((fptr = fopen(name, "wt"))) {
135 fprintf(fptr, "%d\n", getpid());
136 fclose(fptr);
137 pidfile = strdup(name);
138 return TRUE;
139 }
140 logwrite(LOG_WARNING, "could not write pid file: %s\n", strerror(errno));
141 return FALSE;
142 }
144 static void
145 mode_daemon(gboolean do_listen, gint queue_interval, char *argv[])
146 {
147 guint pid;
149 /* daemon */
150 if (!conf.run_as_user) {
151 if ((conf.orig_uid != 0) && (conf.orig_uid != conf.mail_uid)) {
152 fprintf(stderr, "must be root or %s for daemon.\n", DEF_MAIL_USER);
153 exit(EXIT_FAILURE);
154 }
155 }
157 /* reparent to init only if init is not already the parent */
158 if (getppid() != 1) {
159 if ((pid = fork()) > 0) {
160 exit(EXIT_SUCCESS);
161 } else if (pid < 0) {
162 logwrite(LOG_ALERT, "could not fork!\n");
163 exit(EXIT_FAILURE);
164 }
165 }
167 signal(SIGTERM, sigterm_handler);
168 write_pidfile(PIDFILEDIR "/masqmail.pid");
170 conf.do_verbose = FALSE;
172 /* closing and reopening the log ensures that it is open afterwards
173 because it is possible that the log is assigned to fd 1 and gets
174 thus closes by fclose(stdout). Similar for the debugfile.
175 */
176 logclose();
177 fclose(stdin);
178 fclose(stdout);
179 fclose(stderr);
180 logopen();
182 logwrite(LOG_NOTICE, "%s %s daemon starting\n", PACKAGE, VERSION);
183 listen_port(do_listen ? conf.listen_addresses : NULL, queue_interval, argv);
184 }
186 static void
187 mode_smtp()
188 {
189 /* accept smtp message on stdin */
190 /* write responses to stderr. */
192 struct sockaddr_in saddr;
193 gchar *peername = NULL;
194 int dummy = sizeof(saddr);
196 conf.do_verbose = FALSE;
198 if (!conf.run_as_user) {
199 seteuid(conf.orig_uid);
200 setegid(conf.orig_gid);
201 }
203 DEBUG(5) debugf("accepting smtp message on stdin\n");
205 if (getpeername(0, (struct sockaddr *) (&saddr), &dummy) == 0) {
206 peername = g_strdup(inet_ntoa(saddr.sin_addr));
207 } else if (errno != ENOTSOCK)
208 exit(EXIT_FAILURE);
210 smtp_in(stdin, stderr, peername, NULL);
211 }
213 static void
214 mode_accept(address * return_path, gchar * full_sender_name, guint accept_flags, char **addresses, int addr_cnt)
215 {
216 /* accept message on stdin */
217 accept_error err;
218 message *msg = create_message();
219 gint i;
221 if (return_path && !is_privileged_user(conf.orig_uid)) {
222 fprintf(stderr, "must be root, %s or in group %s for setting return path.\n", DEF_MAIL_USER, DEF_MAIL_GROUP);
223 exit(EXIT_FAILURE);
224 }
226 if (!conf.run_as_user) {
227 seteuid(conf.orig_uid);
228 setegid(conf.orig_gid);
229 }
231 DEBUG(5) debugf("accepting message on stdin\n");
233 msg->received_prot = PROT_LOCAL;
234 for (i = 0; i < addr_cnt; i++) {
235 if (addresses[i][0] != '|')
236 msg->rcpt_list = g_list_append(msg->rcpt_list, create_address_qualified(addresses[i], TRUE, conf.host_name));
237 else {
238 logwrite(LOG_ALERT, "no pipe allowed as recipient address: %s\n", addresses[i]);
239 exit(EXIT_FAILURE);
240 }
241 }
243 /* -f option */
244 msg->return_path = return_path;
246 /* -F option */
247 msg->full_sender_name = full_sender_name;
249 if ((err = accept_message(stdin, msg, accept_flags)) == AERR_OK) {
250 if (spool_write(msg, TRUE)) {
251 pid_t pid;
252 logwrite(LOG_NOTICE, "%s <= %s with %s\n", msg->uid, addr_string(msg->return_path), prot_names[PROT_LOCAL]);
254 if (!conf.do_queue) {
255 if ((pid = fork()) == 0) {
256 conf.do_verbose = FALSE;
257 fclose(stdin);
258 fclose(stdout);
259 fclose(stderr);
260 if (deliver(msg)) {
261 exit(EXIT_SUCCESS);
262 } else
263 exit(EXIT_FAILURE);
264 } else if (pid < 0) {
265 logwrite(LOG_ALERT, "could not fork for delivery, id = %s\n", msg->uid);
266 }
267 }
268 } else {
269 fprintf(stderr, "Could not write spool file\n");
270 exit(EXIT_FAILURE);
271 }
272 } else {
273 switch (err) {
274 case AERR_EOF:
275 fprintf(stderr, "unexpected EOF.\n");
276 exit(EXIT_FAILURE);
277 case AERR_NORCPT:
278 fprintf(stderr, "no recipients.\n");
279 exit(EXIT_FAILURE);
280 case AERR_SIZE:
281 fprintf(stderr, "max message size exceeded.\n");
282 exit(EXIT_FAILURE);
283 default:
284 /* should never happen: */
285 fprintf(stderr, "Unknown error (%d)\r\n", err);
286 exit(EXIT_FAILURE);
287 }
288 exit(EXIT_FAILURE);
289 }
290 }
292 int
293 main(int argc, char *argv[])
294 {
295 /* cmd line flags */
296 gchar *conf_file = CONF_FILE;
297 char* opt;
298 gint arg;
300 gboolean do_listen = FALSE;
301 gboolean do_runq = FALSE;
302 gboolean do_runq_online = FALSE;
304 gboolean do_queue = FALSE;
306 gboolean do_verbose = FALSE;
307 gint debug_level = -1;
309 mta_mode mta_mode = MODE_ACCEPT;
311 gint queue_interval = 0;
312 gboolean opt_t = FALSE;
313 gboolean opt_i = FALSE;
314 gboolean exit_failure = FALSE;
316 gchar *M_cmd = NULL;
318 gint exit_code = EXIT_SUCCESS;
319 gchar *route_name = NULL;
320 gchar *progname;
321 gchar *f_address = NULL;
322 gchar *full_sender_name = NULL;
323 address *return_path = NULL; /* may be changed by -f option */
325 progname = get_progname(argv[0]);
327 if (strcmp(progname, "mailq") == 0) {
328 mta_mode = MODE_LIST;
329 } else if (strcmp(progname, "mailrm") == 0) {
330 mta_mode = MODE_MCMD;
331 M_cmd = "rm";
332 } else if (strcmp(progname, "runq") == 0) {
333 mta_mode = MODE_RUNQUEUE;
334 do_runq = TRUE;
335 } else if (strcmp(progname, "rmail") == 0) {
336 /* the `rmail' alias should probably be removed now
337 that we have the rmail script. But let's keep it
338 for some while for compatibility. 2010-06-19 */
339 mta_mode = MODE_ACCEPT;
340 opt_i = TRUE;
341 } else if (strcmp(progname, "smtpd") == 0 || strcmp(progname, "in.smtpd") == 0) {
342 mta_mode = MODE_SMTP;
343 }
345 /* parse cmd line */
346 for (arg=1; arg<argc && argv[arg][0]=='-'; arg++) {
347 opt = argv[arg] + 1;
349 if (strcmp(opt, "-") == 0) {
350 /* everything after `--' are address arguments */
351 arg++;
352 break;
354 } else if (strcmp(opt, "bd") == 0) {
355 do_listen = TRUE;
356 mta_mode = MODE_DAEMON;
358 } else if (strcmp(opt, "bi") == 0) {
359 /* ignored */
360 mta_mode = MODE_BI;
362 } else if (strcmp(opt, "bs") == 0) {
363 mta_mode = MODE_SMTP;
365 } else if (strcmp(opt, "bp") == 0) {
366 mta_mode = MODE_LIST;
368 } else if (strcmp(opt, "bV") == 0) {
369 mta_mode = MODE_VERSION;
371 } else if (strncmp(opt, "B", 1) == 0) {
372 /* we ignore this and throw the argument away */
373 get_optarg(argv, &arg, opt+1);
375 } else if (strncmp(opt, "C", 1) == 0) {
376 if (!(conf_file = get_optarg(argv, &arg, opt+1))) {
377 fprintf(stderr, "-C requires a filename as argument.\n");
378 exit(EXIT_FAILURE);
379 }
381 } else if (strncmp(opt, "d", 1) == 0) {
382 if (getuid() != 0) {
383 fprintf(stderr, "only root may set the debug level.\n");
384 exit(EXIT_FAILURE);
385 }
386 char *lvl = get_optarg(argv, &arg, opt+1);
387 if (!lvl) {
388 fprintf(stderr, "-d requires a number argument.\n");
389 exit(EXIT_FAILURE);
390 }
391 debug_level = atoi(lvl);
393 } else if (strncmp(opt, "f", 1) == 0) {
394 /* set return path */
395 gchar *address = get_optarg(argv, &arg, opt+1);
396 if (!address) {
397 fprintf(stderr, "-f requires an address argument\n");
398 exit(EXIT_FAILURE);
399 }
400 f_address = g_strdup(address);
402 } else if (strncmp(opt, "F", 1) == 0) {
403 full_sender_name = get_optarg(argv, &arg, opt+1);
404 if (!full_sender_name) {
405 fprintf(stderr, "-F requires a name argument\n");
406 exit(EXIT_FAILURE);
407 }
409 } else if (strcmp(opt, "i") == 0) {
410 opt_i = TRUE;
411 exit_failure = FALSE; /* may override -oem */
413 } else if (strcmp(opt, "m") == 0) {
414 /* ignore -m (me too) switch (see man page) */
416 } else if (strcmp(opt, "Mrm") == 0) {
417 mta_mode = MODE_MCMD;
418 M_cmd = "rm";
420 } else if (strcmp(opt, "odq") == 0) {
421 do_queue = TRUE;
423 } else if (strcmp(opt, "oem") == 0) {
424 if (!opt_i) {
425 /* TODO: Why is this related to -i in any way? */
426 exit_failure = TRUE;
427 }
429 } else if (strcmp(opt, "oi") == 0) {
430 exit_failure = FALSE; /* may override -oem */
432 } else if (strncmp(opt, "o", 1) == 0) {
433 /* ignore all other -oXXX options */
435 } else if (strncmp(opt, "qo", 2) == 0) {
436 mta_mode = MODE_RUNQUEUE;
437 do_runq = FALSE;
438 do_runq_online = TRUE;
439 /* can be NULL, then we use online detection method */
440 route_name = get_optarg(argv, &arg, opt+2);
442 } else if (strncmp(opt, "q", 1) == 0) {
443 /* must be after the `qo' check */
444 gchar *optarg;
445 int dummy;
447 do_runq = TRUE;
448 mta_mode = MODE_RUNQUEUE;
449 if ((optarg = get_optarg(argv, &arg, opt+1))) {
450 mta_mode = MODE_DAEMON;
451 queue_interval = time_interval(optarg, &dummy);
452 }
454 } else if (strcmp(opt, "t") == 0) {
455 opt_t = TRUE;
457 } else if (strcmp(opt, "v") == 0) {
458 do_verbose = TRUE;
460 } else {
461 fprintf(stderr, "unrecognized option `-%s'\n", opt);
462 exit(EXIT_FAILURE);
463 }
464 }
466 if (mta_mode == MODE_VERSION) {
467 gchar *with_resolver = "";
468 gchar *with_auth = "";
469 gchar *with_ident = "";
471 #ifdef ENABLE_RESOLVER
472 with_resolver = " +resolver";
473 #endif
474 #ifdef ENABLE_AUTH
475 with_auth = " +auth";
476 #endif
477 #ifdef ENABLE_IDENT
478 with_ident = " +ident";
479 #endif
481 printf("%s %s%s%s%s\n", PACKAGE, VERSION, with_resolver, with_auth, with_ident);
483 exit(EXIT_SUCCESS);
484 }
486 /* initialize random generator */
487 srand(time(NULL));
488 /* ignore SIGPIPE signal */
489 signal(SIGPIPE, SIG_IGN);
491 /* close all possibly open file descriptors, except std{in,out,err} */
492 {
493 int i, max_fd = sysconf(_SC_OPEN_MAX);
495 if (max_fd <= 0)
496 max_fd = 64;
497 for (i = 3; i < max_fd; i++)
498 close(i);
499 }
501 init_conf();
503 /* if we are not privileged, and the config file was changed we
504 implicetely set the the run_as_user flag and give up all
505 privileges.
507 So it is possible for a user to run his own daemon without
508 breaking security.
509 */
510 if (strcmp(conf_file, CONF_FILE) != 0) {
511 if (conf.orig_uid != 0) {
512 conf.run_as_user = TRUE;
513 seteuid(conf.orig_uid);
514 setegid(conf.orig_gid);
515 setuid(conf.orig_uid);
516 setgid(conf.orig_gid);
517 }
518 }
520 conf.log_dir = LOG_DIR;
521 logopen();
522 if (!read_conf(conf_file)) {
523 logwrite(LOG_ALERT, "SHUTTING DOWN due to problems reading config\n");
524 exit(5);
525 }
526 logclose();
528 if (do_queue)
529 conf.do_queue = TRUE;
530 if (do_verbose)
531 conf.do_verbose = TRUE;
532 if (debug_level >= 0) /* if >= 0, it was given by argument */
533 conf.debug_level = debug_level;
535 /* It appears that changing to / ensures that we are never in
536 a directory which we cannot access. This situation could be
537 possible after changing identity.
538 Maybe we should only change to / if we not run as user, to
539 allow relative paths for log files in test setups for
540 instance.
541 */
542 chdir("/");
544 if (!conf.run_as_user) {
545 if (setgid(0) != 0) {
546 fprintf(stderr, "could not set gid to 0. Is the setuid bit set? : %s\n", strerror(errno));
547 exit(EXIT_FAILURE);
548 }
549 if (setuid(0) != 0) {
550 fprintf(stderr, "could not gain root privileges. Is the setuid bit set? : %s\n", strerror(errno));
551 exit(EXIT_FAILURE);
552 }
553 }
555 if (!logopen()) {
556 fprintf(stderr, "could not open log file\n");
557 exit(EXIT_FAILURE);
558 }
560 DEBUG(1) debugf("masqmail %s starting\n", VERSION);
562 DEBUG(5) {
563 gchar **str = argv;
564 debugf("args: \n");
565 while (*str) {
566 debugf("%s \n", *str);
567 str++;
568 }
569 }
570 DEBUG(5) debugf("queue_interval = %d\n", queue_interval);
572 if (f_address) {
573 return_path = create_address_qualified(f_address, TRUE, conf.host_name);
574 g_free(f_address);
575 if (!return_path) {
576 fprintf(stderr, "invalid RFC821 address: %s\n", f_address);
577 exit(EXIT_FAILURE);
578 }
579 }
581 switch (mta_mode) {
582 case MODE_DAEMON:
583 mode_daemon(do_listen, queue_interval, argv);
584 break;
585 case MODE_RUNQUEUE:
586 {
587 /* queue runs */
588 set_identity(conf.orig_uid, "queue run");
590 if (do_runq)
591 exit_code = queue_run() ? EXIT_SUCCESS : EXIT_FAILURE;
593 if (do_runq_online) {
594 if (route_name != NULL) {
595 conf.online_detect = g_strdup("argument");
596 set_online_name(route_name);
597 }
598 exit_code =
599 queue_run_online() ? EXIT_SUCCESS : EXIT_FAILURE;
600 }
601 }
602 break;
604 case MODE_SMTP:
605 mode_smtp();
606 break;
608 case MODE_LIST:
609 queue_list();
610 break;
612 case MODE_BI:
613 exit(EXIT_SUCCESS);
614 break; /* well... */
616 case MODE_MCMD:
617 if (strcmp(M_cmd, "rm") == 0) {
618 gboolean ok = FALSE;
620 set_euidgid(conf.mail_uid, conf.mail_gid, NULL, NULL);
622 if (is_privileged_user(conf.orig_uid)) {
623 for (; arg < argc; arg++) {
624 if (queue_delete(argv[arg]))
625 ok = TRUE;
626 }
627 } else {
628 struct passwd *pw = getpwuid(conf.orig_uid);
629 if (pw) {
630 for (; arg < argc; arg++) {
631 message *msg = msg_spool_read(argv[arg], FALSE);
632 #ifdef ENABLE_IDENT
633 if (((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL))
634 || is_in_netlist(msg->received_host, conf.ident_trusted_nets))
635 #else
636 if ((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL))
637 #endif
638 {
639 if (msg->ident) {
640 if (strcmp(pw->pw_name, msg->ident) == 0) {
641 if (queue_delete(argv[arg]))
642 ok = TRUE;
643 } else {
644 fprintf(stderr, "you do not own message id %s\n", argv[arg]);
645 }
646 } else
647 fprintf(stderr, "message %s does not have an ident.\n", argv[arg]);
648 } else {
649 fprintf(stderr, "message %s was not received locally or from a trusted network.\n", argv[arg]);
650 }
651 }
652 } else {
653 fprintf(stderr, "could not find a passwd entry for uid %d: %s\n", conf.orig_uid, strerror(errno));
654 }
655 }
656 exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
657 } else {
658 fprintf(stderr, "unknown command %s\n", M_cmd);
659 exit(EXIT_FAILURE);
660 }
661 break;
663 case MODE_ACCEPT:
664 {
665 guint accept_flags = (opt_t ? ACC_DEL_RCPTS | ACC_RCPT_FROM_HEAD : 0)
666 | (opt_i ? ACC_DOT_IGNORE : ACC_NODOT_RELAX);
667 mode_accept(return_path, full_sender_name, accept_flags, &(argv[arg]), argc - arg);
668 exit(exit_failure ? EXIT_FAILURE : EXIT_SUCCESS);
669 }
670 break;
671 case MODE_NONE:
672 break;
673 default:
674 fprintf(stderr, "unknown mode: %d\n", mta_mode);
675 break;
676 }
678 logclose();
680 exit(exit_code);
681 }