]>
Commit | Line | Data |
---|---|---|
b16a592a | 1 | /* |
a83ff38a | 2 | * Copyright (c) 2004-2011 Apple Inc. All rights reserved. |
b16a592a A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
db78b1bd | 5 | * |
5dd30d76 A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
db78b1bd | 12 | * |
b16a592a A |
13 | * The Original Code and all software distributed under the License are |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
5dd30d76 A |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
db78b1bd | 20 | * |
b16a592a A |
21 | * @APPLE_LICENSE_HEADER_END@ |
22 | */ | |
23 | ||
24 | #include <sys/types.h> | |
25 | #include <sys/stat.h> | |
26 | #include <sys/socket.h> | |
27 | #include <sys/un.h> | |
28 | #include <sys/uio.h> | |
29 | #include <stdio.h> | |
30 | #include <stdlib.h> | |
31 | #include <string.h> | |
32 | #include <unistd.h> | |
33 | #include <fcntl.h> | |
34 | #include <errno.h> | |
35 | #include <netdb.h> | |
36 | #include <notify.h> | |
c4fdb7d1 | 37 | #include <pthread.h> |
db78b1bd A |
38 | #include <sys/acl.h> |
39 | #include <membership.h> | |
b16a592a | 40 | #include "daemon.h" |
db78b1bd | 41 | #include <dispatch/private.h> |
b16a592a | 42 | |
c4fdb7d1 | 43 | #define _PATH_WALL "/usr/bin/wall" |
b16a592a A |
44 | #define _PATH_ASL_CONF "/etc/asl.conf" |
45 | #define MY_ID "asl_action" | |
46 | ||
db78b1bd A |
47 | #define MAX_FAILURES 5 |
48 | ||
c4fdb7d1 A |
49 | #define ACTION_NONE 0 |
50 | #define ACTION_IGNORE 1 | |
51 | #define ACTION_NOTIFY 2 | |
52 | #define ACTION_BROADCAST 3 | |
53 | #define ACTION_ACCESS 4 | |
db78b1bd A |
54 | #define ACTION_ASL_STORE 5 /* Save in main ASL Database */ |
55 | #define ACTION_ASL_FILE 6 /* Save in an ASL format data file */ | |
56 | #define ACTION_ASL_DIR 7 /* Save in an ASL directory */ | |
57 | #define ACTION_FILE 8 | |
58 | #define ACTION_FORWARD 9 | |
c4fdb7d1 | 59 | |
b16a592a A |
60 | #define forever for(;;) |
61 | ||
db78b1bd A |
62 | #define ACT_FLAG_HAS_LOGGED 0x80000000 |
63 | #define ACT_FLAG_CLEAR_LOGGED 0x7fffffff | |
64 | ||
a83ff38a A |
65 | #define ACT_STORE_FLAG_STAY_OPEN 0x00000001 |
66 | #define ACT_STORE_FLAG_CONTINUE 0x00000002 | |
c4fdb7d1 | 67 | |
db78b1bd A |
68 | #define ACT_FILE_FLAG_DUP_SUPRESS 0x00000001 |
69 | #define ACT_FILE_FLAG_ROTATE 0x00000002 | |
70 | ||
71 | static dispatch_queue_t asl_action_queue; | |
72 | static time_t last_file_day; | |
b16a592a | 73 | |
c4fdb7d1 | 74 | typedef struct action_rule_s |
b16a592a A |
75 | { |
76 | asl_msg_t *query; | |
c4fdb7d1 | 77 | int action; |
b16a592a | 78 | char *options; |
5dd30d76 | 79 | void *data; |
c4fdb7d1 A |
80 | struct action_rule_s *next; |
81 | } action_rule_t; | |
82 | ||
83 | struct store_data | |
84 | { | |
85 | asl_file_t *store; | |
86 | FILE *storedata; | |
87 | char *dir; | |
88 | char *path; | |
89 | mode_t mode; | |
90 | uid_t uid; | |
91 | gid_t gid; | |
92 | uint64_t next_id; | |
db78b1bd | 93 | uint32_t fails; |
c4fdb7d1 | 94 | uint32_t flags; |
db78b1bd | 95 | uint32_t refcount; |
c4fdb7d1 A |
96 | uint32_t p_year; |
97 | uint32_t p_month; | |
98 | uint32_t p_day; | |
b16a592a A |
99 | }; |
100 | ||
db78b1bd A |
101 | struct file_data |
102 | { | |
103 | int fd; | |
104 | char *path; | |
105 | char *fmt; | |
106 | const char *tfmt; | |
107 | mode_t mode; | |
108 | uid_t *uid; | |
109 | uint32_t nuid; | |
110 | gid_t *gid; | |
111 | uint32_t ngid; | |
112 | size_t max_size; | |
113 | uint32_t fails; | |
114 | uint32_t flags; | |
115 | uint32_t refcount; | |
116 | time_t stamp; | |
117 | uint32_t last_hash; | |
118 | uint32_t last_count; | |
119 | time_t last_time; | |
120 | dispatch_source_t dup_timer; | |
121 | char *last_msg; | |
122 | }; | |
123 | ||
c4fdb7d1 A |
124 | static action_rule_t *asl_action_rule = NULL; |
125 | static action_rule_t *asl_datastore_rule = NULL; | |
b16a592a | 126 | |
c4fdb7d1 | 127 | static int _parse_config_file(const char *); |
a83ff38a | 128 | extern void db_save_message(aslmsg m); |
c4fdb7d1 | 129 | |
db78b1bd A |
130 | /* forward */ |
131 | int _act_file_open(struct file_data *fdata); | |
132 | static void _act_file_init(action_rule_t *r); | |
133 | static void _act_store_init(action_rule_t *r); | |
134 | ||
c4fdb7d1 A |
135 | static char * |
136 | _next_word(char **s) | |
137 | { | |
138 | char *a, *p, *e, *out; | |
139 | int quote, len; | |
140 | ||
141 | if (s == NULL) return NULL; | |
142 | if (*s == NULL) return NULL; | |
143 | ||
144 | quote = 0; | |
145 | ||
146 | p = *s; | |
147 | a = p; | |
148 | e = p; | |
149 | ||
150 | while (*p != '\0') | |
151 | { | |
152 | if (*p == '\\') | |
153 | { | |
154 | p++; | |
155 | e = p; | |
156 | ||
157 | if (*p == '\0') | |
158 | { | |
159 | p--; | |
160 | break; | |
161 | } | |
162 | ||
163 | p++; | |
164 | e = p; | |
165 | continue; | |
166 | } | |
167 | ||
168 | if (*p == '"') | |
169 | { | |
170 | if (quote == 0) quote = 1; | |
171 | else quote = 0; | |
172 | } | |
173 | ||
174 | if (((*p == ' ') || (*p == '\t')) && (quote == 0)) | |
175 | { | |
176 | e = p + 1; | |
177 | break; | |
178 | } | |
179 | ||
180 | p++; | |
181 | e = p; | |
182 | } | |
183 | ||
184 | *s = e; | |
185 | ||
186 | len = p - a; | |
187 | if (len == 0) return NULL; | |
188 | ||
189 | out = malloc(len + 1); | |
190 | if (out == NULL) return NULL; | |
191 | ||
192 | memcpy(out, a, len); | |
193 | out[len] = '\0'; | |
194 | return out; | |
195 | } | |
b16a592a | 196 | |
b16a592a A |
197 | /* |
198 | * Config File format: | |
c4fdb7d1 A |
199 | * Set parameter rule - initializes a parameter. |
200 | * = param args... | |
201 | * Query rule - if a message matches the query, then the action is invoked. | |
202 | * The rule may be identified by either "?" or "Q". | |
db78b1bd A |
203 | * ? [k v] [k v] ... action args... |
204 | * Q [k v] [k v] ... action args... | |
c4fdb7d1 A |
205 | * Universal match rule - the action is invoked for all messages |
206 | * * action args... | |
b16a592a A |
207 | */ |
208 | ||
209 | /* Skip over query */ | |
210 | static char * | |
211 | _find_action(char *s) | |
212 | { | |
213 | char *p; | |
214 | ||
215 | p = s; | |
216 | if (p == NULL) return NULL; | |
c4fdb7d1 | 217 | if ((*p != 'Q') && (*p != '?') && (*p != '*')) return NULL; |
b16a592a A |
218 | |
219 | p++; | |
220 | ||
221 | forever | |
222 | { | |
223 | /* Find next [ */ | |
224 | while ((*p == ' ') || (*p == '\t')) p++; | |
225 | ||
226 | if (*p == '\0') return NULL; | |
227 | if (*p != '[') return p; | |
228 | ||
229 | /* skip to closing ] */ | |
230 | while (*p != ']') | |
231 | { | |
232 | p++; | |
233 | if (*p == '\\') | |
234 | { | |
235 | p++; | |
236 | if (*p == ']') p++; | |
237 | } | |
238 | } | |
239 | ||
240 | if (*p == ']') p++; | |
241 | } | |
242 | ||
243 | return NULL; | |
244 | } | |
245 | ||
246 | static int | |
c4fdb7d1 | 247 | _parse_query_action(char *s) |
b16a592a A |
248 | { |
249 | char *act, *p; | |
c4fdb7d1 | 250 | action_rule_t *out, *rule; |
b16a592a A |
251 | |
252 | act = _find_action(s); | |
b16a592a | 253 | if (act == NULL) return -1; |
c4fdb7d1 A |
254 | |
255 | out = (action_rule_t *)calloc(1, sizeof(action_rule_t)); | |
b16a592a A |
256 | if (out == NULL) return -1; |
257 | ||
258 | p = strchr(act, ' '); | |
259 | if (p != NULL) *p = '\0'; | |
b16a592a | 260 | |
c4fdb7d1 A |
261 | if (!strcasecmp(act, "ignore")) out->action = ACTION_IGNORE; |
262 | else if (!strcasecmp(act, "notify")) out->action = ACTION_NOTIFY; | |
263 | else if (!strcasecmp(act, "broadcast")) out->action = ACTION_BROADCAST; | |
264 | else if (!strcasecmp(act, "access")) out->action = ACTION_ACCESS; | |
db78b1bd A |
265 | else if (!strcasecmp(act, "store")) out->action = ACTION_ASL_STORE; |
266 | else if (!strcasecmp(act, "save")) out->action = ACTION_ASL_STORE; | |
267 | else if (!strcasecmp(act, "store_file")) out->action = ACTION_ASL_FILE; | |
268 | else if (!strcasecmp(act, "store_directory")) out->action = ACTION_ASL_DIR; | |
269 | else if (!strcasecmp(act, "store_dir")) out->action = ACTION_ASL_DIR; | |
270 | else if (!strcasecmp(act, "file")) out->action = ACTION_FILE; | |
c4fdb7d1 | 271 | else if (!strcasecmp(act, "forward")) out->action = ACTION_FORWARD; |
b16a592a A |
272 | |
273 | if (p != NULL) | |
274 | { | |
275 | out->options = strdup(p+1); | |
276 | ||
277 | if (out->options == NULL) | |
278 | { | |
b16a592a A |
279 | free(out); |
280 | return -1; | |
281 | } | |
282 | } | |
283 | ||
284 | p = act - 1; | |
285 | ||
286 | *p = '\0'; | |
c4fdb7d1 | 287 | |
a83ff38a | 288 | if (s[0] == '*') out->query = asl_msg_new(ASL_TYPE_QUERY); |
c4fdb7d1 A |
289 | else |
290 | { | |
291 | s[0] = 'Q'; | |
292 | out->query = asl_msg_from_string(s); | |
293 | } | |
b16a592a A |
294 | |
295 | if (out->query == NULL) | |
296 | { | |
c4fdb7d1 | 297 | asldebug("out->query is NULL (ERROR)\n"); |
a83ff38a | 298 | free(out->options); |
b16a592a A |
299 | free(out); |
300 | return -1; | |
301 | } | |
302 | ||
db78b1bd A |
303 | /* store /some/path means save to a file */ |
304 | if ((out->action == ACTION_ASL_STORE) && (out->options != NULL)) out->action = ACTION_ASL_FILE; | |
305 | ||
306 | if (out->action == ACTION_FILE) _act_file_init(out); | |
307 | else if ((out->action == ACTION_ASL_FILE) || (out->action == ACTION_ASL_DIR)) _act_store_init(out); | |
308 | ||
309 | if (out->action == ACTION_ASL_STORE) | |
c4fdb7d1 | 310 | { |
db78b1bd | 311 | asldebug("action = ACTION_ASL_STORE\n"); |
c4fdb7d1 A |
312 | if (asl_datastore_rule == NULL) asl_datastore_rule = out; |
313 | else | |
314 | { | |
315 | for (rule = asl_datastore_rule; rule->next != NULL; rule = rule->next); | |
316 | rule->next = out; | |
317 | } | |
318 | } | |
319 | else | |
320 | { | |
321 | asldebug("action = %d options = %s\n", out->action, out->options); | |
322 | if (asl_action_rule == NULL) asl_action_rule = out; | |
323 | else | |
324 | { | |
325 | for (rule = asl_action_rule; rule->next != NULL; rule = rule->next); | |
326 | rule->next = out; | |
327 | } | |
328 | } | |
b16a592a A |
329 | |
330 | return 0; | |
331 | } | |
332 | ||
c4fdb7d1 A |
333 | static int |
334 | _parse_line(char *s) | |
335 | { | |
336 | char *str; | |
337 | int status; | |
5dd30d76 | 338 | |
c4fdb7d1 A |
339 | if (s == NULL) return -1; |
340 | while ((*s == ' ') || (*s == '\t')) s++; | |
5dd30d76 | 341 | |
c4fdb7d1 A |
342 | /* First non-whitespace char is the rule type */ |
343 | switch (*s) | |
344 | { | |
345 | case '\0': | |
346 | case '#': | |
347 | { | |
348 | /* Blank Line or Comment */ | |
349 | return 0; | |
350 | } | |
351 | case 'Q': | |
352 | case '?': | |
353 | case '*': | |
354 | { | |
355 | /* Query-match action */ | |
356 | status = _parse_query_action(s); | |
357 | break; | |
358 | } | |
359 | case '=': | |
360 | { | |
361 | /* Set parameter */ | |
db78b1bd | 362 | status = control_set_param(s); |
c4fdb7d1 A |
363 | break; |
364 | } | |
365 | default: | |
366 | { | |
367 | status = -1; | |
368 | break; | |
369 | } | |
370 | } | |
371 | ||
372 | if (status != 0) | |
373 | { | |
374 | str = NULL; | |
375 | asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [%s Ignoring unrecognized entry in %s: %s] [%s 0] [%s 0] [Facility syslog]", | |
376 | ASL_KEY_SENDER, | |
377 | ASL_KEY_LEVEL, ASL_LEVEL_ERR, | |
378 | ASL_KEY_PID, getpid(), | |
379 | ASL_KEY_MSG, _PATH_ASL_CONF, s, | |
380 | ASL_KEY_UID, ASL_KEY_GID); | |
381 | ||
db78b1bd | 382 | internal_log_message(str); |
a83ff38a | 383 | free(str); |
c4fdb7d1 A |
384 | } |
385 | ||
386 | return status; | |
5dd30d76 A |
387 | } |
388 | ||
db78b1bd | 389 | static void |
c4fdb7d1 | 390 | _act_notify(action_rule_t *r) |
b16a592a A |
391 | { |
392 | if (r == NULL) return; | |
393 | if (r->options == NULL) return; | |
c4fdb7d1 | 394 | |
b16a592a A |
395 | notify_post(r->options); |
396 | } | |
397 | ||
5dd30d76 | 398 | static void |
a83ff38a | 399 | _act_broadcast(action_rule_t *r, aslmsg msg) |
c4fdb7d1 | 400 | { |
a83ff38a | 401 | #ifndef CONFIG_IPHONE |
c4fdb7d1 A |
402 | FILE *pw; |
403 | const char *val; | |
404 | ||
405 | if (r == NULL) return; | |
406 | if (msg == NULL) return; | |
407 | ||
408 | val = r->options; | |
409 | if (val == NULL) val = asl_get(msg, ASL_KEY_MSG); | |
410 | if (val == NULL) return; | |
411 | ||
412 | pw = popen(_PATH_WALL, "w"); | |
413 | if (pw < 0) | |
414 | { | |
415 | asldebug("%s: error sending wall message: %s\n", MY_ID, strerror(errno)); | |
416 | return; | |
417 | } | |
418 | ||
419 | fprintf(pw, "%s", val); | |
420 | pclose(pw); | |
a83ff38a | 421 | #endif |
c4fdb7d1 A |
422 | } |
423 | ||
424 | static void | |
a83ff38a | 425 | _act_access_control(action_rule_t *r, aslmsg msg) |
5dd30d76 A |
426 | { |
427 | int32_t ruid, rgid; | |
428 | char *p; | |
429 | ||
430 | ruid = atoi(r->options); | |
431 | rgid = -1; | |
432 | p = strchr(r->options, ' '); | |
433 | if (p == NULL) p = strchr(r->options, '\t'); | |
434 | if (p != NULL) | |
435 | { | |
436 | *p = '\0'; | |
437 | p++; | |
438 | rgid = atoi(p); | |
439 | } | |
440 | ||
a83ff38a | 441 | if (ruid != -1) asl_set(msg, ASL_KEY_READ_UID, r->options); |
5dd30d76 A |
442 | if (p != NULL) |
443 | { | |
a83ff38a | 444 | if (rgid != -1) asl_set(msg, ASL_KEY_READ_GID, p); |
5dd30d76 A |
445 | p--; |
446 | *p = ' '; | |
447 | } | |
448 | } | |
449 | ||
c4fdb7d1 A |
450 | static uint32_t |
451 | _act_store_file_setup(struct store_data *sd) | |
452 | { | |
453 | uint32_t status; | |
5dd30d76 | 454 | |
c4fdb7d1 A |
455 | if (sd == NULL) return ASL_STATUS_INVALID_STORE; |
456 | if (sd->store == NULL) return ASL_STATUS_INVALID_STORE; | |
457 | if (sd->store->store == NULL) return ASL_STATUS_INVALID_STORE; | |
458 | ||
459 | status = asl_file_read_set_position(sd->store, ASL_FILE_POSITION_LAST); | |
460 | if (status != ASL_STATUS_OK) return status; | |
461 | ||
462 | sd->next_id = sd->store->cursor_xid + 1; | |
463 | if (fseek(sd->store->store, 0, SEEK_END) != 0) return ASL_STATUS_ACCESS_DENIED; | |
464 | ||
465 | return ASL_STATUS_OK; | |
466 | } | |
467 | ||
468 | static uint32_t | |
469 | _act_store_dir_setup(struct store_data *sd, time_t tick) | |
b16a592a | 470 | { |
c4fdb7d1 A |
471 | struct tm ctm; |
472 | char *path; | |
473 | struct stat sb; | |
474 | uint64_t xid; | |
475 | int status; | |
a83ff38a | 476 | mode_t mask; |
c4fdb7d1 A |
477 | |
478 | if (sd == NULL) return ASL_STATUS_INVALID_STORE; | |
479 | if (sd->dir == NULL) return ASL_STATUS_INVALID_STORE; | |
480 | ||
481 | /* get / set message id from StoreData file */ | |
482 | xid = 0; | |
483 | ||
484 | if (sd->storedata == NULL) | |
485 | { | |
a83ff38a A |
486 | memset(&sb, 0, sizeof(struct stat)); |
487 | status = stat(sd->dir, &sb); | |
488 | if (status == 0) | |
489 | { | |
490 | /* must be a directory */ | |
491 | if (!S_ISDIR(sb.st_mode)) return ASL_STATUS_INVALID_STORE; | |
492 | } | |
493 | else if (errno == ENOENT) | |
494 | { | |
495 | /* doesn't exist - create it */ | |
496 | mask = umask(0); | |
497 | status = mkdir(sd->dir, sd->mode); | |
498 | umask(mask); | |
db78b1bd | 499 | |
a83ff38a | 500 | if (status != 0) return ASL_STATUS_WRITE_FAILED; |
db78b1bd | 501 | if (chown(sd->dir, sd->uid, sd->gid) != 0) return ASL_STATUS_WRITE_FAILED; |
a83ff38a A |
502 | } |
503 | else | |
504 | { | |
505 | /* Unexpected stat error */ | |
506 | return ASL_STATUS_FAILED; | |
507 | } | |
db78b1bd | 508 | |
c4fdb7d1 A |
509 | path = NULL; |
510 | asprintf(&path, "%s/%s", sd->dir, FILE_ASL_STORE_DATA); | |
511 | if (path == NULL) return ASL_STATUS_NO_MEMORY; | |
512 | ||
513 | memset(&sb, 0, sizeof(struct stat)); | |
514 | status = stat(path, &sb); | |
515 | if (status == 0) | |
516 | { | |
517 | /* StoreData exists: open and read last xid */ | |
518 | sd->storedata = fopen(path, "r+"); | |
519 | if (sd->storedata == NULL) | |
520 | { | |
521 | free(path); | |
522 | return ASL_STATUS_FAILED; | |
523 | } | |
524 | ||
525 | if (fread(&xid, sizeof(uint64_t), 1, sd->storedata) != 1) | |
526 | { | |
527 | free(path); | |
528 | fclose(sd->storedata); | |
529 | sd->storedata = NULL; | |
530 | return ASL_STATUS_READ_FAILED; | |
531 | } | |
532 | } | |
a83ff38a | 533 | else if (errno == ENOENT) |
c4fdb7d1 A |
534 | { |
535 | /* StoreData does not exist: create it */ | |
536 | sd->storedata = fopen(path, "w"); | |
537 | if (sd->storedata == NULL) | |
538 | { | |
539 | free(path); | |
540 | return ASL_STATUS_FAILED; | |
541 | } | |
a83ff38a | 542 | |
db78b1bd | 543 | if (chown(path, sd->uid, sd->gid) != 0) |
a83ff38a | 544 | { |
db78b1bd A |
545 | free(path); |
546 | return ASL_STATUS_WRITE_FAILED; | |
a83ff38a A |
547 | } |
548 | } | |
549 | else | |
550 | { | |
551 | /* Unexpected stat error */ | |
552 | free(path); | |
553 | return ASL_STATUS_FAILED; | |
c4fdb7d1 | 554 | } |
b16a592a | 555 | |
c4fdb7d1 A |
556 | free(path); |
557 | } | |
558 | else | |
b16a592a | 559 | { |
c4fdb7d1 A |
560 | rewind(sd->storedata); |
561 | if (fread(&xid, sizeof(uint64_t), 1, sd->storedata) != 1) | |
562 | { | |
563 | fclose(sd->storedata); | |
564 | sd->storedata = NULL; | |
565 | return ASL_STATUS_READ_FAILED; | |
566 | } | |
b16a592a A |
567 | } |
568 | ||
c4fdb7d1 A |
569 | xid = asl_core_ntohq(xid); |
570 | xid++; | |
571 | sd->next_id = xid; | |
572 | ||
573 | xid = asl_core_htonq(xid); | |
574 | rewind(sd->storedata); | |
575 | status = fwrite(&xid, sizeof(uint64_t), 1, sd->storedata); | |
576 | if (status != 1) | |
577 | { | |
578 | fclose(sd->storedata); | |
579 | sd->storedata = NULL; | |
580 | return ASL_STATUS_WRITE_FAILED; | |
581 | } | |
582 | ||
583 | if ((sd->flags & ACT_STORE_FLAG_STAY_OPEN) == 0) | |
584 | { | |
585 | fclose(sd->storedata); | |
586 | sd->storedata = NULL; | |
587 | } | |
588 | ||
589 | memset(&ctm, 0, sizeof(struct tm)); | |
590 | ||
591 | if (localtime_r((const time_t *)&tick, &ctm) == NULL) return ASL_STATUS_FAILED; | |
592 | if ((sd->p_year == ctm.tm_year) && (sd->p_month == ctm.tm_mon) && (sd->p_day == ctm.tm_mday) && (sd->path != NULL)) return ASL_STATUS_OK; | |
593 | ||
594 | if (sd->store != NULL) asl_file_close(sd->store); | |
595 | ||
596 | sd->p_year = 0; | |
597 | sd->p_month = 0; | |
598 | sd->p_day = 0; | |
599 | ||
a83ff38a | 600 | free(sd->path); |
c4fdb7d1 A |
601 | sd->path = NULL; |
602 | ||
603 | asprintf(&(sd->path), "%s/%d.%02d.%02d.asl", sd->dir, ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday); | |
604 | if (sd->path == NULL) return ASL_STATUS_NO_MEMORY; | |
605 | ||
606 | sd->p_year = ctm.tm_year; | |
607 | sd->p_month = ctm.tm_mon; | |
608 | sd->p_day = ctm.tm_mday; | |
609 | ||
610 | return ASL_STATUS_OK; | |
611 | } | |
612 | ||
613 | static void | |
db78b1bd | 614 | _act_store_init(action_rule_t *r) |
c4fdb7d1 | 615 | { |
db78b1bd A |
616 | struct store_data *sd, *xd; |
617 | char *str, *opts, *p, *path; | |
618 | action_rule_t *x; | |
c4fdb7d1 | 619 | |
db78b1bd A |
620 | /* check if the store data is already set up */ |
621 | if (r->data != NULL) return; | |
c4fdb7d1 | 622 | |
db78b1bd A |
623 | opts = r->options; |
624 | path = _next_word(&opts); | |
c4fdb7d1 | 625 | |
db78b1bd | 626 | if ((path == NULL) || (path[0] != '/')) |
c4fdb7d1 | 627 | { |
db78b1bd A |
628 | str = NULL; |
629 | asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Invalid path for \"%s\" action: %s]", | |
630 | ASL_KEY_SENDER, | |
631 | ASL_KEY_LEVEL, ASL_LEVEL_ERR, | |
632 | ASL_KEY_PID, getpid(), | |
633 | ASL_KEY_MSG, | |
634 | (r->action == ACTION_ASL_FILE) ? "store" : "store_directory", | |
635 | (path == NULL) ? "no path specified" : path); | |
c4fdb7d1 | 636 | |
db78b1bd A |
637 | internal_log_message(str); |
638 | free(str); | |
639 | r->action = ACTION_NONE; | |
640 | free(path); | |
641 | return; | |
642 | } | |
c4fdb7d1 | 643 | |
db78b1bd A |
644 | /* check if a previous rule has set up this path (ACTION_ASL_FILE) or dir (ACTION_ASL_DIR) */ |
645 | for (x = asl_action_rule; x != NULL; x = x->next) | |
646 | { | |
647 | if ((x->action == r->action) && (x->data != NULL)) | |
c4fdb7d1 | 648 | { |
db78b1bd A |
649 | xd = (struct store_data *)x->data; |
650 | p = xd->path; | |
651 | if (r->action == ACTION_ASL_DIR) p = xd->dir; | |
a83ff38a | 652 | |
db78b1bd | 653 | if ((p != NULL) && (!strcmp(path, p))) |
c4fdb7d1 | 654 | { |
db78b1bd A |
655 | free(path); |
656 | xd->refcount++; | |
657 | r->data = x->data; | |
c4fdb7d1 A |
658 | return; |
659 | } | |
660 | } | |
db78b1bd A |
661 | } |
662 | ||
663 | /* set up store data */ | |
664 | sd = (struct store_data *)calloc(1, sizeof(struct store_data)); | |
665 | if (sd == NULL) return; | |
c4fdb7d1 | 666 | |
db78b1bd A |
667 | sd->refcount = 1; |
668 | sd->mode = 0755; | |
669 | sd->next_id = 0; | |
670 | sd->uid = 0; | |
671 | sd->gid = 0; | |
672 | sd->flags = 0; | |
c4fdb7d1 | 673 | |
db78b1bd A |
674 | if (r->action == ACTION_ASL_DIR) sd->dir = path; |
675 | else sd->path = path; | |
676 | ||
677 | while (NULL != (p = _next_word(&opts))) | |
678 | { | |
679 | if (!strcmp(p, "stayopen")) | |
c4fdb7d1 | 680 | { |
db78b1bd A |
681 | sd->flags |= ACT_STORE_FLAG_STAY_OPEN; |
682 | } | |
683 | else if (!strcmp(p, "continue")) | |
684 | { | |
685 | sd->flags |= ACT_STORE_FLAG_CONTINUE; | |
686 | } | |
687 | else if (!strncmp(p, "mode=", 5)) sd->mode = strtol(p+5, NULL, 0); | |
688 | else if (!strncmp(p, "uid=", 4)) sd->uid = atoi(p+4); | |
689 | else if (!strncmp(p, "gid=", 4)) sd->gid = atoi(p+4); | |
c4fdb7d1 | 690 | |
db78b1bd A |
691 | free(p); |
692 | p = NULL; | |
693 | } | |
c4fdb7d1 | 694 | |
db78b1bd A |
695 | r->data = sd; |
696 | } | |
c4fdb7d1 | 697 | |
db78b1bd A |
698 | /* |
699 | * Save a message to an ASL format file (ACTION_ASL_FILE) | |
700 | * or to an ASL directory (ACTION_ASL_DIR). | |
701 | */ | |
702 | static void | |
703 | _act_store(action_rule_t *r, aslmsg msg) | |
704 | { | |
705 | struct store_data *sd; | |
706 | asl_file_t *s; | |
707 | uint32_t status; | |
708 | uint64_t mid; | |
709 | mode_t mask; | |
710 | char *str, *opts; | |
711 | const char *val; | |
712 | time_t tick; | |
c4fdb7d1 | 713 | |
db78b1bd | 714 | s = NULL; |
c4fdb7d1 | 715 | |
db78b1bd | 716 | if (r->data == NULL) return; |
c4fdb7d1 | 717 | |
db78b1bd | 718 | sd = (struct store_data *)r->data; |
c4fdb7d1 | 719 | |
db78b1bd A |
720 | if (sd->flags & ACT_FLAG_HAS_LOGGED) return; |
721 | sd->flags |= ACT_FLAG_HAS_LOGGED; | |
c4fdb7d1 | 722 | |
db78b1bd | 723 | if (r->action == ACTION_ASL_DIR) |
c4fdb7d1 A |
724 | { |
725 | val = asl_get(msg, ASL_KEY_TIME); | |
726 | if (val == NULL) return; | |
727 | ||
728 | tick = atol(val); | |
729 | status = _act_store_dir_setup(sd, tick); | |
730 | if (status != ASL_STATUS_OK) | |
731 | { | |
732 | asldebug("_act_store_dir_setup %s failed: %s\n", sd->path, asl_core_error(status)); | |
733 | ||
db78b1bd A |
734 | sd->fails++; |
735 | ||
736 | /* disable further activity after multiple failures */ | |
737 | if (sd->fails > MAX_FAILURES) | |
738 | { | |
739 | char *str = NULL; | |
740 | asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Disabling writes to path %s following %u failures (%s)]", | |
741 | ASL_KEY_SENDER, | |
742 | ASL_KEY_LEVEL, ASL_LEVEL_ERR, | |
743 | ASL_KEY_PID, getpid(), | |
744 | ASL_KEY_MSG, sd->path, sd->fails, asl_core_error(status)); | |
745 | ||
746 | internal_log_message(str); | |
747 | free(str); | |
748 | ||
749 | asl_file_close(sd->store); | |
750 | sd->store = NULL; | |
751 | r->action = ACTION_NONE; | |
752 | return; | |
753 | } | |
754 | } | |
755 | else | |
756 | { | |
757 | sd->fails = 0; | |
c4fdb7d1 A |
758 | } |
759 | } | |
760 | ||
761 | if (sd->store == NULL) | |
762 | { | |
763 | s = NULL; | |
a83ff38a A |
764 | |
765 | mask = umask(0); | |
766 | status = asl_file_open_write(sd->path, (sd->mode & 0666), sd->uid, sd->gid, &s); | |
767 | umask(mask); | |
768 | ||
c4fdb7d1 A |
769 | if ((status != ASL_STATUS_OK) || (s == NULL)) |
770 | { | |
771 | asldebug("asl_file_open_write %s failed: %s\n", sd->path, asl_core_error(status)); | |
772 | ||
db78b1bd A |
773 | sd->fails++; |
774 | ||
775 | /* disable further activity after multiple failures */ | |
776 | if (sd->fails > MAX_FAILURES) | |
777 | { | |
778 | char *str = NULL; | |
779 | asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Disabling writes to path %s following %u failures (%s)]", | |
780 | ASL_KEY_SENDER, | |
781 | ASL_KEY_LEVEL, ASL_LEVEL_ERR, | |
782 | ASL_KEY_PID, getpid(), | |
783 | ASL_KEY_MSG, sd->path, sd->fails, asl_core_error(status)); | |
784 | ||
785 | internal_log_message(str); | |
786 | free(str); | |
787 | ||
788 | asl_file_close(sd->store); | |
789 | sd->store = NULL; | |
790 | r->action = ACTION_NONE; | |
791 | return; | |
792 | } | |
793 | } | |
794 | else if (status == ASL_STATUS_OK) | |
795 | { | |
796 | sd->fails = 0; | |
c4fdb7d1 A |
797 | } |
798 | ||
799 | sd->store = s; | |
800 | } | |
801 | ||
db78b1bd | 802 | if (r->action != ACTION_ASL_DIR) |
c4fdb7d1 A |
803 | { |
804 | status = _act_store_file_setup(sd); | |
805 | if (status != ASL_STATUS_OK) | |
806 | { | |
807 | asldebug("_act_store_file_setup %s failed: %s\n", sd->path, asl_core_error(status)); | |
808 | ||
db78b1bd A |
809 | sd->fails++; |
810 | ||
811 | /* disable further activity after multiple failures */ | |
812 | if (sd->fails > MAX_FAILURES) | |
813 | { | |
814 | char *str = NULL; | |
815 | asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Disabling writes to path %s following %u failures (%s)]", | |
816 | ASL_KEY_SENDER, | |
817 | ASL_KEY_LEVEL, ASL_LEVEL_ERR, | |
818 | ASL_KEY_PID, getpid(), | |
819 | ASL_KEY_MSG, sd->path, sd->fails, asl_core_error(status)); | |
820 | ||
821 | internal_log_message(str); | |
822 | free(str); | |
823 | ||
824 | asl_file_close(sd->store); | |
825 | sd->store = NULL; | |
826 | r->action = ACTION_NONE; | |
827 | return; | |
828 | } | |
829 | } | |
830 | else | |
831 | { | |
832 | sd->fails = 0; | |
c4fdb7d1 A |
833 | } |
834 | } | |
835 | ||
836 | mid = sd->next_id; | |
837 | ||
838 | status = asl_file_save(sd->store, msg, &mid); | |
839 | if (status != ASL_STATUS_OK) | |
840 | { | |
841 | asldebug("asl_file_save %s failed: %s\n", sd->path, asl_core_error(status)); | |
842 | ||
db78b1bd A |
843 | sd->fails++; |
844 | ||
845 | /* disable further activity after multiple failures */ | |
846 | if (sd->fails > MAX_FAILURES) | |
847 | { | |
848 | char *str = NULL; | |
849 | asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Disabling writes to path %s following %u failures (%s)]", | |
850 | ASL_KEY_SENDER, | |
851 | ASL_KEY_LEVEL, ASL_LEVEL_ERR, | |
852 | ASL_KEY_PID, getpid(), | |
853 | ASL_KEY_MSG, sd->path, sd->fails, asl_core_error(status)); | |
854 | ||
855 | internal_log_message(str); | |
856 | free(str); | |
857 | ||
858 | asl_file_close(sd->store); | |
859 | sd->store = NULL; | |
860 | r->action = ACTION_NONE; | |
861 | return; | |
862 | } | |
863 | } | |
864 | else | |
865 | { | |
866 | sd->fails = 0; | |
c4fdb7d1 A |
867 | } |
868 | ||
869 | if ((sd->flags & ACT_STORE_FLAG_STAY_OPEN) == 0) | |
870 | { | |
871 | asl_file_close(sd->store); | |
872 | sd->store = NULL; | |
873 | } | |
874 | ||
a83ff38a | 875 | if ((sd->flags & ACT_STORE_FLAG_CONTINUE) == 0) |
c4fdb7d1 A |
876 | { |
877 | opts = (char *)asl_get(msg, ASL_KEY_OPTION); | |
878 | if (opts == NULL) | |
879 | { | |
880 | asl_set(msg, ASL_KEY_OPTION, ASL_OPT_IGNORE); | |
881 | } | |
882 | else | |
883 | { | |
884 | str = NULL; | |
885 | asprintf(&str, "%s %s", ASL_OPT_IGNORE, opts); | |
886 | if (str != NULL) | |
887 | { | |
888 | asl_set(msg, ASL_KEY_OPTION, str); | |
889 | free(str); | |
890 | } | |
891 | } | |
892 | } | |
893 | } | |
894 | ||
db78b1bd A |
895 | static int |
896 | _act_file_send_repeat_msg(struct file_data *fdata) | |
c4fdb7d1 | 897 | { |
db78b1bd A |
898 | char vt[32], *msg; |
899 | int len, status, closeit; | |
900 | time_t now = time(NULL); | |
c4fdb7d1 | 901 | |
db78b1bd A |
902 | if (fdata == NULL) return -1; |
903 | ||
904 | free(fdata->last_msg); | |
905 | fdata->last_msg = NULL; | |
906 | ||
907 | if (fdata->last_count == 0) return 0; | |
908 | ||
909 | /* stop the timer */ | |
910 | dispatch_suspend(fdata->dup_timer); | |
911 | ||
912 | memset(vt, 0, sizeof(vt)); | |
913 | ctime_r(&now, vt); | |
914 | vt[19] = '\0'; | |
c4fdb7d1 | 915 | |
db78b1bd A |
916 | msg = NULL; |
917 | asprintf(&msg, "%s --- last message repeated %u time%s ---\n", vt + 4, fdata->last_count, (fdata->last_count == 1) ? "" : "s"); | |
918 | fdata->last_count = 0; | |
919 | if (msg == NULL) return -1; | |
920 | ||
921 | closeit = 0; | |
922 | if (fdata->fd < 0) | |
c4fdb7d1 | 923 | { |
db78b1bd A |
924 | closeit = 1; |
925 | fdata->fd = _act_file_open(fdata); | |
926 | if (fdata->fd < 0) | |
c4fdb7d1 | 927 | { |
db78b1bd A |
928 | asldebug("%s: error opening for repeat message (%s): %s\n", MY_ID, fdata->path, strerror(errno)); |
929 | return -1; | |
930 | } | |
931 | } | |
932 | ||
933 | len = strlen(msg); | |
934 | status = write(fdata->fd, msg, len); | |
935 | free(msg); | |
936 | if (closeit != 0) | |
937 | { | |
938 | close(fdata->fd); | |
939 | fdata->fd = -1; | |
940 | } | |
941 | ||
942 | if ((status < 0) || (status < len)) | |
943 | { | |
944 | asldebug("%s: error writing repeat message (%s): %s\n", MY_ID, fdata->path, strerror(errno)); | |
945 | return -1; | |
946 | } | |
947 | ||
948 | return 0; | |
949 | } | |
950 | ||
951 | /* | |
952 | * N.B. This is basic file rotation support. | |
953 | * More rotation options will be added in the future, along | |
954 | * with support in aslmanager for compression and deletion. | |
955 | */ | |
956 | static void | |
957 | _act_file_rotate_file_data(struct file_data *fdata, time_t now) | |
958 | { | |
959 | char str[MAXPATHLEN]; | |
960 | size_t len; | |
961 | int width; | |
962 | ||
963 | if (now == 0) now = time(NULL); | |
964 | ||
965 | /* flush duplicates if pending */ | |
966 | _act_file_send_repeat_msg(fdata); | |
967 | ||
968 | /* sleep to prevent a sub-second rotation */ | |
969 | while (now == fdata->stamp) | |
970 | { | |
971 | sleep(1); | |
972 | now = time(NULL); | |
973 | } | |
974 | ||
975 | len = strlen(fdata->path); | |
976 | width = len - 4; | |
977 | if ((len > 4) && (!strcasecmp(fdata->path + width, ".log"))) | |
978 | { | |
979 | /* ".log" rename: abc.log -> abc.timestamp.log */ | |
980 | snprintf(str, sizeof(str), "%.*s.%lu.log", width, fdata->path, fdata->stamp); | |
981 | } | |
982 | else | |
983 | { | |
984 | snprintf(str, sizeof(str), "%s.%lu", fdata->path, fdata->stamp); | |
985 | } | |
986 | ||
987 | rename(fdata->path, str); | |
988 | ||
989 | fdata->stamp = now; | |
990 | } | |
991 | ||
992 | int | |
993 | _act_file_open(struct file_data *fdata) | |
994 | { | |
995 | acl_t acl; | |
996 | uuid_t uuid; | |
997 | acl_entry_t entry; | |
998 | acl_permset_t perms; | |
999 | int status; | |
1000 | int fd = -1; | |
1001 | mode_t mask; | |
1002 | struct stat sb; | |
1003 | uint32_t i; | |
1004 | ||
1005 | memset(&sb, 0, sizeof(struct stat)); | |
1006 | status = stat(fdata->path, &sb); | |
1007 | if (status == 0) | |
1008 | { | |
1009 | /* must be a regular file */ | |
1010 | if (!S_ISREG(sb.st_mode)) return -1; | |
1011 | ||
1012 | /* use st_birthtimespec if stamp is zero */ | |
1013 | if (fdata->stamp == 0) fdata->stamp = sb.st_birthtimespec.tv_sec; | |
1014 | ||
1015 | /* rotate if over size limit */ | |
1016 | if ((fdata->max_size > 0) && (sb.st_size > fdata->max_size)) | |
1017 | { | |
1018 | _act_file_rotate_file_data(fdata, 0); | |
c4fdb7d1 A |
1019 | } |
1020 | else | |
1021 | { | |
db78b1bd A |
1022 | /* open existing file */ |
1023 | fd = open(fdata->path, O_RDWR | O_APPEND | O_EXCL, 0); | |
1024 | return fd; | |
1025 | } | |
1026 | } | |
1027 | else if (errno != ENOENT) | |
1028 | { | |
1029 | return -1; | |
1030 | } | |
1031 | ||
1032 | #if TARGET_OS_EMBEDDED | |
1033 | return open(fdata->path, O_RDWR | O_CREAT | O_EXCL, (fdata->mode & 0666)); | |
1034 | #else | |
1035 | ||
1036 | acl = acl_init(1); | |
1037 | ||
1038 | for (i = 0; i < fdata->ngid; i++) | |
1039 | { | |
1040 | status = mbr_gid_to_uuid(fdata->gid[i], uuid); | |
1041 | if (status != 0) | |
1042 | { | |
1043 | char *str = NULL; | |
1044 | asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Unknown GID %d for \"file\" action: %s]", | |
1045 | ASL_KEY_SENDER, | |
1046 | ASL_KEY_LEVEL, ASL_LEVEL_ERR, | |
1047 | ASL_KEY_PID, getpid(), | |
1048 | ASL_KEY_MSG, fdata->gid[i], fdata->path); | |
1049 | ||
1050 | internal_log_message(str); | |
1051 | free(str); | |
1052 | continue; | |
1053 | } | |
1054 | ||
1055 | status = acl_create_entry_np(&acl, &entry, ACL_FIRST_ENTRY); | |
1056 | if (status != 0) goto asl_file_create_return; | |
1057 | ||
1058 | status = acl_set_tag_type(entry, ACL_EXTENDED_ALLOW); | |
1059 | if (status != 0) goto asl_file_create_return; | |
1060 | ||
1061 | status = acl_set_qualifier(entry, &uuid); | |
1062 | if (status != 0) goto asl_file_create_return; | |
1063 | ||
1064 | status = acl_get_permset(entry, &perms); | |
1065 | if (status != 0) goto asl_file_create_return; | |
1066 | ||
1067 | status = acl_add_perm(perms, ACL_READ_DATA); | |
1068 | if (status != 0) goto asl_file_create_return; | |
1069 | } | |
1070 | ||
1071 | for (i = 0; i < fdata->nuid; i++) | |
1072 | { | |
1073 | status = mbr_uid_to_uuid(fdata->uid[i], uuid); | |
1074 | if (status != 0) | |
1075 | { | |
1076 | char *str = NULL; | |
1077 | asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Unknown UID %d for \"file\" action: %s]", | |
1078 | ASL_KEY_SENDER, | |
1079 | ASL_KEY_LEVEL, ASL_LEVEL_ERR, | |
1080 | ASL_KEY_PID, getpid(), | |
1081 | ASL_KEY_MSG, fdata->uid[i], fdata->path); | |
1082 | ||
1083 | internal_log_message(str); | |
1084 | free(str); | |
1085 | continue; | |
1086 | } | |
1087 | ||
1088 | status = acl_create_entry_np(&acl, &entry, ACL_FIRST_ENTRY); | |
1089 | if (status != 0) goto asl_file_create_return; | |
1090 | ||
1091 | status = acl_set_tag_type(entry, ACL_EXTENDED_ALLOW); | |
1092 | if (status != 0) goto asl_file_create_return; | |
1093 | ||
1094 | status = acl_set_qualifier(entry, &uuid); | |
1095 | if (status != 0) goto asl_file_create_return; | |
1096 | ||
1097 | status = acl_get_permset(entry, &perms); | |
1098 | if (status != 0) goto asl_file_create_return; | |
1099 | ||
1100 | status = acl_add_perm(perms, ACL_READ_DATA); | |
1101 | if (status != 0) goto asl_file_create_return; | |
1102 | } | |
1103 | ||
1104 | mask = umask(0); | |
1105 | fd = open(fdata->path, O_RDWR | O_CREAT | O_EXCL, (fdata->mode & 0666)); | |
1106 | umask(mask); | |
1107 | if (fd < 0) goto asl_file_create_return; | |
1108 | ||
1109 | errno = 0; | |
1110 | status = acl_set_fd(fd, acl); | |
1111 | ||
1112 | if (status != 0) | |
1113 | { | |
1114 | close(fd); | |
1115 | fd = -1; | |
1116 | unlink(fdata->path); | |
1117 | } | |
1118 | ||
1119 | asl_file_create_return: | |
1120 | ||
1121 | acl_free(acl); | |
1122 | return fd; | |
1123 | #endif | |
1124 | } | |
1125 | ||
1126 | static void | |
1127 | _act_file_rotate(const char *path) | |
1128 | { | |
1129 | action_rule_t *r; | |
1130 | struct file_data *fdata; | |
1131 | time_t now = time(NULL); | |
1132 | ||
1133 | for (r = asl_action_rule; r != NULL; r = r->next) | |
1134 | { | |
1135 | if (r->action == ACTION_FILE) | |
1136 | { | |
1137 | fdata = (struct file_data *)r->data; | |
1138 | if (fdata->flags & ACT_FILE_FLAG_ROTATE) | |
c4fdb7d1 | 1139 | { |
db78b1bd A |
1140 | if ((path == NULL) || ((fdata->path != NULL) && !strcmp(fdata->path, path))) |
1141 | { | |
1142 | _act_file_rotate_file_data(fdata, now); | |
1143 | } | |
c4fdb7d1 | 1144 | } |
db78b1bd A |
1145 | } |
1146 | } | |
1147 | } | |
1148 | ||
1149 | static char * | |
1150 | _act_file_format_string(char *s) | |
1151 | { | |
1152 | char *fmt; | |
1153 | size_t i, len, n; | |
1154 | ||
1155 | if (s == NULL) return NULL; | |
1156 | ||
1157 | len = strlen(s); | |
1158 | n = 0; | |
1159 | for (i = 0; i < len; i++) if (s[i] == '\\') n++; | |
1160 | ||
1161 | fmt = malloc(1 + len - n); | |
1162 | if (fmt == NULL) return NULL; | |
1163 | ||
1164 | for (i = 0, n = 0; i < len; i++) if (s[i] != '\\') fmt[n++] = s[i]; | |
1165 | fmt[n] = '\0'; | |
1166 | return fmt; | |
1167 | } | |
1168 | ||
1169 | static size_t | |
1170 | _act_file_max_size(char *s) | |
1171 | { | |
1172 | size_t len, n, max; | |
1173 | char x; | |
1174 | ||
1175 | if (s == NULL) return 0; | |
1176 | ||
1177 | len = strlen(s); | |
1178 | if (len == 0) return 0; | |
1179 | ||
1180 | n = 1; | |
1181 | x = s[len - 1]; | |
1182 | if (x > 90) x -= 32; | |
1183 | if (x == 'K') n = 1ll << 10; | |
1184 | else if (x == 'M') n = 1ll << 20; | |
1185 | else if (x == 'G') n = 1ll << 30; | |
1186 | else if (x == 'T') n = 1ll << 40; | |
1187 | ||
1188 | max = atoll(s) * n; | |
1189 | return max; | |
1190 | } | |
1191 | ||
1192 | static void | |
1193 | _act_file_add_uid(struct file_data *fdata, char *s) | |
1194 | { | |
1195 | if (fdata == NULL) return; | |
1196 | if (s == NULL) return; | |
1197 | ||
1198 | fdata->uid = reallocf(fdata->uid, (fdata->nuid + 1) * sizeof(uid_t)); | |
1199 | if (fdata->uid == NULL) | |
1200 | { | |
1201 | fdata->nuid = 0; | |
1202 | return; | |
1203 | } | |
1204 | ||
1205 | fdata->uid[fdata->nuid++] = atoi(s); | |
1206 | } | |
1207 | ||
1208 | static void | |
1209 | _act_file_add_gid(struct file_data *fdata, char *s) | |
1210 | { | |
1211 | if (fdata == NULL) return; | |
1212 | if (s == NULL) return; | |
1213 | ||
1214 | fdata->gid = reallocf(fdata->gid, (fdata->ngid + 1) * sizeof(gid_t)); | |
1215 | if (fdata->gid == NULL) | |
1216 | { | |
1217 | fdata->ngid = 0; | |
1218 | return; | |
1219 | } | |
1220 | ||
1221 | fdata->gid[fdata->ngid++] = atoi(s); | |
1222 | } | |
1223 | ||
1224 | static void | |
1225 | _act_file_init(action_rule_t *r) | |
1226 | { | |
1227 | struct file_data *fdata, *xdata; | |
1228 | char *str, *opts, *p, *path; | |
1229 | action_rule_t *x; | |
1230 | ||
1231 | /* check if the file data is already set up */ | |
1232 | if (r->data != NULL) return; | |
1233 | ||
1234 | /* requires at least a path */ | |
1235 | if (r->options == NULL) return; | |
1236 | opts = r->options; | |
1237 | path = _next_word(&opts); | |
1238 | ||
1239 | if ((path == NULL) || (path[0] != '/')) | |
1240 | { | |
1241 | str = NULL; | |
1242 | asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Invalid path for \"file\" action: %s]", | |
1243 | ASL_KEY_SENDER, | |
1244 | ASL_KEY_LEVEL, ASL_LEVEL_ERR, | |
1245 | ASL_KEY_PID, getpid(), | |
1246 | ASL_KEY_MSG, (path == NULL) ? "no path specified" : path); | |
1247 | ||
1248 | internal_log_message(str); | |
1249 | free(str); | |
1250 | free(path); | |
1251 | r->action = ACTION_NONE; | |
1252 | return; | |
1253 | } | |
1254 | ||
1255 | /* check if a previous rule has set up this path */ | |
1256 | for (x = asl_action_rule; x != NULL; x = x->next) | |
1257 | { | |
1258 | if ((x->action == ACTION_FILE) && (x->data != NULL)) | |
1259 | { | |
1260 | xdata = (struct file_data *)x->data; | |
1261 | if ((xdata->path != NULL) && (!strcmp(path, xdata->path))) | |
c4fdb7d1 | 1262 | { |
db78b1bd A |
1263 | free(path); |
1264 | xdata->refcount++; | |
1265 | r->data = x->data; | |
1266 | return; | |
c4fdb7d1 A |
1267 | } |
1268 | } | |
1269 | } | |
1270 | ||
db78b1bd A |
1271 | /* set up file data */ |
1272 | fdata = (struct file_data *)calloc(1, sizeof(struct file_data)); | |
1273 | if (fdata == NULL) return; | |
1274 | ||
1275 | fdata->refcount = 1; | |
1276 | fdata->path = path; | |
1277 | ||
1278 | /* | |
1279 | * options: | |
1280 | * mode= set file creation mode | |
1281 | * uid= user added to read ACL | |
1282 | * gid= group added to read ACL | |
1283 | * format= format string (also fmt=) | |
1284 | * no_dup_supress no duplicate supression | |
1285 | * | |
1286 | * rotate automatic daily rotation | |
1287 | * this is basic rotation - more support is TBD | |
1288 | */ | |
1289 | fdata->mode = 0644; | |
1290 | fdata->flags = ACT_FILE_FLAG_DUP_SUPRESS; | |
1291 | ||
1292 | while (NULL != (p = _next_word(&opts))) | |
c4fdb7d1 | 1293 | { |
db78b1bd A |
1294 | if (!strncmp(p, "mode=", 5)) fdata->mode = strtol(p+5, NULL, 0); |
1295 | else if (!strncmp(p, "uid=", 4)) _act_file_add_uid(fdata, p+4); | |
1296 | else if (!strncmp(p, "gid=", 4)) _act_file_add_gid(fdata, p+4); | |
1297 | else if (!strncmp(p, "fmt=", 4)) fdata->fmt = _act_file_format_string(p+4); | |
1298 | else if (!strncmp(p, "format=", 7)) fdata->fmt = _act_file_format_string(p+7); | |
1299 | else if (!strncmp(p, "no_dup_supress", 14)) fdata->flags &= ~ACT_FILE_FLAG_DUP_SUPRESS; | |
1300 | else if (!strncmp(p, "rotate", 6)) fdata->flags |= ACT_FILE_FLAG_ROTATE; | |
1301 | else if (!strncmp(p, "max_size=", 9)) fdata->max_size = _act_file_max_size(p+9); | |
1302 | ||
1303 | free(p); | |
1304 | p = NULL; | |
c4fdb7d1 A |
1305 | } |
1306 | ||
db78b1bd A |
1307 | if (fdata->fmt == NULL) fdata->fmt = strdup("std"); |
1308 | ||
1309 | /* duplicate compression is only possible for std and bsd formats */ | |
1310 | if (strcmp(fdata->fmt, "std") && strcmp(fdata->fmt, "bsd")) fdata->flags &= ~ACT_FILE_FLAG_DUP_SUPRESS; | |
1311 | ||
1312 | /* set time format for raw output */ | |
1313 | if (!strcmp(fdata->fmt, "raw")) fdata->tfmt = "sec"; | |
1314 | ||
1315 | r->data = fdata; | |
1316 | } | |
1317 | ||
1318 | static void | |
1319 | _act_file(action_rule_t *r, aslmsg msg) | |
1320 | { | |
1321 | struct file_data *fdata; | |
1322 | int is_dup; | |
1323 | uint32_t len, msg_hash = 0; | |
1324 | char *str; | |
1325 | time_t now, today; | |
1326 | struct tm ctm; | |
1327 | ||
1328 | if (r->data == NULL) return; | |
1329 | ||
1330 | fdata = (struct file_data *)r->data; | |
1331 | ||
1332 | now = time(NULL); | |
1333 | today = now; | |
1334 | ||
1335 | memset(&ctm, 0, sizeof(struct tm)); | |
1336 | if (localtime_r((const time_t *)&now, &ctm) != NULL) | |
c4fdb7d1 | 1337 | { |
db78b1bd A |
1338 | ctm.tm_sec = 0; |
1339 | ctm.tm_min = 0; | |
1340 | ctm.tm_hour = 0; | |
1341 | today = mktime(&ctm); | |
1342 | } | |
1343 | ||
1344 | /* check for rotation */ | |
1345 | if ((last_file_day != 0) && (last_file_day != today)) | |
1346 | { | |
1347 | _act_file_rotate(NULL); | |
1348 | } | |
1349 | ||
1350 | last_file_day = today; | |
1351 | ||
1352 | ||
1353 | /* | |
1354 | * asl.conf may contain multuple rules for the same file, eg: | |
1355 | * ? [= Facility zippy] /var/log/abc.log | |
1356 | * ? [= Color purple] /var/log/abc.log | |
1357 | * | |
1358 | * To prevent duplicates we set a flag bit when a message is logged | |
1359 | * to this file, and bail out if it has already been logged. | |
1360 | * Note that asl_out_message clears the flag bit in all file_data | |
1361 | * structures before processing each message. | |
1362 | */ | |
1363 | if (fdata->flags & ACT_FLAG_HAS_LOGGED) return; | |
1364 | fdata->flags |= ACT_FLAG_HAS_LOGGED; | |
1365 | ||
1366 | is_dup = 0; | |
1367 | ||
1368 | str = asl_format_message((asl_msg_t *)msg, fdata->fmt, fdata->tfmt, ASL_ENCODE_SAFE, &len); | |
1369 | ||
1370 | if (fdata->flags & ACT_FILE_FLAG_DUP_SUPRESS) | |
1371 | { | |
1372 | if (fdata->dup_timer == NULL) | |
c4fdb7d1 | 1373 | { |
db78b1bd A |
1374 | /* create a timer to flush dups on this file */ |
1375 | fdata->dup_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, asl_action_queue); | |
1376 | dispatch_source_set_event_handler(fdata->dup_timer, ^{ _act_file_send_repeat_msg((struct file_data *)r->data); }); | |
1377 | } | |
1378 | ||
1379 | if ((global.bsd_max_dup_time > 0) && (str != NULL) && (fdata->last_msg != NULL)) | |
1380 | { | |
1381 | msg_hash = asl_core_string_hash(str + 16, len - 16); | |
1382 | if ((fdata->last_hash == msg_hash) && (!strcmp(fdata->last_msg, str + 16))) | |
1383 | { | |
1384 | if ((now - fdata->last_time) < global.bsd_max_dup_time) is_dup = 1; | |
1385 | } | |
c4fdb7d1 A |
1386 | } |
1387 | } | |
1388 | ||
db78b1bd A |
1389 | if (is_dup == 1) |
1390 | { | |
1391 | if (fdata->last_count == 0) | |
1392 | { | |
1393 | /* start the timer */ | |
1394 | dispatch_source_set_timer(fdata->dup_timer, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * global.bsd_max_dup_time), DISPATCH_TIME_FOREVER, 0); | |
1395 | dispatch_resume(fdata->dup_timer); | |
1396 | } | |
1397 | ||
1398 | fdata->last_count++; | |
1399 | } | |
c4fdb7d1 A |
1400 | else |
1401 | { | |
db78b1bd A |
1402 | fdata->fd = _act_file_open(fdata); |
1403 | if (fdata->fd < 0) | |
1404 | { | |
1405 | asldebug("_act_file_open %s failed: %s\n", fdata->path, strerror(errno)); | |
1406 | ||
1407 | fdata->fails++; | |
1408 | ||
1409 | /* disable further activity after multiple failures */ | |
1410 | if (fdata->fails > MAX_FAILURES) | |
1411 | { | |
1412 | char *tmp = NULL; | |
1413 | asprintf(&tmp, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Disabling writes to path %s following %u failures (%s)]", | |
1414 | ASL_KEY_SENDER, | |
1415 | ASL_KEY_LEVEL, ASL_LEVEL_ERR, | |
1416 | ASL_KEY_PID, getpid(), | |
1417 | ASL_KEY_MSG, fdata->path, fdata->fails, strerror(errno)); | |
1418 | ||
1419 | internal_log_message(tmp); | |
1420 | free(tmp); | |
1421 | ||
1422 | r->action = ACTION_NONE; | |
1423 | free(str); | |
1424 | return; | |
1425 | } | |
1426 | } | |
1427 | else | |
1428 | { | |
1429 | fdata->fails = 0; | |
1430 | } | |
1431 | ||
1432 | /* | |
1433 | * The current message is not a duplicate. If fdata->last_count > 0 | |
1434 | * we need to write a "last message repeated N times" log entry. | |
1435 | * _act_file_send_repeat_msg will free last_msg and do nothing if | |
1436 | * last_count == 0, but we test and free here to avoid a function call. | |
1437 | */ | |
1438 | if (fdata->last_count > 0) | |
1439 | { | |
1440 | _act_file_send_repeat_msg(fdata); | |
1441 | } | |
1442 | else | |
1443 | { | |
1444 | free(fdata->last_msg); | |
1445 | fdata->last_msg = NULL; | |
1446 | } | |
1447 | ||
1448 | if (str != NULL) fdata->last_msg = strdup(str + 16); | |
1449 | ||
1450 | fdata->last_hash = msg_hash; | |
1451 | fdata->last_count = 0; | |
1452 | fdata->last_time = now; | |
1453 | ||
1454 | if ((str != NULL) && (len > 1)) write(fdata->fd, str, len - 1); | |
1455 | close(fdata->fd); | |
1456 | fdata->fd = -1; | |
c4fdb7d1 A |
1457 | } |
1458 | ||
db78b1bd A |
1459 | free(str); |
1460 | } | |
1461 | ||
1462 | static void | |
1463 | _act_forward(action_rule_t *r, aslmsg msg) | |
1464 | { | |
1465 | /* To do: <rdar://problem/6130747> Add a "forward" action to asl.conf */ | |
1466 | } | |
1467 | ||
1468 | static void | |
1469 | _send_to_asl_store(aslmsg msg) | |
1470 | { | |
1471 | int log_me; | |
1472 | action_rule_t *r; | |
1473 | ||
1474 | /* ASLOption "store" forces a message to be saved */ | |
1475 | log_me = asl_check_option(msg, ASL_OPT_STORE); | |
1476 | if (log_me == 1) | |
1477 | { | |
1478 | db_save_message(msg); | |
1479 | return; | |
1480 | } | |
c4fdb7d1 A |
1481 | |
1482 | /* if there are no rules, save the message */ | |
1483 | if (asl_datastore_rule == NULL) | |
1484 | { | |
1485 | db_save_message(msg); | |
1486 | return; | |
1487 | } | |
1488 | ||
1489 | for (r = asl_datastore_rule; r != NULL; r = r->next) | |
1490 | { | |
a83ff38a | 1491 | if (asl_msg_cmp(r->query, (asl_msg_t *)msg) == 1) |
c4fdb7d1 A |
1492 | { |
1493 | /* if any rule matches, save the message (once!) */ | |
1494 | db_save_message(msg); | |
1495 | return; | |
1496 | } | |
1497 | } | |
1498 | } | |
1499 | ||
db78b1bd A |
1500 | static void |
1501 | _asl_action_message(aslmsg msg) | |
c4fdb7d1 A |
1502 | { |
1503 | action_rule_t *r; | |
1504 | ||
db78b1bd | 1505 | if (msg == NULL) return; |
c4fdb7d1 | 1506 | |
db78b1bd A |
1507 | /* reset flag bit used for file duplicate avoidance */ |
1508 | for (r = asl_action_rule; r != NULL; r = r->next) | |
1509 | { | |
1510 | if ((r->action == ACTION_FILE) && (r->data != NULL)) | |
1511 | { | |
1512 | ((struct file_data *)(r->data))->flags &= ACT_FLAG_CLEAR_LOGGED; | |
1513 | } | |
1514 | else if (((r->action == ACTION_ASL_DIR) || (r->action == ACTION_ASL_FILE)) && (r->data != NULL)) | |
1515 | { | |
1516 | ((struct store_data *)(r->data))->flags &= ACT_FLAG_CLEAR_LOGGED; | |
1517 | } | |
1518 | } | |
b16a592a | 1519 | |
c4fdb7d1 | 1520 | for (r = asl_action_rule; r != NULL; r = r->next) |
b16a592a | 1521 | { |
a83ff38a | 1522 | if (asl_msg_cmp(r->query, (asl_msg_t *)msg) == 1) |
b16a592a | 1523 | { |
db78b1bd | 1524 | if ((r->action == ACTION_ASL_FILE) || (r->action == ACTION_ASL_DIR)) |
a83ff38a A |
1525 | { |
1526 | _act_store(r, msg); | |
db78b1bd | 1527 | if (asl_check_option(msg, ASL_OPT_IGNORE) != 0) return; |
a83ff38a A |
1528 | } |
1529 | ||
c4fdb7d1 | 1530 | if (r->action == ACTION_NONE) continue; |
db78b1bd | 1531 | else if (r->action == ACTION_IGNORE) return; |
c4fdb7d1 A |
1532 | else if (r->action == ACTION_ACCESS) _act_access_control(r, msg); |
1533 | else if (r->action == ACTION_NOTIFY) _act_notify(r); | |
c4fdb7d1 | 1534 | else if (r->action == ACTION_BROADCAST) _act_broadcast(r, msg); |
db78b1bd | 1535 | else if (r->action == ACTION_FILE) _act_file(r, msg); |
c4fdb7d1 | 1536 | else if (r->action == ACTION_FORWARD) _act_forward(r, msg); |
b16a592a A |
1537 | } |
1538 | } | |
1539 | ||
db78b1bd | 1540 | if (asl_check_option(msg, ASL_OPT_IGNORE) != 0) return; |
a83ff38a | 1541 | |
db78b1bd A |
1542 | _send_to_asl_store(msg); |
1543 | } | |
c4fdb7d1 | 1544 | |
db78b1bd A |
1545 | void |
1546 | asl_out_message(aslmsg msg) | |
1547 | { | |
1548 | dispatch_flush_continuation_cache(); | |
1549 | ||
1550 | asl_msg_retain((asl_msg_t *)msg); | |
1551 | ||
1552 | dispatch_async(asl_action_queue, ^{ | |
1553 | _asl_action_message(msg); | |
1554 | asl_msg_release((asl_msg_t *)msg); | |
1555 | }); | |
b16a592a A |
1556 | } |
1557 | ||
1558 | static int | |
c4fdb7d1 | 1559 | _parse_config_file(const char *name) |
b16a592a A |
1560 | { |
1561 | FILE *cf; | |
1562 | char *line; | |
1563 | ||
1564 | cf = fopen(name, "r"); | |
1565 | if (cf == NULL) return 1; | |
1566 | ||
1567 | while (NULL != (line = get_line_from_file(cf))) | |
1568 | { | |
1569 | _parse_line(line); | |
1570 | free(line); | |
1571 | } | |
1572 | ||
1573 | fclose(cf); | |
1574 | ||
1575 | return 0; | |
1576 | } | |
1577 | ||
1578 | int | |
1579 | asl_action_init(void) | |
1580 | { | |
db78b1bd | 1581 | static dispatch_once_t once; |
b16a592a | 1582 | |
db78b1bd | 1583 | asldebug("%s: init\n", MY_ID); |
c4fdb7d1 | 1584 | _parse_config_file(_PATH_ASL_CONF); |
b16a592a | 1585 | |
db78b1bd A |
1586 | dispatch_once(&once, ^{ |
1587 | asl_action_queue = dispatch_queue_create("ASL Action Queue", NULL); | |
1588 | }); | |
1589 | ||
b16a592a A |
1590 | return 0; |
1591 | } | |
1592 | ||
1593 | int | |
db78b1bd | 1594 | _asl_action_close_internal(void) |
b16a592a | 1595 | { |
c4fdb7d1 A |
1596 | action_rule_t *r, *n; |
1597 | struct store_data *sd; | |
db78b1bd | 1598 | struct file_data *fdata; |
c4fdb7d1 A |
1599 | n = NULL; |
1600 | for (r = asl_action_rule; r != NULL; r = n) | |
1601 | { | |
1602 | n = r->next; | |
db78b1bd | 1603 | if (r->data != NULL) |
c4fdb7d1 | 1604 | { |
db78b1bd A |
1605 | if (((r->action == ACTION_ASL_FILE) || (r->action == ACTION_ASL_DIR) || (r->action == ACTION_NONE))) |
1606 | { | |
1607 | sd = (struct store_data *)r->data; | |
1608 | if (sd->refcount > 0) sd->refcount--; | |
1609 | if (sd->refcount == 0) | |
1610 | { | |
1611 | if (sd->store != NULL) asl_file_close(sd->store); | |
1612 | if (sd->storedata != NULL) fclose(sd->storedata); | |
1613 | ||
1614 | free(sd->dir); | |
1615 | free(sd->path); | |
1616 | free(sd); | |
1617 | } | |
1618 | } | |
1619 | ||
1620 | if (r->action == ACTION_FILE) | |
1621 | { | |
1622 | fdata = (struct file_data *)r->data; | |
1623 | if (fdata->refcount > 0) fdata->refcount--; | |
1624 | if (fdata->refcount == 0) | |
1625 | { | |
1626 | _act_file_send_repeat_msg(fdata); | |
1627 | ||
1628 | if (fdata->dup_timer != NULL) | |
1629 | { | |
1630 | dispatch_source_cancel(fdata->dup_timer); | |
1631 | dispatch_resume(fdata->dup_timer); | |
1632 | dispatch_release(fdata->dup_timer); | |
1633 | } | |
1634 | ||
1635 | free(fdata->path); | |
1636 | free(fdata->fmt); | |
1637 | free(fdata->uid); | |
1638 | free(fdata->gid); | |
1639 | free(fdata->last_msg); | |
1640 | free(fdata); | |
1641 | } | |
1642 | } | |
c4fdb7d1 A |
1643 | } |
1644 | ||
a83ff38a A |
1645 | if (r->query != NULL) asl_msg_release(r->query); |
1646 | free(r->options); | |
c4fdb7d1 A |
1647 | |
1648 | free(r); | |
1649 | } | |
1650 | ||
1651 | asl_action_rule = NULL; | |
b16a592a A |
1652 | |
1653 | n = NULL; | |
c4fdb7d1 | 1654 | for (r = asl_datastore_rule; r != NULL; r = n) |
b16a592a | 1655 | { |
c4fdb7d1 | 1656 | n = r->next; |
b16a592a | 1657 | |
a83ff38a A |
1658 | if (r->query != NULL) asl_msg_release(r->query); |
1659 | free(r->options); | |
b16a592a | 1660 | |
b16a592a A |
1661 | free(r); |
1662 | } | |
1663 | ||
c4fdb7d1 A |
1664 | asl_datastore_rule = NULL; |
1665 | ||
b16a592a A |
1666 | return 0; |
1667 | } | |
db78b1bd A |
1668 | |
1669 | int | |
1670 | asl_action_close(void) | |
1671 | { | |
1672 | dispatch_async(asl_action_queue, ^{ | |
1673 | _asl_action_close_internal(); | |
1674 | }); | |
1675 | ||
1676 | return 0; | |
1677 | } | |
1678 | ||
1679 | int | |
1680 | asl_action_reset(void) | |
1681 | { | |
1682 | dispatch_async(asl_action_queue, ^{ | |
1683 | _asl_action_close_internal(); | |
1684 | asl_action_init(); | |
1685 | }); | |
1686 | ||
1687 | return 0; | |
1688 | } | |
1689 | ||
1690 | int | |
1691 | asl_action_file_rotate(const char *path) | |
1692 | { | |
1693 | /* | |
1694 | * The caller may want to know when the rotation has been completed, | |
1695 | * so this is synchronous. Also ensures the string stays intact while we work. | |
1696 | */ | |
1697 | dispatch_sync(asl_action_queue, ^{ | |
1698 | _act_file_rotate(path); | |
1699 | }); | |
1700 | ||
1701 | return 0; | |
1702 | } | |
1703 |