]> git.saurik.com Git - apple/system_cmds.git/blob - auditd.tproj/auditd.c
system_cmds-431.tar.gz
[apple/system_cmds.git] / auditd.tproj / auditd.c
1 /*
2 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights
7 * Reserved. This file contains Original Code and/or Modifications of
8 * Original Code as defined in and that are subject to the Apple Public
9 * Source License Version 1.0 (the 'License'). You may not use this file
10 * except in compliance with the License. Please obtain a copy of the
11 * License at http://www.apple.com/publicsource and read it before using
12 * this file.
13 *
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
19 * License for the specific language governing rights and limitations
20 * under the License."
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24
25 #include <mach/port.h>
26 #include <mach/mach_error.h>
27 #include <mach/mach_traps.h>
28 #include <mach/mach.h>
29 #include <mach/host_special_ports.h>
30
31 #include <sys/types.h>
32 #include <sys/mman.h>
33 #include <sys/queue.h>
34 #include <sys/stat.h>
35 #include <sys/time.h>
36 #include <sys/wait.h>
37
38 #include <fcntl.h>
39 #include <time.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <errno.h>
44 #include <syslog.h>
45 #include <signal.h>
46 #include <string.h>
47 #include <notify.h>
48
49 #include <bsm/audit.h>
50 #include <bsm/audit_uevents.h>
51 #include <bsm/libbsm.h>
52
53 #include <auditd.h>
54 #include "auditd_control_server.h"
55 #include "audit_triggers_server.h"
56 #define NA_EVENT_STR_SIZE 25
57
58 static int ret, minval;
59 static char *lastfile = NULL;
60
61 static int allhardcount = 0;
62
63 mach_port_t bp = MACH_PORT_NULL;
64 mach_port_t control_port = MACH_PORT_NULL;
65 mach_port_t signal_port = MACH_PORT_NULL;
66 mach_port_t port_set = MACH_PORT_NULL;
67
68 #ifndef __BSM_INTERNAL_NOTIFY_KEY
69 #define __BSM_INTERNAL_NOTIFY_KEY "com.apple.audit.change"
70 #endif /* __BSM_INTERNAL_NOTIFY_KEY */
71
72 TAILQ_HEAD(, dir_ent) dir_q;
73
74
75 /* Error starting auditd */
76 void fail_exit()
77 {
78 audit_warn_nostart();
79 exit(1);
80 }
81
82 /*
83 * Free our local list of directory names
84 */
85 void free_dir_q()
86 {
87 struct dir_ent *dirent;
88
89 while ((dirent = TAILQ_FIRST(&dir_q))) {
90 TAILQ_REMOVE(&dir_q, dirent, dirs);
91 free(dirent->dirname);
92 free(dirent);
93 }
94 }
95
96 /*
97 * generate the timestamp string
98 */
99 int getTSstr(char *buf, int len)
100 {
101 struct timeval ts;
102 struct timezone tzp;
103 time_t tt;
104
105 if(gettimeofday(&ts, &tzp) != 0) {
106 return -1;
107 }
108 tt = (time_t)ts.tv_sec;
109 if(!strftime(buf, len, "%Y%m%d%H%M%S", gmtime(&tt))) {
110 return -1;
111 }
112
113 return 0;
114 }
115
116 /*
117 * Concat the directory name to the given file name
118 * XXX We should affix the hostname also
119 */
120 char *affixdir(char *name, struct dir_ent *dirent)
121 {
122 char *fn;
123 char *curdir;
124 const char *sep = "/";
125
126 curdir = dirent->dirname;
127 syslog(LOG_INFO, "dir = %s\n", dirent->dirname);
128
129 fn = (char *) malloc (strlen(curdir) + strlen(sep)
130 + (2 * POSTFIX_LEN) + 1);
131 if(fn == NULL) {
132 return NULL;
133 }
134 strcpy(fn, curdir);
135 strcat(fn, sep);
136 strcat(fn, name);
137
138 return fn;
139 }
140
141 /* Close the previous audit trail file */
142 int close_lastfile(char *TS)
143 {
144 char *ptr;
145 char *oldname;
146
147 if(lastfile != NULL) {
148 oldname = (char *)malloc(strlen(lastfile) + 1);
149 if(oldname == NULL) {
150 return -1;
151 }
152 strcpy(oldname, lastfile);
153
154 /* rename the last file -- append timestamp */
155
156 if((ptr = strstr(lastfile, NOT_TERMINATED)) != NULL) {
157 *ptr = '.';
158 strcpy(ptr+1, TS);
159 if(rename(oldname, lastfile) != 0) {
160 syslog(LOG_ERR, "Could not rename %s to %s \n",
161 oldname, lastfile);
162 }
163 else {
164 syslog(LOG_INFO, "renamed %s to %s \n",
165 oldname, lastfile);
166 }
167 }
168
169 free(lastfile);
170 free(oldname);
171
172 lastfile = NULL;
173 }
174
175 return 0;
176 }
177
178 /*
179 * Create the new file name, swap with existing audit file
180 */
181 int swap_audit_file()
182 {
183 char timestr[2 * POSTFIX_LEN];
184 char *fn;
185 char TS[POSTFIX_LEN];
186 struct dir_ent *dirent;
187
188 if(getTSstr(TS, POSTFIX_LEN) != 0) {
189 return -1;
190 }
191
192 strcpy(timestr, TS);
193 strcat(timestr, NOT_TERMINATED);
194
195 /* try until we succeed */
196 while((dirent = TAILQ_FIRST(&dir_q))) {
197 if((fn = affixdir(timestr, dirent)) == NULL) {
198 return -1;
199 }
200
201 syslog(LOG_INFO, "New audit file is %s\n", fn);
202 if (open(fn, O_RDONLY | O_CREAT, S_IRUSR | S_IRGRP) < 0) {
203 perror("File open");
204 }
205 else if (auditctl(fn) != 0) {
206 syslog(LOG_ERR, "auditctl failed! : %s\n",
207 strerror(errno));
208 }
209 else {
210 /* Success */
211 close_lastfile(TS);
212 lastfile = fn;
213 return 0;
214 }
215
216 /* Tell the administrator about lack of permissions for dirent */
217 audit_warn_getacdir(dirent->dirname);
218
219 /* Try again with a different directory */
220 TAILQ_REMOVE(&dir_q, dirent, dirs);
221 free(dirent->dirname);
222 free(dirent);
223 }
224 return -1;
225 }
226
227 /*
228 * Read the audit_control file contents
229 */
230 int read_control_file()
231 {
232 char cur_dir[MAX_DIR_SIZE];
233 struct dir_ent *dirent;
234 au_qctrl_t qctrl;
235
236 /* Clear old values */
237 free_dir_q();
238 endac(); // force a re-read of the file the next time
239
240 /* Post that the audit config changed */
241 notify_post(__BSM_INTERNAL_NOTIFY_KEY);
242
243 /* Read the list of directories into a local linked list */
244 /* XXX We should use the reentrant interfaces once they are available */
245 while(getacdir(cur_dir, MAX_DIR_SIZE) >= 0) {
246 dirent = (struct dir_ent *) malloc (sizeof(struct dir_ent));
247 if(dirent == NULL) {
248 return -1;
249 }
250
251 dirent->softlim = 0;
252 dirent->dirname = (char *) malloc (MAX_DIR_SIZE);
253 if(dirent->dirname == NULL) {
254 free(dirent);
255 return -1;
256 }
257
258 strcpy(dirent->dirname, cur_dir);
259 TAILQ_INSERT_TAIL(&dir_q, dirent, dirs);
260 }
261
262 allhardcount = 0;
263
264 if(swap_audit_file() == -1) {
265 syslog(LOG_ERR, "Could not swap audit file\n");
266 /*
267 * XXX Faulty directory listing? - user should be given
268 * XXX an opportunity to change the audit_control file
269 * XXX switch to a reduced mode of auditing?
270 */
271 return -1;
272 }
273
274 /*
275 * XXX There are synchronization problems here
276 * XXX what should we do if a trigger for the earlier limit
277 * XXX is generated here?
278 */
279 if(0 == (ret = getacmin(&minval))) {
280
281 syslog(LOG_INFO, "min free = %d\n", minval);
282
283 if (auditon(A_GETQCTRL, &qctrl, sizeof(qctrl)) != 0) {
284 syslog(LOG_ERR,
285 "could not get audit queue settings\n");
286 return -1;
287 }
288 qctrl.aq_minfree = minval;
289 if (auditon(A_SETQCTRL, &qctrl, sizeof(qctrl)) != 0) {
290 syslog(LOG_ERR,
291 "could not set audit queue settings\n");
292 return -1;
293 }
294 }
295
296 return 0;
297 }
298
299 /*
300 * Close all log files, control files, and tell the audit system.
301 */
302 int close_all()
303 {
304 int err_ret = 0;
305 char TS[POSTFIX_LEN];
306 int aufd;
307 token_t *tok;
308
309 /* Generate an audit record */
310 if((aufd = au_open()) == -1) {
311 syslog(LOG_ERR, "Could not create audit shutdown event.\n");
312 } else {
313
314 if((tok = au_to_text("auditd::Audit shutdown")) != NULL) {
315 au_write(aufd, tok);
316 }
317
318 if(au_close(aufd, 1, AUE_audit_shutdown) == -1) {
319 syslog(LOG_ERR, "Could not close audit shutdown event.\n");
320 }
321 }
322
323 /* flush contents */
324 err_ret = auditctl(NULL);
325 if (err_ret != 0) {
326 syslog(LOG_ERR, "auditctl failed! : %s\n",
327 strerror(errno));
328 err_ret = 1;
329 }
330 if(getTSstr(TS, POSTFIX_LEN) == 0) {
331 close_lastfile(TS);
332 }
333 if(lastfile != NULL)
334 free(lastfile);
335
336 free_dir_q();
337 if((remove(AUDITD_PIDFILE) == -1) || err_ret) {
338 syslog(LOG_ERR, "Could not unregister\n");
339 audit_warn_postsigterm();
340 return (1);
341 }
342 endac();
343 syslog(LOG_INFO, "Finished.\n");
344 return (0);
345 }
346
347 /*
348 * When we get a signal, we are often not at a clean point.
349 * So, little can be done in the signal handler itself. Instead,
350 * we send a message to the main servicing loop to do proper
351 * handling from a non-signal-handler context.
352 */
353 static void
354 relay_signal(int signal)
355 {
356 mach_msg_empty_send_t msg;
357
358 msg.header.msgh_id = signal;
359 msg.header.msgh_remote_port = signal_port;
360 msg.header.msgh_local_port = MACH_PORT_NULL;
361 msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
362 mach_msg(&(msg.header), MACH_SEND_MSG|MACH_SEND_TIMEOUT, sizeof(msg),
363 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
364 }
365
366 /* registering the daemon */
367 int register_daemon()
368 {
369 FILE * pidfile;
370 int fd;
371 pid_t pid;
372
373 /* Set up the signal hander */
374 if (signal(SIGTERM, relay_signal) == SIG_ERR) {
375 fail_exit();
376 }
377 if (signal(SIGCHLD, relay_signal) == SIG_ERR) {
378 fail_exit();
379 }
380
381 if ((pidfile = fopen(AUDITD_PIDFILE, "a")) == NULL) {
382 audit_warn_tmpfile();
383 return -1;
384 }
385
386 /* attempt to lock the pid file; if a lock is present, exit */
387 fd = fileno(pidfile);
388 if(flock(fd, LOCK_EX | LOCK_NB) < 0) {
389 syslog(LOG_ERR, "PID file is locked (is another auditd running?).\n");
390 audit_warn_ebusy();
391 return -1;
392 }
393
394 pid = getpid();
395 ftruncate(fd, 0);
396 if(fprintf(pidfile, "%u\n", pid) < 0) {
397 /* should not start the daemon */
398 fail_exit();
399 }
400
401 fflush(pidfile);
402 return 0;
403 }
404
405 /*
406 * React to input from the audit tool
407 */
408 kern_return_t auditd_control(auditd_port, flags)
409 mach_port_t auditd_port;
410 int flags;
411 {
412 int err_ret = 0;
413
414 switch(flags) {
415
416 case OPEN_NEW :
417 /* create a new file and swap with the one being used in kernel */
418 if(swap_audit_file() == -1) {
419 syslog(LOG_ERR, "Error swapping audit file\n");
420 }
421 break;
422
423 case READ_FILE :
424 if(read_control_file() == -1) {
425 syslog(LOG_ERR, "Error in audit control file\n");
426 }
427 break;
428
429 case CLOSE_AND_DIE :
430 err_ret = close_all();
431 exit (err_ret);
432 break;
433
434 default :
435 break;
436 }
437
438 return KERN_SUCCESS;
439 }
440
441 /*
442 * Suppress duplicate messages within a 30 second interval.
443 * This should be enough to time to rotate log files without
444 * thrashing from soft warnings generated before the log is
445 * actually rotated.
446 */
447 #define DUPLICATE_INTERVAL 30
448 /*
449 * Implementation of the audit_triggers() MIG routine.
450 */
451 kern_return_t audit_triggers(audit_port, flags)
452 mach_port_t audit_port;
453 int flags;
454 {
455 static int last_flags;
456 static time_t last_time;
457 struct dir_ent *dirent;
458
459 /*
460 * Suppres duplicate messages from the kernel within the specified interval
461 */
462 struct timeval ts;
463 struct timezone tzp;
464 time_t tt;
465
466 if(gettimeofday(&ts, &tzp) == 0) {
467 tt = (time_t)ts.tv_sec;
468 if ((flags == last_flags) && (tt < (last_time + DUPLICATE_INTERVAL))) {
469 return KERN_SUCCESS;
470 }
471 last_flags = flags;
472 last_time = tt;
473 }
474
475 syslog(LOG_INFO,
476 "audit_triggers() called within auditd with flags = %d\n",
477 flags);
478 /*
479 * XXX Message processing is done here
480 */
481 dirent = TAILQ_FIRST(&dir_q);
482 if(flags == AUDIT_TRIGGER_LOW_SPACE) {
483 if(dirent && (dirent->softlim != 1)) {
484 TAILQ_REMOVE(&dir_q, dirent, dirs);
485 /* add this node to the end of the list */
486 TAILQ_INSERT_TAIL(&dir_q, dirent, dirs);
487 audit_warn_soft(dirent->dirname);
488 dirent->softlim = 1;
489
490 if (TAILQ_NEXT(TAILQ_FIRST(&dir_q), dirs) != NULL && swap_audit_file() == -1) {
491 syslog(LOG_ERR, "Error swapping audit file\n");
492 }
493
494 /*
495 * check if the next dir has already reached its
496 * soft limit
497 */
498 dirent = TAILQ_FIRST(&dir_q);
499 if(dirent->softlim == 1) {
500 /* all dirs have reached their soft limit */
501 audit_warn_allsoft();
502 }
503 }
504 else {
505 /*
506 * Continue auditing to the current file
507 * Also generate an allsoft warning
508 * XXX do we want to do this ?
509 */
510 audit_warn_allsoft();
511 }
512 }
513 else if (flags == AUDIT_TRIGGER_FILE_FULL) {
514
515 /* delete current dir, go on to next */
516 TAILQ_REMOVE(&dir_q, dirent, dirs);
517 audit_warn_hard(dirent->dirname);
518 free(dirent->dirname);
519 free(dirent);
520
521 if(swap_audit_file() == -1) {
522 syslog(LOG_ERR, "Error swapping audit file in response to AUDIT_TRIGGER_FILE_FULL message\n");
523
524 /* Nowhere to write to */
525 audit_warn_allhard(++allhardcount);
526 }
527 }
528 return KERN_SUCCESS;
529 }
530
531 /*
532 * Reap our children.
533 */
534 static void
535 reap_children(void)
536 {
537 pid_t child;
538 int wstatus;
539
540 while ((child = waitpid(-1, &wstatus, WNOHANG)) > 0) {
541 if (wstatus) {
542 syslog(LOG_INFO, "warn process [pid=%d] %s %d.\n", child,
543 ((WIFEXITED(wstatus)) ?
544 "exited with non-zero status" :
545 "exited as a result of signal"),
546 ((WIFEXITED(wstatus)) ?
547 WEXITSTATUS(wstatus) :
548 WTERMSIG(wstatus)));
549 }
550 }
551 }
552
553 /*
554 * Handle an RPC call
555 */
556 boolean_t auditd_combined_server(
557 mach_msg_header_t *InHeadP,
558 mach_msg_header_t *OutHeadP)
559 {
560 mach_port_t local_port = InHeadP->msgh_local_port;
561
562 if (local_port == signal_port) {
563 int signo = InHeadP->msgh_id;
564 int ret;
565
566 if (SIGTERM == signo) {
567 ret = close_all();
568 exit (ret);
569 } else if (SIGCHLD == signo) {
570 reap_children();
571 return TRUE;
572 } else {
573 syslog(LOG_INFO, "Recevied signal %d.\n", signo);
574 return TRUE;
575 }
576 } else if (local_port == control_port) {
577 boolean_t result;
578
579 result = audit_triggers_server(InHeadP, OutHeadP);
580 if (!result)
581 result = auditd_control_server(InHeadP, OutHeadP);
582 return result;
583 }
584 syslog(LOG_INFO, "Recevied msg on bad port 0x%x.\n", local_port);
585 return FALSE;
586 }
587
588 void wait_on_audit_trigger(port_set)
589 mach_port_t port_set;
590 {
591 kern_return_t result;
592 result = mach_msg_server(auditd_combined_server, 4096, port_set, MACH_MSG_OPTION_NONE);
593 syslog(LOG_ERR, "abnormal exit\n");
594 }
595
596 /*
597 * Configure the audit controls in the kernel: the event to class mapping,
598 * kernel preselection mask, etc.
599 */
600 int config_audit_controls(long flags)
601 {
602 au_event_ent_t *ev;
603 au_evclass_map_t evc_map;
604 au_mask_t aumask;
605 int ctr = 0;
606 char naeventstr[NA_EVENT_STR_SIZE];
607
608 /* Process the audit event file, obtaining a class mapping for each
609 * event, and send that mapping into the kernel.
610 * XXX There's a risk here that the BSM library will return NULL
611 * for an event when it can't properly map it to a class. In that
612 * case, we will not process any events beyond the one that failed,
613 * but should. We need a way to get a count of the events.
614 */
615
616 setauevent();
617 while((ev = getauevent()) != NULL) {
618 evc_map.ec_number = ev->ae_number;
619 evc_map.ec_class = ev->ae_class;
620 if (auditon(A_SETCLASS, &evc_map, sizeof(au_evclass_map_t)) != 0) {
621 syslog(LOG_ERR,
622 "Failed to register class mapping for event %s",
623 ev->ae_name);
624 } else {
625 ctr++;
626 }
627 free(ev->ae_name);
628 free(ev->ae_desc);
629 free(ev);
630 }
631 endauevent();
632 if (ctr == 0)
633 syslog(LOG_ERR, "No events to class mappings registered.");
634 else
635 syslog(LOG_INFO, "Registered %d event to class mappings.", ctr);
636
637 /* Get the non-attributable event string and set the kernel mask
638 * from that.
639 */
640 if ((getacna(naeventstr, NA_EVENT_STR_SIZE) == 0)
641 && ( getauditflagsbin(naeventstr, &aumask) == 0)) {
642
643 if (auditon(A_SETKMASK, &aumask, sizeof(au_mask_t))){
644 syslog(LOG_ERR,
645 "Failed to register non-attributable event mask.");
646 } else {
647 syslog(LOG_INFO, "Registered non-attributable event mask.");
648 }
649
650 } else {
651 syslog(LOG_ERR,"Failed to obtain non-attributable event mask.");
652 }
653
654 /*
655 * Set the audit policy flags based on passed in parameter values.
656 */
657 if (auditon(A_SETPOLICY, &flags, sizeof(flags))) {
658 syslog(LOG_ERR,
659 "Failed to set audit policy.");
660 }
661
662 return 0;
663 }
664
665 void setup(long flags)
666 {
667 mach_msg_type_name_t poly;
668 int aufd;
669 token_t *tok;
670
671 /* Allocate a port set */
672 if (mach_port_allocate(mach_task_self(),
673 MACH_PORT_RIGHT_PORT_SET,
674 &port_set) != KERN_SUCCESS) {
675 syslog(LOG_ERR, "allocation of port set failed\n");
676 fail_exit();
677 }
678
679 /* Allocate a signal reflection port */
680 if (mach_port_allocate(mach_task_self(),
681 MACH_PORT_RIGHT_RECEIVE,
682 &signal_port) != KERN_SUCCESS ||
683 mach_port_move_member(mach_task_self(),
684 signal_port,
685 port_set) != KERN_SUCCESS) {
686 syslog(LOG_ERR, "allocation of signal port failed\n");
687 fail_exit();
688 }
689
690 /* Allocate a trigger port */
691 if (mach_port_allocate(mach_task_self(),
692 MACH_PORT_RIGHT_RECEIVE,
693 &control_port) != KERN_SUCCESS ||
694 mach_port_move_member(mach_task_self(),
695 control_port,
696 port_set) != KERN_SUCCESS) {
697 syslog(LOG_ERR, "allocation of trigger port failed\n");
698 fail_exit();
699 }
700
701 /* create a send right on our trigger port */
702 mach_port_extract_right(mach_task_self(), control_port,
703 MACH_MSG_TYPE_MAKE_SEND, &control_port, &poly);
704
705 TAILQ_INIT(&dir_q);
706
707 /* register the trigger port with the kernel */
708 if(host_set_audit_control_port(mach_host_self(), control_port) != KERN_SUCCESS) {
709 syslog(LOG_ERR, "Cannot set Mach control port\n");
710 fail_exit();
711 }
712 else {
713 syslog(LOG_ERR, "Mach control port registered\n");
714 }
715
716 if(read_control_file() == -1) {
717 syslog(LOG_ERR, "Error reading control file\n");
718 fail_exit();
719 }
720
721 /* Generate an audit record */
722 if((aufd = au_open()) == -1) {
723 syslog(LOG_ERR, "Could not create audit startup event.\n");
724 } else {
725
726 if((tok = au_to_text("auditd::Audit startup")) != NULL) {
727 au_write(aufd, tok);
728 }
729
730 if(au_close(aufd, 1, AUE_audit_startup) == -1) {
731 syslog(LOG_ERR, "Could not close audit startup event.\n");
732 }
733 }
734
735 if (config_audit_controls(flags) == 0)
736 syslog(LOG_INFO, "Initialization successful\n");
737 else
738 syslog(LOG_INFO, "Initialization failed\n");
739 }
740
741
742 int main(int argc, char **argv)
743 {
744 char ch;
745 long flags = AUDIT_CNT;
746 int debug = 0;
747
748 while ((ch = getopt(argc, argv, "dhs")) != -1) {
749 switch(ch) {
750
751 /* debug option */
752 case 'd':
753 debug = 1;
754 break;
755
756 /* fail-stop option */
757 case 's':
758 flags &= ~(AUDIT_CNT);
759 break;
760
761 /* halt-stop option */
762 case 'h':
763 flags |= AUDIT_AHLT;
764 break;
765
766 case '?':
767 default:
768 (void)fprintf(stderr,
769 "usage: auditd [-h | -s]\n");
770 exit(1);
771 }
772 }
773
774 openlog("auditd", LOG_CONS | LOG_PID, LOG_DAEMON);
775 syslog(LOG_INFO, "starting...\n");
776
777 if (debug == 0 && daemon(0, 0) == -1) {
778 syslog(LOG_ERR, "Failed to daemonize\n");
779 exit(1);
780 }
781
782 if(register_daemon() == -1) {
783 syslog(LOG_ERR, "Could not register as daemon\n");
784 exit(1);
785 }
786
787 setup(flags);
788 wait_on_audit_trigger(port_set);
789 syslog(LOG_INFO, "exiting.\n");
790
791 exit(1);
792 }