blob: 5c41ddebc08f1eb673fb37e8e71d8d878fcf9e45 [file] [log] [blame]
/*
* 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);
}