2 * Copyright (c) 2004-2012 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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,
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #include <TargetConditionals.h>
26 #if TARGET_OS_SIMULATOR
30 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <netinet/in.h>
34 #include <netinet/tcp.h>
35 #include <arpa/inet.h>
49 #define forever for(;;)
51 #define MY_ID "remote"
53 #define LOCKDOWN_PATH "/var/run/lockdown"
54 #define SYSLOG_SOCK_PATH "/var/run/lockdown/syslog.sock"
55 #define ASL_REMOTE_PORT 203
61 #define WATCH_LOCKDOWN_START 1
64 #define SESSION_FLAGS_LOCKDOWN 0x00000001
72 static dispatch_source_t in_src_local
;
73 static dispatch_source_t in_src_tcp
;
74 static dispatch_queue_t in_queue
;
77 typedef uint32_t notify_state_t
;
78 extern int notify_set_state(int, notify_state_t
);
80 typedef uint64_t notify_state_t
;
83 extern size_t asl_memory_size(asl_memory_t
*s
);
84 extern uint32_t db_query(asl_msg_list_t
*query
, asl_msg_list_t
**res
, uint64_t startid
, int count
, uint32_t duration
, int direction
, uint64_t *lastid
, int32_t ruid
, int32_t rgid
, int raccess
);
86 extern void add_lockdown_session(int fd
);
87 extern void remove_lockdown_session(int fd
);
89 #define SESSION_WRITE(f,x) if (write(f, x, strlen(x)) < 0) goto exit_session
98 remote_db_size(uint32_t sel
)
100 if (sel
== DB_TYPE_FILE
) return global
.db_file_max
;
101 if (sel
== DB_TYPE_MEMORY
) return global
.db_memory_max
;
106 remote_db_set_size(uint32_t sel
, uint32_t size
)
108 if (sel
== DB_TYPE_FILE
) global
.db_file_max
= size
;
109 if (sel
== DB_TYPE_MEMORY
) global
.db_memory_max
= size
;
114 remote_db_stats(uint32_t sel
)
119 if (sel
== DB_TYPE_FILE
) asl_store_statistics(global
.file_db
, &m
);
120 if (sel
== DB_TYPE_MEMORY
) asl_memory_statistics(global
.memory_db
, &m
);
127 int i
, s
, wfd
, status
, pfmt
, watch
, wtoken
, nfd
, do_prompt
;
134 char str
[1024], *p
, *qs
, *out
;
136 fd_set readfds
, errfds
;
137 uint64_t low_id
, high_id
;
138 uint32_t dbselect
, flags
;
141 if (x
== NULL
) pthread_exit(NULL
);
143 sp
= (session_args_t
*)x
;
148 asldebug("%s %d: starting interactive session for %ssocket %d\n", MY_ID
, s
, (flags
& SESSION_FLAGS_LOCKDOWN
) ? "lockdown " : "", s
);
156 if (global
.dbtype
& DB_TYPE_MEMORY
) dbselect
= DB_TYPE_MEMORY
;
157 else if (global
.dbtype
& DB_TYPE_FILE
) dbselect
= DB_TYPE_FILE
;
164 memset(&ql
, 0, sizeof(asl_msg_list_t
));
166 if (flags
& SESSION_FLAGS_LOCKDOWN
) sleep(1);
168 snprintf(str
, sizeof(str
), "\n========================\nASL is here to serve you\n");
169 if (write(s
, str
, strlen(str
)) < 0)
176 if (flags
& SESSION_FLAGS_LOCKDOWN
)
178 snprintf(str
, sizeof(str
), "> ");
179 SESSION_WRITE(s
, str
);
184 if (((flags
& SESSION_FLAGS_LOCKDOWN
) == 0) && (do_prompt
> 0))
186 snprintf(str
, sizeof(str
), "> ");
187 SESSION_WRITE(s
, str
);
192 memset(str
, 0, sizeof(str
));
202 FD_SET(wfd
, &readfds
);
203 if (wfd
> nfd
) nfd
= wfd
;
206 status
= select(nfd
+ 1, &readfds
, NULL
, &errfds
, NULL
);
207 if (status
== 0) continue;
210 asldebug("%s %d: select %d %s\n", MY_ID
, s
, errno
, strerror(errno
));
214 if (FD_ISSET(s
, &errfds
))
216 asldebug("%s %d: error on socket %d\n", MY_ID
, s
, s
);
220 if ((wfd
!= -1) && (FD_ISSET(wfd
, &readfds
)))
222 (void)read(wfd
, &i
, sizeof(int));
225 if (FD_ISSET(s
, &errfds
))
227 asldebug("%s %d: socket %d reported error\n", MY_ID
, s
, s
);
231 if (FD_ISSET(s
, &readfds
))
233 len
= read(s
, str
, sizeof(str
) - 1);
236 asldebug("%s %d: read error on socket %d: %d %s\n", MY_ID
, s
, s
, errno
, strerror(errno
));
240 while ((len
> 1) && ((str
[len
- 1] == '\n') || (str
[len
- 1] == '\r')))
246 if ((!strcmp(str
, "q")) || (!strcmp(str
, "quit")) || (!strcmp(str
, "exit")))
248 snprintf(str
, sizeof(str
), "Goodbye\n");
249 write(s
, str
, strlen(str
));
255 if ((!strcmp(str
, "?")) || (!strcmp(str
, "help")))
257 snprintf(str
, sizeof(str
), "Commands\n");
258 SESSION_WRITE(s
, str
);
259 snprintf(str
, sizeof(str
), " quit exit session\n");
260 SESSION_WRITE(s
, str
);
261 snprintf(str
, sizeof(str
), " select [val] get [set] current database\n");
262 SESSION_WRITE(s
, str
);
263 snprintf(str
, sizeof(str
), " val must be \"file\" or \"mem\"\n");
264 SESSION_WRITE(s
, str
);
265 snprintf(str
, sizeof(str
), " file [on/off] enable / disable file store\n");
266 SESSION_WRITE(s
, str
);
267 snprintf(str
, sizeof(str
), " memory [on/off] enable / disable memory store\n");
268 SESSION_WRITE(s
, str
);
269 snprintf(str
, sizeof(str
), " stats database statistics\n");
270 SESSION_WRITE(s
, str
);
271 snprintf(str
, sizeof(str
), " flush flush database\n");
272 SESSION_WRITE(s
, str
);
273 snprintf(str
, sizeof(str
), " dbsize [val] get [set] database size (# of records)\n");
274 SESSION_WRITE(s
, str
);
275 snprintf(str
, sizeof(str
), " watch print new messages as they arrive\n");
276 SESSION_WRITE(s
, str
);
277 snprintf(str
, sizeof(str
), " stop stop watching for new messages\n");
278 SESSION_WRITE(s
, str
);
279 snprintf(str
, sizeof(str
), " raw use raw format for printing messages\n");
280 SESSION_WRITE(s
, str
);
281 snprintf(str
, sizeof(str
), " std use standard format for printing messages\n");
282 SESSION_WRITE(s
, str
);
283 snprintf(str
, sizeof(str
), " * show all log messages\n");
284 SESSION_WRITE(s
, str
);
285 snprintf(str
, sizeof(str
), " * key val equality search for messages (single key/value pair)\n");
286 SESSION_WRITE(s
, str
);
287 snprintf(str
, sizeof(str
), " * op key val search for matching messages (single key/value pair)\n");
288 SESSION_WRITE(s
, str
);
289 snprintf(str
, sizeof(str
), " * [op key val] ... search for matching messages (multiple key/value pairs)\n");
290 SESSION_WRITE(s
, str
);
291 snprintf(str
, sizeof(str
), " operators: = < > ! (not equal) T (key exists) R (regex)\n");
292 SESSION_WRITE(s
, str
);
293 snprintf(str
, sizeof(str
), " modifiers (must follow operator):\n");
294 SESSION_WRITE(s
, str
);
295 snprintf(str
, sizeof(str
), " C=casefold N=numeric S=substring A=prefix Z=suffix\n");
296 SESSION_WRITE(s
, str
);
297 snprintf(str
, sizeof(str
), "\n");
298 SESSION_WRITE(s
, str
);
301 else if (!strcmp(str
, "stats"))
303 stats
= remote_db_stats(dbselect
);
304 out
= asl_format_message((asl_msg_t
*)stats
, ASL_MSG_FMT_RAW
, ASL_TIME_FMT_SEC
, ASL_ENCODE_NONE
, &outlen
);
305 write(s
, out
, outlen
);
307 asl_msg_release(stats
);
310 else if (!strcmp(str
, "flush"))
312 else if (!strncmp(str
, "select", 6))
315 while ((*p
== ' ') || (*p
== '\t')) p
++;
318 if (dbselect
== 0) snprintf(str
, sizeof(str
), "no store\n");
319 else if (dbselect
== DB_TYPE_FILE
) snprintf(str
, sizeof(str
), "file store\n");
320 else if (dbselect
== DB_TYPE_MEMORY
) snprintf(str
, sizeof(str
), "memory store\n");
321 SESSION_WRITE(s
, str
);
325 if (!strncmp(p
, "file", 4))
327 if ((global
.dbtype
& DB_TYPE_FILE
) == 0)
329 snprintf(str
, sizeof(str
), "file database is not enabled\n");
330 SESSION_WRITE(s
, str
);
334 dbselect
= DB_TYPE_FILE
;
336 else if (!strncmp(p
, "mem", 3))
338 if ((global
.dbtype
& DB_TYPE_MEMORY
) == 0)
340 snprintf(str
, sizeof(str
), "memory database is not enabled\n");
341 SESSION_WRITE(s
, str
);
345 dbselect
= DB_TYPE_MEMORY
;
349 snprintf(str
, sizeof(str
), "unknown database type\n");
350 SESSION_WRITE(s
, str
);
354 snprintf(str
, sizeof(str
), "OK\n");
355 SESSION_WRITE(s
, str
);
358 else if (!strncmp(str
, "file", 4))
361 while ((*p
== ' ') || (*p
== '\t')) p
++;
364 snprintf(str
, sizeof(str
), "file database is %senabled\n", (global
.dbtype
& DB_TYPE_FILE
) ? "" : "not ");
365 SESSION_WRITE(s
, str
);
366 if ((global
.dbtype
& DB_TYPE_FILE
) != 0) dbselect
= DB_TYPE_FILE
;
370 if (!strcmp(p
, "on")) global
.dbtype
|= DB_TYPE_FILE
;
371 else if (!strcmp(p
, "off")) global
.dbtype
&= ~ DB_TYPE_FILE
;
373 snprintf(str
, sizeof(str
), "OK\n");
374 SESSION_WRITE(s
, str
);
377 else if (!strncmp(str
, "memory", 6))
380 while ((*p
== ' ') || (*p
== '\t')) p
++;
383 snprintf(str
, sizeof(str
), "memory database is %senabled\n", (global
.dbtype
& DB_TYPE_MEMORY
) ? "" : "not ");
384 SESSION_WRITE(s
, str
);
385 if ((global
.dbtype
& DB_TYPE_MEMORY
) != 0) dbselect
= DB_TYPE_MEMORY
;
389 if (!strcmp(p
, "on")) global
.dbtype
|= DB_TYPE_MEMORY
;
390 else if (!strcmp(p
, "off")) global
.dbtype
&= ~ DB_TYPE_MEMORY
;
392 snprintf(str
, sizeof(str
), "OK\n");
393 SESSION_WRITE(s
, str
);
396 else if (!strncmp(str
, "dbsize", 6))
400 snprintf(str
, sizeof(str
), "no store\n");
401 SESSION_WRITE(s
, str
);
406 while ((*p
== ' ') || (*p
== '\t')) p
++;
409 snprintf(str
, sizeof(str
), "DB size %u\n", remote_db_size(dbselect
));
410 SESSION_WRITE(s
, str
);
415 remote_db_set_size(dbselect
, i
);
417 snprintf(str
, sizeof(str
), "OK\n");
418 SESSION_WRITE(s
, str
);
421 else if (!strcmp(str
, "stop"))
423 if (watch
!= WATCH_OFF
)
426 notify_cancel(wtoken
);
433 if (query
!= NULL
) free(query
);
436 snprintf(str
, sizeof(str
), "OK\n");
437 SESSION_WRITE(s
, str
);
441 snprintf(str
, sizeof(str
), "not watching!\n");
442 SESSION_WRITE(s
, str
);
445 else if (!strcmp(str
, "raw"))
450 else if (!strcmp(str
, "std"))
455 else if (!strcmp(str
, "watch"))
457 if (((flags
& SESSION_FLAGS_LOCKDOWN
) == 0) && (watch
!= WATCH_OFF
))
459 snprintf(str
, sizeof(str
), "already watching!\n");
460 SESSION_WRITE(s
, str
);
464 if (flags
& SESSION_FLAGS_LOCKDOWN
)
467 * If this session is PurpleConsole or Xcode watching for log messages,
468 * we pass through the bottom of the loop (below) once to pick up
469 * existing messages already in memory. After that, dbserver will
470 * send new messages in send_to_direct_watchers(). We wait until
471 * the initial messages are sent before adding the connection to
472 * global.lockdown_session_fds to allow this query to complete before
473 * dbserver starts sending. To prevent a race between this query and
474 * when messages are sent by send_to_direct_watchers, we suspend the
475 * work queue and resume it when lockdown_session_fds has been updated.
477 watch
= WATCH_LOCKDOWN_START
;
478 dispatch_suspend(global
.work_queue
);
482 status
= notify_register_file_descriptor(kNotifyASLDBUpdate
, &wfd
, 0, &wtoken
);
485 snprintf(str
, sizeof(str
), "notify_register_file_descriptor failed: %d\n", status
);
486 SESSION_WRITE(s
, str
);
493 snprintf(str
, sizeof(str
), "OK\n");
494 SESSION_WRITE(s
, str
);
497 else if ((str
[0] == '*') || (str
[0] == 'T') || (str
[0] == '=') || (str
[0] == '!') || (str
[0] == '<') || (str
[0] == '>'))
499 memset(&ql
, 0, sizeof(asl_msg_list_t
));
500 if (query
!= NULL
) free(query
);
505 while ((*p
== ' ') || (*p
== '\t')) p
++;
514 asprintf(&qs
, "Q %s", p
);
515 query
= asl_msg_from_string(qs
);
518 else if ((*p
== 'T') || (*p
== '=') || (*p
== '!') || (*p
== '<') || (*p
== '>') || (*p
== 'R'))
521 asprintf(&qs
, "Q [%s]", p
);
522 query
= asl_msg_from_string(qs
);
528 asprintf(&qs
, "Q [= %s]", p
);
529 query
= asl_msg_from_string(qs
);
535 snprintf(str
, sizeof(str
), "unrecognized command\n");
536 SESSION_WRITE(s
, str
);
537 snprintf(str
, sizeof(str
), "enter \"help\" for help\n");
538 SESSION_WRITE(s
, str
);
543 if ((flags
& SESSION_FLAGS_LOCKDOWN
) && (watch
== WATCH_RUN
)) continue;
545 /* Bottom of the loop: do a database query and print the results */
554 if (watch
== WATCH_OFF
) low_id
= 0;
558 (void)db_query(&ql
, &res
, low_id
, 0, 0, 0, &high_id
, 0, 0, 0);
560 if ((watch
== WATCH_RUN
) && (high_id
>= low_id
)) low_id
= high_id
+ 1;
564 if (watch
== WATCH_OFF
)
566 snprintf(str
, sizeof(str
), "-nil-\n");
567 SESSION_WRITE(s
, str
);
571 if (do_prompt
!= 2) do_prompt
= 0;
574 else if (pfmt
== PRINT_RAW
)
576 if (watch
== WATCH_RUN
)
578 snprintf(str
, sizeof(str
), "\n");
579 SESSION_WRITE(s
, str
);
583 out
= asl_msg_list_to_string(res
, &outlen
);
584 write(s
, out
, outlen
);
587 snprintf(str
, sizeof(str
), "\n");
588 SESSION_WRITE(s
, str
);
592 if ((watch
== WATCH_RUN
) || (watch
== WATCH_LOCKDOWN_START
))
594 snprintf(str
, sizeof(str
), "\n");
595 SESSION_WRITE(s
, str
);
598 snprintf(str
, sizeof(str
), "\n");
599 for (i
= 0; i
< res
->count
; i
++)
603 out
= asl_format_message(res
->msg
[i
], ASL_MSG_FMT_STD
, ASL_TIME_FMT_LCL
, ASL_ENCODE_SAFE
, &outlen
);
610 wstatus
= write(s
, out
, outlen
);
613 asldebug("%s %d: %d/%d write data failed: %d %s\n", MY_ID
, s
, i
, res
->count
, errno
, strerror(errno
));
625 asl_msg_list_release(res
);
631 } while (errno
== EAGAIN
);
634 if (global
.remote_delay_time
> 0) usleep(global
.remote_delay_time
);
638 asl_msg_list_release(res
);
640 if (watch
== WATCH_LOCKDOWN_START
)
642 add_lockdown_session(s
);
644 dispatch_resume(global
.work_queue
);
650 asldebug("%s %d: terminating session for %ssocket %d\n", MY_ID
, s
, (flags
& SESSION_FLAGS_LOCKDOWN
) ? "lockdown " : "", s
);
654 if (flags
& SESSION_FLAGS_LOCKDOWN
) remove_lockdown_session(s
);
658 if (watch
== WATCH_LOCKDOWN_START
) dispatch_resume(global
.work_queue
);
659 if (wtoken
>= 0) notify_cancel(wtoken
);
660 if (query
!= NULL
) asl_msg_release(query
);
665 remote_acceptmsg(int fd
, int tcp
)
668 int s
, flags
, status
, v
;
671 struct sockaddr_storage from
;
674 fromlen
= sizeof(struct sockaddr_un
);
675 if (tcp
== 1) fromlen
= sizeof(struct sockaddr_storage
);
677 memset(&from
, 0, sizeof(from
));
679 s
= accept(fd
, (struct sockaddr
*)&from
, &fromlen
);
682 asldebug("%s: accept: %s\n", MY_ID
, strerror(errno
));
686 flags
= fcntl(s
, F_GETFL
, 0);
687 flags
&= ~ O_NONBLOCK
;
688 status
= fcntl(s
, F_SETFL
, flags
);
691 asldebug("%s: fcntl: %s\n", MY_ID
, strerror(errno
));
697 setsockopt(s
, SOL_SOCKET
, SO_NOSIGPIPE
, &v
, sizeof(v
));
702 setsockopt(s
, IPPROTO_TCP
, TCP_NODELAY
, &flags
, sizeof(int));
705 sp
= (session_args_t
*)calloc(1, sizeof(session_args_t
));
708 asldebug("%s: malloc: %s\n", MY_ID
, strerror(errno
));
714 if (tcp
== 0) sp
->flags
|= SESSION_FLAGS_LOCKDOWN
;
716 pthread_attr_init(&attr
);
717 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
718 pthread_create(&t
, &attr
, (void *(*)(void *))session
, (void *)sp
);
719 pthread_attr_destroy(&attr
);
725 remote_acceptmsg_local(int fd
)
727 return remote_acceptmsg(fd
, 0);
731 remote_acceptmsg_tcp(int fd
)
733 return remote_acceptmsg(fd
, 1);
737 remote_init_lockdown(void)
739 int status
, reuse
, fd
;
740 struct sockaddr_un local
;
742 fd
= socket(AF_UNIX
, SOCK_STREAM
, 0);
745 asldebug("%s: socket: %s\n", MY_ID
, strerror(errno
));
750 status
= setsockopt(fd
, SOL_SOCKET
, SO_REUSEPORT
, &reuse
, sizeof(int));
753 asldebug("%s: setsockopt: %s\n", MY_ID
, strerror(errno
));
758 /* make sure the lockdown directory exists */
759 mkdir(LOCKDOWN_PATH
, 0777);
761 memset(&local
, 0, sizeof(local
));
762 local
.sun_family
= AF_UNIX
;
763 strlcpy(local
.sun_path
, SYSLOG_SOCK_PATH
, sizeof(local
.sun_path
));
764 unlink(local
.sun_path
);
766 status
= bind(fd
, (struct sockaddr
*)&local
, sizeof(local
.sun_family
) + sizeof(local
.sun_path
));
770 asldebug("%s: bind: %s\n", MY_ID
, strerror(errno
));
775 status
= fcntl(fd
, F_SETFL
, O_NONBLOCK
);
778 asldebug("%s: fcntl: %s\n", MY_ID
, strerror(errno
));
783 status
= listen(fd
, 5);
786 asldebug("%s: listen: %s\n", MY_ID
, strerror(errno
));
791 chmod(SYSLOG_SOCK_PATH
, 0666);
793 in_src_local
= dispatch_source_create(DISPATCH_SOURCE_TYPE_READ
, (uintptr_t)fd
, 0, in_queue
);
794 dispatch_source_set_event_handler(in_src_local
, ^{ remote_acceptmsg_local(fd
); });
795 dispatch_resume(in_src_local
);
801 remote_init_tcp(int family
)
803 int status
, reuse
, fd
;
804 struct sockaddr_in a4
;
805 struct sockaddr_in6 a6
;
809 fd
= socket(family
, SOCK_STREAM
, 0);
812 asldebug("%s: socket: %s\n", MY_ID
, strerror(errno
));
817 status
= setsockopt(fd
, SOL_SOCKET
, SO_REUSEPORT
, &reuse
, sizeof(int));
820 asldebug("%s: setsockopt: %s\n", MY_ID
, strerror(errno
));
825 memset(&(a4
.sin_addr
), 0, sizeof(struct in_addr
));
826 a4
.sin_family
= AF_INET
;
827 a4
.sin_port
= htons(ASL_REMOTE_PORT
);
829 memset(&(a6
.sin6_addr
), 0, sizeof(struct in6_addr
));
830 a6
.sin6_family
= AF_INET6
;
831 a6
.sin6_port
= htons(ASL_REMOTE_PORT
);
833 s
= (struct sockaddr
*)&a4
;
834 len
= sizeof(struct sockaddr_in
);
836 if (family
== AF_INET6
)
838 s
= (struct sockaddr
*)&a6
;
839 len
= sizeof(struct sockaddr_in6
);
842 status
= bind(fd
, s
, len
);
845 asldebug("%s: bind: %s\n", MY_ID
, strerror(errno
));
850 status
= fcntl(fd
, F_SETFL
, O_NONBLOCK
);
853 asldebug("%s: fcntl: %s\n", MY_ID
, strerror(errno
));
858 status
= listen(fd
, 5);
861 asldebug("%s: listen: %s\n", MY_ID
, strerror(errno
));
866 in_src_tcp
= dispatch_source_create(DISPATCH_SOURCE_TYPE_READ
, (uintptr_t)fd
, 0, in_queue
);
867 dispatch_source_set_event_handler(in_src_tcp
, ^{ remote_acceptmsg_tcp(fd
); });
868 dispatch_resume(in_src_tcp
);
876 static dispatch_once_t once
;
878 dispatch_once(&once
, ^{
879 in_queue
= dispatch_queue_create(MY_ID
, NULL
);
882 asldebug("%s: init\n", MY_ID
);
885 rfdl
= remote_init_lockdown();
889 rfd4
= remote_init_tcp(AF_INET
);
893 rfd6
= remote_init_tcp(AF_INET6
);
932 return remote_init();
935 #endif /* !TARGET_OS_SIMULATOR */