cmdmon: listen on Unix domain socket

In addition to the IPv4/IPv6 command sockets, create also a Unix domain
socket to process cmdmon requests. For now, there is no difference for
authorized commands, packets from all sockets need to be authenticated.

The default path of the socket is /var/run/chrony/chronyd.sock. It can
be configured with the bindcmdaddress directive with an address starting
with /.
This commit is contained in:
Miroslav Lichvar 2015-07-28 15:29:30 +02:00
parent 46b7148f3b
commit 0bcd10560a
3 changed files with 87 additions and 28 deletions

View file

@ -53,15 +53,17 @@
/* ================================================== */
union sockaddr_in46 {
union sockaddr_all {
struct sockaddr_in in4;
#ifdef FEAT_IPV6
struct sockaddr_in6 in6;
#endif
struct sockaddr u;
struct sockaddr_un un;
struct sockaddr sa;
};
/* File descriptors for command and monitoring sockets */
static int sock_fdu;
static int sock_fd4;
#ifdef FEAT_IPV6
static int sock_fd6;
@ -184,7 +186,7 @@ prepare_socket(int family, int port_number)
{
int sock_fd;
socklen_t my_addr_len;
union sockaddr_in46 my_addr;
union sockaddr_all my_addr;
IPAddr bind_address;
int on_off = 1;
@ -198,29 +200,31 @@ prepare_socket(int family, int port_number)
/* Close on exec */
UTI_FdSetCloexec(sock_fd);
/* Allow reuse of port number */
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, LOGF_CmdMon, "Could not set reuseaddr socket options");
/* Don't quit - we might survive anyway */
}
if (family != AF_UNIX) {
/* Allow reuse of port number */
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, LOGF_CmdMon, "Could not set reuseaddr socket options");
/* Don't quit - we might survive anyway */
}
#ifdef IP_FREEBIND
/* Allow binding to address that doesn't exist yet */
if (setsockopt(sock_fd, IPPROTO_IP, IP_FREEBIND, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, LOGF_CmdMon, "Could not set free bind socket option");
}
/* Allow binding to address that doesn't exist yet */
if (setsockopt(sock_fd, IPPROTO_IP, IP_FREEBIND, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, LOGF_CmdMon, "Could not set free bind socket option");
}
#endif
#ifdef FEAT_IPV6
if (family == AF_INET6) {
if (family == AF_INET6) {
#ifdef IPV6_V6ONLY
/* Receive IPv6 packets only */
if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, LOGF_CmdMon, "Could not request IPV6_V6ONLY socket option");
/* Receive IPv6 packets only */
if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, LOGF_CmdMon, "Could not request IPV6_V6ONLY socket option");
}
#endif
}
#endif
}
#endif
memset(&my_addr, 0, sizeof (my_addr));
@ -252,11 +256,19 @@ prepare_socket(int family, int port_number)
my_addr.in6.sin6_addr = in6addr_loopback;
break;
#endif
case AF_UNIX:
my_addr_len = sizeof (my_addr.un);
my_addr.un.sun_family = family;
if (snprintf(my_addr.un.sun_path, sizeof (my_addr.un.sun_path), "%s",
CNF_GetBindCommandPath()) >= sizeof (my_addr.un.sun_path))
LOG_FATAL(LOGF_CmdMon, "Unix socket path too long");
unlink(my_addr.un.sun_path);
break;
default:
assert(0);
}
if (bind(sock_fd, &my_addr.u, my_addr_len) < 0) {
if (bind(sock_fd, &my_addr.sa, my_addr_len) < 0) {
LOG(LOGS_ERR, LOGF_CmdMon, "Could not bind %s command socket : %s",
UTI_SockaddrFamilyToString(family), strerror(errno));
close(sock_fd);
@ -302,6 +314,11 @@ CAM_Initialise(int family)
free_replies = NULL;
kept_replies.next = NULL;
if (CNF_GetBindCommandPath()[0])
sock_fdu = prepare_socket(AF_UNIX, 0);
else
sock_fdu = -1;
port_number = CNF_GetCommandPort();
if (port_number && (family == IPADDR_UNSPEC || family == IPADDR_INET4))
@ -332,6 +349,12 @@ CAM_Initialise(int family)
void
CAM_Finalise(void)
{
if (sock_fdu >= 0) {
SCH_RemoveInputFileHandler(sock_fdu);
close(sock_fdu);
unlink(CNF_GetBindCommandPath());
}
sock_fdu = -1;
if (sock_fd4 >= 0) {
SCH_RemoveInputFileHandler(sock_fd4);
close(sock_fd4);
@ -680,7 +703,7 @@ token_acknowledged(unsigned long token, struct timeval *now)
/* ================================================== */
static void
transmit_reply(CMD_Reply *msg, union sockaddr_in46 *where_to, int auth_len)
transmit_reply(CMD_Reply *msg, union sockaddr_all *where_to, int auth_len)
{
int status;
int tx_message_length;
@ -689,9 +712,9 @@ transmit_reply(CMD_Reply *msg, union sockaddr_in46 *where_to, int auth_len)
unsigned short port;
IPAddr ip;
UTI_SockaddrToIPAndPort(&where_to->u, &ip, &port);
UTI_SockaddrToIPAndPort(&where_to->sa, &ip, &port);
switch (where_to->u.sa_family) {
switch (where_to->sa.sa_family) {
case AF_INET:
sock_fd = sock_fd4;
addrlen = sizeof (where_to->in4);
@ -702,13 +725,17 @@ transmit_reply(CMD_Reply *msg, union sockaddr_in46 *where_to, int auth_len)
addrlen = sizeof (where_to->in6);
break;
#endif
case AF_UNIX:
sock_fd = sock_fdu;
addrlen = sizeof (where_to->un);
break;
default:
assert(0);
}
tx_message_length = PKL_ReplyLength(msg) + auth_len;
status = sendto(sock_fd, (void *) msg, tx_message_length, 0,
&where_to->u, addrlen);
&where_to->sa, addrlen);
if (status < 0) {
DEBUG_LOG(LOGF_CmdMon, "Could not send to %s:%hu fd %d : %s",
@ -1497,7 +1524,7 @@ read_from_cmd_socket(void *anything)
CMD_Reply tx_message, *prev_tx_message;
int rx_message_length, tx_message_length;
int sock_fd;
union sockaddr_in46 where_from;
union sockaddr_all where_from;
socklen_t from_length;
IPAddr remote_ip;
unsigned short remote_port;
@ -1523,7 +1550,7 @@ read_from_cmd_socket(void *anything)
sock_fd = (long)anything;
status = recvfrom(sock_fd, (char *)&rx_message, rx_message_length, flags,
&where_from.u, &from_length);
&where_from.sa, &from_length);
if (status < 0) {
LOG(LOGS_WARN, LOGF_CmdMon, "Error [%s] reading from control socket %d",
@ -1539,19 +1566,30 @@ read_from_cmd_socket(void *anything)
/* Get current time cheaply */
SCH_GetLastEventTime(&cooked_now, NULL, &now);
UTI_SockaddrToIPAndPort(&where_from.u, &remote_ip, &remote_port);
UTI_SockaddrToIPAndPort(&where_from.sa, &remote_ip, &remote_port);
/* Check if it's a loopback address (127.0.0.1 or ::1) */
/* Check if it's from localhost (127.0.0.1, ::1, or Unix domain) */
switch (remote_ip.family) {
case IPADDR_INET4:
assert(sock_fd == sock_fd4);
localhost = remote_ip.addr.in4 == INADDR_LOOPBACK;
break;
#ifdef FEAT_IPV6
case IPADDR_INET6:
assert(sock_fd == sock_fd6);
localhost = !memcmp(remote_ip.addr.in6, &in6addr_loopback,
sizeof (in6addr_loopback));
break;
#endif
case IPADDR_UNSPEC:
/* Unix domain socket */
if (where_from.sa.sa_family != AF_UNIX) {
DEBUG_LOG(LOGF_CmdMon, "Read command packet with no address");
return;
}
assert(sock_fd == sock_fdu);
localhost = 1;
break;
default:
assert(0);
}
@ -1711,7 +1749,7 @@ read_from_cmd_socket(void *anything)
/* Just send this message again */
tx_message_length = PKL_ReplyLength(prev_tx_message);
status = sendto(sock_fd, (void *) prev_tx_message, tx_message_length, 0,
&where_from.u, from_length);
&where_from.sa, from_length);
if (status < 0) {
DEBUG_LOG(LOGF_CmdMon, "Could not send response to %s:%hu", UTI_IPToString(&remote_ip), remote_port);
}

22
conf.c
View file

@ -182,6 +182,9 @@ static IPAddr bind_acq_address4, bind_acq_address6;
the loopback address will be used */
static IPAddr bind_cmd_address4, bind_cmd_address6;
/* Path to the Unix domain command socket. */
static char *bind_cmd_path;
/* Filename to use for storing pid of running chronyd, to prevent multiple
* chronyds being started. */
static char *pidfile;
@ -320,6 +323,7 @@ CNF_Initialise(int r)
dumpdir = Strdup(".");
logdir = Strdup(".");
bind_cmd_path = Strdup("/var/run/chrony/chronyd.sock");
pidfile = Strdup("/var/run/chronyd.pid");
rtc_device = Strdup("/dev/rtc");
user = Strdup(DEFAULT_USER);
@ -349,6 +353,7 @@ CNF_Finalise(void)
Free(keys_file);
Free(leapsec_tz);
Free(logdir);
Free(bind_cmd_path);
Free(pidfile);
Free(rtc_device);
Free(rtc_file);
@ -1113,7 +1118,14 @@ parse_bindcmdaddress(char *line)
IPAddr ip;
check_number_of_args(line, 1);
if (UTI_StringToIP(line, &ip)) {
/* Address starting with / is for the Unix domain socket */
if (line[0] == '/') {
parse_string(line, &bind_cmd_path);
/* / disables the socket */
if (!strcmp(bind_cmd_path, "/"))
bind_cmd_path[0] = '\0';
} else if (UTI_StringToIP(line, &ip)) {
if (ip.family == IPADDR_INET4)
bind_cmd_address4 = ip;
else if (ip.family == IPADDR_INET6)
@ -1697,6 +1709,14 @@ CNF_GetBindAcquisitionAddress(int family, IPAddr *addr)
/* ================================================== */
char *
CNF_GetBindCommandPath(void)
{
return bind_cmd_path;
}
/* ================================================== */
void
CNF_GetBindCommandAddress(int family, IPAddr *addr)
{

1
conf.h
View file

@ -75,6 +75,7 @@ extern void CNF_GetFallbackDrifts(int *min, int *max);
extern void CNF_GetBindAddress(int family, IPAddr *addr);
extern void CNF_GetBindAcquisitionAddress(int family, IPAddr *addr);
extern void CNF_GetBindCommandAddress(int family, IPAddr *addr);
extern char *CNF_GetBindCommandPath(void);
extern char *CNF_GetPidFile(void);
extern REF_LeapMode CNF_GetLeapSecMode(void);
extern char *CNF_GetLeapSecTimezone(void);