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