/* chronyd/chronyc - Programs for keeping computer clocks accurate. ********************************************************************** * Copyright (C) Bryan Christianson 2015 * Copyright (C) Miroslav Lichvar 2017 * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ********************************************************************** ======================================================================= Perform privileged operations over a unix socket to a privileged fork. */ #include "config.h" #include "sysincl.h" #include "conf.h" #include "nameserv.h" #include "logging.h" #include "privops.h" #include "util.h" #define OP_ADJUSTTIME 1024 #define OP_ADJUSTTIMEX 1025 #define OP_SETTIME 1026 #define OP_BINDSOCKET 1027 #define OP_NAME2IPADDRESS 1028 #define OP_RELOADDNS 1029 #define OP_QUIT 1099 union sockaddr_in46 { struct sockaddr_in in4; #ifdef FEAT_IPV6 struct sockaddr_in6 in6; #endif struct sockaddr u; }; /* daemon request structs */ typedef struct { struct timeval tv; } ReqAdjustTime; #ifdef PRIVOPS_ADJUSTTIMEX typedef struct { struct timex tmx; } ReqAdjustTimex; #endif typedef struct { struct timeval tv; } ReqSetTime; typedef struct { int sock; socklen_t sa_len; union sockaddr_in46 sa; } ReqBindSocket; typedef struct { char name[256]; } ReqName2IPAddress; typedef struct { int op; union { ReqAdjustTime adjust_time; #ifdef PRIVOPS_ADJUSTTIMEX ReqAdjustTimex adjust_timex; #endif ReqSetTime set_time; ReqBindSocket bind_socket; #ifdef PRIVOPS_NAME2IPADDRESS ReqName2IPAddress name_to_ipaddress; #endif } data; } PrvRequest; /* helper response structs */ typedef struct { struct timeval tv; } ResAdjustTime; #ifdef PRIVOPS_ADJUSTTIMEX typedef struct { struct timex tmx; } ResAdjustTimex; #endif typedef struct { IPAddr addresses[DNS_MAX_ADDRESSES]; } ResName2IPAddress; typedef struct { char msg[256]; } ResFatalMsg; typedef struct { int fatal_error; int rc; int res_errno; union { ResFatalMsg fatal_msg; ResAdjustTime adjust_time; #ifdef PRIVOPS_ADJUSTTIMEX ResAdjustTimex adjust_timex; #endif #ifdef PRIVOPS_NAME2IPADDRESS ResName2IPAddress name_to_ipaddress; #endif } data; } PrvResponse; static int helper_fd; static pid_t helper_pid; static int have_helper(void) { return helper_fd >= 0; } /* ======================================================================= */ /* HELPER - prepare fatal error for daemon */ static void res_fatal(PrvResponse *res, const char *fmt, ...) { va_list ap; res->fatal_error = 1; va_start(ap, fmt); vsnprintf(res->data.fatal_msg.msg, sizeof (res->data.fatal_msg.msg), fmt, ap); va_end(ap); } /* ======================================================================= */ /* HELPER - send response to the fd */ static int send_response(int fd, const PrvResponse *res) { if (send(fd, res, sizeof (*res), 0) != sizeof (*res)) return 0; return 1; } /* ======================================================================= */ /* receive daemon request plus optional file descriptor over a unix socket */ static int receive_from_daemon(int fd, PrvRequest *req) { struct msghdr msg; struct cmsghdr *cmsg; struct iovec iov; char cmsgbuf[256]; iov.iov_base = req; iov.iov_len = sizeof (*req); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = (void *)cmsgbuf; msg.msg_controllen = sizeof (cmsgbuf); msg.msg_flags = MSG_WAITALL; /* read the data */ if (recvmsg(fd, &msg, 0) != sizeof (*req)) return 0; if (req->op == OP_BINDSOCKET) { /* extract transferred descriptor */ req->data.bind_socket.sock = -1; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) memcpy(&req->data.bind_socket.sock, CMSG_DATA(cmsg), sizeof (int)); } /* return error if valid descriptor not found */ if (req->data.bind_socket.sock < 0) return 0; } return 1; } /* ======================================================================= */ /* HELPER - perform adjtime() */ #ifdef PRIVOPS_ADJUSTTIME static void do_adjust_time(const ReqAdjustTime *req, PrvResponse *res) { res->rc = adjtime(&req->tv, &res->data.adjust_time.tv); if (res->rc) res->res_errno = errno; } #endif /* ======================================================================= */ /* HELPER - perform ntp_adjtime() */ #ifdef PRIVOPS_ADJUSTTIMEX static void do_adjust_timex(const ReqAdjustTimex *req, PrvResponse *res) { res->data.adjust_timex.tmx = req->tmx; res->rc = ntp_adjtime(&res->data.adjust_timex.tmx); if (res->rc < 0) res->res_errno = errno; } #endif /* ======================================================================= */ /* HELPER - perform settimeofday() */ #ifdef PRIVOPS_SETTIME static void do_set_time(const ReqSetTime *req, PrvResponse *res) { res->rc = settimeofday(&req->tv, NULL); if (res->rc) res->res_errno = errno; } #endif /* ======================================================================= */ /* HELPER - perform bind() */ #ifdef PRIVOPS_BINDSOCKET static void do_bind_socket(ReqBindSocket *req, PrvResponse *res) { unsigned short port; IPAddr ip; int sock_fd; struct sockaddr *sa; socklen_t sa_len; sa = &req->sa.u; sa_len = req->sa_len; sock_fd = req->sock; UTI_SockaddrToIPAndPort(sa, &ip, &port); if (port && port != CNF_GetNTPPort() && port != CNF_GetAcquisitionPort()) { close(sock_fd); res_fatal(res, "Invalid port %d", port); return; } res->rc = bind(sock_fd, sa, sa_len); if (res->rc) res->res_errno = errno; /* sock is still open on daemon side, but we're done with it in the helper */ close(sock_fd); } #endif /* ======================================================================= */ /* HELPER - perform DNS_Name2IPAddress() */ #ifdef PRIVOPS_NAME2IPADDRESS static void do_name_to_ipaddress(ReqName2IPAddress *req, PrvResponse *res) { /* make sure the string is terminated */ req->name[sizeof (req->name) - 1] = '\0'; res->rc = DNS_Name2IPAddress(req->name, res->data.name_to_ipaddress.addresses, DNS_MAX_ADDRESSES); } #endif /* ======================================================================= */ /* HELPER - perform DNS_Reload() */ #ifdef PRIVOPS_RELOADDNS static void do_reload_dns(PrvResponse *res) { DNS_Reload(); res->rc = 0; } #endif /* ======================================================================= */ /* HELPER - main loop - action requests from the daemon */ static void helper_main(int fd) { PrvRequest req; PrvResponse res; int quit = 0; while (!quit) { if (!receive_from_daemon(fd, &req)) /* read error or closed input - we cannot recover - give up */ break; memset(&res, 0, sizeof (res)); switch (req.op) { #ifdef PRIVOPS_ADJUSTTIME case OP_ADJUSTTIME: do_adjust_time(&req.data.adjust_time, &res); break; #endif #ifdef PRIVOPS_ADJUSTTIMEX case OP_ADJUSTTIMEX: do_adjust_timex(&req.data.adjust_timex, &res); break; #endif #ifdef PRIVOPS_SETTIME case OP_SETTIME: do_set_time(&req.data.set_time, &res); break; #endif #ifdef PRIVOPS_BINDSOCKET case OP_BINDSOCKET: do_bind_socket(&req.data.bind_socket, &res); break; #endif #ifdef PRIVOPS_NAME2IPADDRESS case OP_NAME2IPADDRESS: do_name_to_ipaddress(&req.data.name_to_ipaddress, &res); break; #endif #ifdef PRIVOPS_RELOADDNS case OP_RELOADDNS: do_reload_dns(&res); break; #endif case OP_QUIT: quit = 1; continue; default: res_fatal(&res, "Unexpected operator %d", req.op); break; } send_response(fd, &res); } close(fd); exit(0); } /* ======================================================================= */ /* DAEMON - receive helper response */ static void receive_response(PrvResponse *res) { int resp_len; resp_len = recv(helper_fd, res, sizeof (*res), 0); if (resp_len < 0) LOG_FATAL("Could not read from helper : %s", strerror(errno)); if (resp_len != sizeof (*res)) LOG_FATAL("Invalid helper response"); if (res->fatal_error) LOG_FATAL("Error in helper : %s", res->data.fatal_msg.msg); DEBUG_LOG("Received response rc=%d", res->rc); /* if operation failed in the helper, set errno so daemon can print log message */ if (res->res_errno) errno = res->res_errno; } /* ======================================================================= */ /* DAEMON - send daemon request to the helper */ static void send_request(PrvRequest *req) { struct msghdr msg; struct iovec iov; char cmsgbuf[256]; iov.iov_base = req; iov.iov_len = sizeof (*req); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; if (req->op == OP_BINDSOCKET) { /* send file descriptor as a control message */ struct cmsghdr *cmsg; int *ptr_send_fd; msg.msg_control = cmsgbuf; msg.msg_controllen = CMSG_SPACE(sizeof (int)); cmsg = CMSG_FIRSTHDR(&msg); memset(cmsg, 0, CMSG_SPACE(sizeof (int))); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof (int)); ptr_send_fd = (int *)CMSG_DATA(cmsg); *ptr_send_fd = req->data.bind_socket.sock; } if (sendmsg(helper_fd, &msg, 0) < 0) { /* don't try to send another request from exit() */ helper_fd = -1; LOG_FATAL("Could not send to helper : %s", strerror(errno)); } DEBUG_LOG("Sent request op=%d", req->op); } /* ======================================================================= */ /* DAEMON - send daemon request and wait for response */ static void submit_request(PrvRequest *req, PrvResponse *res) { send_request(req); receive_response(res); } /* ======================================================================= */ /* DAEMON - send the helper a request to exit and wait until it exits */ static void stop_helper(void) { PrvRequest req; int status; if (!have_helper()) return; memset(&req, 0, sizeof (req)); req.op = OP_QUIT; send_request(&req); waitpid(helper_pid, &status, 0); } /* ======================================================================= */ /* DAEMON - request adjtime() */ #ifdef PRIVOPS_ADJUSTTIME int PRV_AdjustTime(const struct timeval *delta, struct timeval *olddelta) { PrvRequest req; PrvResponse res; if (!have_helper() || delta == NULL) /* helper is not running or read adjustment call */ return adjtime(delta, olddelta); memset(&req, 0, sizeof (req)); req.op = OP_ADJUSTTIME; req.data.adjust_time.tv = *delta; submit_request(&req, &res); if (olddelta) *olddelta = res.data.adjust_time.tv; return res.rc; } #endif /* ======================================================================= */ /* DAEMON - request ntp_adjtime() */ #ifdef PRIVOPS_ADJUSTTIMEX int PRV_AdjustTimex(struct timex *tmx) { PrvRequest req; PrvResponse res; if (!have_helper()) return ntp_adjtime(tmx); memset(&req, 0, sizeof (req)); req.op = OP_ADJUSTTIMEX; req.data.adjust_timex.tmx = *tmx; submit_request(&req, &res); *tmx = res.data.adjust_timex.tmx; return res.rc; } #endif /* ======================================================================= */ /* DAEMON - request settimeofday() */ #ifdef PRIVOPS_SETTIME int PRV_SetTime(const struct timeval *tp, const struct timezone *tzp) { PrvRequest req; PrvResponse res; /* only support setting the time */ assert(tp != NULL); assert(tzp == NULL); if (!have_helper()) return settimeofday(tp, NULL); memset(&req, 0, sizeof (req)); req.op = OP_SETTIME; req.data.set_time.tv = *tp; submit_request(&req, &res); return res.rc; } #endif /* ======================================================================= */ /* DAEMON - request bind() */ #ifdef PRIVOPS_BINDSOCKET int PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len) { PrvRequest req; PrvResponse res; IPAddr ip; unsigned short port; UTI_SockaddrToIPAndPort(address, &ip, &port); if (port && port != CNF_GetNTPPort() && port != CNF_GetAcquisitionPort()) assert(0); if (!have_helper()) return bind(sock, address, address_len); memset(&req, 0, sizeof (req)); req.op = OP_BINDSOCKET; req.data.bind_socket.sock = sock; req.data.bind_socket.sa_len = address_len; memcpy(&req.data.bind_socket.sa.u, address, address_len); submit_request(&req, &res); return res.rc; } #endif /* ======================================================================= */ /* DAEMON - request DNS_Name2IPAddress() */ #ifdef PRIVOPS_NAME2IPADDRESS int PRV_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) { PrvRequest req; PrvResponse res; int i; if (!have_helper()) return DNS_Name2IPAddress(name, ip_addrs, max_addrs); memset(&req, 0, sizeof (req)); req.op = OP_NAME2IPADDRESS; if (snprintf(req.data.name_to_ipaddress.name, sizeof (req.data.name_to_ipaddress.name), "%s", name) >= sizeof (req.data.name_to_ipaddress.name)) { DEBUG_LOG("Name too long"); return DNS_Failure; } submit_request(&req, &res); for (i = 0; i < max_addrs && i < DNS_MAX_ADDRESSES; i++) ip_addrs[i] = res.data.name_to_ipaddress.addresses[i]; return res.rc; } #endif /* ======================================================================= */ /* DAEMON - request res_init() */ #ifdef PRIVOPS_RELOADDNS void PRV_ReloadDNS(void) { PrvRequest req; PrvResponse res; if (!have_helper()) { DNS_Reload(); return; } memset(&req, 0, sizeof (req)); req.op = OP_RELOADDNS; submit_request(&req, &res); assert(!res.rc); } #endif /* ======================================================================= */ void PRV_Initialise(void) { helper_fd = -1; } /* ======================================================================= */ /* DAEMON - setup socket(s) then fork to run the helper */ /* must be called before privileges are dropped */ void PRV_StartHelper(void) { pid_t pid; int fd, sock_pair[2]; if (have_helper()) LOG_FATAL("Helper already running"); if ( #ifdef SOCK_SEQPACKET socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sock_pair) && #endif socketpair(AF_UNIX, SOCK_DGRAM, 0, sock_pair)) LOG_FATAL("socketpair() failed : %s", strerror(errno)); UTI_FdSetCloexec(sock_pair[0]); UTI_FdSetCloexec(sock_pair[1]); pid = fork(); if (pid < 0) LOG_FATAL("fork() failed : %s", strerror(errno)); if (pid == 0) { /* child process */ close(sock_pair[0]); /* close other descriptors inherited from the parent process */ for (fd = 0; fd < 1024; fd++) { if (fd != sock_pair[1]) close(fd); } /* ignore signals, the process will exit on OP_QUIT request */ UTI_SetQuitSignalsHandler(SIG_IGN, 1); helper_main(sock_pair[1]); } else { /* parent process */ close(sock_pair[1]); helper_fd = sock_pair[0]; helper_pid = pid; /* stop the helper even when not exiting cleanly from the main function */ atexit(stop_helper); } } /* ======================================================================= */ /* DAEMON - graceful shutdown of the helper */ void PRV_Finalise(void) { if (!have_helper()) return; stop_helper(); close(helper_fd); helper_fd = -1; }