#include <sys/cdefs.h>
#include <sys/time.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <bootstrap_priv.h>
#include <launch.h>
#include <pwd.h>
#include <dirent.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#include "LKDCHelper-main.h"
#include "LKDCHelper.h"
#include "LKDCHelperMessage.h"
#include "LKDCHelperMessageServer.h"
union max_msg_size {
union __RequestUnion__request_LKDCHelper_subsystem request;
union __ReplyUnion__request_LKDCHelper_subsystem reply;
};
static const mach_msg_size_t MAX_MSG_SIZE = sizeof(union max_msg_size) + MAX_TRAILER_SIZE;
static aslclient logclient = NULL;
static int opt_debug;
static struct timeval last_message;
static pthread_t idletimer_thread;
volatile int LKDCLogLevel = ASL_LEVEL_DEBUG;
unsigned long maxidle = 0;
static uid_t LKDCHelperUID = -1;
static gid_t LKDCHelperGID = -1;
static void
helplogv(int level, const char *fmt, va_list ap)
{
if (NULL == logclient) {
vfprintf(stderr, fmt, ap);
fflush(stderr);
} else {
asl_vlog(logclient, NULL, level, fmt, ap);
}
}
void
helplog(int level, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
helplogv(level, fmt, ap);
va_end(ap);
}
void
debug_(const char *func, const char *fmt, ...)
{
char buf[2048];
va_list ap;
ssize_t n = snprintf(buf, sizeof(buf), "%s: ", func);
if (n >= (int)sizeof(buf)) {
return;
}
va_start(ap, fmt);
vsnprintf(&buf[n], sizeof(buf)-n, fmt, ap);
va_end(ap);
helplog(ASL_LEVEL_DEBUG, buf);
}
int
authorized(audit_token_t *token)
{
int ok = 0;
pid_t pid = (pid_t)-1;
uid_t euid = (uid_t)-1;
audit_token_to_au32(*token, NULL, &euid, NULL, NULL, NULL, &pid, NULL, NULL);
ok = (euid == LKDCHelperUID || euid == 0);
if (!ok) {
helplog(ASL_LEVEL_NOTICE, "Unauthorized access by euid=%lu pid=%lu",
(unsigned long)euid, (unsigned long)pid);
}
return ok;
}
static void
initialize_logging(void)
{
logclient = asl_open(NULL, NULL, (opt_debug ? ASL_OPT_STDERR : 0));
if (NULL == logclient) {
fprintf(stderr, "Could not initialize ASL logging.\n");
fflush(stderr);
return;
}
if (opt_debug) {
asl_set_filter(logclient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
}
}
void
update_idle_timer(void)
{
assert(0 == gettimeofday(&last_message, NULL));
}
static void *
idletimer_main (void *context)
{
static struct timeval now;
for (;;) {
assert(0 == gettimeofday(&now, NULL));
if (now.tv_sec - last_message.tv_sec > (long) maxidle) {
(void) request_LKDCHelperExit ((mach_port_t) context);
sleep(1);
} else {
int t = maxidle - (now.tv_sec - last_message.tv_sec);
if (t < 1)
t = 1;
sleep(t);
}
}
return NULL;
}
static void
initialize_timer(mach_port_t port)
{
int err;
update_idle_timer();
err = pthread_create(&idletimer_thread, NULL, idletimer_main, (void *)port);
if (0 != err) {
helplog(ASL_LEVEL_ERR, "Failed to start idletimer thread: %s", strerror(err));
}
}
static mach_port_t
checkin(char *service_name)
{
kern_return_t kr = KERN_SUCCESS;
mach_port_t port = MACH_PORT_NULL;
launch_data_t msg, reply = NULL, datum = NULL;
msg = launch_data_new_string (LAUNCH_KEY_CHECKIN);
if (NULL == msg) {
helplog(ASL_LEVEL_ERR, "Could not create checkin message for launchd.");
goto fin;
}
reply = launch_msg (msg);
if (NULL == reply) {
helplog(ASL_LEVEL_ERR, "Could not message launchd.");
goto fin;
}
if (LAUNCH_DATA_ERRNO == launch_data_get_type (reply)) {
helplog(ASL_LEVEL_ERR, "Launchd checkin failed: %s.",
strerror (launch_data_get_errno (reply))
);
goto fin;
}
datum = launch_data_dict_lookup (reply, LAUNCH_JOBKEY_MACHSERVICES);
if (NULL == datum || LAUNCH_DATA_DICTIONARY != launch_data_get_type (datum)) {
helplog(ASL_LEVEL_ERR, "Launchd reply does not contain %s dictionary.", LAUNCH_JOBKEY_MACHSERVICES);
goto fin;
}
datum = launch_data_dict_lookup (datum, service_name);
if (NULL == datum || LAUNCH_DATA_MACHPORT != launch_data_get_type (datum)) {
helplog(ASL_LEVEL_ERR, "Launchd reply does not contain %s Mach port.", service_name);
goto fin;
}
port = launch_data_get_machport (datum);
if (MACH_PORT_NULL == port) {
helplog(ASL_LEVEL_ERR, "Launchd gave me a null Mach port.");
goto fin;
}
kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
if (KERN_SUCCESS != kr) {
helplog(ASL_LEVEL_ERR, "mach_port_insert_right: %s", mach_error_string(kr));
goto fin;
}
fin:
if (NULL != msg) { launch_data_free(msg); }
if (NULL != reply) { launch_data_free(reply); }
if (MACH_PORT_NULL == port) { exit(EXIT_FAILURE); }
return port;
}
static mach_port_t
register_service (const char *service_name)
{
mach_port_t port = MACH_PORT_NULL;
kern_return_t kr;
kr = mach_port_allocate (mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
if (KERN_SUCCESS != kr) {
helplog(ASL_LEVEL_ERR, "mach_port_allocate: %s", mach_error_string(kr));
goto error;
}
kr = mach_port_insert_right (mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
if (KERN_SUCCESS != kr) {
helplog(ASL_LEVEL_ERR, "mach_port_insert_right: %s", mach_error_string (kr));
goto error;
}
kr = bootstrap_register2 (bootstrap_port, (char *)service_name, port, 0);
if (KERN_SUCCESS != kr) {
helplog(ASL_LEVEL_ERR, "bootstrap_register2 failed: %s", mach_error_string (kr));
goto error;
}
return port;
error:
if (MACH_PORT_NULL != port) { mach_port_deallocate (mach_task_self (), port); }
return MACH_PORT_NULL;
}
int
main(int ac, char *av[])
{
char *p = NULL;
kern_return_t kr = KERN_FAILURE;
mach_port_t port = MACH_PORT_NULL;
long n;
int ch;
while ((ch = getopt(ac, av, "dt:")) != -1)
switch (ch) {
case 'd':
opt_debug = 1;
LKDCLogLevel = ASL_LEVEL_NOTICE;
break;
case 't':
n = strtol(optarg, &p, 0);
if ('\0' == optarg[0] || '\0' != *p || n > LONG_MAX || n < 1) {
fprintf(stderr, "Invalid idle timeout: %s\n", optarg);
exit(EXIT_FAILURE);
}
maxidle = n;
break;
case '?':
default:
fprintf(stderr, "Usage: [-d] [-t maxidle]\n");
exit(EXIT_FAILURE);
}
ac -= optind;
av += optind;
initialize_logging ();
LKDCHelperUID = getuid ();
LKDCHelperGID = getgid ();
helplog (ASL_LEVEL_NOTICE, "Starting (uid=%ul)", (unsigned long)LKDCHelperUID);
if (opt_debug) {
port = register_service (kLKDCHelperName);
} else {
port = checkin (kLKDCHelperName);
}
if (maxidle > 0) {
initialize_timer(port);
}
kr = mach_msg_server(LKDCHelper_server, MAX_MSG_SIZE, port,
MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) |
MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)
);
if (KERN_SUCCESS != kr) {
helplog(ASL_LEVEL_ERR, "mach_msg_server: %s\n", mach_error_string(kr));
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}