| /* |
| * Copyright (C) Tildeslash Ltd. All rights reserved. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Affero General Public License version 3. |
| * |
| * 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 Affero General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * In addition, as a special exception, the copyright holders give |
| * permission to link the code of portions of this program with the |
| * OpenSSL library under certain conditions as described in each |
| * individual source file, and distribute linked combinations |
| * including the two. |
| * |
| * You must obey the GNU Affero General Public License in all respects |
| * for all of the code used other than OpenSSL. |
| */ |
| |
| #include "config.h" |
| |
| #ifdef HAVE_STDIO_H |
| #include <stdio.h> |
| #endif |
| |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| |
| #ifdef HAVE_ERRNO_H |
| #include <errno.h> |
| #endif |
| |
| #ifdef HAVE_SYS_TYPES_H |
| #include <sys/types.h> |
| #endif |
| |
| #ifdef HAVE_SYS_SOCKET_H |
| #include <sys/socket.h> |
| #endif |
| |
| #if TIME_WITH_SYS_TIME |
| # include <sys/time.h> |
| # include <time.h> |
| #else |
| # if HAVE_SYS_TIME_H |
| # include <sys/time.h> |
| # else |
| # include <time.h> |
| # endif |
| #endif |
| |
| #ifdef HAVE_NETDB_H |
| #include <netdb.h> |
| #endif |
| |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| |
| #ifdef HAVE_FCNTL_H |
| #include <fcntl.h> |
| #endif |
| |
| #ifdef HAVE_STRING_H |
| #include <string.h> |
| #endif |
| |
| #ifdef HAVE_CTYPE_H |
| #include <ctype.h> |
| #endif |
| |
| #ifdef HAVE_STRINGS_H |
| #include <strings.h> |
| #endif |
| |
| #ifdef HAVE_NETINET_IN_H |
| #include <netinet/in.h> |
| #endif |
| |
| #ifdef HAVE_ARPA_INET_H |
| #include <arpa/inet.h> |
| #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/pam 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(). |
| * |
| * @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= Str_dup(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<<shortmask)-1; |
| net->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; |
| socklen_t 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); |
| |
| } |
| |