/* * syslog_relay.c * com.apple.syslog_relay service implementation. * * Copyright (c) 2019-2020 Nikias Bassen, All Rights Reserved. * Copyright (c) 2013-2015 Martin Szulecki, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "syslog_relay.h" #include "lockdown.h" #include "common/debug.h" struct syslog_relay_worker_thread { syslog_relay_client_t client; syslog_relay_receive_cb_t cbfunc; void *user_data; int is_raw; }; /** * Convert a service_error_t value to a syslog_relay_error_t value. * Used internally to get correct error codes. * * @param err An service_error_t error code * * @return A matching syslog_relay_error_t error code, * SYSLOG_RELAY_E_UNKNOWN_ERROR otherwise. */ static syslog_relay_error_t syslog_relay_error(service_error_t err) { switch (err) { case SERVICE_E_SUCCESS: return SYSLOG_RELAY_E_SUCCESS; case SERVICE_E_INVALID_ARG: return SYSLOG_RELAY_E_INVALID_ARG; case SERVICE_E_MUX_ERROR: return SYSLOG_RELAY_E_MUX_ERROR; case SERVICE_E_SSL_ERROR: return SYSLOG_RELAY_E_SSL_ERROR; case SERVICE_E_NOT_ENOUGH_DATA: return SYSLOG_RELAY_E_NOT_ENOUGH_DATA; case SERVICE_E_TIMEOUT: return SYSLOG_RELAY_E_TIMEOUT; default: break; } return SYSLOG_RELAY_E_UNKNOWN_ERROR; } syslog_relay_error_t syslog_relay_client_new(idevice_t device, lockdownd_service_descriptor_t service, syslog_relay_client_t * client) { *client = NULL; if (!device || !service || service->port == 0 || !client || *client) { debug_info("Incorrect parameter passed to syslog_relay_client_new."); return SYSLOG_RELAY_E_INVALID_ARG; } debug_info("Creating syslog_relay_client, port = %d.", service->port); service_client_t parent = NULL; syslog_relay_error_t ret = syslog_relay_error(service_client_new(device, service, &parent)); if (ret != SYSLOG_RELAY_E_SUCCESS) { debug_info("Creating base service client failed. Error: %i", ret); return ret; } syslog_relay_client_t client_loc = (syslog_relay_client_t) malloc(sizeof(struct syslog_relay_client_private)); client_loc->parent = parent; client_loc->worker = THREAD_T_NULL; *client = client_loc; debug_info("syslog_relay_client successfully created."); return 0; } syslog_relay_error_t syslog_relay_client_start_service(idevice_t device, syslog_relay_client_t * client, const char* label) { syslog_relay_error_t err = SYSLOG_RELAY_E_UNKNOWN_ERROR; service_client_factory_start_service(device, SYSLOG_RELAY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(syslog_relay_client_new), &err); return err; } syslog_relay_error_t syslog_relay_client_free(syslog_relay_client_t client) { if (!client) return SYSLOG_RELAY_E_INVALID_ARG; syslog_relay_stop_capture(client); syslog_relay_error_t err = syslog_relay_error(service_client_free(client->parent)); free(client); return err; } syslog_relay_error_t syslog_relay_receive(syslog_relay_client_t client, char* data, uint32_t size, uint32_t *received) { return syslog_relay_receive_with_timeout(client, data, size, received, 1000); } syslog_relay_error_t syslog_relay_receive_with_timeout(syslog_relay_client_t client, char* data, uint32_t size, uint32_t *received, unsigned int timeout) { syslog_relay_error_t res = SYSLOG_RELAY_E_UNKNOWN_ERROR; int bytes = 0; if (!client || !data || (size == 0)) { return SYSLOG_RELAY_E_INVALID_ARG; } res = syslog_relay_error(service_receive_with_timeout(client->parent, data, size, (uint32_t*)&bytes, timeout)); if (res != SYSLOG_RELAY_E_SUCCESS && res != SYSLOG_RELAY_E_TIMEOUT && res != SYSLOG_RELAY_E_NOT_ENOUGH_DATA) { debug_info("Could not read data, error %d", res); } if (received) { *received = (uint32_t)bytes; } return res; } void *syslog_relay_worker(void *arg) { syslog_relay_error_t ret = SYSLOG_RELAY_E_UNKNOWN_ERROR; struct syslog_relay_worker_thread *srwt = (struct syslog_relay_worker_thread*)arg; if (!srwt) return NULL; debug_info("Running"); while (srwt->client->parent) { char c; uint32_t bytes = 0; ret = syslog_relay_receive_with_timeout(srwt->client, &c, 1, &bytes, 100); if (ret == SYSLOG_RELAY_E_TIMEOUT || ret == SYSLOG_RELAY_E_NOT_ENOUGH_DATA || ((bytes == 0) && (ret == SYSLOG_RELAY_E_SUCCESS))) { continue; } if (ret < 0) { debug_info("Connection to syslog relay interrupted"); break; } if (srwt->is_raw) { srwt->cbfunc(c, srwt->user_data); } else if (c != 0) { srwt->cbfunc(c, srwt->user_data); } } if (srwt) { free(srwt); } debug_info("Exiting"); return NULL; } syslog_relay_error_t syslog_relay_start_capture(syslog_relay_client_t client, syslog_relay_receive_cb_t callback, void* user_data) { if (!client || !callback) return SYSLOG_RELAY_E_INVALID_ARG; syslog_relay_error_t res = SYSLOG_RELAY_E_UNKNOWN_ERROR; if (client->worker) { debug_info("Another syslog capture thread appears to be running already."); return res; } /* start worker thread */ struct syslog_relay_worker_thread *srwt = (struct syslog_relay_worker_thread*)malloc(sizeof(struct syslog_relay_worker_thread)); if (srwt) { srwt->client = client; srwt->cbfunc = callback; srwt->user_data = user_data; srwt->is_raw = 0; if (thread_new(&client->worker, syslog_relay_worker, srwt) == 0) { res = SYSLOG_RELAY_E_SUCCESS; } } return res; } syslog_relay_error_t syslog_relay_start_capture_raw(syslog_relay_client_t client, syslog_relay_receive_cb_t callback, void* user_data) { if (!client || !callback) return SYSLOG_RELAY_E_INVALID_ARG; syslog_relay_error_t res = SYSLOG_RELAY_E_UNKNOWN_ERROR; if (client->worker) { debug_info("Another syslog capture thread appears to be running already."); return res; } /* start worker thread */ struct syslog_relay_worker_thread *srwt = (struct syslog_relay_worker_thread*)malloc(sizeof(struct syslog_relay_worker_thread)); if (srwt) { srwt->client = client; srwt->cbfunc = callback; srwt->user_data = user_data; srwt->is_raw = 1; if (thread_new(&client->worker, syslog_relay_worker, srwt) == 0) { res = SYSLOG_RELAY_E_SUCCESS; } } return res; } syslog_relay_error_t syslog_relay_stop_capture(syslog_relay_client_t client) { if (client->worker) { /* notify thread to finish */ service_client_t parent = client->parent; client->parent = NULL; /* join thread to make it exit */ thread_join(client->worker); thread_free(client->worker); client->worker = THREAD_T_NULL; client->parent = parent; } return SYSLOG_RELAY_E_SUCCESS; }