/* * Copyright (C), 2000-2007 by the monit project group. * All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #ifdef HAVE_STDIO_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_SOCKET_H #include #endif #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #ifdef HAVE_NETDB_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_CTYPE_H #include #endif #ifdef HAVE_STRINGS_H #include #endif #ifdef HAVE_NETINET_IN_H #include #endif #ifdef HAVE_ARPA_INET_H #include #endif #include "engine.h" #include "socket.h" /** * A naive http 1.0 server. The server delegates handling of a HTTP * request and response to the processor module. * * NOTE * This server does not use threads or forks; Requests are * serialized and pending requests will be popped from the * connection queue when the current request finish. * * Since this server is written for monit, low traffic is expected. * Connect from not-authenicated clients will be closed down * promptly. The authentication schema or access control is based * on client name/address and only requests from known clients are * accepted. Hosts allowed to connect to this server should be * added to the access control list by calling add_host_allow(). * * * @author Jan-Henrik Haukeland, * @author Christian Hopp, * @author Martin Pala * * @version \$Id: engine.c,v 1.78 2007/07/25 12:54:31 hauk Exp $ * * @file */ /* ------------------------------------------------------------- Definitions */ static int myServerSocket= 0; static HostsAllow hostlist= NULL; static volatile int stopped= FALSE; ssl_server_connection *mySSLServerConnection= NULL; static pthread_mutex_t hostlist_mutex= PTHREAD_MUTEX_INITIALIZER; struct ulong_net { unsigned long network; unsigned long mask; }; /* -------------------------------------------------------------- Prototypes */ static void check_Impl(); static void initialize_service(); static int authenticate(const struct in_addr); static int is_host_allow(const struct in_addr); static void destroy_host_allow(HostsAllow); static Socket_T socket_producer(int, int, void*); static int parse_network(char *, struct ulong_net *); /* ------------------------------------------------------------------ Public */ /** * Start the HTTPD server * @param port The Port number to start the server at * @param backlog The maximum length of the incomming connection queue * @param bindAddr the local address the server will bind to */ void start_httpd(int port, int backlog, char *bindAddr) { Socket_T S= NULL; stopped= Run.stopped; if((myServerSocket= create_server_socket(port, backlog, bindAddr)) < 0) { LogError("http server: Could not create a server socket at port %d -- %s\n", port, STRERROR); LogError("monit HTTP server not available\n"); if(Run.init) { sleep(1); kill_daemon(SIGTERM); } } else { initialize_service(); if(Run.httpdssl) { mySSLServerConnection= init_ssl_server( Run.httpsslpem, Run.httpsslclientpem); if(mySSLServerConnection == NULL) { LogError("http server: Could not initialize SSL engine\n"); LogError("monit HTTP server not available\n"); return; } #ifdef HAVE_OPENSSL mySSLServerConnection->server_socket= myServerSocket; #endif } while(! stopped) { if(!(S= socket_producer(myServerSocket, port, mySSLServerConnection))) { continue; } http_processor(S); } delete_ssl_server_socket(mySSLServerConnection); close_socket(myServerSocket); } } /** * Stop the HTTPD server. */ void stop_httpd() { stopped= TRUE; } /* -------------------------------------------------------------- Properties */ /** * Add hosts allowed to connect to this server. * @param name A hostname (A-Record) or IP address to be added to the * hosts allow list * @return FALSE if the given host does not resolve, otherwise TRUE */ int add_host_allow(char *name) { struct addrinfo hints; struct addrinfo *res; struct addrinfo *_res; ASSERT(name); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = PF_INET; /* we support just IPv4 currently */ if(getaddrinfo(name, NULL, &hints, &res) != 0) return FALSE; for(_res = res; _res; _res = _res->ai_next) { if(_res->ai_family == AF_INET) { HostsAllow h; struct sockaddr_in *sin = (struct sockaddr_in *)_res->ai_addr; NEW(h); memcpy(&h->network, &sin->sin_addr, 4); h->mask= 0xffffffff; LOCK(hostlist_mutex) if(hostlist) { HostsAllow p, n; for(n= p= hostlist; p; n= p, p= p->next) { if((p->network == h->network) && ((p->mask == h->mask))) { DEBUG("%s: Debug: Skipping redundant host '%s'\n", prog, name); destroy_host_allow(h); goto done; } } DEBUG("%s: Debug: Adding host allow '%s'\n", prog, name); n->next= h; } else { DEBUG("%s: Debug: Adding host allow '%s'\n", prog, name); hostlist= h; } done: END_LOCK; } } freeaddrinfo(res); return TRUE; } /** * Add network allowed to connect to this server. * @param s_network A network identifier in IP/mask format to be added * to the hosts allow list * @return FALSE if no correct network identifier is provided, * otherwise TRUE */ int add_net_allow(char *s_network) { struct ulong_net net={0, 0}; HostsAllow h; ASSERT(s_network); /* Add the network */ if (!parse_network(s_network, &net)) { return FALSE; } NEW(h); h->network=net.network; h->mask=net.mask; LOCK(hostlist_mutex) if(hostlist) { HostsAllow p, n; for(n= p= hostlist; p; n= p, p= p->next) { if((p->network == net.network) && ((p->mask == net.mask))) { DEBUG("%s: Debug: Skipping redundant net '%s'.\n", prog, s_network); destroy_host_allow(h); goto done; } } DEBUG("%s: Debug: Adding net allow '%s'.\n", prog, s_network); n->next= h; } else { DEBUG("%s: Debug: Adding net allow '%s'.\n", prog, s_network); hostlist= h; } done: END_LOCK; return TRUE; } /** * Are any hosts present in the host allow list? * @return TRUE if the host allow list is non-empty, otherwise FALSE */ int has_hosts_allow() { int rv; LOCK(hostlist_mutex) rv= (hostlist != NULL); END_LOCK; return rv; } /** * Free the host allow list */ void destroy_hosts_allow() { if(has_hosts_allow()) { LOCK(hostlist_mutex) destroy_host_allow(hostlist); hostlist= NULL; END_LOCK; } } /* ----------------------------------------------------------------- Private */ /** * Setup the cervlet service and verify that a cervlet implementation * exist. Only one cervlet is supported in this version. In a standalone * versions this function will load cervlets from a repository and * initialize each cervlet. */ static void initialize_service() { init_service(); check_Impl(); } /** * Abort if no Service implementors are found */ static void check_Impl() { if((Impl.doGet == 0) || (Impl.doPost == 0)) { LogError("http server: Service Methods not implemented\n"); _exit(1); } } /** * Returns TRUE if remote host is allowed to connect, otherwise return * FALSE. If allow Basic Authentication is defined in the Run.Auth * object, authentication is delegated to the processor module. */ static int authenticate(const struct in_addr addr) { if(is_host_allow(addr)) { return TRUE; } if(! has_hosts_allow() && (Run.credentials!=NULL)) { return TRUE; } LogError("%s: Denied connection from non-authorized client [%s]\n", prog, inet_ntoa(addr)); return FALSE; } /** * Returns TRUE if host is allowed to connect to * this server */ static int is_host_allow(const struct in_addr addr) { HostsAllow p; int rv= FALSE; LOCK(hostlist_mutex) for(p= hostlist; p; p= p->next) { if((p->network & p->mask) == (addr.s_addr & p->mask)) { rv= TRUE; break; } } END_LOCK; if (rv) return rv; return rv; } /** * Parse network string and return numeric IP and netmask * @param s_network A network identifier in IP/mask format to be parsed * @param net A structure holding IP and mask of the network * @return FALSE if parsing fails otherwise TRUE */ static int parse_network(char *s_network, struct ulong_net *net) { char *temp=NULL; char *copy=NULL; char *longmask=NULL; int shortmask=0; int slashcount=0; int dotcount=0; int count=0; int rv=FALSE; struct in_addr inp; ASSERT(s_network); ASSERT(net); temp= copy= xstrdup(s_network); /* decide if we have xxx.xxx.xxx.xxx/yyy or xxx.xxx.xxx.xxx/yyy.yyy.yyy.yyy */ while (*temp!=0) { if (*temp=='/') { /* We have found a "/" -> we are preceeding to the netmask */ if ((slashcount==1) || (dotcount !=3)) { /* We have already found a "/" or we haven't had enough dots before finding the slash -> Error! */ goto done; } *temp=0; longmask= *(temp+1)?temp+1:NULL; count=0; slashcount=1; dotcount=0; } else if (*temp=='.') { /* We have found the next dot! */ dotcount++; } else if (!isdigit((int)*temp)) { /* No number, "." or "/" -> Error! */ goto done; } count++; temp++; } if (slashcount == 0) { /* We have just host portion */ shortmask= 32; } else if ((dotcount==0) && (count > 1) && (count < 4)) { /* We have no dots but 1 or 2 numbers after the slash -> short netmask */ if (longmask!=NULL) { shortmask=atoi(longmask); longmask=NULL; } } else if (dotcount != 3) { /* A long netmask requires three dots */ goto done; } /* Parse the network */ if (inet_aton(copy, &inp) == 0) { /* Failed! */ goto done; } net->network=inp.s_addr; /* Convert short netmasks to integer */ if (longmask==NULL) { if ((shortmask > 32) || (shortmask < 0)) { goto done; } else if ( shortmask == 32 ) { net->mask=-1; } else { net->mask= (1<mask= htonl(net->mask<<(32-shortmask)); } } else { /* Parse long netmasks */ if (inet_aton(longmask, &inp) == 0) { goto done; } net->mask=inp.s_addr; } /* Remove bogus network components */ net->network&=net->mask; /* Everything went fine, so we return TRUE! */ rv=TRUE; done: FREE(copy); return rv; } /* --------------------------------------------------------------- Factories */ /** * Accept connections from Clients and create a Socket_T object for * each successful accept. If accept fails, return a NULL object */ static Socket_T socket_producer(int server, int port, void *sslserver) { int client; struct sockaddr_in in; unsigned int len= sizeof(struct sockaddr_in); if(can_read(server, 1)) { if( (client= accept(server, (struct sockaddr*)&in, &len)) < 0) { if(stopped) { LogError("http server: service stopped\n"); } else { LogError("http server: cannot accept connection -- %s\n", STRERROR); } return NULL; } } else { /* If timeout or error occured, return NULL to allow the caller to * handle various states (such as stopped) which can occure in the * meantime */ return NULL; } if(set_noblock(client) < 0) { goto error; } if(!check_socket(client)) { goto error; } if(! authenticate(in.sin_addr)) { goto error; } return socket_create_a(client, inet_ntoa(in.sin_addr), port, sslserver); error: close_socket(client); return NULL; } /* ----------------------------------------------------------------- Cleanup */ /** * Free a (linked list of) host_allow ojbect(s). */ static void destroy_host_allow(HostsAllow p) { HostsAllow a= p; if(a->next) { destroy_host_allow(a->next); } FREE(a); }