static void read_from_cmd_socket(int sock_fd, int event, void *anything) { CMD_Request rx_message; CMD_Reply tx_message; int status, read_length, expected_length, rx_message_length; int localhost, allowed, log_index; union sockaddr_all where_from; socklen_t from_length; IPAddr remote_ip; unsigned short remote_port, rx_command; struct timeval now, cooked_now; rx_message_length = sizeof(rx_message); from_length = sizeof(where_from); status = recvfrom(sock_fd, (char *)&rx_message, rx_message_length, 0, &where_from.sa, &from_length); if (status < 0) { LOG(LOGS_WARN, LOGF_CmdMon, "Error [%s] reading from control socket %d", strerror(errno), sock_fd); return; } if (from_length > sizeof (where_from) || from_length <= sizeof (where_from.sa.sa_family)) { DEBUG_LOG(LOGF_CmdMon, "Read command packet without source address"); return; } read_length = status; /* Get current time cheaply */ SCH_GetLastEventTime(&cooked_now, NULL, &now); UTI_SockaddrToIPAndPort(&where_from.sa, &remote_ip, &remote_port); /* 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: /* This should be the Unix domain socket */ if (where_from.sa.sa_family != AF_UNIX) return; assert(sock_fd == sock_fdu); localhost = 1; break; default: assert(0); } DEBUG_LOG(LOGF_CmdMon, "Received %d bytes from %s fd %d", status, UTI_SockaddrToString(&where_from.sa), sock_fd); if (!(localhost || ADF_IsAllowed(access_auth_table, &remote_ip))) { /* The client is not allowed access, so don't waste any more time on him. Note that localhost is always allowed access regardless of the defined access rules - otherwise, we could shut ourselves out completely! */ return; } if (read_length < offsetof(CMD_Request, data) || read_length < offsetof(CMD_Reply, data) || rx_message.pkt_type != PKT_TYPE_CMD_REQUEST || rx_message.res1 != 0 || rx_message.res2 != 0) { /* We don't know how to process anything like this or an error reply would be larger than the request */ DEBUG_LOG(LOGF_CmdMon, "Command packet dropped"); return; } expected_length = PKL_CommandLength(&rx_message); rx_command = ntohs(rx_message.command); tx_message.version = PROTO_VERSION_NUMBER; tx_message.pkt_type = PKT_TYPE_CMD_REPLY; tx_message.res1 = 0; tx_message.res2 = 0; tx_message.command = rx_message.command; tx_message.reply = htons(RPY_NULL); tx_message.status = htons(STT_SUCCESS); tx_message.pad1 = 0; tx_message.pad2 = 0; tx_message.pad3 = 0; tx_message.sequence = rx_message.sequence; tx_message.pad4 = 0; tx_message.pad5 = 0; if (rx_message.version != PROTO_VERSION_NUMBER) { DEBUG_LOG(LOGF_CmdMon, "Command packet has invalid version (%d != %d)", rx_message.version, PROTO_VERSION_NUMBER); if (rx_message.version >= PROTO_VERSION_MISMATCH_COMPAT_SERVER) { tx_message.status = htons(STT_BADPKTVERSION); transmit_reply(&tx_message, &where_from); } return; } if (rx_command >= N_REQUEST_TYPES || expected_length < (int)offsetof(CMD_Request, data)) { DEBUG_LOG(LOGF_CmdMon, "Command packet has invalid command %d", rx_command); tx_message.status = htons(STT_INVALID); transmit_reply(&tx_message, &where_from); return; } if (read_length < expected_length) { DEBUG_LOG(LOGF_CmdMon, "Command packet is too short (%d < %d)", read_length, expected_length); tx_message.status = htons(STT_BADPKTLENGTH); transmit_reply(&tx_message, &where_from); return; } /* OK, we have a valid message. Now dispatch on message type and process it. */ log_index = CLG_LogCommandAccess(&remote_ip, &cooked_now); /* Don't reply to all requests from hosts other than localhost if the rate is excessive */ if (!localhost && log_index >= 0 && CLG_LimitCommandResponseRate(log_index)) { DEBUG_LOG(LOGF_CmdMon, "Command packet discarded to limit response rate"); return; } if (rx_command >= N_REQUEST_TYPES) { /* This should be already handled */ assert(0); } else { /* Check level of authority required to issue the command. All commands from the Unix domain socket (which is accessible only by the root and chrony user/group) are allowed. */ if (where_from.sa.sa_family == AF_UNIX) { assert(sock_fd == sock_fdu); allowed = 1; } else { switch (permissions[rx_command]) { case PERMIT_AUTH: allowed = 0; break; case PERMIT_LOCAL: allowed = localhost; break; case PERMIT_OPEN: allowed = 1; break; default: assert(0); allowed = 0; } } if (allowed) { switch(rx_command) { case REQ_NULL: /* Do nothing */ break; case REQ_DUMP: handle_dump(&rx_message, &tx_message); break; case REQ_ONLINE: handle_online(&rx_message, &tx_message); break; case REQ_OFFLINE: handle_offline(&rx_message, &tx_message); break; case REQ_BURST: handle_burst(&rx_message, &tx_message); break; case REQ_MODIFY_MINPOLL: handle_modify_minpoll(&rx_message, &tx_message); break; case REQ_MODIFY_MAXPOLL: handle_modify_maxpoll(&rx_message, &tx_message); break; case REQ_MODIFY_MAXDELAY: handle_modify_maxdelay(&rx_message, &tx_message); break; case REQ_MODIFY_MAXDELAYRATIO: handle_modify_maxdelayratio(&rx_message, &tx_message); break; case REQ_MODIFY_MAXDELAYDEVRATIO: handle_modify_maxdelaydevratio(&rx_message, &tx_message); break; case REQ_MODIFY_MAXUPDATESKEW: handle_modify_maxupdateskew(&rx_message, &tx_message); break; case REQ_MODIFY_MAKESTEP: handle_modify_makestep(&rx_message, &tx_message); break; case REQ_LOGON: /* Authentication is no longer supported, log-on always fails */ tx_message.status = htons(STT_FAILED); break; case REQ_SETTIME: handle_settime(&rx_message, &tx_message); break; case REQ_LOCAL2: handle_local(&rx_message, &tx_message); break; case REQ_MANUAL: handle_manual(&rx_message, &tx_message); break; case REQ_N_SOURCES: handle_n_sources(&rx_message, &tx_message); break; case REQ_SOURCE_DATA: handle_source_data(&rx_message, &tx_message); break; case REQ_REKEY: handle_rekey(&rx_message, &tx_message); break; case REQ_ALLOW: handle_allowdeny(&rx_message, &tx_message, 1, 0); break; case REQ_ALLOWALL: handle_allowdeny(&rx_message, &tx_message, 1, 1); break; case REQ_DENY: handle_allowdeny(&rx_message, &tx_message, 0, 0); break; case REQ_DENYALL: handle_allowdeny(&rx_message, &tx_message, 0, 1); break; case REQ_CMDALLOW: handle_cmdallowdeny(&rx_message, &tx_message, 1, 0); break; case REQ_CMDALLOWALL: handle_cmdallowdeny(&rx_message, &tx_message, 1, 1); break; case REQ_CMDDENY: handle_cmdallowdeny(&rx_message, &tx_message, 0, 0); break; case REQ_CMDDENYALL: handle_cmdallowdeny(&rx_message, &tx_message, 0, 1); break; case REQ_ACCHECK: handle_accheck(&rx_message, &tx_message); break; case REQ_CMDACCHECK: handle_cmdaccheck(&rx_message, &tx_message); break; case REQ_ADD_SERVER: handle_add_source(NTP_SERVER, &rx_message, &tx_message); break; case REQ_ADD_PEER: handle_add_source(NTP_PEER, &rx_message, &tx_message); break; case REQ_DEL_SOURCE: handle_del_source(&rx_message, &tx_message); break; case REQ_WRITERTC: handle_writertc(&rx_message, &tx_message); break; case REQ_DFREQ: handle_dfreq(&rx_message, &tx_message); break; case REQ_DOFFSET: handle_doffset(&rx_message, &tx_message); break; case REQ_TRACKING: handle_tracking(&rx_message, &tx_message); break; case REQ_SMOOTHING: handle_smoothing(&rx_message, &tx_message); break; case REQ_SMOOTHTIME: handle_smoothtime(&rx_message, &tx_message); break; case REQ_SOURCESTATS: handle_sourcestats(&rx_message, &tx_message); break; case REQ_RTCREPORT: handle_rtcreport(&rx_message, &tx_message); break; case REQ_TRIMRTC: handle_trimrtc(&rx_message, &tx_message); break; case REQ_CYCLELOGS: handle_cyclelogs(&rx_message, &tx_message); break; case REQ_CLIENT_ACCESSES_BY_INDEX2: handle_client_accesses_by_index(&rx_message, &tx_message); break; case REQ_MANUAL_LIST: handle_manual_list(&rx_message, &tx_message); break; case REQ_MANUAL_DELETE: handle_manual_delete(&rx_message, &tx_message); break; case REQ_MAKESTEP: handle_make_step(&rx_message, &tx_message); break; case REQ_ACTIVITY: handle_activity(&rx_message, &tx_message); break; case REQ_RESELECTDISTANCE: handle_reselect_distance(&rx_message, &tx_message); break; case REQ_RESELECT: handle_reselect(&rx_message, &tx_message); break; case REQ_MODIFY_MINSTRATUM: handle_modify_minstratum(&rx_message, &tx_message); break; case REQ_MODIFY_POLLTARGET: handle_modify_polltarget(&rx_message, &tx_message); break; case REQ_REFRESH: handle_refresh(&rx_message, &tx_message); break; case REQ_SERVER_STATS: handle_server_stats(&rx_message, &tx_message); break; default: DEBUG_LOG(LOGF_CmdMon, "Unhandled command %d", rx_command); tx_message.status = htons(STT_FAILED); break; } } else { tx_message.status = htons(STT_UNAUTH); } } /* Transmit the response */ { /* Include a simple way to lose one message in three to test resend */ static int do_it=1; if (do_it) { transmit_reply(&tx_message, &where_from); } #if 0 do_it = ((do_it + 1) % 3); #endif } }
void test_unit(void) { int i, j, index; struct timespec ts; IPAddr ip; char conf[][100] = { "clientloglimit 10000", "ratelimit interval 3 burst 4 leak 3", "cmdratelimit interval 3 burst 4 leak 3", }; CNF_Initialise(0, 0); for (i = 0; i < sizeof conf / sizeof conf[0]; i++) CNF_ParseLine(NULL, i + 1, conf[i]); CLG_Initialise(); TEST_CHECK(ARR_GetSize(records) == 16); for (i = 0; i < 500; i++) { DEBUG_LOG("iteration %d", i); ts.tv_sec = (time_t)random() & 0x0fffffff; ts.tv_nsec = 0; for (j = 0; j < 1000; j++) { TST_GetRandomAddress(&ip, IPADDR_UNSPEC, i % 8 ? -1 : i / 8 % 9); DEBUG_LOG("address %s", UTI_IPToString(&ip)); if (random() % 2) { index = CLG_LogNTPAccess(&ip, &ts); TEST_CHECK(index >= 0); CLG_LimitNTPResponseRate(index); } else { index = CLG_LogCommandAccess(&ip, &ts); TEST_CHECK(index >= 0); CLG_LimitCommandResponseRate(index); } UTI_AddDoubleToTimespec(&ts, (1 << random() % 14) / 100.0, &ts); } } DEBUG_LOG("records %u", ARR_GetSize(records)); TEST_CHECK(ARR_GetSize(records) == 64); for (i = j = 0; i < 10000; i++) { ts.tv_sec += 1; index = CLG_LogNTPAccess(&ip, &ts); TEST_CHECK(index >= 0); if (!CLG_LimitNTPResponseRate(index)) j++; } DEBUG_LOG("requests %d responses %d", i, j); TEST_CHECK(j * 4 < i && j * 6 > i); CLG_Finalise(); CNF_Finalise(); }