/* 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 "socket.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 (SCK_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) { SCK_Message *message; message = SCK_ReceiveMessage(fd, SCK_FLAG_MSG_DESCRIPTOR); if (!message || message->length != sizeof (*req)) return 0; memcpy(req, message->data, sizeof (*req)); if (req->op == OP_BINDSOCKET) { req->data.bind_socket.sock = message->descriptor; /* return error if valid descriptor not found */ if (req->data.bind_socket.sock < 0) return 0; } else if (message->descriptor >= 0) { SCK_CloseSocket(message->descriptor); 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) { IPSockAddr ip_saddr; int sock_fd; struct sockaddr *sa; socklen_t sa_len; sa = &req->sa.u; sa_len = req->sa_len; sock_fd = req->sock; SCK_SockaddrToIPSockAddr(sa, sa_len, &ip_saddr); if (ip_saddr.port != 0 && ip_saddr.port != CNF_GetNTPPort() && ip_saddr.port != CNF_GetAcquisitionPort() && ip_saddr.port != CNF_GetPtpPort()) { SCK_CloseSocket(sock_fd); res_fatal(res, "Invalid port %d", ip_saddr.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 */ SCK_CloseSocket(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); } SCK_CloseSocket(fd); exit(0); } /* ======================================================================= */ /* DAEMON - receive helper response */ static void receive_response(PrvResponse *res) { int resp_len; resp_len = SCK_Receive(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) { SCK_Message message; int flags; SCK_InitMessage(&message, SCK_ADDR_UNSPEC); message.data = req; message.length = sizeof (*req); flags = 0; if (req->op == OP_BINDSOCKET) { /* send file descriptor as a control message */ message.descriptor = req->data.bind_socket.sock; flags |= SCK_FLAG_MSG_DESCRIPTOR; } if (!SCK_SendMessage(helper_fd, &message, flags)) { /* 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) { IPSockAddr ip_saddr; PrvRequest req; PrvResponse res; SCK_SockaddrToIPSockAddr(address, address_len, &ip_saddr); if (ip_saddr.port != 0 && ip_saddr.port != CNF_GetNTPPort() && ip_saddr.port != CNF_GetAcquisitionPort() && ip_saddr.port != CNF_GetPtpPort()) 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; assert(address_len <= sizeof (req.data.bind_socket.sa)); 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)) { 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_fd1, sock_fd2; if (have_helper()) LOG_FATAL("Helper already running"); sock_fd1 = SCK_OpenUnixSocketPair(SCK_FLAG_BLOCK, &sock_fd2); if (sock_fd1 < 0) LOG_FATAL("Could not open socket pair"); pid = fork(); if (pid < 0) LOG_FATAL("fork() failed : %s", strerror(errno)); if (pid == 0) { /* child process */ SCK_CloseSocket(sock_fd1); /* close other descriptors inherited from the parent process, except stdin, stdout, and stderr */ for (fd = STDERR_FILENO + 1; fd < 1024; fd++) { if (fd != sock_fd2) close(fd); } UTI_ResetGetRandomFunctions(); /* ignore signals, the process will exit on OP_QUIT request */ UTI_SetQuitSignalsHandler(SIG_IGN, 1); helper_main(sock_fd2); } else { /* parent process */ SCK_CloseSocket(sock_fd2); helper_fd = sock_fd1; 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; }