diff options
Diffstat (limited to 'privops.c')
-rw-r--r-- | privops.c | 694 |
1 files changed, 694 insertions, 0 deletions
diff --git a/privops.c b/privops.c new file mode 100644 index 0000000..9870717 --- /dev/null +++ b/privops.c @@ -0,0 +1,694 @@ +/* + 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()) { + 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()) + 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); + } + + /* 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; +} |