diff options
Diffstat (limited to 'lib/libpcap/libpcap/rpcapd/rpcapd.c')
-rw-r--r-- | lib/libpcap/libpcap/rpcapd/rpcapd.c | 1359 |
1 files changed, 1359 insertions, 0 deletions
diff --git a/lib/libpcap/libpcap/rpcapd/rpcapd.c b/lib/libpcap/libpcap/rpcapd/rpcapd.c new file mode 100644 index 0000000..430acdc --- /dev/null +++ b/lib/libpcap/libpcap/rpcapd/rpcapd.c @@ -0,0 +1,1359 @@ +/* + * Copyright (c) 2002 - 2003 + * NetGroup, Politecnico di Torino (Italy) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Politecnico di Torino nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "ftmacros.h" + +#include <errno.h> // for the errno variable +#include <string.h> // for strtok, etc +#include <stdlib.h> // for malloc(), free(), ... +#include <pcap.h> // for PCAP_ERRBUF_SIZE +#include <signal.h> // for signal() + +#include "fmtutils.h" +#include "sockutils.h" // for socket calls +#include "varattrs.h" // for _U_ +#include "portability.h" +#include "rpcapd.h" +#include "config_params.h" // configuration file parameters +#include "fileconf.h" // for the configuration file management +#include "rpcap-protocol.h" +#include "daemon.h" // the true main() method of this daemon +#include "log.h" + +#ifdef _WIN32 + #include <process.h> // for thread stuff + #include "win32-svc.h" // for Win32 service stuff + #include "getopt.h" // for getopt()-for-Windows +#else + #include <fcntl.h> // for open() + #include <unistd.h> // for exit() + #include <sys/wait.h> // waitpid() +#endif + +// +// Element in list of sockets on which we're listening for connections. +// +struct listen_sock { + struct listen_sock *next; + SOCKET sock; +}; + +// Global variables +char hostlist[MAX_HOST_LIST + 1]; //!< Keeps the list of the hosts that are allowed to connect to this server +struct active_pars activelist[MAX_ACTIVE_LIST]; //!< Keeps the list of the hosts (host, port) on which I want to connect to (active mode) +int nullAuthAllowed; //!< '1' if we permit NULL authentication, '0' otherwise +static struct listen_sock *listen_socks; //!< sockets on which we listen +char loadfile[MAX_LINE + 1]; //!< Name of the file from which we have to load the configuration +static int passivemode = 1; //!< '1' if we want to run in passive mode as well +static struct addrinfo mainhints; //!< temporary struct to keep settings needed to open the new socket +static char address[MAX_LINE + 1]; //!< keeps the network address (either numeric or literal) to bind to +static char port[MAX_LINE + 1]; //!< keeps the network port to bind to +#ifdef _WIN32 +static HANDLE state_change_event; //!< event to signal that a state change should take place +#endif +static volatile sig_atomic_t shutdown_server; //!< '1' if the server is to shut down +static volatile sig_atomic_t reread_config; //!< '1' if the server is to re-read its configuration + +extern char *optarg; // for getopt() + +// Function definition +#ifdef _WIN32 +static unsigned __stdcall main_active(void *ptr); +static BOOL WINAPI main_ctrl_event(DWORD); +#else +static void *main_active(void *ptr); +static void main_terminate(int sign); +static void main_reread_config(int sign); +#endif +static void accept_connections(void); +static void accept_connection(SOCKET listen_sock); +#ifndef _WIN32 +static void main_reap_children(int sign); +#endif +#ifdef _WIN32 +static unsigned __stdcall main_passive_serviceloop_thread(void *ptr); +#endif + +#define RPCAP_ACTIVE_WAIT 30 /* Waiting time between two attempts to open a connection, in active mode (default: 30 sec) */ + +/*! + \brief Prints the usage screen if it is launched in console mode. +*/ +static void printusage(void) +{ + const char *usagetext = + "USAGE:" + " " PROGRAM_NAME " [-b <address>] [-p <port>] [-4] [-l <host_list>] [-a <host,port>]\n" + " [-n] [-v] [-d] " +#ifndef _WIN32 + "[-i] " +#endif + "[-D] [-s <config_file>] [-f <config_file>]\n\n" + " -b <address> the address to bind to (either numeric or literal).\n" + " Default: binds to all local IPv4 and IPv6 addresses\n\n" + " -p <port> the port to bind to.\n" + " Default: binds to port " RPCAP_DEFAULT_NETPORT "\n\n" + " -4 use only IPv4.\n" + " Default: use both IPv4 and IPv6 waiting sockets\n\n" + " -l <host_list> a file that contains a list of hosts that are allowed\n" + " to connect to this server (if more than one, list them one\n" + " per line).\n" + " We suggest to use literal names (instead of numeric ones)\n" + " in order to avoid problems with different address families.\n\n" + " -n permit NULL authentication (usually used with '-l')\n\n" + " -a <host,port> run in active mode when connecting to 'host' on port 'port'\n" + " In case 'port' is omitted, the default port (" RPCAP_DEFAULT_NETPORT_ACTIVE ") is used\n\n" + " -v run in active mode only (default: if '-a' is specified, it\n" + " accepts passive connections as well)\n\n" + " -d run in daemon mode (UNIX only) or as a service (Win32 only)\n" + " Warning (Win32): this switch is provided automatically when\n" + " the service is started from the control panel\n\n" +#ifndef _WIN32 + " -i run in inetd mode (UNIX only)\n\n" +#endif + " -D log debugging messages\n\n" + " -s <config_file> save the current configuration to file\n\n" + " -f <config_file> load the current configuration from file; all switches\n" + " specified from the command line are ignored\n\n" + " -h print this help screen\n\n"; + + (void)fprintf(stderr, "RPCAPD, a remote packet capture daemon.\n" + "Compiled with %s\n\n", pcap_lib_version()); + printf("%s", usagetext); +} + + + +//! Program main +int main(int argc, char *argv[]) +{ + char savefile[MAX_LINE + 1]; // name of the file on which we have to save the configuration + int log_to_systemlog = 0; // Non-zero if we should log to the "system log" rather than the standard error + int isdaemon = 0; // Non-zero if the user wants to run this program as a daemon +#ifndef _WIN32 + int isrunbyinetd = 0; // Non-zero if this is being run by inetd or something inetd-like +#endif + int log_debug_messages = 0; // Non-zero if the user wants debug messages logged + int retval; // keeps the returning value from several functions + char errbuf[PCAP_ERRBUF_SIZE + 1]; // keeps the error string, prior to be printed +#ifndef _WIN32 + struct sigaction action; +#endif + + savefile[0] = 0; + loadfile[0] = 0; + hostlist[0] = 0; + + // Initialize errbuf + memset(errbuf, 0, sizeof(errbuf)); + + strncpy(address, RPCAP_DEFAULT_NETADDR, MAX_LINE); + strncpy(port, RPCAP_DEFAULT_NETPORT, MAX_LINE); + + // Prepare to open a new server socket + memset(&mainhints, 0, sizeof(struct addrinfo)); + + mainhints.ai_family = PF_UNSPEC; + mainhints.ai_flags = AI_PASSIVE; // Ready to a bind() socket + mainhints.ai_socktype = SOCK_STREAM; + + // Getting the proper command line options + while ((retval = getopt(argc, argv, "b:dDhip:4l:na:s:f:v")) != -1) + { + switch (retval) + { + case 'D': + log_debug_messages = 1; + rpcapd_log_set(log_to_systemlog, log_debug_messages); + break; + case 'b': + strncpy(address, optarg, MAX_LINE); + break; + case 'p': + strncpy(port, optarg, MAX_LINE); + break; + case '4': + mainhints.ai_family = PF_INET; // IPv4 server only + break; + case 'd': + isdaemon = 1; + log_to_systemlog = 1; + rpcapd_log_set(log_to_systemlog, log_debug_messages); + break; + case 'i': +#ifdef _WIN32 + printusage(); + exit(1); +#else + isrunbyinetd = 1; + log_to_systemlog = 1; + rpcapd_log_set(log_to_systemlog, log_debug_messages); +#endif + break; + case 'n': + nullAuthAllowed = 1; + break; + case 'v': + passivemode = 0; + break; + case 'l': + { + strncpy(hostlist, optarg, sizeof(hostlist)); + break; + } + case 'a': + { + char *tmpaddress, *tmpport; + char *lasts; + int i = 0; + + tmpaddress = pcap_strtok_r(optarg, RPCAP_HOSTLIST_SEP, &lasts); + + while ((tmpaddress != NULL) && (i < MAX_ACTIVE_LIST)) + { + tmpport = pcap_strtok_r(NULL, RPCAP_HOSTLIST_SEP, &lasts); + + pcap_strlcpy(activelist[i].address, tmpaddress, MAX_LINE); + + if ((tmpport == NULL) || (strcmp(tmpport, "DEFAULT") == 0)) // the user choose a custom port + pcap_strlcpy(activelist[i].port, RPCAP_DEFAULT_NETPORT_ACTIVE, MAX_LINE); + else + pcap_strlcpy(activelist[i].port, tmpport, MAX_LINE); + + tmpaddress = pcap_strtok_r(NULL, RPCAP_HOSTLIST_SEP, &lasts); + + i++; + } + + if (i > MAX_ACTIVE_LIST) + rpcapd_log(LOGPRIO_ERROR, "Only MAX_ACTIVE_LIST active connections are currently supported."); + + // I don't initialize the remaining part of the structure, since + // it is already zeroed (it is a global var) + break; + } + case 'f': + pcap_strlcpy(loadfile, optarg, MAX_LINE); + break; + case 's': + pcap_strlcpy(savefile, optarg, MAX_LINE); + break; + case 'h': + printusage(); + exit(0); + /*NOTREACHED*/ + default: + exit(1); + /*NOTREACHED*/ + } + } + +#ifndef _WIN32 + if (isdaemon && isrunbyinetd) + { + rpcapd_log(LOGPRIO_ERROR, "rpcapd: -d and -i can't be used together"); + exit(1); + } +#endif + + if (sock_init(errbuf, PCAP_ERRBUF_SIZE) == -1) + { + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + exit(-1); + } + + if (savefile[0] && fileconf_save(savefile)) + rpcapd_log(LOGPRIO_DEBUG, "Error when saving the configuration to file"); + + // If the file does not exist, it keeps the settings provided by the command line + if (loadfile[0]) + fileconf_read(); + +#ifdef WIN32 + // + // Create a handle to signal the main loop to tell it to do + // something. + // + state_change_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (state_change_event == NULL) + { + sock_geterror("Can't create state change event", errbuf, + PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + exit(2); + } + + // + // Catch control signals. + // + if (!SetConsoleCtrlHandler(main_ctrl_event, TRUE)) + { + sock_geterror("Can't set control handler", errbuf, + PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + exit(2); + } +#else + memset(&action, 0, sizeof (action)); + action.sa_handler = main_terminate; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + sigaction(SIGTERM, &action, NULL); + memset(&action, 0, sizeof (action)); + action.sa_handler = main_reap_children; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + sigaction(SIGCHLD, &action, NULL); + // Ignore SIGPIPE - we'll get EPIPE when trying to write to a closed + // connection, we don't want to get killed by a signal in that case + signal(SIGPIPE, SIG_IGN); +#endif + +#ifndef _WIN32 + if (isrunbyinetd) + { + // + // -i was specified, indicating that this is being run + // by inetd or something that can run network daemons + // as if it were inetd (xinetd, launchd, systemd, etc.). + // + // We assume that the program that launched us just + // duplicated a single socket for the connection + // to our standard input, output, and error, so we + // can just use the standard input as our control + // socket. + // + int sockctrl; + int devnull_fd; + + // + // Duplicate the standard input as the control socket. + // + sockctrl = dup(0); + if (sockctrl == -1) + { + sock_geterror("Can't dup standard input", errbuf, + PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + exit(2); + } + + // + // Try to set the standard input, output, and error + // to /dev/null. + // + devnull_fd = open("/dev/null", O_RDWR); + if (devnull_fd != -1) + { + // + // If this fails, just drive on. + // + (void)dup2(devnull_fd, 0); + (void)dup2(devnull_fd, 1); + (void)dup2(devnull_fd, 2); + close(devnull_fd); + } + + // + // Handle this client. + // This is passive mode, so we don't care whether we were + // told by the client to close. + // + char *hostlist_copy = strdup(hostlist); + if (hostlist_copy == NULL) + { + rpcapd_log(LOGPRIO_ERROR, "Out of memory copying the host/port list"); + exit(0); + } + (void)daemon_serviceloop(sockctrl, 0, hostlist_copy, + nullAuthAllowed); + + // + // Nothing more to do. + // + exit(0); + } +#endif + + if (isdaemon) + { + // + // This is being run as a daemon. + // On UN*X, it might be manually run, or run from an + // rc file. + // +#ifndef _WIN32 + int pid; + + // + // Daemonize ourselves. + // + // Unix Network Programming, pg 336 + // + if ((pid = fork()) != 0) + exit(0); // Parent terminates + + // First child continues + // Set daemon mode + setsid(); + + // generated under unix with 'kill -HUP', needed to reload the configuration + memset(&action, 0, sizeof (action)); + action.sa_handler = main_reread_config; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + sigaction(SIGHUP, &action, NULL); + + if ((pid = fork()) != 0) + exit(0); // First child terminates + + // LINUX WARNING: the current linux implementation of pthreads requires a management thread + // to handle some hidden stuff. So, as soon as you create the first thread, two threads are + // created. Fom this point on, the number of threads active are always one more compared + // to the number you're expecting + + // Second child continues +// umask(0); +// chdir("/"); +#else + // + // This is being run as a service on Windows. + // + // If this call succeeds, it is blocking on Win32 + // + if (svc_start() != 1) + rpcapd_log(LOGPRIO_DEBUG, "Unable to start the service"); + + // When the previous call returns, the entire application has to be stopped. + exit(0); +#endif + } + else // Console mode + { +#ifndef _WIN32 + // Enable the catching of Ctrl+C + memset(&action, 0, sizeof (action)); + action.sa_handler = main_terminate; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + sigaction(SIGINT, &action, NULL); + + // generated under unix with 'kill -HUP', needed to reload the configuration + // We do not have this kind of signal in Win32 + memset(&action, 0, sizeof (action)); + action.sa_handler = main_reread_config; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + sigaction(SIGHUP, &action, NULL); +#endif + + printf("Press CTRL + C to stop the server...\n"); + } + + // If we're a Win32 service, we have already called this function in the service_main + main_startup(); + + // The code should never arrive here (since the main_startup is blocking) + // however this avoids a compiler warning + exit(0); +} + +void main_startup(void) +{ + char errbuf[PCAP_ERRBUF_SIZE + 1]; // keeps the error string, prior to be printed + struct addrinfo *addrinfo; // keeps the addrinfo chain; required to open a new socket + int i; +#ifdef _WIN32 + HANDLE threadId; // handle for the subthread +#else + pid_t pid; +#endif + + i = 0; + addrinfo = NULL; + memset(errbuf, 0, sizeof(errbuf)); + + // Starts all the active threads + while ((i < MAX_ACTIVE_LIST) && (activelist[i].address[0] != 0)) + { + activelist[i].ai_family = mainhints.ai_family; + +#ifdef _WIN32 + threadId = (HANDLE)_beginthreadex(NULL, 0, main_active, + (void *)&activelist[i], 0, NULL); + if (threadId == 0) + { + rpcapd_log(LOGPRIO_DEBUG, "Error creating the active child threads"); + continue; + } + CloseHandle(threadId); +#else + if ((pid = fork()) == 0) // I am the child + { + main_active((void *) &activelist[i]); + exit(0); + } +#endif + i++; + } + + /* + * The code that manages the active connections is not blocking; + * the code that manages the passive connection is blocking. + * So, if the user does not want to run in passive mode, we have + * to block the main thread here, otherwise the program ends and + * all threads are stopped. + * + * WARNING: this means that in case we have only active mode, + * the program does not terminate even if all the child thread + * terminates. The user has always to press Ctrl+C (or send a + * SIGTERM) to terminate the program. + */ + if (passivemode) + { + struct addrinfo *tempaddrinfo; + + // + // Get a list of sockets on which to listen. + // + if (sock_initaddress((address[0]) ? address : NULL, port, &mainhints, &addrinfo, errbuf, PCAP_ERRBUF_SIZE) == -1) + { + rpcapd_log(LOGPRIO_DEBUG, "%s", errbuf); + return; + } + + for (tempaddrinfo = addrinfo; tempaddrinfo; + tempaddrinfo = tempaddrinfo->ai_next) + { + SOCKET sock; + struct listen_sock *sock_info; + + if ((sock = sock_open(tempaddrinfo, SOCKOPEN_SERVER, SOCKET_MAXCONN, errbuf, PCAP_ERRBUF_SIZE)) == INVALID_SOCKET) + { + switch (tempaddrinfo->ai_family) + { + case AF_INET: + { + struct sockaddr_in *in; + char addrbuf[INET_ADDRSTRLEN]; + + in = (struct sockaddr_in *)tempaddrinfo->ai_addr; + rpcapd_log(LOGPRIO_WARNING, "Can't listen on socket for %s:%u: %s", + inet_ntop(AF_INET, &in->sin_addr, + addrbuf, sizeof (addrbuf)), + ntohs(in->sin_port), + errbuf); + break; + } + + case AF_INET6: + { + struct sockaddr_in6 *in6; + char addrbuf[INET6_ADDRSTRLEN]; + + in6 = (struct sockaddr_in6 *)tempaddrinfo->ai_addr; + rpcapd_log(LOGPRIO_WARNING, "Can't listen on socket for %s:%u: %s", + inet_ntop(AF_INET6, &in6->sin6_addr, + addrbuf, sizeof (addrbuf)), + ntohs(in6->sin6_port), + errbuf); + break; + } + + default: + rpcapd_log(LOGPRIO_WARNING, "Can't listen on socket for address family %u: %s", + tempaddrinfo->ai_family, + errbuf); + break; + } + continue; + } + + sock_info = (struct listen_sock *) malloc(sizeof (struct listen_sock)); + if (sock_info == NULL) + { + rpcapd_log(LOGPRIO_ERROR, "Can't allocate structure for listen socket"); + exit(2); + } + sock_info->sock = sock; + sock_info->next = listen_socks; + listen_socks = sock_info; + } + + freeaddrinfo(addrinfo); + + if (listen_socks == NULL) + { + rpcapd_log(LOGPRIO_ERROR, "Can't listen on any address"); + exit(2); + } + + // + // Now listen on all of them, waiting for connections. + // + accept_connections(); + } + + // + // We're done; exit. + // + rpcapd_log(LOGPRIO_DEBUG, PROGRAM_NAME " is closing.\n"); + +#ifndef _WIN32 + // + // Sends a KILL signal to all the processes in this process's + // process group; i.e., it kills all the child processes + // we've created. + // + // XXX - that also includes us, so we will be killed as well; + // that may cause a message to be printed or logged. + // + kill(0, SIGKILL); +#endif + + // + // Just leave. We shouldn't need to clean up sockets or + // anything else, and if we try to do so, we'll could end + // up closing sockets, or shutting Winsock down, out from + // under service loops, causing all sorts of noisy error + // messages. + // + // We shouldn't need to worry about cleaning up any resources + // such as handles, sockets, threads, etc. - exit() should + // terminate the process, causing all those resources to be + // cleaned up (including the threads; Microsoft claims in the + // ExitProcess() documentation that, if ExitProcess() is called, + // "If a thread is waiting on a kernel object, it will not be + // terminated until the wait has completed.", but claims in the + // _beginthread()/_beginthreadex() documentation that "All threads + // are terminated if any thread calls abort, exit, _exit, or + // ExitProcess." - the latter appears to be the case, even for + // threads waiting on the event for a pcap_t). + // + exit(0); +} + +#ifdef _WIN32 +static void +send_state_change_event(void) +{ + char errbuf[PCAP_ERRBUF_SIZE + 1]; // keeps the error string, prior to be printed + + if (!SetEvent(state_change_event)) + { + sock_geterror("SetEvent on shutdown event failed", errbuf, + PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + } +} + +void +send_shutdown_notification(void) +{ + // + // Indicate that the server should shut down. + // + shutdown_server = 1; + + // + // Send a state change event, to wake up WSAWaitForMultipleEvents(). + // + send_state_change_event(); +} + +void +send_reread_configuration_notification(void) +{ + // + // Indicate that the server should re-read its configuration file. + // + reread_config = 1; + + // + // Send a state change event, to wake up WSAWaitForMultipleEvents(). + // + send_state_change_event(); +} + +static BOOL WINAPI main_ctrl_event(DWORD ctrltype) +{ + // + // ctrltype is one of: + // + // CTRL_C_EVENT - we got a ^C; this is like SIGINT + // CTRL_BREAK_EVENT - we got Ctrl+Break + // CTRL_CLOSE_EVENT - the console was closed; this is like SIGHUP + // CTRL_LOGOFF_EVENT - a user is logging off; this is received + // only by services + // CTRL_SHUTDOWN_EVENT - the systemis shutting down; this is + // received only by services + // + // For now, we treat all but CTRL_LOGOFF_EVENT as indications + // that we should shut down. + // + switch (ctrltype) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_SHUTDOWN_EVENT: + // + // Set a shutdown notification. + // + send_shutdown_notification(); + break; + + default: + break; + } + + // + // We handled this. + // + return TRUE; +} +#else +static void main_terminate(int sign _U_) +{ + // + // Note that the server should shut down. + // select() should get an EINTR error when we return, + // so it will wake up and know it needs to check the flag. + // + shutdown_server = 1; +} + +static void main_reread_config(int sign _U_) +{ + // + // Note that the server should re-read its configuration file. + // select() should get an EINTR error when we return, + // so it will wake up and know it needs to check the flag. + // + reread_config = 1; +} + +static void main_reap_children(int sign _U_) +{ + pid_t pid; + int exitstat; + + // Reap all child processes that have exited. + // For reference, Stevens, pg 128 + + while ((pid = waitpid(-1, &exitstat, WNOHANG)) > 0) + rpcapd_log(LOGPRIO_DEBUG, "Child terminated"); + + return; +} +#endif + +// +// Loop waiting for incoming connections and accepting them. +// +static void +accept_connections(void) +{ +#ifdef _WIN32 + struct listen_sock *sock_info; + DWORD num_events; + WSAEVENT *events; + int i; + char errbuf[PCAP_ERRBUF_SIZE + 1]; // keeps the error string, prior to be printed + + // + // How big does the set of events need to be? + // One for the shutdown event, plus one for every socket on which + // we'll be listening. + // + num_events = 1; // shutdown event + for (sock_info = listen_socks; sock_info; + sock_info = sock_info->next) + { + if (num_events == WSA_MAXIMUM_WAIT_EVENTS) + { + // + // WSAWaitForMultipleEvents() doesn't support + // more than WSA_MAXIMUM_WAIT_EVENTS events + // on which to wait. + // + rpcapd_log(LOGPRIO_ERROR, "Too many sockets on which to listen"); + exit(2); + } + num_events++; + } + + // + // Allocate the array of events. + // + events = (WSAEVENT *) malloc(num_events * sizeof (WSAEVENT)); + if (events == NULL) + { + rpcapd_log(LOGPRIO_ERROR, "Can't allocate array of events which to listen"); + exit(2); + } + + // + // Fill it in. + // + events[0] = state_change_event; // state change event first + for (sock_info = listen_socks, i = 1; sock_info; + sock_info = sock_info->next, i++) + { + WSAEVENT event; + + // + // Create an event that is signaled if there's a connection + // to accept on the socket in question. + // + event = WSACreateEvent(); + if (event == WSA_INVALID_EVENT) + { + sock_geterror("Can't create socket event", errbuf, + PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + exit(2); + } + if (WSAEventSelect(sock_info->sock, event, FD_ACCEPT) == SOCKET_ERROR) + { + sock_geterror("Can't setup socket event", errbuf, + PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + exit(2); + } + events[i] = event; + } + + for (;;) + { + // + // Wait for incoming connections. + // + DWORD ret; + + ret = WSAWaitForMultipleEvents(num_events, events, FALSE, + WSA_INFINITE, FALSE); + if (ret == WSA_WAIT_FAILED) + { + sock_geterror("WSAWaitForMultipleEvents failed", errbuf, + PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + exit(2); + } + + if (ret == WSA_WAIT_EVENT_0) + { + // + // The state change event was set. + // + if (shutdown_server) + { + // + // Time to quit. Exit the loop. + // + break; + } + if (reread_config) + { + // + // We should re-read the configuration + // file. + // + reread_config = 0; // clear the indicator + fileconf_read(); + } + } + + // + // Check each socket. + // + for (sock_info = listen_socks, i = 1; sock_info; + sock_info = sock_info->next, i++) + { + WSANETWORKEVENTS network_events; + + if (WSAEnumNetworkEvents(sock_info->sock, + events[i], &network_events) == SOCKET_ERROR) + { + sock_geterror("WSAEnumNetworkEvents failed", + errbuf, PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + exit(2); + } + if (network_events.lNetworkEvents & FD_ACCEPT) + { + // + // Did an error occur? + // + if (network_events.iErrorCode[FD_ACCEPT_BIT] != 0) + { + // + // Yes - report it and keep going. + // + sock_fmterror("Socket error", + network_events.iErrorCode[FD_ACCEPT_BIT], + errbuf, + PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + continue; + } + + // + // Accept the connection. + // + accept_connection(sock_info->sock); + } + } + } +#else + struct listen_sock *sock_info; + int num_sock_fds; + + // + // How big does the bitset of sockets on which to select() have + // to be? + // + num_sock_fds = 0; + for (sock_info = listen_socks; sock_info; sock_info = sock_info->next) + { + if (sock_info->sock + 1 > num_sock_fds) + { + if ((unsigned int)(sock_info->sock + 1) > + (unsigned int)FD_SETSIZE) + { + rpcapd_log(LOGPRIO_ERROR, "Socket FD is too bit for an fd_set"); + exit(2); + } + num_sock_fds = sock_info->sock + 1; + } + } + + for (;;) + { + fd_set sock_fds; + int ret; + + // + // Set up an fd_set for all the sockets on which we're + // listening. + // + // This set is modified by select(), so we have to + // construct it anew each time. + // + FD_ZERO(&sock_fds); + for (sock_info = listen_socks; sock_info; + sock_info = sock_info->next) + { + FD_SET(sock_info->sock, &sock_fds); + } + + // + // Wait for incoming connections. + // + ret = select(num_sock_fds, &sock_fds, NULL, NULL, NULL); + if (ret == -1) + { + if (errno == EINTR) + { + // + // If this is a "terminate the + // server" signal, exit the loop, + // otherwise just keep trying. + // + if (shutdown_server) + { + // + // Time to quit. Exit the loop. + // + break; + } + if (reread_config) + { + // + // We should re-read the configuration + // file. + // + reread_config = 0; // clear the indicator + fileconf_read(); + } + + // + // Go back and wait again. + // + continue; + } + else + { + rpcapd_log(LOGPRIO_ERROR, "select failed: %s", + strerror(errno)); + exit(2); + } + } + + // + // Check each socket. + // + for (sock_info = listen_socks; sock_info; + sock_info = sock_info->next) + { + if (FD_ISSET(sock_info->sock, &sock_fds)) + { + // + // Accept the connection. + // + accept_connection(sock_info->sock); + } + } + } +#endif + + // + // Close all the listen sockets. + // + for (sock_info = listen_socks; sock_info; sock_info = sock_info->next) + { + closesocket(sock_info->sock); + } + sock_cleanup(); +} + +#ifdef _WIN32 +// +// A structure to hold the parameters to the daemon service loop +// thread on Windows. +// +// (On UN*X, there is no need for this explicit copy since the +// fork "inherits" the parent stack.) +// +struct params_copy { + SOCKET sockctrl; + char *hostlist; +}; +#endif + +// +// Accept a connection and start a worker thread, on Windows, or a +// worker process, on UN*X, to handle the connection. +// +static void +accept_connection(SOCKET listen_sock) +{ + char errbuf[PCAP_ERRBUF_SIZE + 1]; // keeps the error string, prior to be printed + SOCKET sockctrl; // keeps the socket ID for this control connection + struct sockaddr_storage from; // generic sockaddr_storage variable + socklen_t fromlen; // keeps the length of the sockaddr_storage variable + +#ifdef _WIN32 + HANDLE threadId; // handle for the subthread + u_long off = 0; + struct params_copy *params_copy = NULL; +#else + pid_t pid; +#endif + + // Initialize errbuf + memset(errbuf, 0, sizeof(errbuf)); + + for (;;) + { + // Accept the connection + fromlen = sizeof(struct sockaddr_storage); + + sockctrl = accept(listen_sock, (struct sockaddr *) &from, &fromlen); + + if (sockctrl != INVALID_SOCKET) + { + // Success. + break; + } + + // The accept() call can return this error when a signal is catched + // In this case, we have simply to ignore this error code + // Stevens, pg 124 +#ifdef _WIN32 + if (WSAGetLastError() == WSAEINTR) +#else + if (errno == EINTR) +#endif + continue; + + // Don't check for errors here, since the error can be due to the fact that the thread + // has been killed + sock_geterror("accept()", errbuf, PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "Accept of control connection from client failed: %s", + errbuf); + return; + } + +#ifdef _WIN32 + // + // Put the socket back into blocking mode; doing WSAEventSelect() + // on the listen socket makes that socket non-blocking, and it + // appears that sockets returned from an accept() on that socket + // are also non-blocking. + // + // First, we have to un-WSAEventSelect() this socket, and then + // we can turn non-blocking mode off. + // + // If this fails, we aren't guaranteed that, for example, any + // of the error message will be sent - if it can't be put in + // the socket queue, the send will just fail. + // + // So we just log the message and close the connection. + // + if (WSAEventSelect(sockctrl, NULL, 0) == SOCKET_ERROR) + { + sock_geterror("WSAEventSelect()", errbuf, PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + sock_close(sockctrl, NULL, 0); + return; + } + if (ioctlsocket(sockctrl, FIONBIO, &off) == SOCKET_ERROR) + { + sock_geterror("ioctlsocket(FIONBIO)", errbuf, PCAP_ERRBUF_SIZE); + rpcapd_log(LOGPRIO_ERROR, "%s", errbuf); + sock_close(sockctrl, NULL, 0); + return; + } + + // + // Make a copy of the host list to pass to the new thread, so that + // if we update it in the main thread, it won't catch us in the + // middle of updating it. + // + // daemon_serviceloop() will free it once it's done with it. + // + char *hostlist_copy = strdup(hostlist); + if (hostlist_copy == NULL) + { + rpcapd_log(LOGPRIO_ERROR, "Out of memory copying the host/port list"); + sock_close(sockctrl, NULL, 0); + return; + } + + // + // Allocate a location to hold the values of sockctrl. + // It will be freed in the newly-created thread once it's + // finished with it. + // + params_copy = malloc(sizeof(*params_copy)); + if (params_copy == NULL) + { + rpcapd_log(LOGPRIO_ERROR, "Out of memory allocating the parameter copy structure"); + free(hostlist_copy); + sock_close(sockctrl, NULL, 0); + return; + } + params_copy->sockctrl = sockctrl; + params_copy->hostlist = hostlist_copy; + + threadId = (HANDLE)_beginthreadex(NULL, 0, + main_passive_serviceloop_thread, (void *) params_copy, 0, NULL); + if (threadId == 0) + { + rpcapd_log(LOGPRIO_ERROR, "Error creating the child thread"); + free(params_copy); + free(hostlist_copy); + sock_close(sockctrl, NULL, 0); + return; + } + CloseHandle(threadId); +#else /* _WIN32 */ + pid = fork(); + if (pid == -1) + { + rpcapd_log(LOGPRIO_ERROR, "Error creating the child process: %s", + strerror(errno)); + sock_close(sockctrl, NULL, 0); + return; + } + if (pid == 0) + { + // + // Child process. + // + // Close the socket on which we're listening (must + // be open only in the parent). + // + closesocket(listen_sock); + +#if 0 + // + // Modify thread params so that it can be killed at any time + // XXX - is this necessary? This is the main and, currently, + // only thread in the child process, and nobody tries to + // cancel us, although *we* may cancel the thread that's + // handling the capture loop. + // + if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)) + goto end; + if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL)) + goto end; +#endif + + // + // Run the service loop. + // This is passive mode, so we don't care whether we were + // told by the client to close. + // + char *hostlist_copy = strdup(hostlist); + if (hostlist_copy == NULL) + { + rpcapd_log(LOGPRIO_ERROR, "Out of memory copying the host/port list"); + exit(0); + } + (void)daemon_serviceloop(sockctrl, 0, hostlist_copy, + nullAuthAllowed); + + exit(0); + } + + // I am the parent + // Close the socket for this session (must be open only in the child) + closesocket(sockctrl); +#endif /* _WIN32 */ +} + +/*! + \brief 'true' main of the program in case the active mode is turned on. + + This function loops forever trying to connect to the remote host, until the + daemon is turned down. + + \param ptr: it keeps the 'activepars' parameters. It is a 'void *' + just because the thread APIs want this format. +*/ +#ifdef _WIN32 +static unsigned __stdcall +#else +static void * +#endif +main_active(void *ptr) +{ + char errbuf[PCAP_ERRBUF_SIZE + 1]; // keeps the error string, prior to be printed + SOCKET sockctrl; // keeps the socket ID for this control connection + struct addrinfo hints; // temporary struct to keep settings needed to open the new socket + struct addrinfo *addrinfo; // keeps the addrinfo chain; required to open a new socket + struct active_pars *activepars; + + activepars = (struct active_pars *) ptr; + + // Prepare to open a new server socket + memset(&hints, 0, sizeof(struct addrinfo)); + // WARNING Currently it supports only ONE socket family among IPv4 and IPv6 + hints.ai_family = AF_INET; // PF_UNSPEC to have both IPv4 and IPv6 server + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = activepars->ai_family; + + rpcapd_log(LOGPRIO_DEBUG, "Connecting to host %s, port %s, using protocol %s", + activepars->address, activepars->port, (hints.ai_family == AF_INET) ? "IPv4": + (hints.ai_family == AF_INET6) ? "IPv6" : "Unspecified"); + + // Initialize errbuf + memset(errbuf, 0, sizeof(errbuf)); + + // Do the work + if (sock_initaddress(activepars->address, activepars->port, &hints, &addrinfo, errbuf, PCAP_ERRBUF_SIZE) == -1) + { + rpcapd_log(LOGPRIO_DEBUG, "%s", errbuf); + return 0; + } + + for (;;) + { + int activeclose; + + if ((sockctrl = sock_open(addrinfo, SOCKOPEN_CLIENT, 0, errbuf, PCAP_ERRBUF_SIZE)) == INVALID_SOCKET) + { + rpcapd_log(LOGPRIO_DEBUG, "%s", errbuf); + + pcap_snprintf(errbuf, PCAP_ERRBUF_SIZE, "Error connecting to host %s, port %s, using protocol %s", + activepars->address, activepars->port, (hints.ai_family == AF_INET) ? "IPv4": + (hints.ai_family == AF_INET6) ? "IPv6" : "Unspecified"); + + rpcapd_log(LOGPRIO_DEBUG, "%s", errbuf); + + sleep_secs(RPCAP_ACTIVE_WAIT); + + continue; + } + + char *hostlist_copy = strdup(hostlist); + if (hostlist_copy == NULL) + { + rpcapd_log(LOGPRIO_ERROR, "Out of memory copying the host/port list"); + activeclose = 0; + sock_close(sockctrl, NULL, 0); + } + else + { + // + // daemon_serviceloop() will free the copy. + // + activeclose = daemon_serviceloop(sockctrl, 1, + hostlist_copy, nullAuthAllowed); + } + + // If the connection is closed by the user explicitely, don't try to connect to it again + // just exit the program + if (activeclose == 1) + break; + } + + freeaddrinfo(addrinfo); + return 0; +} + +#ifdef _WIN32 +// +// Main routine of a passive-mode service thread. +// +unsigned __stdcall main_passive_serviceloop_thread(void *ptr) +{ + struct params_copy params = *(struct params_copy *)ptr; + free(ptr); + + // + // Handle this client. + // This is passive mode, so we don't care whether we were + // told by the client to close. + // + (void)daemon_serviceloop(params.sockctrl, 0, params.hostlist, + nullAuthAllowed); + + return 0; +} +#endif |