| /* |
| * util.c - routeup/tlsdated utility functions |
| * Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "config.h" |
| #include "tlsdate.h" |
| |
| #include <fcntl.h> |
| #include <grp.h> |
| #include <limits.h> |
| #ifdef HAVE_LINUX_RTC_H |
| #include <linux/rtc.h> |
| #endif |
| #include <pwd.h> |
| #include <signal.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <syslog.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #ifdef WITH_EVENTS |
| #include <event2/event.h> |
| #endif |
| |
| #include "src/tlsdate.h" |
| #include "src/util.h" |
| |
| #ifdef HAVE_SECCOMP_FILTER |
| #include "src/seccomp.h" |
| #endif |
| |
| #if defined(HAVE_STRUCT_RTC_TIME) && defined(RTC_SET_TIME) && defined(RTC_RD_TIME) |
| #define ENABLE_RTC |
| #endif |
| |
| const char *kTempSuffix = DEFAULT_DAEMON_TMPSUFFIX; |
| |
| /** helper function to print message and die */ |
| void |
| die (const char *fmt, ...) |
| { |
| va_list ap; |
| va_start (ap, fmt); |
| vfprintf (stderr, fmt, ap); |
| fprintf (stderr, "\n"); |
| va_end (ap); |
| exit (1); |
| } |
| |
| /* Initalize syslog */ |
| void initalize_syslog (void) |
| { |
| openlog ("tlsdated", LOG_PID, LOG_DAEMON); |
| } |
| |
| /* Signal to syslog that we're finished logging */ |
| void terminate_syslog (void) |
| { |
| closelog (); |
| } |
| |
| /** helper function for 'verbose' output without syslog support */ |
| void |
| verb_no_syslog (const char *fmt, ...) |
| { |
| va_list ap; |
| |
| if (! verbose ) return; |
| va_start(ap, fmt); |
| vfprintf(stderr, fmt, ap); |
| fprintf (stderr, "\n"); |
| va_end(ap); |
| } |
| |
| /** helper function for 'verbose' output */ |
| void |
| verb (const char *fmt, ...) |
| { |
| va_list ap; |
| |
| if (! verbose ) return; |
| va_start(ap, fmt); |
| vfprintf(stderr, fmt, ap); |
| fprintf (stderr, "\n"); |
| va_end(ap); |
| va_start(ap, fmt); |
| vsyslog (LOG_DEBUG, fmt, ap); |
| va_end(ap); |
| } |
| |
| void API logat (int isverbose, const char *fmt, ...) |
| { |
| va_list ap; |
| if (isverbose && !verbose) |
| return; |
| va_start (ap, fmt); |
| vfprintf (stderr, fmt, ap); |
| fprintf (stderr, "\n"); |
| va_end (ap); |
| va_start (ap, fmt); |
| vsyslog (LOG_INFO, fmt, ap); |
| va_end (ap); |
| } |
| |
| void no_new_privs(void) |
| { |
| #ifdef TARGET_OS_LINUX |
| #ifdef HAVE_PRCTL // XXX: Make this specific to PR_SET_NO_NEW_PRIVS |
| // Check to see if we're already set PR_SET_NO_NEW_PRIVS |
| // This happens in tlsdated earlier than when tlsdate-helper drops |
| // privileges. |
| if (0 == prctl (PR_GET_NO_NEW_PRIVS)) { |
| // Remove the ability to regain privileges. |
| if (0 != prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) |
| die ("Failed to PR_SET_NO_NEW_PRIVS"); |
| } else { |
| verb ("V: Parent process has already set PR_SET_NO_NEW_PRIVS"); |
| } |
| #else |
| verb ("V: we are unwilling to set PR_SET_NO_NEW_PRIVS"); |
| #endif |
| #endif |
| } |
| |
| void enable_seccomp(void) |
| { |
| #ifdef HAVE_SECCOMP_FILTER |
| int status; |
| prctl (PR_SET_NAME, "tlsdate seccomp"); |
| verb ("V: seccomp support is enabled"); |
| if (enable_setter_seccomp()) |
| { |
| status = SETTER_NO_SBOX; |
| _exit (status); |
| } |
| #else |
| verb ("V: seccomp support is disabled"); |
| #endif |
| } |
| |
| static gid_t |
| get_unpriv_gid (const char *group) |
| { |
| struct group *gr = getgrnam (group); |
| if (NULL == gr) |
| die ("Failed to obtain GID for `%s'\n", group); |
| if (0 == gr->gr_gid) |
| die ("GID for `%s' is 0, refusing to run SSL\n", group); |
| return gr->gr_gid; |
| } |
| |
| void |
| drop_privs_to (const char *user, const char *group, const char **supp_groups) |
| { |
| uid_t uid; |
| gid_t gid; |
| struct passwd *pw; |
| size_t num_supp, i; |
| |
| if (0 != getuid ()) |
| return; /* not running as root to begin with; should (!) be harmless to continue |
| without dropping to 'nobody' (setting time will fail in the end) */ |
| pw = getpwnam (user); |
| if (NULL == pw) |
| die ("Failed to obtain UID for `%s'\n", user); |
| uid = pw->pw_uid; |
| if (0 == uid) |
| die ("UID for `%s' is 0, refusing to run SSL\n", user); |
| gid = get_unpriv_gid (group); |
| if (pw->pw_gid != gid) |
| die ("GID for `%s' is not `%s' as expected, refusing to run SSL\n", |
| user, group); |
| if (0 != initgroups ( (const char *) user, gid)) |
| die ("Unable to initgroups for `%s' in group `%s' as expected\n", |
| user, group); |
| #ifdef HAVE_SETRESGID |
| if (0 != setresgid (gid, gid, gid)) |
| die ("Failed to setresgid: %s\n", strerror (errno)); |
| #else |
| if (0 != (setgid (gid) | setegid (gid))) |
| die ("Failed to setgid: %s\n", strerror (errno)); |
| #endif |
| if (supp_groups) |
| { |
| for (num_supp = 0; supp_groups[num_supp]; num_supp++) ; |
| gid_t *supp_gids = (gid_t *) calloc (num_supp, sizeof (gid_t)); |
| if (!supp_gids) |
| die ("Failed to allocate memory for supplementary GIDs\n"); |
| for (i = 0; i < num_supp; i++) |
| supp_gids[i] = get_unpriv_gid (supp_groups[i]); |
| if (0 != setgroups (num_supp, supp_gids)) |
| die ("Failed to setgroups: %s\n", strerror (errno)); |
| free (supp_gids); |
| } |
| #ifdef HAVE_SETRESUID |
| if (0 != setresuid (uid, uid, uid)) |
| die ("Failed to setresuid: %s\n", strerror (errno)); |
| #else |
| if (0 != (setuid (uid) | seteuid (uid))) |
| die ("Failed to setuid: %s\n", strerror (errno)); |
| #endif |
| } |
| |
| #ifdef ENABLE_RTC |
| int rtc_open(struct rtc_handle *h) |
| { |
| if (!h) |
| return -1; |
| h->fd = -1; |
| /* TODO: Use platform->file_open but drop NOFOLLOW? */ |
| h->fd = open(DEFAULT_RTC_DEVICE, O_RDONLY); |
| if (h->fd < 0) |
| { |
| pinfo("can't open rtc"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* |
| * Set the hardware clock referred to by fd (which should be a descriptor to |
| * some device that implements the interface documented in rtc(4)) to the system |
| * time. See hwclock(8) for details of why this is important. If we fail, we |
| * just return - there's nothing the caller can really do about a failure of |
| * this function except try later. |
| */ |
| int rtc_write(struct rtc_handle *handle, const struct timeval *tv) |
| { |
| struct tm tmr; |
| struct tm *tm; |
| struct rtc_time rtctm; |
| int fd = handle->fd; |
| |
| tm = gmtime_r (&tv->tv_sec, &tmr); |
| |
| /* these structs are identical, but separately defined */ |
| rtctm.tm_sec = tm->tm_sec; |
| rtctm.tm_min = tm->tm_min; |
| rtctm.tm_hour = tm->tm_hour; |
| rtctm.tm_mday = tm->tm_mday; |
| rtctm.tm_mon = tm->tm_mon; |
| rtctm.tm_year = tm->tm_year; |
| rtctm.tm_wday = tm->tm_wday; |
| rtctm.tm_yday = tm->tm_yday; |
| rtctm.tm_isdst = tm->tm_isdst; |
| |
| if (ioctl (fd, RTC_SET_TIME, &rtctm)) |
| { |
| pinfo ("ioctl(%d, RTC_SET_TIME, ...) failed", fd); |
| return 1; |
| } |
| |
| info ("synced rtc to sysclock"); |
| return 0; |
| } |
| |
| int rtc_read(struct rtc_handle *handle, struct timeval *tv) |
| { |
| struct tm tm; |
| struct rtc_time rtctm; |
| int fd = handle->fd; |
| |
| if (ioctl (fd, RTC_RD_TIME, &rtctm)) |
| { |
| pinfo ("ioctl(%d, RTC_RD_TIME, ...) failed", fd); |
| return 1; |
| } |
| |
| tm.tm_sec = rtctm.tm_sec; |
| tm.tm_min = rtctm.tm_min; |
| tm.tm_hour = rtctm.tm_hour; |
| tm.tm_mday = rtctm.tm_mday; |
| tm.tm_mon = rtctm.tm_mon; |
| tm.tm_year = rtctm.tm_year; |
| tm.tm_wday = rtctm.tm_wday; |
| tm.tm_yday = rtctm.tm_yday; |
| tm.tm_isdst = rtctm.tm_isdst; |
| |
| tv->tv_sec = mktime(&tm); |
| tv->tv_usec = 0; |
| |
| return 0; |
| } |
| |
| int rtc_close(struct rtc_handle *handle) |
| { |
| struct rtc_handle *h = handle; |
| platform->file_close(h->fd); |
| h->fd = -1; |
| return 0; |
| } |
| #endif |
| |
| int file_write(int fd, void *buf, size_t sz) |
| { |
| ssize_t ret = IGNORE_EINTR (pwrite (fd, buf, sz, 0)); |
| return (ret >= 0 && ((size_t) ret) == sz ? 0 : -1); |
| } |
| |
| int file_open(const char *path, int write, int cloexec) |
| { |
| int fd; |
| int oflags = cloexec ? O_CLOEXEC : 0; |
| if (write) |
| { |
| int perms = S_IRUSR | S_IWUSR; |
| oflags |= O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC; |
| /* Rely on atomic write calls rather than rename() calls. */ |
| fd = open(path, oflags, perms); |
| } |
| else |
| { |
| oflags |= O_RDONLY | O_NOFOLLOW; |
| fd = open(path, oflags); |
| } |
| if (fd < 0) |
| { |
| pinfo("open(%s) failed", path); |
| return -1; |
| } |
| return fd; |
| } |
| |
| int file_close(int fd) |
| { |
| return close(fd); |
| } |
| |
| int file_read(int fd, void *buf, size_t sz) |
| { |
| ssize_t ret = IGNORE_EINTR (pread (fd, buf, sz, 0)); |
| return (ret >= 0 && ((size_t) ret) == sz ? 0 : -1); |
| } |
| |
| int time_get(struct timeval *tv) |
| { |
| return gettimeofday(tv, NULL); |
| } |
| |
| int pgrp_enter(void) |
| { |
| return setpgid(0, 0); |
| } |
| |
| int pgrp_kill(void) |
| { |
| pid_t grp = getpgrp(); |
| return kill(-grp, SIGKILL); |
| } |
| |
| int process_signal(pid_t pid, int signal) |
| { |
| return kill (pid, signal); |
| } |
| |
| pid_t process_wait(pid_t pid, int *status, int forever) |
| { |
| int flag = forever ? 0 : WNOHANG; |
| return waitpid (pid, status, flag); |
| } |
| |
| static struct platform default_platform = { |
| #ifdef ENABLE_RTC |
| .rtc_open = rtc_open, |
| .rtc_write = rtc_write, |
| .rtc_read = rtc_read, |
| .rtc_close = rtc_close, |
| #endif |
| |
| .file_open = file_open, |
| .file_close = file_close, |
| .file_write = file_write, |
| .file_read = file_read, |
| |
| .time_get = time_get, |
| |
| .pgrp_enter = pgrp_enter, |
| .pgrp_kill = pgrp_kill, |
| |
| .process_signal = process_signal, |
| .process_wait = process_wait |
| }; |
| |
| struct platform *platform = &default_platform; |
| |
| /* TODO(wad) rename to schedule_event */ |
| void |
| trigger_event (struct state *state, enum event_id_t id, int sec) |
| { |
| #ifdef WITH_EVENTS |
| struct event *e = state->events[id]; |
| struct timeval delay = { sec, 0 }; |
| /* Fallthrough to tlsdate if there is no resolver. */ |
| if (!e && id == E_RESOLVER) |
| e = state->events[E_TLSDATE]; |
| if (!e) |
| { |
| info ("trigger_event with NULL |e|. I hope this is a test!"); |
| return; |
| } |
| if (event_pending (e, EV_READ|EV_WRITE|EV_TIMEOUT|EV_SIGNAL, NULL)) |
| event_del (e); |
| if (sec >= 0) |
| event_add (e, &delay); |
| else /* Note! This will not fire a TIMEOUT event. */ |
| event_add (e, NULL); |
| #endif |
| } |
| |
| const char * |
| sync_type_str (int sync_type) |
| { |
| switch (sync_type) |
| { |
| case SYNC_TYPE_NONE: |
| return "none"; |
| case SYNC_TYPE_BUILD: |
| return "build-timestamp"; |
| case SYNC_TYPE_DISK: |
| return "disk-timestamp"; |
| case SYNC_TYPE_RTC: |
| return "system-clock"; |
| case SYNC_TYPE_PLATFORM: |
| return "platform-feature"; |
| case SYNC_TYPE_NET: |
| return "network"; |
| default: |
| return "error"; |
| } |
| } |