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