masqmail

view src/masqmail.c @ 366:41958685480d

Switched to `type *name' style Andrew Koenig's ``C Traps and Pitfalls'' (Ch.2.1) convinced me that it is best to go with the way C had been designed. The ``declaration reflects use'' concept conflicts with a ``type* name'' notation. Hence I switched.
author markus schnalke <meillo@marmaro.de>
date Thu, 22 Sep 2011 15:07:40 +0200
parents b45dc53f2829
children b27f66555ba8
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 enum mta_mode {
41 MODE_NONE = 0, /* to check if a mode was set */
42 MODE_ACCEPT, /* accept message on stdin (fallback mode) */
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 };
51 enum mta_mode mta_mode = MODE_NONE;
53 char *pidfile = NULL;
54 volatile int sigterm_in_progress = 0;
56 static void
57 sigterm_handler(int sig)
58 {
59 if (sigterm_in_progress)
60 raise(sig);
61 sigterm_in_progress = 1;
63 if (pidfile) {
64 uid_t uid = geteuid();
65 if (seteuid(0) != 0) {
66 logwrite(LOG_ALERT, "sigterm_handler: could not set euid to %d: %s\n", 0, strerror(errno));
67 }
68 if (unlink(pidfile) != 0)
69 logwrite(LOG_WARNING, "could not delete pid file %s: %s\n", pidfile, strerror(errno));
70 seteuid(uid); /* we exit anyway after this, just to be sure */
71 }
73 signal(sig, SIG_DFL);
74 raise(sig);
75 }
77 #ifdef ENABLE_IDENT /* so far used for that only */
78 static gboolean
79 is_in_netlist(gchar *host, GList *netlist)
80 {
81 guint hostip = inet_addr(host);
82 struct in_addr addr;
84 addr.s_addr = hostip;
85 if (addr.s_addr != INADDR_NONE) {
86 GList *node;
87 foreach(netlist, node) {
88 struct in_addr *net = (struct in_addr *) (node->data);
89 if ((addr.s_addr & net->s_addr) == net->s_addr)
90 return TRUE;
91 }
92 }
93 return FALSE;
94 }
95 #endif
97 /*
98 argv: the original argv
99 argp: number of arg (may get modified!)
100 cp: pointing to the char after the option
101 e.g. `-d 6' `-d6'
102 ^ ^
103 */
104 gchar*
105 get_optarg(char *argv[], gint *argp, char *cp)
106 {
107 if (*cp) {
108 /* this kind: -xval */
109 return cp;
110 }
111 cp = argv[*argp+1];
112 if (cp && (*cp != '-')) {
113 /* this kind: -x val */
114 (*argp)++;
115 return cp;
116 }
117 return NULL;
118 }
120 gboolean
121 write_pidfile(gchar *name)
122 {
123 FILE *fptr;
125 if ((fptr = fopen(name, "wt"))) {
126 fprintf(fptr, "%d\n", getpid());
127 fclose(fptr);
128 pidfile = strdup(name);
129 return TRUE;
130 }
131 logwrite(LOG_WARNING, "could not write pid file: %s\n", strerror(errno));
132 return FALSE;
133 }
135 /* on -bd or if -q has an argument */
136 static void
137 mode_daemon(gboolean do_listen, gint queue_interval, char *argv[])
138 {
139 guint pid;
141 /* daemon */
142 if (!conf.run_as_user) {
143 if ((conf.orig_uid != 0) && (conf.orig_uid != conf.mail_uid)) {
144 fprintf(stderr, "must be root or %s for daemon.\n", DEF_MAIL_USER);
145 exit(1);
146 }
147 }
149 /* reparent to init only if init is not already the parent */
150 if (getppid() != 1) {
151 if ((pid = fork()) > 0) {
152 exit(0);
153 } else if (pid < 0) {
154 logwrite(LOG_ALERT, "could not fork!\n");
155 exit(1);
156 }
157 }
159 signal(SIGTERM, sigterm_handler);
160 write_pidfile(PIDFILEDIR "/masqmail.pid");
162 conf.do_verbose = FALSE;
164 /* closing and reopening the log ensures that it is open afterwards
165 because it is possible that the log is assigned to fd 1 and gets
166 thus closes by fclose(stdout). Similar for the debugfile.
167 */
168 logclose();
169 fclose(stdin);
170 fclose(stdout);
171 fclose(stderr);
172 logopen();
174 logwrite(LOG_NOTICE, "%s %s daemon starting\n", PACKAGE, VERSION);
175 listen_port(do_listen ? conf.listen_addresses : NULL, queue_interval, argv);
176 }
178 /* -bs or called as smtpd or in.smtpd */
179 static void
180 mode_smtp()
181 {
182 /* accept smtp message on stdin */
183 /* write responses to stderr. */
185 struct sockaddr_in saddr;
186 gchar *peername = NULL;
187 int dummy = sizeof(saddr);
189 conf.do_verbose = FALSE;
191 if (!conf.run_as_user) {
192 set_euidgid(conf.orig_uid, conf.orig_gid, NULL, NULL);
193 }
195 DEBUG(5) debugf("accepting smtp message on stdin\n");
197 if (getpeername(0, (struct sockaddr *) (&saddr), &dummy) == 0) {
198 peername = g_strdup(inet_ntoa(saddr.sin_addr));
199 } else if (errno != ENOTSOCK)
200 exit(1);
202 smtp_in(stdin, stderr, peername, NULL);
203 }
205 /* default mode if address args or -t is specified, or called as rmail */
206 static void
207 mode_accept(address *return_path, gchar *full_sender_name, guint accept_flags, char **addresses, int addr_cnt)
208 {
209 /* accept message on stdin */
210 accept_error err;
211 message *msg = create_message();
212 gint i;
213 pid_t pid;
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(1);
218 }
220 if (!conf.run_as_user) {
221 set_euidgid(conf.orig_uid, conf.orig_gid, NULL, NULL);
222 }
224 DEBUG(5) debugf("accepting message on stdin\n");
226 msg->received_prot = PROT_LOCAL;
228 /* warn if -t option and cmdline addr args */
229 if (addr_cnt && (accept_flags & ACC_RCPT_FROM_HEAD)) {
230 logwrite(LOG_ALERT, "command line address arguments are now *added* to the mail header\\\n");
231 logwrite(LOG_ALERT, " recipient addresses (instead of substracted) when -t is given.\\\n");
232 logwrite(LOG_ALERT, " this changed with version 0.3.1\n");
233 }
235 for (i = 0; i < addr_cnt; i++) {
236 if (addresses[i][0] == '|') {
237 logwrite(LOG_ALERT, "no pipe allowed as recipient address: %s\n", addresses[i]);
238 /* should we better ignore this one addr? */
239 exit(1);
240 }
241 msg->rcpt_list = g_list_append(msg->rcpt_list, create_address_qualified(addresses[i], TRUE, conf.host_name));
242 }
244 /* -f option */
245 msg->return_path = return_path;
247 /* -F option */
248 msg->full_sender_name = full_sender_name;
250 err = accept_message(stdin, msg, accept_flags);
252 switch (err) {
253 case AERR_OK:
254 /* to continue; all other cases exit */
255 break;
256 case AERR_EOF:
257 fprintf(stderr, "unexpected EOF.\n");
258 exit(1);
259 case AERR_NORCPT:
260 fprintf(stderr, "no recipients.\n");
261 exit(1);
262 case AERR_SIZE:
263 fprintf(stderr, "max message size exceeded.\n");
264 exit(1);
265 default:
266 /* should never happen: */
267 fprintf(stderr, "Unknown error (%d)\r\n", err);
268 exit(1);
269 }
271 if (!spool_write(msg, TRUE)) {
272 fprintf(stderr, "Could not write spool file\n");
273 exit(1);
274 }
276 /* here the mail is queued and thus in our responsibility */
277 logwrite(LOG_NOTICE, "%s <= %s with %s\n", msg->uid, addr_string(msg->return_path), prot_names[PROT_LOCAL]);
279 if (conf.do_queue) {
280 /* we're finished as we only need to queue it */
281 return;
282 }
284 /* deliver at once */
285 if ((pid = fork()) < 0) {
286 logwrite(LOG_ALERT, "could not fork for delivery, id = %s\n", msg->uid);
287 } else if (pid == 0) {
288 conf.do_verbose = FALSE;
289 fclose(stdin);
290 fclose(stdout);
291 fclose(stderr);
292 if (deliver(msg)) {
293 exit(0);
294 } else {
295 /*
296 TODO:
297 Should we really fail here? Because the mail is queued
298 already. If we fail the client might submit it again.
299 If at-once-delivery is seen as an additional best-effort
300 service, then we should still exit successful here.
301 */
302 exit(1);
303 }
304 }
305 }
307 /*
308 if -Mrm is given
310 currently only the `rm' command is supported
311 until this changes, we don't need any facility for further commands
312 return success if at least one message had been deleted
313 */
314 static int
315 manipulate_queue(char *cmd, char *id[])
316 {
317 gboolean ok = FALSE;
319 if (strcmp(cmd, "rm") != 0) {
320 fprintf(stderr, "unknown command %s\n", cmd);
321 return FALSE;
322 }
324 set_euidgid(conf.mail_uid, conf.mail_gid, NULL, NULL);
326 /* privileged users may delete any mail */
327 if (is_privileged_user(conf.orig_uid)) {
328 for (; *id; id++) {
329 fprintf(stderr, "id: %s\n", *id);
330 if (queue_delete(*id)) {
331 ok = TRUE;
332 }
333 }
334 return ok;
335 }
337 struct passwd *pw = getpwuid(conf.orig_uid);
338 if (!pw) {
339 fprintf(stderr, "could not find a passwd entry for uid %d: %s\n",
340 conf.orig_uid, strerror(errno));
341 return FALSE;
342 }
344 /* non-privileged users may only delete their own messages */
345 for (; *id; id++) {
346 message *msg = msg_spool_read(*id);
348 fprintf(stderr, "id: %s\n", *id);
350 if (!msg->ident) {
351 fprintf(stderr, "message %s does not have an ident\n", *id);
352 continue;
353 }
354 if (strcmp(pw->pw_name, msg->ident) != 0) {
355 fprintf(stderr, "you do not own message id %s\n", *id);
356 continue;
357 }
359 if ( (msg->received_host || (msg->received_prot != PROT_LOCAL))
360 #ifdef ENABLE_IDENT
361 && !is_in_netlist(msg->received_host, conf.ident_trusted_nets)
362 #endif
363 ) {
364 fprintf(stderr, "message %s was not received locally or from a trusted network\n", *id);
365 continue;
366 }
368 ok = queue_delete(*id);
369 }
370 return ok;
371 }
373 /* -qo, -q (without argument), or called as runq */
374 static int
375 run_queue(gboolean do_runq, gboolean do_runq_online, char *route_name)
376 {
377 int ret;
379 /* queue runs */
380 set_identity(conf.orig_uid, "queue run");
382 if (do_runq) {
383 ret = queue_run();
384 }
386 if (do_runq_online) {
387 if (route_name) {
388 conf.online_query = g_strdup_printf("/bin/echo %s", route_name);
389 }
390 /* TODO: change behavior of `-qo without argument'?
391 Because that behavior is included in -q. */
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 void
419 set_mode(enum mta_mode mode)
420 {
421 if (mta_mode && mta_mode!=mode) {
422 fprintf(stderr, "operation mode was already specified (%d vs. %d)\n", mta_mode, mode);
423 exit(1);
424 }
426 mta_mode = mode;
427 return;
428 }
430 int
431 main(int argc, char *argv[])
432 {
433 gchar *progname;
434 char *opt;
435 gint arg;
437 gboolean do_listen = FALSE;
438 gboolean do_runq = FALSE;
439 gboolean do_runq_online = FALSE;
440 gboolean do_queue = FALSE;
441 gint queue_interval = 0;
442 gchar *M_cmd = NULL;
443 gboolean opt_t = FALSE;
444 gboolean opt_i = FALSE;
445 gchar *conf_file = CONF_FILE;
446 gchar *route_name = NULL;
447 gchar *f_address = NULL;
448 address *return_path = NULL; /* may be changed by -f option */
449 gchar *full_sender_name = NULL;
450 gboolean do_verbose = FALSE;
451 gint debug_level = -1;
453 /* strip the path part */
454 progname = strrchr(argv[0], '/');
455 progname = (progname) ? progname+1 : argv[0];
457 if (strcmp(progname, "mailq") == 0) {
458 mta_mode = MODE_LIST;
459 } else if (strcmp(progname, "mailrm") == 0) {
460 mta_mode = MODE_MCMD;
461 M_cmd = "rm";
462 } else if (strcmp(progname, "newaliases") == 0) {
463 mta_mode = MODE_BI;
464 } else if (strcmp(progname, "rmail") == 0) {
465 /* the `rmail' alias should probably be removed now
466 that we have the rmail script. But let's keep it
467 for some while for compatibility. 2010-06-19 */
468 mta_mode = MODE_ACCEPT;
469 opt_i = TRUE;
470 } else if (strcmp(progname, "runq") == 0) {
471 mta_mode = MODE_RUNQUEUE;
472 do_runq = TRUE;
473 } else if (strcmp(progname, "smtpd") == 0
474 || strcmp(progname, "in.smtpd") == 0) {
475 mta_mode = MODE_SMTP;
476 }
478 /* parse cmd line */
479 for (arg=1; arg<argc && argv[arg][0]=='-'; arg++) {
480 opt = argv[arg] + 1; /* points to the char after the dash */
482 if (strcmp(opt, "-") == 0) {
483 /* everything after `--' are address arguments */
484 arg++;
485 break;
487 } else if (strcmp(opt, "bm") == 0) {
488 set_mode(MODE_ACCEPT);
490 } else if (strcmp(opt, "bd") == 0) {
491 set_mode(MODE_DAEMON);
492 do_listen = TRUE;
494 } else if (strcmp(opt, "bi") == 0) {
495 set_mode(MODE_BI);
497 } else if (strcmp(opt, "bs") == 0) {
498 set_mode(MODE_SMTP);
500 } else if (strcmp(opt, "bp") == 0) {
501 set_mode(MODE_LIST);
503 } else if (strcmp(opt, "bV") == 0) {
504 set_mode(MODE_VERSION);
506 } else if (strncmp(opt, "B", 1) == 0) {
507 /* we ignore this and throw the argument away */
508 get_optarg(argv, &arg, opt+1);
510 } else if (strncmp(opt, "C", 1) == 0) {
511 conf_file = get_optarg(argv, &arg, opt+1);
512 if (!conf_file) {
513 fprintf(stderr, "-C requires a filename as argument.\n");
514 exit(1);
515 }
517 } else if (strncmp(opt, "d", 1) == 0) {
518 if (getuid() != 0) {
519 fprintf(stderr, "only root may set the debug level.\n");
520 exit(1);
521 }
522 char *lvl = get_optarg(argv, &arg, opt+1);
523 if (!lvl) {
524 fprintf(stderr, "-d requires a number argument.\n");
525 exit(1);
526 }
527 debug_level = atoi(lvl);
529 } else if (strncmp(opt, "f", 1) == 0) {
530 /* set return path */
531 gchar *address = get_optarg(argv, &arg, opt+1);
532 if (!address) {
533 fprintf(stderr, "-f requires an address argument\n");
534 exit(1);
535 }
536 f_address = g_strdup(address);
538 } else if (strncmp(opt, "F", 1) == 0) {
539 full_sender_name = get_optarg(argv, &arg, opt+1);
540 if (!full_sender_name) {
541 fprintf(stderr, "-F requires a name argument\n");
542 exit(1);
543 }
545 } else if (strcmp(opt, "i") == 0) {
546 opt_i = TRUE;
548 } else if (strcmp(opt, "m") == 0) {
549 /* ignore -m (me too) switch (see man page) */
551 } else if (strcmp(opt, "Mrm") == 0) {
552 set_mode(MODE_MCMD);
553 M_cmd = "rm";
555 } else if (strcmp(opt, "odq") == 0) {
556 do_queue = TRUE;
558 } else if (strcmp(opt, "oi") == 0) {
559 opt_i = TRUE;
561 } else if (strncmp(opt, "o", 1) == 0) {
562 /* ignore all other -oXXX options */
564 } else if (strncmp(opt, "qo", 2) == 0) {
565 /* must be before the `q' check */
566 set_mode(MODE_RUNQUEUE);
567 do_runq_online = TRUE;
568 /* can be NULL, then we use online detection method */
569 /* TODO: behavior might change if it is NULL */
570 route_name = get_optarg(argv, &arg, opt+2);
571 if (!route_name) {
572 fprintf(stderr, "Please do not use -qo without argument anymore; use -q instead.\n");
573 fprintf(stderr, "The behavior for -qo without argument is likely to change.\n");
574 }
576 } else if (strncmp(opt, "q", 1) == 0) {
577 /* must be after the `qo' check */
578 gchar *optarg;
580 optarg = get_optarg(argv, &arg, opt+1);
581 if (optarg) {
582 /* not just one single queue run but regular runs */
583 set_mode(MODE_DAEMON);
584 queue_interval = time_interval(optarg);
585 } else {
586 set_mode(MODE_RUNQUEUE);
587 do_runq = TRUE;
588 }
590 } else if (strcmp(opt, "t") == 0) {
591 opt_t = TRUE;
593 } else if (strcmp(opt, "v") == 0) {
594 do_verbose = TRUE;
596 } else {
597 fprintf(stderr, "unrecognized option `-%s'\n", opt);
598 exit(1);
599 }
600 }
602 if (!mta_mode && arg==argc && !opt_t) {
603 /*
604 In this case no rcpts can be found, thus no mail
605 can be sent, thus masqmail will always fail. We
606 rather do something better instead. This covers
607 also the case of calling masqmail without args.
608 */
609 mode_version();
610 exit(0);
611 }
613 if (mta_mode == MODE_VERSION) {
614 mode_version();
615 exit(0);
616 }
618 if (!mta_mode) {
619 mta_mode = MODE_ACCEPT;
620 }
622 /* initialize random generator */
623 srand(time(NULL));
624 /* ignore SIGPIPE signal */
625 signal(SIGPIPE, SIG_IGN);
627 /* close all possibly open file descriptors, except std{in,out,err} */
628 {
629 int i, max_fd = sysconf(_SC_OPEN_MAX);
631 if (max_fd <= 0) {
632 max_fd = 64;
633 }
634 for (i=3; i<max_fd; i++) {
635 close(i);
636 }
637 }
639 init_conf();
641 /* if we are not privileged, and the config file was changed we
642 implicetely set the the run_as_user flag and give up all
643 privileges.
645 So it is possible for a user to run his own daemon without
646 breaking security.
647 */
648 if ((strcmp(conf_file, CONF_FILE) != 0) && (conf.orig_uid != 0)) {
649 conf.run_as_user = TRUE;
650 set_euidgid(conf.orig_uid, conf.orig_gid, NULL, NULL);
651 if (setgid(conf.orig_gid)) {
652 logwrite(LOG_ALERT, "could not set gid to %d: %s\n", conf.orig_gid, strerror(errno));
653 exit(1);
654 }
655 if (setuid(conf.orig_uid)) {
656 logwrite(LOG_ALERT, "could not set uid to %d: %s\n", conf.orig_uid, strerror(errno));
657 exit(1);
658 }
659 }
661 conf.log_dir = LOG_DIR;
662 /* FIXME: fails if we run as user */
663 logopen();
664 if (!read_conf(conf_file)) {
665 logwrite(LOG_ALERT, "SHUTTING DOWN due to problems reading config\n");
666 exit(5);
667 }
668 logclose();
670 if (do_queue) {
671 conf.do_queue = TRUE;
672 }
673 if (do_verbose) {
674 conf.do_verbose = TRUE;
675 }
676 if (debug_level >= 0) { /* if >= 0, it was given by argument */
677 conf.debug_level = debug_level;
678 }
680 /* It appears that changing to / ensures that we are never in
681 a directory which we cannot access. This situation could be
682 possible after changing identity.
683 Maybe we should only change to / if we not run as user, to
684 allow relative paths for log files in test setups for
685 instance.
686 */
687 chdir("/");
689 if (!conf.run_as_user) {
690 if (setgid(0) != 0) {
691 fprintf(stderr, "could not set gid to 0. Is the setuid bit set? : %s\n", strerror(errno));
692 exit(1);
693 }
694 if (setuid(0) != 0) {
695 fprintf(stderr, "could not gain root privileges. Is the setuid bit set? : %s\n", strerror(errno));
696 exit(1);
697 }
698 }
700 if (!logopen()) {
701 fprintf(stderr, "could not open log file\n");
702 exit(1);
703 }
705 DEBUG(1) debugf("masqmail %s starting\n", VERSION);
707 DEBUG(5) {
708 gchar **str = argv;
709 debugf("args: \n");
710 while (*str) {
711 debugf("%s \n", *str);
712 str++;
713 }
714 }
715 DEBUG(5) debugf("queue_interval = %d\n", queue_interval);
717 if (f_address) {
718 return_path = create_address_qualified(f_address, TRUE, conf.host_name);
719 g_free(f_address);
720 if (!return_path) {
721 fprintf(stderr, "invalid RFC821 address: %s\n", f_address);
722 exit(1);
723 }
724 }
726 switch (mta_mode) {
727 case MODE_DAEMON:
728 mode_daemon(do_listen, queue_interval, argv);
729 break;
731 case MODE_RUNQUEUE:
732 exit(run_queue(do_runq, do_runq_online, route_name) ? 0 : 1);
733 break;
735 case MODE_SMTP:
736 mode_smtp();
737 break;
739 case MODE_LIST:
740 queue_list();
741 break;
743 case MODE_BI:
744 exit(0);
745 break; /* well... */
747 case MODE_MCMD:
748 exit(manipulate_queue(M_cmd, &argv[arg]) ? 0 : 1);
749 break;
751 case MODE_ACCEPT:
752 {
753 guint accept_flags = (opt_t ? ACC_RCPT_FROM_HEAD : 0)
754 | (opt_i ? ACC_DOT_IGNORE : ACC_NODOT_RELAX);
755 mode_accept(return_path, full_sender_name, accept_flags, &(argv[arg]), argc - arg);
756 exit(0);
757 }
758 break;
760 default:
761 fprintf(stderr, "unknown mode: %d\n", mta_mode);
762 break;
763 }
765 logclose();
767 exit(0);
768 }