| /* |
| * 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 |
| "[-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 |
| " -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 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 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)); |
| |
| if (sock_init(errbuf, PCAP_ERRBUF_SIZE) == -1) |
| { |
| SOCK_DEBUG_MESSAGE(errbuf); |
| exit(-1); |
| } |
| |
| 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:dhip:4l:na:s:f:v")) != -1) |
| { |
| switch (retval) |
| { |
| 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; |
| break; |
| case 'i': |
| #ifdef _WIN32 |
| printusage(); |
| exit(1); |
| #else |
| isrunbyinetd = 1; |
| #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); |
| |
| strlcpy(activelist[i].address, tmpaddress, MAX_LINE); |
| |
| if ((tmpport == NULL) || (strcmp(tmpport, "DEFAULT") == 0)) // the user choose a custom port |
| strlcpy(activelist[i].port, RPCAP_DEFAULT_NETPORT_ACTIVE, MAX_LINE); |
| else |
| strlcpy(activelist[i].port, tmpport, MAX_LINE); |
| |
| tmpaddress = pcap_strtok_r(NULL, RPCAP_HOSTLIST_SEP, &lasts); |
| |
| i++; |
| } |
| |
| if (i > MAX_ACTIVE_LIST) |
| SOCK_DEBUG_MESSAGE("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': |
| strlcpy(loadfile, optarg, MAX_LINE); |
| break; |
| case 's': |
| strlcpy(savefile, optarg, MAX_LINE); |
| break; |
| case 'h': |
| printusage(); |
| exit(0); |
| break; |
| default: |
| exit(1); |
| break; |
| } |
| } |
| |
| #ifndef _WIN32 |
| if (isdaemon && isrunbyinetd) |
| { |
| fprintf(stderr, "rpcapd: -d and -i can't be used together\n"); |
| exit(1); |
| } |
| #endif |
| |
| if (savefile[0] && fileconf_save(savefile)) |
| SOCK_DEBUG_MESSAGE("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(NULL, errbuf, PCAP_ERRBUF_SIZE); |
| rpcapd_log(LOGPRIO_ERROR, "Can't create state change event: %s", |
| errbuf); |
| exit(2); |
| } |
| |
| // |
| // Catch control signals. |
| // |
| if (!SetConsoleCtrlHandler(main_ctrl_event, TRUE)) |
| { |
| sock_geterror(NULL, errbuf, PCAP_ERRBUF_SIZE); |
| rpcapd_log(LOGPRIO_ERROR, "Can't set control handler: %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.). |
| // |
| // Our standard input is the input side of a connection, |
| // and our standard output is the output side of a |
| // connection. |
| // |
| int sockctrl_in, sockctrl_out; |
| int devnull_fd; |
| |
| // |
| // Duplicate the standard input and output, making them |
| // the input and output side of the control connection. |
| // |
| sockctrl_in = dup(0); |
| if (sockctrl_in == -1) |
| { |
| sock_geterror(NULL, errbuf, PCAP_ERRBUF_SIZE); |
| rpcapd_log(LOGPRIO_ERROR, "Can't dup standard input: %s", |
| errbuf); |
| exit(2); |
| } |
| sockctrl_out = dup(1); |
| if (sockctrl_out == -1) |
| { |
| sock_geterror(NULL, errbuf, PCAP_ERRBUF_SIZE); |
| rpcapd_log(LOGPRIO_ERROR, "Can't dup standard output: %s", |
| errbuf); |
| exit(2); |
| } |
| |
| // |
| // Try to set the standard input and output 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); |
| close(devnull_fd); |
| } |
| |
| // |
| // Handle this client. |
| // This is passive mode, so we don't care whether we were |
| // told by the client to close. |
| // |
| (void)daemon_serviceloop(sockctrl_in, sockctrl_out, 0, |
| 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) |
| SOCK_DEBUG_MESSAGE("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) |
| { |
| SOCK_DEBUG_MESSAGE("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) |
| { |
| SOCK_DEBUG_MESSAGE(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. |
| // |
| SOCK_DEBUG_MESSAGE(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(NULL, errbuf, PCAP_ERRBUF_SIZE); |
| rpcapd_log(LOGPRIO_ERROR, "SetEvent on shutdown event failed: %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) |
| SOCK_DEBUG_MESSAGE("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(NULL, errbuf, PCAP_ERRBUF_SIZE); |
| rpcapd_log(LOGPRIO_ERROR, "Can't create socket event: %s", errbuf); |
| exit(2); |
| } |
| if (WSAEventSelect(sock_info->sock, event, FD_ACCEPT) == SOCKET_ERROR) |
| { |
| sock_geterror(NULL, errbuf, PCAP_ERRBUF_SIZE); |
| rpcapd_log(LOGPRIO_ERROR, "Can't setup socket event: %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(NULL, errbuf, PCAP_ERRBUF_SIZE); |
| rpcapd_log(LOGPRIO_ERROR, "WSAWaitForMultipleEvents failed: %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(NULL, errbuf, PCAP_ERRBUF_SIZE); |
| rpcapd_log(LOGPRIO_ERROR, "WSAEnumNetworkEvents failed: %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(NULL, |
| network_events.iErrorCode[FD_ACCEPT_BIT], |
| errbuf, |
| PCAP_ERRBUF_SIZE); |
| rpcapd_log(LOGPRIO_ERROR, "Socket 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(); |
| } |
| |
| // |
| // 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; |
| SOCKET *sockctrl_temp; |
| #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; |
| } |
| |
| // |
| // We have a connection. |
| // Check whether the connecting host is among the ones allowed. |
| // |
| if (sock_check_hostlist(hostlist, RPCAP_HOSTLIST_SEP, &from, errbuf, PCAP_ERRBUF_SIZE) < 0) |
| { |
| rpcap_senderror(sockctrl, 0, PCAP_ERR_HOSTNOAUTH, errbuf, NULL); |
| sock_close(sockctrl, NULL, 0); |
| 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 (WSAEventSelect(sockctrl, NULL, 0) == SOCKET_ERROR) |
| { |
| sock_geterror("ioctlsocket(FIONBIO): ", errbuf, PCAP_ERRBUF_SIZE); |
| rpcap_senderror(sockctrl, 0, PCAP_ERR_HOSTNOAUTH, errbuf, NULL); |
| sock_close(sockctrl, NULL, 0); |
| return; |
| } |
| if (ioctlsocket(sockctrl, FIONBIO, &off) == SOCKET_ERROR) |
| { |
| sock_geterror("ioctlsocket(FIONBIO): ", errbuf, PCAP_ERRBUF_SIZE); |
| rpcap_senderror(sockctrl, 0, PCAP_ERR_HOSTNOAUTH, errbuf, NULL); |
| sock_close(sockctrl, NULL, 0); |
| return; |
| } |
| |
| // |
| // Allocate a location to hold the value of sockctrl. |
| // It will be freed in the newly-created thread once it's |
| // finished with it. |
| // I guess we *could* just cast sockctrl to a void *, but that's |
| // a bit ugly. |
| // |
| sockctrl_temp = (SOCKET *)malloc(sizeof (SOCKET)); |
| if (sockctrl_temp == NULL) |
| { |
| pcap_fmt_errmsg_for_errno(errbuf, PCAP_ERRBUF_SIZE, |
| errno, "malloc() failed"); |
| rpcap_senderror(sockctrl, 0, PCAP_ERR_OPEN, errbuf, NULL); |
| sock_close(sockctrl, NULL, 0); |
| return; |
| } |
| *sockctrl_temp = sockctrl; |
| |
| threadId = (HANDLE)_beginthreadex(NULL, 0, |
| main_passive_serviceloop_thread, (void *) sockctrl_temp, 0, NULL); |
| if (threadId == 0) |
| { |
| pcap_snprintf(errbuf, PCAP_ERRBUF_SIZE, "Error creating the child thread"); |
| rpcap_senderror(sockctrl, 0, PCAP_ERR_OPEN, errbuf, NULL); |
| sock_close(sockctrl, NULL, 0); |
| free(sockctrl_temp); |
| return; |
| } |
| CloseHandle(threadId); |
| #else |
| pid = fork(); |
| if (pid == -1) |
| { |
| pcap_snprintf(errbuf, PCAP_ERRBUF_SIZE, "Error creating the child process"); |
| rpcap_senderror(sockctrl, 0, PCAP_ERR_OPEN, errbuf, NULL); |
| 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. |
| // |
| (void)daemon_serviceloop(sockctrl, sockctrl, 0, |
| nullAuthAllowed); |
| |
| close(sockctrl); |
| |
| exit(0); |
| } |
| |
| // I am the parent |
| // Close the socket for this session (must be open only in the child) |
| closesocket(sockctrl); |
| #endif |
| } |
| |
| /*! |
| \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; |
| |
| pcap_snprintf(errbuf, PCAP_ERRBUF_SIZE, "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"); |
| SOCK_DEBUG_MESSAGE(errbuf); |
| |
| // Initialize errbuf |
| memset(errbuf, 0, sizeof(errbuf)); |
| |
| // Do the work |
| if (sock_initaddress(activepars->address, activepars->port, &hints, &addrinfo, errbuf, PCAP_ERRBUF_SIZE) == -1) |
| { |
| SOCK_DEBUG_MESSAGE(errbuf); |
| return 0; |
| } |
| |
| for (;;) |
| { |
| int activeclose; |
| |
| if ((sockctrl = sock_open(addrinfo, SOCKOPEN_CLIENT, 0, errbuf, PCAP_ERRBUF_SIZE)) == INVALID_SOCKET) |
| { |
| SOCK_DEBUG_MESSAGE(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"); |
| |
| SOCK_DEBUG_MESSAGE(errbuf); |
| |
| sleep_secs(RPCAP_ACTIVE_WAIT); |
| |
| continue; |
| } |
| |
| activeclose = daemon_serviceloop(sockctrl, sockctrl, 1, |
| nullAuthAllowed); |
| |
| sock_close(sockctrl, NULL, 0); |
| |
| // 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) |
| { |
| SOCKET sockctrl; |
| |
| sockctrl = *((SOCKET *)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(sockctrl, sockctrl, 0, nullAuthAllowed); |
| |
| sock_close(sockctrl, NULL, 0); |
| |
| return 0; |
| } |
| #endif |