#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <fts.h>
#include <pthread.h>
#include <pwd.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <servers/bootstrap.h>
#include <bsm/libbsm.h>
#include <asl.h>
#include <membership.h>
#include <launch.h>
#include <dirhelper_priv.h>
#include "dirhelper_server.h"
struct idle_globals {
mach_port_t mp;
long timeout;
struct timeval lastmsg;
};
struct idle_globals idle_globals;
void* idle_thread(void* param __attribute__((unused)));
int file_check(const char* path, int mode, int uid, int gid);
#define is_file(x) file_check((x), S_IFREG, -1, -1)
#define is_directory(x) file_check((x), S_IFDIR, -1, -1)
#define is_root_wheel_directory(x) file_check((x), S_IFDIR, 0, 0)
int is_safeboot(void);
void clean_files_older_than(const char* path, time_t when);
void clean_directories(const char* names[], int);
kern_return_t
do___dirhelper_create_user_local(
mach_port_t server_port __attribute__((unused)),
audit_token_t au_tok)
{
int res = 0;
uid_t euid;
gid_t gid = 0;
struct passwd* pwd = NULL;
gettimeofday(&idle_globals.lastmsg, NULL);
audit_token_to_au32(au_tok,
NULL, &euid, NULL, NULL, NULL, NULL, NULL, NULL);
pwd = getpwuid(euid);
if (pwd) gid = pwd->pw_gid;
do { char path[PATH_MAX];
char *next;
if (__user_local_dirname(euid, DIRHELPER_USER_LOCAL, path, sizeof(path)) == NULL) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"__user_local_dirname: %s", strerror(errno));
break;
}
next = path + strlen(VAR_FOLDERS_PATH);
while ((next = strchr(next, '/')) != NULL) {
*next = 0; res = mkdir(path, 0755);
if (res != 0 && errno != EEXIST) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"mkdir(%s): %s", path, strerror(errno));
break;
}
*next++ = '/'; }
if(next || res) break;
res = chown(path, euid, gid);
if (res != 0) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"chown(%s): %s", path, strerror(errno));
}
} while(0); return KERN_SUCCESS;
}
kern_return_t
do___dirhelper_idle_exit(
mach_port_t server_port __attribute__((unused)),
audit_token_t au_tok __attribute__((unused))) {
struct timeval now;
gettimeofday(&now, NULL);
long delta = now.tv_sec - idle_globals.lastmsg.tv_sec;
if (delta >= idle_globals.timeout) {
asl_log(NULL, NULL, ASL_LEVEL_DEBUG,
"idle exit after %ld seconds", delta);
exit(EXIT_SUCCESS);
}
return KERN_SUCCESS;
}
void*
idle_thread(void* param __attribute__((unused))) {
for(;;) {
struct timeval now;
gettimeofday(&now, NULL);
long delta = (now.tv_sec - idle_globals.lastmsg.tv_sec);
if (delta < idle_globals.timeout) {
sleep(idle_globals.timeout - delta);
} else {
__dirhelper_idle_exit(idle_globals.mp);
}
}
return NULL;
}
void
clean_files_older_than(const char* path, time_t when) {
FTS* fts;
char* path_argv[] = { (char*)path, NULL };
fts = fts_open(path_argv, FTS_PHYSICAL | FTS_XDEV, NULL);
if (fts) {
FTSENT* ent;
asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning " VAR_FOLDERS_PATH "%s", path);
while ((ent = fts_read(fts))) {
switch(ent->fts_info) {
case FTS_F:
case FTS_DEFAULT:
if (when == 0) {
#if DEBUG
asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
#endif
(void)unlink(ent->fts_path);
} else if (S_ISREG(ent->fts_statp->st_mode) && ent->fts_statp->st_atime < when) {
int fd = open(ent->fts_path, O_RDONLY | O_NONBLOCK);
if (fd != -1) {
int res = flock(fd, LOCK_EX | LOCK_NB);
if (res == 0) {
struct stat sb;
res = fstat(fd, &sb);
if (res == 0 && sb.st_atime < when) {
#if DEBUG
asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
#endif
(void)unlink(ent->fts_path);
}
(void)flock(fd, LOCK_UN);
}
close(fd);
}
}
break;
case FTS_SL:
case FTS_SLNONE:
if (when == 0) {
#if DEBUG
asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
#endif
(void)unlink(ent->fts_path);
}
break;
case FTS_DP:
if (when == 0) {
#if DEBUG
asl_log(NULL, NULL, ASL_LEVEL_ALERT, "rmdir(" VAR_FOLDERS_PATH "%s)", ent->fts_path);
#endif
(void)rmdir(ent->fts_path);
}
break;
case FTS_ERR:
case FTS_NS:
asl_log(NULL, NULL, ASL_LEVEL_ERR, VAR_FOLDERS_PATH "%s: %s", ent->fts_path, strerror(ent->fts_errno));
break;
default:
break;
}
}
fts_close(fts);
} else {
asl_log(NULL, NULL, ASL_LEVEL_ERR, VAR_FOLDERS_PATH "%s: %s", path, strerror(errno));
}
}
int
file_check(const char* path, int mode, int uid, int gid) {
int check = 1;
struct stat sb;
if (lstat(path, &sb) == 0) {
check = check && ((sb.st_mode & S_IFMT) == mode);
check = check && ((sb.st_uid == (uid_t)uid) || uid == -1);
check = check && ((sb.st_gid == (gid_t)gid) || gid == -1);
} else {
if (errno != ENOENT) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno));
}
check = 0;
}
return check;
}
int
is_safeboot(void) {
uint32_t sb = 0;
size_t sbsz = sizeof(sb);
if (sysctlbyname("kern.safeboot", &sb, &sbsz, NULL, 0) != 0) {
return 0;
} else {
return (int)sb;
}
}
void
clean_directories(const char* dirs[], int machineBoot) {
DIR* d;
time_t when = 0;
int i;
if (!machineBoot) {
struct timeval now;
long days = 3;
const char* str = getenv("CLEAN_FILES_OLDER_THAN_DAYS");
if (str) {
days = strtol(str, NULL, 0);
}
(void)gettimeofday(&now, NULL);
for (i = 0; dirs[i]; i++)
asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning %s older than %ld days", dirs[i], days);
when = now.tv_sec - (days * 60 * 60 * 24);
}
struct timespec boottime;
size_t len = sizeof(boottime);
if (sysctlbyname("kern.boottime", &boottime, &len, NULL, 0) == -1) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", "sysctl kern.boottime", strerror(errno));
return;
}
if (!is_root_wheel_directory(VAR_FOLDERS_PATH)) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, "invalid ownership");
return;
}
if (chroot(VAR_FOLDERS_PATH)) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "chroot(%s) failed: %s",
VAR_FOLDERS_PATH, strerror(errno));
}
if ((d = opendir("/"))) {
struct dirent* e;
char path[PATH_MAX];
while ((e = readdir(d))) {
if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
snprintf(path, sizeof(path), "%s%s", "/", e->d_name);
if (is_root_wheel_directory(path)) {
DIR* d2 = opendir(path);
if (d2) {
struct dirent* e2;
while ((e2 = readdir(d2))) {
char temporary_items[PATH_MAX];
if (strcmp(e2->d_name, ".") == 0 || strcmp(e2->d_name, "..") == 0) continue;
for (i = 0; dirs[i]; i++) {
const char *name = dirs[i];
snprintf(temporary_items, sizeof(temporary_items),
"%s/%s/%s", path, e2->d_name, name);
if (is_directory(temporary_items)) {
clean_files_older_than(temporary_items, when);
}
}
}
closedir(d2);
} else {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno));
}
}
}
closedir(d);
} else {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, strerror(errno));
}
}
int
main(int argc, char* argv[]) {
mach_msg_size_t mxmsgsz = MAX_TRAILER_SIZE;
kern_return_t kr;
long idle_timeout = 30;
if (argc > 1 && strcmp(argv[1], "-machineBoot") == 0) {
const char *dirs[5];
int i = 0;
dirs[i++] = DIRHELPER_TEMP_STR;
dirs[i++] = "TemporaryItems";
dirs[i++] = "Cleanup At Startup";
if (is_safeboot()) {
dirs[i++] = DIRHELPER_CACHE_STR;
}
dirs[i] = NULL;
clean_directories(dirs, 1);
exit(EXIT_SUCCESS);
} else if (argc > 1 && strcmp(argv[1], "-cleanTemporaryItems") == 0) {
const char *dirs[] = {
DIRHELPER_TEMP_STR,
"TemporaryItems",
NULL
};
clean_directories(dirs, 0);
exit(EXIT_SUCCESS);
} else if (argc > 1) {
exit(EXIT_FAILURE);
}
launch_data_t config = NULL, checkin = NULL;
checkin = launch_data_new_string(LAUNCH_KEY_CHECKIN);
config = launch_msg(checkin);
if (!config || launch_data_get_type(config) == LAUNCH_DATA_ERRNO) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "launchd checkin failed");
exit(EXIT_FAILURE);
}
launch_data_t tmv;
tmv = launch_data_dict_lookup(config, LAUNCH_JOBKEY_TIMEOUT);
if (tmv) {
idle_timeout = launch_data_get_integer(tmv);
asl_log(NULL, NULL, ASL_LEVEL_DEBUG,
"idle timeout set: %ld seconds", idle_timeout);
}
launch_data_t svc;
svc = launch_data_dict_lookup(config, LAUNCH_JOBKEY_MACHSERVICES);
if (!svc) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach services");
exit(EXIT_FAILURE);
}
svc = launch_data_dict_lookup(svc, DIRHELPER_BOOTSTRAP_NAME);
if (!svc) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach service: %s",
DIRHELPER_BOOTSTRAP_NAME);
exit(EXIT_FAILURE);
}
mach_port_t mp = launch_data_get_machport(svc);
if (mp == MACH_PORT_NULL) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "NULL mach service: %s",
DIRHELPER_BOOTSTRAP_NAME);
exit(EXIT_FAILURE);
}
kr = mach_port_insert_right(mach_task_self(), mp, mp,
MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "send right failed: %s",
mach_error_string(kr));
exit(EXIT_FAILURE);
}
pthread_t thread;
idle_globals.mp = mp;
idle_globals.timeout = idle_timeout;
gettimeofday(&idle_globals.lastmsg, NULL);
pthread_create(&thread, NULL, &idle_thread, NULL);
mach_msg_type_number_t status_count = MACH_PORT_RECEIVE_STATUS_COUNT;
mach_port_status_t status;
kr = mach_port_get_attributes(mach_task_self(), mp,
MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status, &status_count);
if (kr == KERN_SUCCESS && status.mps_msgcount == 0) {
const char *dirs[] = {
DIRHELPER_TEMP_STR,
"TemporaryItems",
NULL
};
clean_directories(dirs, 0);
exit(EXIT_SUCCESS);
}
kr = mach_msg_server(dirhelper_server, mxmsgsz, mp,
MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) |
MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0));
if (kr != KERN_SUCCESS) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"mach_msg_server(mp): %s", mach_error_string(kr));
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}