]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/xpc_services/xpc_service_log_utility.c
mDNSResponder-1310.80.1.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / xpc_services / xpc_service_log_utility.c
1 /*
2 * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <xpc/xpc.h>
18 #include <dirent.h> // opendir
19 #include <sys/stat.h> // stat
20 #include <archive.h>
21 #include <archive_entry.h>
22 #include <AssertMacros.h> // require, require_action
23
24 #include "mDNSMacOSX.h"
25 #include "helper.h"
26 #include "xpc_services.h"
27 #include "xpc_service_log_utility.h"
28 #include "xpc_clients.h"
29 #include "system_utilities.h" // IsAppleInternalBuild
30
31 #define STATE_DUMP_PLAIN_SUFFIX "txt"
32 #define STATE_DUMP_COMPRESSED_SUFFIX "tar.bz2"
33
34 // global variables
35 extern mDNS mDNSStorage;
36 static dispatch_queue_t log_utility_server_queue = NULL;
37
38 // function declaration
39 extern void
40 dump_state_to_fd(int fd);
41
42 mDNSlocal void
43 accept_client(xpc_connection_t conn);
44
45 mDNSlocal mDNSs8
46 handle_requests(xpc_object_t req);
47
48 mDNSlocal mDNSs8
49 check_permission(xpc_connection_t connection);
50
51 mDNSlocal mDNSs8
52 handle_state_dump(mDNSu32 dump_option, char *full_file_name, mDNSu32 name_buffer_len, int client_fd,
53 mDNSs32 *time_ms_used);
54
55 mDNSlocal mDNSs32
56 find_oldest_state_dump(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len,
57 char *oldest_file_name);
58
59 mDNSlocal mDNSs8
60 remove_state_dump_if_too_many(const char *dump_dir, const char *oldest_file_name, mDNSs32 dump_file_count,
61 mDNSs32 max_allowed);
62
63 mDNSlocal int
64 create_new_state_dump_file(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len);
65
66 mDNSlocal mDNSs8
67 handle_state_dump_to_fd(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len,
68 mDNSBool if_compress);
69
70 mDNSlocal mDNSs8
71 compress_state_dump_and_delete(char *input_file, mDNSu32 buffer_len);
72
73 mDNSlocal mDNSs32
74 timediff_ms(struct timeval *t1, struct timeval *t2);
75
76 // function definition
77 mDNSexport void
78 init_log_utility_service(void)
79 {
80 xpc_connection_t log_utility_listener = xpc_connection_create_mach_service(kDNSLogUtilityService, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
81 if (!log_utility_listener || xpc_get_type(log_utility_listener) != XPC_TYPE_CONNECTION) {
82 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "Error Creating XPC Listener for Log Utility Server!");
83 return;
84 }
85
86 log_utility_server_queue = dispatch_queue_create("com.apple.mDNSResponder.log_utility_server_queue", NULL);
87
88 xpc_connection_set_event_handler(log_utility_listener, ^(xpc_object_t eventmsg) {
89 xpc_type_t type = xpc_get_type(eventmsg);
90
91 if (type == XPC_TYPE_CONNECTION) {
92 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_INFO, "C%p {action='receives connection'}", eventmsg);
93 accept_client(eventmsg);
94 } else if (type == XPC_TYPE_ERROR) {
95 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "C%p {xpc_error=\n" PUB_S "\n}", eventmsg,
96 xpc_dictionary_get_string(eventmsg, XPC_ERROR_KEY_DESCRIPTION));
97 } else {
98 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "C%p {error='receives unknown xpc request'}", eventmsg);
99 }
100 });
101
102 xpc_connection_resume(log_utility_listener);
103 }
104
105 mDNSlocal void
106 accept_client(xpc_connection_t conn)
107 {
108 xpc_retain(conn);
109 xpc_connection_set_target_queue(conn, log_utility_server_queue);
110 xpc_connection_set_event_handler(conn, ^(xpc_object_t req_msg) {
111 xpc_type_t type = xpc_get_type(req_msg);
112
113 if (type == XPC_TYPE_DICTIONARY) {
114 handle_requests(req_msg);
115 } else { // We hit this case ONLY if Client Terminated Connection OR Crashed
116 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT, "C%p {status='client closed the connection'}", conn);
117 xpc_release(conn);
118 }
119 });
120
121 xpc_connection_resume(conn);
122 }
123
124 mDNSlocal mDNSs8
125 handle_requests(xpc_object_t req)
126 {
127 mDNSs8 ret = 0;
128 xpc_connection_t remote_conn = xpc_dictionary_get_remote_connection(req);
129
130 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_INFO, "C%p {action='handling log utility request'}", remote_conn);
131
132 // create the dictionary for response purpose
133 xpc_object_t response = xpc_dictionary_create_reply(req);
134 if (response == mDNSNULL) {
135 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "C%p {error='cannot create reply response dictionary'}", remote_conn);
136 return -1;
137 }
138
139 mDNSu32 reply_value;
140 ret = check_permission(remote_conn);
141 if (ret < 0) {
142 // permission error
143 reply_value = kDNSMsg_Error;
144 if (ret == -1) {
145 xpc_dictionary_set_string(response, kDNSErrorDescription, "Client must be running as root");
146 } else if (ret == -2) {
147 xpc_dictionary_set_string(response, kDNSErrorDescription, "Client is missing the entitlement");
148 }
149 } else if (xpc_dictionary_get_uint64(req, kDNSStateDump)) {
150 mDNSu32 dump_option = (mDNSs32)xpc_dictionary_get_uint64(req, kDNSStateDump);
151 char full_file_name[PATH_MAX];
152 mDNSs32 time_used;
153
154 // We do not dump state in the customer build due to privacy consideration.
155 if (IsAppleInternalBuild()) {
156 int client_fd = xpc_dictionary_dup_fd(req, kDNSStateDumpFD);
157 ret = handle_state_dump(dump_option, full_file_name, sizeof(full_file_name), client_fd, &time_used);
158 if (ret == 0) {
159 reply_value = kDNSMsg_NoError;
160 xpc_dictionary_set_int64(response, kDNSStateDumpTimeUsed, time_used);
161
162 if (dump_option != full_state_to_stdout) {
163 xpc_dictionary_set_string(response, kDNSDumpFilePath, full_file_name);
164 }
165 } else {
166 reply_value = kDNSMsg_Error;
167 xpc_dictionary_set_string(response, kDNSErrorDescription, "State dump fails");
168 }
169 close(client_fd);
170 } else {
171 reply_value = kDNSMsg_Error;
172 xpc_dictionary_set_string(response, kDNSErrorDescription, "State dump is only enabled in internal builds");
173 }
174 } else {
175 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR,
176 "C%p {error='unknown log utility request from client'}", remote_conn);
177 reply_value = kDNSMsg_UnknownRequest;
178 xpc_dictionary_set_string(response, kDNSErrorDescription, "unknown log utility request from client");
179 }
180
181 xpc_dictionary_set_uint64(response, kDNSDaemonReply, reply_value);
182 xpc_connection_send_message(remote_conn, response);
183 xpc_release(response);
184
185 return 0;
186 }
187
188 mDNSlocal mDNSs8
189 check_permission(xpc_connection_t connection)
190 {
191 uid_t client_euid = xpc_connection_get_euid(connection);
192 int client_pid = xpc_connection_get_pid(connection);
193 mDNSs8 ret = 0;
194
195 if (client_euid != 0) {
196 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
197 "C%p {client_pid=%d,error='not running as root'}", connection, client_pid);
198 ret = -1;
199 }
200
201 if (!IsEntitled(connection, kDNSLogUtilityService)){
202 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
203 "C%p {client_pid=%d,error='Client is missing entitlement'}", connection, client_pid);
204 ret = -2;
205 }
206
207 return ret;
208 }
209
210 /*
211 * pointers of full_file_name and time_used are passed into, when function returns, full_file_name will be filled
212 * with the full path to the dumped file, and time_used is filled with time duration(ms) when mDNSResponder is
213 * blocked. if_get_lock indicates if we should lock the kqueue before dumping the state.
214 */
215 mDNSlocal mDNSs8
216 handle_state_dump(mDNSu32 dump_option, char *full_file_name, mDNSu32 name_buffer_len, int client_fd,
217 mDNSs32 *time_ms_used)
218 {
219 mDNSs8 ret;
220
221 // record the start time, and lock the kqueue
222 struct timeval time_start;
223 gettimeofday(&time_start, mDNSNULL);
224 KQueueLock();
225
226 if (dump_option == full_state_to_stdout) {
227 dump_state_to_fd(client_fd);
228 ret = 0;
229 } else {
230 // dump_option == full_state || dump_option == full_state_with_compression
231 ret = handle_state_dump_to_fd(MDSNRESPONDER_STATE_DUMP_DIR, MDSNRESPONDER_STATE_DUMP_FILE_NAME,
232 full_file_name, name_buffer_len, dump_option == full_state_with_compression ? mDNStrue : mDNSfalse);
233 }
234
235 // unlock the kqueue, record the end time and calculate the duration.
236 KQueueUnlock("State Dump");
237 struct timeval time_end;
238 gettimeofday(&time_end, mDNSNULL);
239 *time_ms_used = timediff_ms(&time_end, &time_start);
240
241 return ret;
242 }
243
244 #define MAX_NUM_DUMP_FILES 5 // controls how many files we are allowed to created for the state dump
245 mDNSlocal mDNSs8
246 handle_state_dump_to_fd(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len,
247 mDNSBool if_compress){
248
249 char oldest_file_name[PATH_MAX];
250 mDNSs32 dump_file_count = 0;
251 int ret;
252
253 dump_file_count = find_oldest_state_dump(dump_dir, file_name, full_file_name, buffer_len, oldest_file_name);
254 require_action(dump_file_count >= 0, error,
255 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "find_oldest_state_dump fails"));
256
257 ret = remove_state_dump_if_too_many(dump_dir, oldest_file_name, dump_file_count, MAX_NUM_DUMP_FILES);
258 require_action(ret == 0, error,
259 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "remove_state_dump_if_too_many fails"));
260
261 int fd = create_new_state_dump_file(dump_dir, file_name, full_file_name, buffer_len);
262 require_action(fd >= 0, error,
263 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "create_new_state_dump_file fails"));
264
265 dump_state_to_fd(fd);
266 close(fd); // create_new_state_dump_file open the file, we have to close it here
267
268 if (if_compress) {
269 ret = compress_state_dump_and_delete(full_file_name, buffer_len);
270 require_action(ret == 0, error,
271 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT, "State Dump: Error happens when trying to compress the state dump, reason: %s", strerror(errno)));
272 }
273
274 return 0;
275
276 error:
277 return -1;
278 }
279
280 /*
281 * Scan the directory, find all the files that start with <mDNSResponder state dump file name>. Return the number of
282 * state dump files and the name of the oldest file created.
283 */
284 mDNSlocal mDNSs32
285 find_oldest_state_dump(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len,
286 char *oldest_file_name)
287 {
288 int ret;
289
290 full_file_name[0] = '\0';
291 full_file_name[buffer_len - 1]= '\0';
292 snprintf(full_file_name, buffer_len - 1, "%s/%s", dump_dir, file_name);
293
294 DIR *dir_p = opendir(dump_dir);
295 if (dir_p == mDNSNULL) {
296 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
297 "State Dump: directory " PUB_S " cannot be opened, reason: " PUB_S, dump_dir, strerror(errno));
298 return -1;
299 }
300
301 // scan every entry under directory, if starts with <mDNSResponder state dump file name>, check its create time.
302 struct dirent *dir_entry_p = mDNSNULL;
303 size_t file_name_len = strnlen(file_name, MAXPATHLEN);
304 mDNSu8 dump_file_count = 0;
305 struct timespec oldest_time = {LONG_MAX, LONG_MAX};
306
307 while ((dir_entry_p = readdir(dir_p)) != mDNSNULL) {
308 if (dir_entry_p->d_namlen <= file_name_len)
309 continue;
310
311 if (strncmp(dir_entry_p->d_name, file_name, file_name_len) == 0) {
312 struct stat file_state;
313 snprintf(full_file_name, buffer_len - 1, "%s/%s", dump_dir, dir_entry_p->d_name);
314
315 // use stat to get creation time
316 ret = stat(full_file_name, &file_state);
317 if (ret != 0) {
318 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
319 "State Dump: error when reading file properties, reason: " PUB_S, strerror(errno));
320 return -1;
321 }
322 // if the file is older than the current record
323 if (oldest_time.tv_sec > file_state.st_birthtimespec.tv_sec
324 || (oldest_time.tv_sec == file_state.st_birthtimespec.tv_sec
325 && oldest_time.tv_sec > file_state.st_birthtimespec.tv_sec)) {
326 oldest_time = file_state.st_birthtimespec;
327 strncpy(oldest_file_name, dir_entry_p->d_name, MIN(PATH_MAX - 1, dir_entry_p->d_namlen + 1));
328 }
329
330 dump_file_count++;
331 }
332 }
333 closedir(dir_p);
334
335 return dump_file_count;
336 }
337
338 mDNSlocal mDNSs8
339 remove_state_dump_if_too_many(const char *dump_dir, const char *oldest_file_name, mDNSs32 dump_file_count,
340 mDNSs32 max_allowed)
341 {
342 char path_file_to_remove[PATH_MAX];
343 path_file_to_remove[PATH_MAX - 1] = '\0';
344 // If the number of state dump files has reached the maximum value, we delete the oldest one.
345 if (dump_file_count == max_allowed) {
346 // construct the full name
347 snprintf(path_file_to_remove, PATH_MAX - 1, "%s/%s", dump_dir, oldest_file_name);
348 if (remove(path_file_to_remove) != 0) {
349 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
350 "State Dump: file " PUB_S " cannot be deleted, reason: " PUB_S, path_file_to_remove, strerror(errno));
351 return -1;
352 }
353 }
354
355 return 0;
356 }
357
358 /*
359 * Generate the file name of state dump with current time stamp, and return the FILE pointer, anyone who calls this
360 * function must call fclose() to release the FILE pointer.
361 */
362 mDNSlocal int
363 create_new_state_dump_file(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len)
364 {
365 struct timeval now;
366 struct tm local_time;
367 char date_time_str[32];
368 char time_zone_str[32];
369
370 gettimeofday(&now, NULL);
371 localtime_r(&now.tv_sec, &local_time);
372
373 // 2008-08-08_20-00-00
374 strftime(date_time_str, sizeof(date_time_str), "%F_%H-%M-%S", &local_time);
375 // +0800
376 strftime(time_zone_str, sizeof(time_zone_str), "%z", &local_time);
377 // /private/var/log/mDNSResponder/mDNSResponder_state_dump_2008-08-08_20-00-00-000000+0800.txt
378 snprintf(full_file_name, buffer_len, "%s/%s_%s-%06lu%s." STATE_DUMP_PLAIN_SUFFIX,
379 dump_dir, file_name, date_time_str, (unsigned long)now.tv_usec, time_zone_str);
380
381 int fd = open(full_file_name, O_WRONLY | O_CREAT, 0644); // 0644 means * (owning) User: read & write * Group: read * Other: read
382 if (fd < 0) {
383 LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
384 "State Dump: file " PUB_S " cannot be opened, reason: " PUB_S, full_file_name, strerror(errno));
385 return -1;
386 }
387
388 return fd;
389 }
390
391 /*
392 * Compress the state dump from pliantext to tar.bz2, remove the original one, and change the content of input_file to
393 * newly created compressed file if compression succeeds.
394 */
395 mDNSlocal mDNSs8
396 compress_state_dump_and_delete(char *input_file, mDNSu32 buffer_len)
397 {
398 struct archive *a = mDNSNULL;
399 mDNSBool archive_opened = mDNSfalse;
400 struct archive_entry *entry = mDNSNULL;
401 struct stat st;
402 int fd = -1;
403 char output_file[PATH_MAX];
404 void *mapped_pointer = MAP_FAILED;
405 int ret;
406 int error;
407 mDNSu32 file_size;
408
409 output_file[PATH_MAX - 1] = '\0';
410
411 a = archive_write_new();
412 require_action(a != mDNSNULL, exit, error = -1;
413 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_new fails: " PUB_S, archive_error_string(a)));
414 archive_write_add_filter_bzip2(a);
415 archive_write_set_format_ustar(a);
416
417 // remove the .txt suffix, and append .tar.bz2 suffix
418 size_t plain_file_name_len = strlen(input_file); // input_file is guaranteed to be '\0'-terminated
419 strncpy(output_file, input_file, plain_file_name_len - sizeof(STATE_DUMP_PLAIN_SUFFIX));
420 output_file[plain_file_name_len - sizeof(STATE_DUMP_PLAIN_SUFFIX)] = '\0';
421 strncat(output_file, "." STATE_DUMP_COMPRESSED_SUFFIX, 1 + sizeof(STATE_DUMP_COMPRESSED_SUFFIX));
422
423 // open/create the archive for the given path name
424 ret = archive_write_open_filename(a, output_file);
425 archive_opened = (ret == ARCHIVE_OK);
426 require_action(archive_opened, exit, error = -1;
427 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_open_filename fails: " PUB_S, archive_error_string(a)));
428
429 // get the state of file to be compressed
430 stat(input_file, &st);
431
432 // entry is required to create an archive
433 entry = archive_entry_new();
434 require_action(entry != mDNSNULL, exit, error = -1;
435 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_entry_new fails: " PUB_S, strerror(errno)));
436
437 // set the name of file in the compressed file
438 const char *file_name_with_timestamp = strstr(input_file, MDSNRESPONDER_STATE_DUMP_FILE_NAME);
439 if (file_name_with_timestamp == mDNSNULL) {
440 file_name_with_timestamp = MDSNRESPONDER_STATE_DUMP_FILE_NAME "." STATE_DUMP_PLAIN_SUFFIX;
441 }
442
443 // copy the original file state to entry
444 archive_entry_copy_stat(entry, &st);
445 archive_entry_set_pathname(entry, file_name_with_timestamp);
446
447 // write entry into archive
448 do {
449 ret = archive_write_header(a, entry);
450 } while (ret == ARCHIVE_RETRY);
451 require_action(ret == ARCHIVE_OK, exit, error = -1;
452 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_header fails: " PUB_S, archive_error_string(a)));
453
454 // if the original file has something to compress, use mmap to read its content
455 require_action_quiet(st.st_size > 0, exit, error = -1);
456 require_action_quiet(st.st_size <= UINT32_MAX, exit, error = -1);
457
458 file_size = (mDNSu32)st.st_size;
459 fd = open(input_file, O_RDONLY);
460 require_action_quiet(fd != -1, exit, error = -1);
461
462 mapped_pointer = mmap(NULL, file_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
463 require_action(mapped_pointer != MAP_FAILED, exit, error = -1;
464 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "mmap fails: " PUB_S, strerror(errno)));
465
466 const ssize_t amount_written = archive_write_data(a, mapped_pointer, file_size);
467 require_action((amount_written >= 0) && (((size_t)amount_written) == file_size), exit, error = -1;
468 LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_data fails: amount_written(%ld) != (%u)",
469 (long)amount_written, file_size));
470
471 error = 0;
472
473 exit:
474 if (mapped_pointer != MAP_FAILED) {
475 munmap(mapped_pointer, file_size); // undo mmap
476 }
477 if (fd != -1) {
478 close(fd); // undo open
479 }
480 if (entry != mDNSNULL) {
481 archive_entry_free(entry); // undo archive_entry_new
482 }
483 if (archive_opened) {
484 archive_write_close(a); // undo archive_write_open_filename
485 }
486 if (a != mDNSNULL) {
487 archive_write_free(a); // undo archive_write_new
488 }
489 remove(input_file);
490 if (error == 0) {
491 strncpy(input_file, output_file, buffer_len);
492 } else {
493 input_file[0] = '\0';
494 }
495 return error;
496 }
497
498 /*
499 * Return the time difference(ms) between two struct timeval.
500 */
501 #define US_PER_S 1000000
502 #define MS_PER_S 1000
503 mDNSlocal mDNSs32
504 timediff_ms(struct timeval *t1, struct timeval *t2)
505 {
506 int usec, ms, sec;
507
508 if (t1->tv_sec < t2->tv_sec || (t1->tv_sec == t2->tv_sec && t1->tv_usec < t2->tv_usec)) {
509 return -timediff_ms(t2, t1);
510 }
511
512 sec = (int)(t1->tv_sec - t2->tv_sec);
513 if (t1->tv_usec >= t2->tv_usec) {
514 usec = t1->tv_usec - t2->tv_usec;
515 } else {
516 usec = t1->tv_usec + US_PER_S - t2->tv_usec;
517 sec -= 1;
518 }
519 ms = sec * MS_PER_S;
520 ms += usec / MS_PER_S;
521 return ms;
522 }