/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


/* 
 * binder.c --
 *
 *Support for pre-bound privileged ports.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/binder.c,v 1.18 2005/08/17 21:18:21 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

/*
 * Locals defined in this file
 */

static void PreBind(char *line);
static Tcl_HashTable prebound;
static Ns_Mutex lock;


/*
 *----------------------------------------------------------------------
 *
 * NsSockGetBound --
 *
 *	Get a pre-bound socket if available.
 *
 * Results:
 *	Socket descriptor or INVALID_SOCKET on error.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

SOCKET
NsSockGetBound(struct sockaddr_in *saPtr)
{
    SOCKET sock = INVALID_SOCKET;
    Tcl_HashEntry *hPtr;

    Ns_MutexLock(&lock);
    hPtr = Tcl_FindHashEntry(&prebound, (char *) saPtr);
    if (hPtr != NULL) {
	sock = (int) Tcl_GetHashValue(hPtr);
	Tcl_DeleteHashEntry(hPtr);
    }
    Ns_MutexUnlock(&lock);
    return sock;
}


/*
 *----------------------------------------------------------------------
 *
 * NsInitBinder --
 *
 *	Initialize the pre-bind table.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
NsInitBinder(void)
{
    Ns_MutexSetName(&lock, "ns:binder");
    Tcl_InitHashTable(&prebound, sizeof(struct sockaddr_in)/sizeof(int));
}


/*
 *----------------------------------------------------------------------
 *
 * NsPreBind --
 *
 *	Pre-bind any requested ports, called from Ns_Main at startup.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May pre-bind to one or more ports.
 *
 *----------------------------------------------------------------------
 */

void
NsPreBind(char *args, char *file)
{
    char line[1024];
    FILE *fp;

    if (args != NULL) {
	PreBind(args);
    }
    if (file != NULL && (fp = fopen(file, "r")) != NULL) {
	while (fgets(line, sizeof(line), fp) != NULL) {
	    PreBind(line);
	}
	fclose(fp);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * NsClosePreBound --
 *
 *	Close any remaining pre-bound sockets.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Pre-bound sockets closed.
 *
 *----------------------------------------------------------------------
 */

void
NsClosePreBound(void)
{
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;
    char *addr;
    int port, sock;
    struct sockaddr_in *saPtr;

    Ns_MutexLock(&lock);
    hPtr = Tcl_FirstHashEntry(&prebound, &search);
    while (hPtr != NULL) {
	saPtr = (struct sockaddr_in *) Tcl_GetHashKey(&prebound, hPtr);
	addr = ns_inet_ntoa(saPtr->sin_addr);
	port = htons(saPtr->sin_port);
	sock = (int) Tcl_GetHashValue(hPtr);
	Ns_Log(Warning, "prebind: closed unused: %s:%d = %d", addr, port, sock);
	close(sock);
	hPtr = Tcl_NextHashEntry(&search);
    }
    Tcl_DeleteHashTable(&prebound);
    Tcl_InitHashTable(&prebound, sizeof(struct sockaddr_in)/sizeof(int));
    Ns_MutexUnlock(&lock);
}


/*
 *----------------------------------------------------------------------
 *
 * PreBind --
 *
 *	Pre-bind to one or more ports in a comma-separated list.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Sockets are left in bound state for later listen 
 *	in Ns_SockListen.  
 *
 *----------------------------------------------------------------------
 */

static void
PreBind(char *line)
{
    Tcl_HashEntry *hPtr;
    int new, sock, port;
    struct sockaddr_in sa;
    char *err, *ent, *p, *q, *addr, *baddr;

    ent = line;
    do {
	p = strchr(ent, ',');
	if (p != NULL) {
	    *p = '\0';
	}
	baddr = NULL;
    	addr = "0.0.0.0";
    	q = strchr(ent, ':');
	if (q == NULL) {
	    port = atoi(ent);
	} else {
	    *q = '\0';
	    port = atoi(q+1);
	    baddr = addr = ent;
	}
	if (port == 0) {
	    err = "invalid port";
	} else if (Ns_GetSockAddr(&sa, baddr, port) != NS_OK) {
	    err = "invalid address";
	} else {
	    hPtr = Tcl_CreateHashEntry(&prebound, (char *) &sa, &new);
	    if (!new) {
		err = "duplicate entry";
	    } else if ((sock = Ns_SockBind(&sa)) == -1) {
		Tcl_DeleteHashEntry(hPtr);
	    	err = strerror(errno);
	    } else {
	    	Tcl_SetHashValue(hPtr, sock);
		err = NULL;
	    }
	}
	if (q != NULL) {
	    *q = ':';
	}
	if (p != NULL) {
	    *p++ = ',';
	}
	if (err != NULL) {
	    Ns_Log(Error, "prebind: invalid entry: %s: %s", ent, err);
	} else {
	    Ns_Log(Notice, "prebind: bound: %s", ent);
	}
	ent = p;
    } while (ent != NULL);
}
