/* Copyright 2000-2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * mod_tls.c - Apache SSL/TLS module for NetWare by Mike Gardiner.
 *
 * This module gives Apache the ability to do SSL/TLS with a minimum amount
 * of effort.  All of the SSL/TLS logic is already on NetWare versions 5 and
 * above and is interfaced through WinSock on NetWare.  As you can see in
 * the code below SSL/TLS sockets can be created with three WinSock calls.
 *
 * To load, simply place the module in the modules directory under the main
 * apache tree.  Then add a "SecureListen" with two arguments.  The first
 * argument is an address and/or port.  The second argument is the key pair
 * name as created in ConsoleOne.
 *
 *  Examples:
 *
 *          SecureListen 443 "SSL CertificateIP"  
 *          SecureListen 123.45.67.89:443 mycert
 */

#define CORE_PRIVATE
#define WS_SSL

#define  MAX_ADDRESS  512
#define  MAX_KEY       80

#include "httpd.h"
#include "http_config.h"
#include "http_conf_globals.h"
#include "http_log.h"
#include "http_main.h"

module MODULE_VAR_EXPORT tls_module;

typedef struct TLSSrvConfigRec TLSSrvConfigRec;
typedef struct seclisten_rec seclisten_rec;
static fd_set listenfds;

struct seclisten_rec {
    seclisten_rec *next;
    struct sockaddr_in local_addr;	/* local IP address and port */
    int fd;
    int used;			            /* Only used during restart */
    char key[MAX_KEY];
    int mutual;
};

struct TLSSrvConfigRec {
    table *sltable;
};

static seclisten_rec* ap_seclisteners = NULL;

#define get_tls_cfg(srv) (TLSSrvConfigRec *) ap_get_module_config(srv->module_config, &tls_module)


static int find_secure_listener(seclisten_rec *lr)
{
    seclisten_rec *sl;

    for (sl = ap_seclisteners; sl; sl = sl->next) {
        if (!memcmp(&sl->local_addr, &lr->local_addr, sizeof(sl->local_addr))) {
            sl->used = 1;
            return sl->fd;
        }
    }    
    return -1;
}


static int make_secure_socket(pool *p, const struct sockaddr_in *server,
                              char* key, int mutual, server_rec *server_conf)
{
    int s;
    int one = 1;
    char addr[MAX_ADDRESS];
    struct sslserveropts opts;
    struct linger li;
    unsigned int optParam;
    WSAPROTOCOL_INFO SecureProtoInfo;
    int no = 1;
    
    if (server->sin_addr.s_addr != htonl(INADDR_ANY))
        ap_snprintf(addr, sizeof(addr), "address %s port %d",
            inet_ntoa(server->sin_addr), ntohs(server->sin_port));
    else
        ap_snprintf(addr, sizeof(addr), "port %d", ntohs(server->sin_port));

    /* note that because we're about to slack we don't use psocket */
    ap_block_alarms();
    memset(&SecureProtoInfo, 0, sizeof(WSAPROTOCOL_INFO));

    SecureProtoInfo.iAddressFamily = AF_INET;
    SecureProtoInfo.iSocketType = SOCK_STREAM;
    SecureProtoInfo.iProtocol = IPPROTO_TCP;   
    SecureProtoInfo.iSecurityScheme = SECURITY_PROTOCOL_SSL;

    s = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP,
            (LPWSAPROTOCOL_INFO)&SecureProtoInfo, 0, 0);
            
    if (s == INVALID_SOCKET) {
        errno = WSAGetLastError();
        ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
            "make_secure_socket: failed to get a socket for %s", addr);
        ap_unblock_alarms();
        return -1;
    }
        
    if (!mutual) {
        optParam = SO_SSL_ENABLE | SO_SSL_SERVER;
		    
        if (WSAIoctl(s, SO_SSL_SET_FLAGS, (char *)&optParam,
            sizeof(optParam), NULL, 0, NULL, NULL, NULL)) {
            errno = WSAGetLastError();
            ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
                "make_secure_socket: for %s, WSAIoctl: (SO_SSL_SET_FLAGS)", addr);
            ap_unblock_alarms();
            return -1;
        }
    }

    opts.cert = key;
    opts.certlen = strlen(key);
    opts.sidtimeout = 0;
    opts.sidentries = 0;
    opts.siddir = NULL;

    if (WSAIoctl(s, SO_SSL_SET_SERVER, (char *)&opts, sizeof(opts),
        NULL, 0, NULL, NULL, NULL) != 0) {
        errno = WSAGetLastError();
        ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
            "make_secure_socket: for %s, WSAIoctl: (SO_SSL_SET_SERVER)", addr);
        ap_unblock_alarms();
        return -1;
    }

    if (mutual) {
        optParam = 0x07;               // SO_SSL_AUTH_CLIENT

        if(WSAIoctl(s, SO_SSL_SET_FLAGS, (char*)&optParam,
            sizeof(optParam), NULL, 0, NULL, NULL, NULL)) {
            errno = WSAGetLastError();
            ap_log_error( APLOG_MARK, APLOG_CRIT, server_conf,
                "make_secure_socket: for %s, WSAIoctl: (SO_SSL_SET_FLAGS)", addr );
            ap_unblock_alarms();
            return -1;
        }
    }

    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(int)) < 0) {
        errno = WSAGetLastError();
        ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
            "make_secure_socket: for %s, setsockopt: (SO_REUSEADDR)", addr);
        ap_unblock_alarms();
        return -1;
    }

    one = 1;
#ifdef SO_KEEPALIVE
    if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(int)) < 0) {
        errno = WSAGetLastError();
        ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
            "make_secure_socket: for %s, setsockopt: (SO_KEEPALIVE)", addr);
#endif
        ap_unblock_alarms();
        return -1;
    }

    if (server_conf->send_buffer_size) {
        if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
            (char *) &server_conf->send_buffer_size, sizeof(int)) < 0) {
            errno = WSAGetLastError();
            ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf,
                "make_secure_socket: failed to set SendBufferSize for %s, "
			    "using default", addr);
			ap_unblock_alarms();
	        return -1;
        }
    }

    if (bind(s, (struct sockaddr *) server, sizeof(struct sockaddr_in)) == -1) {
        errno = WSAGetLastError();
        ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
            "make_secure_socket: could not bind to %s", addr);
        ap_unblock_alarms();
        return -1;
    }

    if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *) &no, sizeof(int)) < 0) {
        errno = WSAGetLastError();
        ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf,
            "setsockopt: (TCP_NODELAY)");
    }

    if (listen(s, ap_listenbacklog) == -1) {
        errno = WSAGetLastError();
        ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
            "make_secure_socket: unable to listen for connections on %s", addr);
        ap_unblock_alarms();
        return -1;
    }

    ap_unblock_alarms();
    return s;
}

static const char *set_secure_listener(cmd_parms *cmd, void *dummy, char *ips, char* key, char* mutual)
{
    TLSSrvConfigRec* sc = get_tls_cfg(cmd->server);
    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    char *ports;
    unsigned short port;
    seclisten_rec *new;

    
    if (err != NULL) 
        return err;

    ports = strchr(ips, ':');
    
    if (ports != NULL) {    
	    if (ports == ips)
	        return "Missing IP address";
	    else if (ports[1] == '\0')
	        return "Address must end in :<port-number>";
	        
	    *(ports++) = '\0';
    }
    else {
	    ports = ips;
    }
    
    new = ap_pcalloc(cmd->pool, sizeof(seclisten_rec)); 
    new->local_addr.sin_family = AF_INET;
    
    if (ports == ips)
	    new->local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    else
	    new->local_addr.sin_addr.s_addr = ap_get_virthost_addr(ips, NULL);
    
    port = atoi(ports);
    
    if (!port) 
	    return "Port must be numeric";
	    
    ap_table_set(sc->sltable, ports, "T");
    
    new->local_addr.sin_port = htons(port);
    new->fd = -1;
    new->used = 0;
    new->next = ap_seclisteners;
    strcpy(new->key, key);
    new->mutual = (mutual) ? 1 : 0;
    ap_seclisteners = new;
    return NULL;
}

static void InitTLS(server_rec *s, pool *p)
{
    seclisten_rec* sl;
    listen_rec* lr;
    
    for (sl = ap_seclisteners; sl != NULL; sl = sl->next) {
        sl->fd = find_secure_listener(sl);

        if (sl->fd < 0)
            sl->fd = make_secure_socket(p, &sl->local_addr, sl->key, sl->mutual, s);            
        else
            ap_note_cleanups_for_socket(p, sl->fd);
            
        if (sl->fd >= 0) {
            FD_SET(sl->fd, &listenfds);
            ap_note_cleanups_for_socket(p, sl->fd);
            
            lr = ap_pcalloc(p, sizeof(listen_rec));
        
            if (lr) {
                lr->local_addr = sl->local_addr;
                lr->used = 0;
                lr->fd = sl->fd;
                lr->next = ap_listeners;
                ap_listeners = lr;
            }                        
        } else {
            clean_parent_exit(1);
        }
    } 
}

void *tls_config_server_create(pool *p, server_rec *s)
{
    TLSSrvConfigRec *new = ap_palloc(p, sizeof(TLSSrvConfigRec));
    new->sltable = ap_make_table(p, 5);
    return new;
}

void *tls_config_server_merge(pool *p, void *basev, void *addv)
{
    TLSSrvConfigRec *base = (TLSSrvConfigRec *)basev;
    TLSSrvConfigRec *add  = (TLSSrvConfigRec *)addv;
    TLSSrvConfigRec *merged  = (TLSSrvConfigRec *)ap_palloc(p, sizeof(TLSSrvConfigRec));
    return merged;
}

int tls_hook_Fixup(request_rec *r)
{
    TLSSrvConfigRec *sc = get_tls_cfg(r->server);
    table *e = r->subprocess_env;    
    const char *s_secure;
    char port[8];
    
    
    /* For some reason r->server->port always return 80 rather than
     * the current port.  So for now we will get it straight from
     * the horses mouth.
     */
    /*  itoa(r->server->port, port, 10); */
    itoa(ntohs(((r->connection)->local_addr).sin_port), port, 10);
    s_secure = ap_table_get(sc->sltable, port);    
    
    if (!s_secure)
        return DECLINED;
    
    ap_table_set(e, "HTTPS", "on");
    
    return DECLINED;
}

static const command_rec tls_module_cmds[] = {
    { "SecureListen", set_secure_listener, NULL, RSRC_CONF, TAKE23,
      "specify an address and/or port with a key pair name.\n"
      "Optional third parameter of MUTUAL configures the port for mutual authentication."},
    { NULL }
};

module MODULE_VAR_EXPORT tls_module =
{
    STANDARD_MODULE_STUFF,
    InitTLS,                  /* initializer */
    NULL,                     /* dir config creater */
    NULL,                     /* dir merger --- default is to override */
    tls_config_server_create, /* server config */
    tls_config_server_merge,  /* merge server config */
    tls_module_cmds,          /* command table */
    NULL,                     /* handlers */
    NULL,                     /* filename translation */
    NULL,                     /* check_user_id */
    NULL,                     /* check auth */
    NULL,                     /* check access */
    NULL,                     /* type_checker */    
    NULL,			          /* fixups */
    NULL,                     /* logger */
    NULL,               	  /* header parser */
    NULL,               	  /* child_init */
    NULL,			       	  /* child_exit */
    tls_hook_Fixup         	  /* post read request */
};


