/*
* 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";
}
}