/** * \file info_master.c * * \brief mush-end functions for talking to info_slave */ #include "copyrite.h" #include "config.h" #ifdef I_SYS_TYPES #include <sys/types.h> #endif #ifdef I_UNISTD #include <unistd.h> #endif #ifdef I_SYS_TIME #include <sys/time.h> #endif #if !defined(I_SYS_TIME) || defined(TIME_WITH_SYS_TIME) #include <time.h> #endif #ifdef I_NETDB #include <netdb.h> #endif #ifdef I_SYS_SOCKET #include <sys/socket.h> #endif #include <stdlib.h> #include <signal.h> #include <stdio.h> #include <errno.h> #include "conf.h" #include "externs.h" #include "access.h" #include "mysocket.h" #include "ident.h" #include "lookup.h" #include "log.h" #include "wait.h" #ifdef INFO_SLAVE #ifdef WIN32 #error "info_slave will not work on Windows." #endif #ifndef HAVE_SOCKETPAIR #error "no supported communication options for talking with info_slave are available." #endif static fd_set info_pending; /**< Keep track of fds pending a slave lookup */ static int pending_max = 0; int info_slave = -1; pid_t info_slave_pid = -1; /**< Process id of the info_slave process */ enum is_state info_slave_state = INFO_SLAVE_DOWN; /**< State of the info_slave process */ time_t info_queue_time; /**< Time of last write to slave */ /* From bsd.c */ extern int maxd; DESC *initializesock(int s, char *addr, char *ip, int use_ssl); /** Re-query lookups that have timed out */ void update_pending_info_slaves(void) { time_t now; int newsock; now = time(NULL); if (info_slave_state == INFO_SLAVE_PENDING && now > info_queue_time + 30) { /* rerun any pending queries that got lost */ info_queue_time = now; for (newsock = 0; newsock < pending_max; newsock++) if (FD_ISSET(newsock, &info_pending)) query_info_slave(newsock); } } void init_info_slave(void) { FD_ZERO(&info_pending); make_info_slave(); } void make_info_slave(void) { int socks[2]; char num[NI_MAXSERV]; pid_t child; int n; if (info_slave_state != INFO_SLAVE_DOWN) { if (info_slave_pid > 0) kill_info_slave(); info_slave_state = INFO_SLAVE_DOWN; } #ifndef AF_LOCAL /* Use Posix.1g names. */ #define AF_LOCAL AF_UNIX #endif #ifdef HAVE_SOCKETPAIR if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, socks) < 0) { perror("creating slave datagram socketpair"); return; } if (socks[0] >= maxd) maxd = socks[0] + 1; if (socks[1] >= maxd) maxd = socks[1] + 1; #endif child = fork(); if (child < 0) { perror("forking info slave"); #ifdef HAVE_SOCKETPAIR closesocket(socks[0]); closesocket(socks[1]); #endif return; } else if (child > 0) { info_slave_state = INFO_SLAVE_READY; info_slave_pid = child; #ifdef HAVE_SOCKETPAIR info_slave = socks[0]; closesocket(socks[1]); do_rawlog(LT_ERR, "Spawning info slave, communicating using socketpair, pid %d", child); #endif make_nonblocking(info_slave); } else { int errfd = fileno(stderr); /* Close unneeded fds and sockets: Everything but stdout and the socket used to talk to the mush */ for (n = 0; n < maxd; n++) { if (n == errfd) continue; #ifdef HAVE_SOCKETPAIR if (n == socks[1]) continue; #endif close(n); } snprintf(num, NI_MAXSERV, "%d", socks[1]); execl("./info_slave", "info_slave", num, (char *) NULL); perror("execing info slave"); _exit(1); } if (info_slave >= maxd) maxd = info_slave + 1; lower_priority_by(info_slave, 4); for (n = 0; n < maxd; n++) if (FD_ISSET(n, &info_pending)) query_info_slave(n); } void query_info_slave(int fd) { struct request_dgram req; struct hostname_info *hi; char buf[BUFFER_LEN], *bp; ssize_t slen; FD_SET(fd, &info_pending); if (fd > pending_max) pending_max = fd + 1; info_queue_time = time(NULL); if (info_slave_state == INFO_SLAVE_DOWN) { make_info_slave(); return; } req.rlen = MAXSOCKADDR; if (getpeername(fd, (struct sockaddr *) req.remote.data, &req.rlen) < 0) { perror("socket peer vanished"); shutdown(fd, 2); closesocket(fd); FD_CLR(fd, &info_pending); return; } /* Check for forbidden sites before bothering with ident */ bp = buf; hi = ip_convert(&req.remote.addr, req.rlen); safe_str(hi ? hi->hostname : "Not found", buf, &bp); *bp = '\0'; if (Forbidden_Site(buf)) { char port[NI_MAXSERV]; if (getnameinfo(&req.remote.addr, req.rlen, NULL, 0, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV) != 0) perror("getting remote port number"); else { if (!Deny_Silent_Site(buf, AMBIGUOUS)) { do_log(LT_CONN, 0, 0, T("[%d/%s] Refused connection (remote port %s)"), fd, buf, port); } } closesocket(fd); FD_CLR(fd, &info_pending); return; } req.llen = MAXSOCKADDR; if (getsockname(fd, (struct sockaddr *) req.local.data, &req.llen) < 0) { perror("socket self vanished"); closesocket(fd); FD_CLR(fd, &info_pending); return; } req.fd = fd; req.use_dns = USE_DNS; req.use_ident = USE_IDENT; req.timeout = IDENT_TIMEOUT; slen = send(info_slave, &req, sizeof req, 0); if (slen < 0) { perror("info slave query: write error"); make_info_slave(); return; } else if (slen != (int) sizeof req) { /* Shouldn't happen! */ perror("info slave query: partial packet"); make_info_slave(); return; } info_slave_state = INFO_SLAVE_PENDING; } void reap_info_slave(void) { struct response_dgram resp; ssize_t len; char hostname[BUFFER_LEN], *hp; int n, count; if (info_slave_state != INFO_SLAVE_PENDING) { if (info_slave_state == INFO_SLAVE_DOWN) make_info_slave(); return; } len = recv(info_slave, &resp, sizeof resp, 0); if (len < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) return; else if (len < 0 || len != (int) sizeof resp) { perror("reading info_slave response"); return; } /* okay, now we have some info! */ if (!FD_ISSET(resp.fd, &info_pending)) { /* Duplicate or spoof. Ignore. */ return; } FD_CLR(resp.fd, &info_pending); /* See if we have any other pending queries and change state if not. */ for (n = 0, count = 0; n < pending_max; n++) if (FD_ISSET(n, &info_pending)) count++; if (count == 0) { info_slave_state = INFO_SLAVE_READY; pending_max = 0; } hp = hostname; if (resp.ident[0]) { safe_str(resp.ident, hostname, &hp); safe_chr('@', hostname, &hp); } if (resp.hostname[0]) safe_str(resp.hostname, hostname, &hp); else safe_str(resp.ipaddr, hostname, &hp); *hp = '\0'; if (Forbidden_Site(resp.ipaddr) || Forbidden_Site(hostname)) { if (!Deny_Silent_Site(resp.ipaddr, AMBIGUOUS) || !Deny_Silent_Site(hostname, AMBIGUOUS)) { do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Refused connection."), resp.fd, hostname, resp.ipaddr); } shutdown(resp.fd, 2); closesocket(resp.fd); return; } do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connection opened."), resp.fd, hostname, resp.ipaddr); set_keepalive(resp.fd); initializesock(resp.fd, hostname, resp.ipaddr, (resp.connected_to == SSLPORT)); } /** Kill the info_slave process, typically at shutdown. */ void kill_info_slave(void) { WAIT_TYPE my_stat; pid_t pid; struct timeval pad; if (info_slave_state != INFO_SLAVE_DOWN) { if (info_slave_pid > 0) { do_rawlog(LT_ERR, "Terminating info_slave pid %d", info_slave_pid); block_a_signal(SIGCHLD); closesocket(info_slave); kill(info_slave_pid, 15); /* Have to wait long enough for the info_slave to actually die. This will hopefully be enough time. */ pad.tv_sec = 0; pad.tv_usec = 100; select(0, NULL, NULL, NULL, &pad); pid = mush_wait(info_slave_pid, &my_stat, WNOHANG); info_slave_pid = -1; unblock_a_signal(SIGCHLD); } info_slave_state = INFO_SLAVE_DOWN; } } #endif /* INFO_SLAVE */