gateway.c

00001 /********************************************************************\
00002  * This program is free software; you can redistribute it and/or    *
00003  * modify it under the terms of the GNU General Public License as   *
00004  * published by the Free:Software Foundation; either version 2 of   *
00005  * the License, or (at your option) any later version.              *
00006  *                                                                  *
00007  * This program is distributed in the hope that it will be useful,  *
00008  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00009  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00010  * GNU General Public License for more details.                     *
00011  *                                                                  *
00012  * You should have received a copy of the GNU General Public License*
00013  * along with this program; if not, contact:                        *
00014  *                                                                  *
00015  * Free Software Foundation           Voice:  +1-617-542-5942       *
00016  * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
00017  * Boston, MA  02111-1307,  USA       gnu@gnu.org                   *
00018  *                                                                  *
00019  \********************************************************************/
00020 
00021 /* $Id: gateway.c 1305 2007-11-01 20:04:20Z benoitg $ */
00029 #include <stdio.h>
00030 #include <stdlib.h>
00031 #include <syslog.h>
00032 #include <pthread.h>
00033 #include <signal.h>
00034 #include <errno.h>
00035 #include <time.h>
00036 
00037 /* for strerror() */
00038 #include <string.h>
00039 
00040 /* for wait() */
00041 #include <sys/wait.h>
00042 
00043 /* for unix socket communication*/
00044 #include <sys/socket.h>
00045 #include <sys/un.h>
00046 
00047 #include "common.h"
00048 #include "httpd.h"
00049 #include "safe.h"
00050 #include "debug.h"
00051 #include "conf.h"
00052 #include "gateway.h"
00053 #include "firewall.h"
00054 #include "commandline.h"
00055 #include "auth.h"
00056 #include "http.h"
00057 #include "client_list.h"
00058 #include "wdctl_thread.h"
00059 #include "ping_thread.h"
00060 #include "httpd_thread.h"
00061 #include "util.h"
00062 
00067 static pthread_t tid_fw_counter = 0;
00068 static pthread_t tid_ping = 0; 
00069 
00070 /* The internal web server */
00071 httpd * webserver = NULL;
00072 
00073 /* from commandline.c */
00074 extern char ** restartargv;
00075 extern pid_t restart_orig_pid;
00076 t_client *firstclient;
00077 
00078 /* from client_list.c */
00079 extern pthread_mutex_t client_list_mutex;
00080 
00081 /* Time when wifidog started  */
00082 time_t started_time = 0;
00083 
00084 /* Appends -x, the current PID, and NULL to restartargv
00085  * see parse_commandline in commandline.c for details
00086  *
00087  * Why is restartargv global? Shouldn't it be at most static to commandline.c
00088  * and this function static there? -Alex @ 8oct2006
00089  */
00090 void append_x_restartargv(void) {
00091         int i;
00092 
00093         for (i=0; restartargv[i]; i++);
00094 
00095         restartargv[i++] = safe_strdup("-x");
00096         safe_asprintf(&(restartargv[i++]), "%d", getpid());
00097 }
00098 
00099 /* @internal
00100  * @brief During gateway restart, connects to the parent process via the internal socket
00101  * Downloads from it the active client list
00102  */
00103 void get_clients_from_parent(void) {
00104         int sock;
00105         struct sockaddr_un      sa_un;
00106         s_config * config = NULL;
00107         char linebuffer[MAX_BUF];
00108         int len = 0;
00109         char *running1 = NULL;
00110         char *running2 = NULL;
00111         char *token1 = NULL;
00112         char *token2 = NULL;
00113         char onechar;
00114         char *command = NULL;
00115         char *key = NULL;
00116         char *value = NULL;
00117         t_client * client = NULL;
00118         t_client * lastclient = NULL;
00119 
00120         config = config_get_config();
00121         
00122         debug(LOG_INFO, "Connecting to parent to download clients");
00123 
00124         /* Connect to socket */
00125         sock = socket(AF_UNIX, SOCK_STREAM, 0);
00126         memset(&sa_un, 0, sizeof(sa_un));
00127         sa_un.sun_family = AF_UNIX;
00128         strncpy(sa_un.sun_path, config->internal_sock, (sizeof(sa_un.sun_path) - 1));
00129 
00130         if (connect(sock, (struct sockaddr *)&sa_un, strlen(sa_un.sun_path) + sizeof(sa_un.sun_family))) {
00131                 debug(LOG_ERR, "Failed to connect to parent (%s) - client list not downloaded", strerror(errno));
00132                 return;
00133         }
00134 
00135         debug(LOG_INFO, "Connected to parent.  Downloading clients");
00136 
00137         LOCK_CLIENT_LIST();
00138 
00139         command = NULL;
00140         memset(linebuffer, 0, sizeof(linebuffer));
00141         len = 0;
00142         client = NULL;
00143         /* Get line by line */
00144         while (read(sock, &onechar, 1) == 1) {
00145                 if (onechar == '\n') {
00146                         /* End of line */
00147                         onechar = '\0';
00148                 }
00149                 linebuffer[len++] = onechar;
00150 
00151                 if (!onechar) {
00152                         /* We have a complete entry in linebuffer - parse it */
00153                         debug(LOG_DEBUG, "Received from parent: [%s]", linebuffer);
00154                         running1 = linebuffer;
00155                         while ((token1 = strsep(&running1, "|")) != NULL) {
00156                                 if (!command) {
00157                                         /* The first token is the command */
00158                                         command = token1;
00159                                 }
00160                                 else {
00161                                 /* Token1 has something like "foo=bar" */
00162                                         running2 = token1;
00163                                         key = value = NULL;
00164                                         while ((token2 = strsep(&running2, "=")) != NULL) {
00165                                                 if (!key) {
00166                                                         key = token2;
00167                                                 }
00168                                                 else if (!value) {
00169                                                         value = token2;
00170                                                 }
00171                                         }
00172                                 }
00173 
00174                                 if (strcmp(command, "CLIENT") == 0) {
00175                                         /* This line has info about a client in the client list */
00176                                         if (!client) {
00177                                                 /* Create a new client struct */
00178                                                 client = safe_malloc(sizeof(t_client));
00179                                                 memset(client, 0, sizeof(t_client));
00180                                         }
00181                                 }
00182 
00183                                 if (key && value) {
00184                                         if (strcmp(command, "CLIENT") == 0) {
00185                                                 /* Assign the key into the appropriate slot in the connection structure */
00186                                                 if (strcmp(key, "ip") == 0) {
00187                                                         client->ip = safe_strdup(value);
00188                                                 }
00189                                                 else if (strcmp(key, "mac") == 0) {
00190                                                         client->mac = safe_strdup(value);
00191                                                 }
00192                                                 else if (strcmp(key, "token") == 0) {
00193                                                         client->token = safe_strdup(value);
00194                                                 }
00195                                                 else if (strcmp(key, "fw_connection_state") == 0) {
00196                                                         client->fw_connection_state = atoi(value);
00197                                                 }
00198                                                 else if (strcmp(key, "fd") == 0) {
00199                                                         client->fd = atoi(value);
00200                                                 }
00201                                                 else if (strcmp(key, "counters_incoming") == 0) {
00202                                                         client->counters.incoming_history = atoll(value);
00203                                                         client->counters.incoming = client->counters.incoming_history;
00204                                                 }
00205                                                 else if (strcmp(key, "counters_outgoing") == 0) {
00206                                                         client->counters.outgoing_history = atoll(value);
00207                                                         client->counters.outgoing = client->counters.outgoing_history;
00208                                                 }
00209                                                 else if (strcmp(key, "counters_last_updated") == 0) {
00210                                                         client->counters.last_updated = atol(value);
00211                                                 }
00212                                                 else {
00213                                                         debug(LOG_NOTICE, "I don't know how to inherit key [%s] value [%s] from parent", key, value);
00214                                                 }
00215                                         }
00216                                 }
00217                         }
00218 
00219                         /* End of parsing this command */
00220                         if (client) {
00221                                 /* Add this client to the client list */
00222                                 if (!firstclient) {
00223                                         firstclient = client;
00224                                         lastclient = firstclient;
00225                                 }
00226                                 else {
00227                                         lastclient->next = client;
00228                                         lastclient = client;
00229                                 }
00230                         }
00231 
00232                         /* Clean up */
00233                         command = NULL;
00234                         memset(linebuffer, 0, sizeof(linebuffer));
00235                         len = 0;
00236                         client = NULL;
00237                 }
00238         }
00239 
00240         UNLOCK_CLIENT_LIST();
00241         debug(LOG_INFO, "Client list downloaded successfully from parent");
00242 
00243         close(sock);
00244 }
00245 
00253 void
00254 sigchld_handler(int s)
00255 {
00256         int     status;
00257         pid_t rc;
00258         
00259         debug(LOG_DEBUG, "Handler for SIGCHLD called. Trying to reap a child");
00260 
00261         rc = waitpid(-1, &status, WNOHANG);
00262 
00263         debug(LOG_DEBUG, "Handler for SIGCHLD reaped child PID %d", rc);
00264 }
00265 
00268 void
00269 termination_handler(int s)
00270 {
00271         static  pthread_mutex_t sigterm_mutex = PTHREAD_MUTEX_INITIALIZER;
00272 
00273         debug(LOG_INFO, "Handler for termination caught signal %d", s);
00274 
00275         /* Makes sure we only call fw_destroy() once. */
00276         if (pthread_mutex_trylock(&sigterm_mutex)) {
00277                 debug(LOG_INFO, "Another thread already began global termination handler. I'm exiting");
00278                 pthread_exit(NULL);
00279         }
00280         else {
00281                 debug(LOG_INFO, "Cleaning up and exiting");
00282         }
00283 
00284         debug(LOG_INFO, "Flushing firewall rules...");
00285         fw_destroy();
00286 
00287         /* XXX Hack
00288          * Aparently pthread_cond_timedwait under openwrt prevents signals (and therefore
00289          * termination handler) from happening so we need to explicitly kill the threads 
00290          * that use that
00291          */
00292         if (tid_fw_counter) {
00293                 debug(LOG_INFO, "Explicitly killing the fw_counter thread");
00294                 pthread_kill(tid_fw_counter, SIGKILL);
00295         }
00296         if (tid_ping) {
00297                 debug(LOG_INFO, "Explicitly killing the ping thread");
00298                 pthread_kill(tid_ping, SIGKILL);
00299         }
00300 
00301         debug(LOG_NOTICE, "Exiting...");
00302         exit(s == 0 ? 1 : 0);
00303 }
00304 
00308 static void
00309 init_signals(void)
00310 {
00311         struct sigaction sa;
00312 
00313         debug(LOG_DEBUG, "Initializing signal handlers");
00314         
00315         sa.sa_handler = sigchld_handler;
00316         sigemptyset(&sa.sa_mask);
00317         sa.sa_flags = SA_RESTART;
00318         if (sigaction(SIGCHLD, &sa, NULL) == -1) {
00319                 debug(LOG_ERR, "sigaction(): %s", strerror(errno));
00320                 exit(1);
00321         }
00322 
00323         /* Trap SIGPIPE */
00324         /* This is done so that when libhttpd does a socket operation on
00325          * a disconnected socket (i.e.: Broken Pipes) we catch the signal
00326          * and do nothing. The alternative is to exit. SIGPIPE are harmless
00327          * if not desirable.
00328          */
00329         sa.sa_handler = SIG_IGN;
00330         if (sigaction(SIGPIPE, &sa, NULL) == -1) {
00331                 debug(LOG_ERR, "sigaction(): %s", strerror(errno));
00332                 exit(1);
00333         }
00334 
00335         sa.sa_handler = termination_handler;
00336         sigemptyset(&sa.sa_mask);
00337         sa.sa_flags = SA_RESTART;
00338 
00339         /* Trap SIGTERM */
00340         if (sigaction(SIGTERM, &sa, NULL) == -1) {
00341                 debug(LOG_ERR, "sigaction(): %s", strerror(errno));
00342                 exit(1);
00343         }
00344 
00345         /* Trap SIGQUIT */
00346         if (sigaction(SIGQUIT, &sa, NULL) == -1) {
00347                 debug(LOG_ERR, "sigaction(): %s", strerror(errno));
00348                 exit(1);
00349         }
00350 
00351         /* Trap SIGINT */
00352         if (sigaction(SIGINT, &sa, NULL) == -1) {
00353                 debug(LOG_ERR, "sigaction(): %s", strerror(errno));
00354                 exit(1);
00355         }
00356 }
00357 
00361 static void
00362 main_loop(void)
00363 {
00364         int result;
00365         pthread_t       tid;
00366         s_config *config = config_get_config();
00367         request *r;
00368         void **params;
00369 
00370     /* Set the time when wifidog started */
00371         if (!started_time) {
00372                 debug(LOG_INFO, "Setting started_time");
00373                 started_time = time(NULL);
00374         }
00375         else if (started_time < MINIMUM_STARTED_TIME) {
00376                 debug(LOG_WARNING, "Detected possible clock skew - re-setting started_time");
00377                 started_time = time(NULL);
00378         }
00379 
00380         /* If we don't have the Gateway IP address, get it. Can't fail. */
00381         if (!config->gw_address) {
00382                 debug(LOG_DEBUG, "Finding IP address of %s", config->gw_interface);
00383                 if ((config->gw_address = get_iface_ip(config->gw_interface)) == NULL) {
00384                         debug(LOG_ERR, "Could not get IP address information of %s, exiting...", config->gw_interface);
00385                         exit(1);
00386                 }
00387                 debug(LOG_DEBUG, "%s = %s", config->gw_interface, config->gw_address);
00388         }
00389 
00390         /* If we don't have the Gateway ID, construct it from the internal MAC address.
00391          * "Can't fail" so exit() if the impossible happens. */
00392         if (!config->gw_id) {
00393         debug(LOG_DEBUG, "Finding MAC address of %s", config->gw_interface);
00394         if ((config->gw_id = get_iface_mac(config->gw_interface)) == NULL) {
00395                         debug(LOG_ERR, "Could not get MAC address information of %s, exiting...", config->gw_interface);
00396                         exit(1);
00397                 }
00398                 debug(LOG_DEBUG, "%s = %s", config->gw_interface, config->gw_id);
00399         }
00400 
00401         /* Initializes the web server */
00402         debug(LOG_NOTICE, "Creating web server on %s:%d", config->gw_address, config->gw_port);
00403         if ((webserver = httpdCreate(config->gw_address, config->gw_port)) == NULL) {
00404                 debug(LOG_ERR, "Could not create web server: %s", strerror(errno));
00405                 exit(1);
00406         }
00407 
00408         debug(LOG_DEBUG, "Assigning callbacks to web server");
00409         httpdAddCContent(webserver, "/", "wifidog", 0, NULL, http_callback_wifidog);
00410         httpdAddCContent(webserver, "/wifidog", "", 0, NULL, http_callback_wifidog);
00411         httpdAddCContent(webserver, "/wifidog", "about", 0, NULL, http_callback_about);
00412         httpdAddCContent(webserver, "/wifidog", "status", 0, NULL, http_callback_status);
00413         httpdAddCContent(webserver, "/wifidog", "auth", 0, NULL, http_callback_auth);
00414 
00415         httpdAddC404Content(webserver, http_callback_404);
00416 
00417         /* Reset the firewall (if WiFiDog crashed) */
00418         fw_destroy();
00419         /* Then initialize it */
00420         if (!fw_init()) {
00421                 debug(LOG_ERR, "FATAL: Failed to initialize firewall");
00422                 exit(1);
00423         }
00424 
00425         /* Start clean up thread */
00426         result = pthread_create(&tid_fw_counter, NULL, (void *)thread_client_timeout_check, NULL);
00427         if (result != 0) {
00428             debug(LOG_ERR, "FATAL: Failed to create a new thread (fw_counter) - exiting");
00429             termination_handler(0);
00430         }
00431         pthread_detach(tid_fw_counter);
00432 
00433         /* Start control thread */
00434         result = pthread_create(&tid, NULL, (void *)thread_wdctl, (void *)safe_strdup(config->wdctl_sock));
00435         if (result != 0) {
00436                 debug(LOG_ERR, "FATAL: Failed to create a new thread (wdctl) - exiting");
00437                 termination_handler(0);
00438         }
00439         pthread_detach(tid);
00440         
00441         /* Start heartbeat thread */
00442         result = pthread_create(&tid_ping, NULL, (void *)thread_ping, NULL);
00443         if (result != 0) {
00444             debug(LOG_ERR, "FATAL: Failed to create a new thread (ping) - exiting");
00445                 termination_handler(0);
00446         }
00447         pthread_detach(tid_ping);
00448         
00449         debug(LOG_NOTICE, "Waiting for connections");
00450         while(1) {
00451                 r = httpdGetConnection(webserver, NULL);
00452 
00453                 /* We can't convert this to a switch because there might be
00454                  * values that are not -1, 0 or 1. */
00455                 if (webserver->lastError == -1) {
00456                         /* Interrupted system call */
00457                         continue; /* restart loop */
00458                 }
00459                 else if (webserver->lastError < -1) {
00460                         /*
00461                          * FIXME
00462                          * An error occurred - should we abort?
00463                          * reboot the device ?
00464                          */
00465                         debug(LOG_ERR, "FATAL: httpdGetConnection returned unexpected value %d, exiting.", webserver->lastError);
00466                         termination_handler(0);
00467                 }
00468                 else if (r != NULL) {
00469                         /*
00470                          * We got a connection
00471                          *
00472                          * We should create another thread
00473                          */
00474                         debug(LOG_INFO, "Received connection from %s, spawning worker thread", r->clientAddr);
00475                         /* The void**'s are a simulation of the normal C
00476                          * function calling sequence. */
00477                         params = safe_malloc(2 * sizeof(void *));
00478                         *params = webserver;
00479                         *(params + 1) = r;
00480 
00481                         result = pthread_create(&tid, NULL, (void *)thread_httpd, (void *)params);
00482                         if (result != 0) {
00483                                 debug(LOG_ERR, "FATAL: Failed to create a new thread (httpd) - exiting");
00484                                 termination_handler(0);
00485                         }
00486                         pthread_detach(tid);
00487                 }
00488                 else {
00489                         /* webserver->lastError should be 2 */
00490                         /* XXX We failed an ACL.... No handling because
00491                          * we don't set any... */
00492                 }
00493         }
00494 
00495         /* never reached */
00496 }
00497 
00499 int main(int argc, char **argv) {
00500 
00501         s_config *config = config_get_config();
00502         config_init();
00503 
00504         parse_commandline(argc, argv);
00505 
00506         /* Initialize the config */
00507         config_read(config->configfile);
00508         config_validate();
00509 
00510         /* Initializes the linked list of connected clients */
00511         client_list_init();
00512 
00513         /* Init the signals to catch chld/quit/etc */
00514         init_signals();
00515 
00516         if (restart_orig_pid) {
00517                 /*
00518                  * We were restarted and our parent is waiting for us to talk to it over the socket
00519                  */
00520                 get_clients_from_parent();
00521 
00522                 /*
00523                  * At this point the parent will start destroying itself and the firewall. Let it finish it's job before we continue
00524                  */
00525                 while (kill(restart_orig_pid, 0) != -1) {
00526                         debug(LOG_INFO, "Waiting for parent PID %d to die before continuing loading", restart_orig_pid);
00527                         sleep(1);
00528                 }
00529 
00530                 debug(LOG_INFO, "Parent PID %d seems to be dead. Continuing loading.");
00531         }
00532 
00533         if (config->daemon) {
00534 
00535                 debug(LOG_INFO, "Forking into background");
00536 
00537                 switch(safe_fork()) {
00538                         case 0: /* child */
00539                                 setsid();
00540                                 append_x_restartargv();
00541                                 main_loop();
00542                                 break;
00543 
00544                         default: /* parent */
00545                                 exit(0);
00546                                 break;
00547                 }
00548         }
00549         else {
00550                 append_x_restartargv();
00551                 main_loop();
00552         }
00553 
00554         return(0); /* never reached */
00555 }

Generated on Sun Apr 13 21:55:00 2008 for WifiDog by  doxygen 1.5.3