/*
 * The contents of this file are subject to the Mozilla 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://mozilla.org/MPL/MPL-1.1.html>.
 *
 * 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 dqd_log.c and related documentation
 * distributed by Rob Mayoff.
 * 
 * The Initial Developer of the Original Code is Rob Mayoff.
 * Copyright (C) 2000 Rob Mayoff.
 *
 * 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.
 */

static const char *RCSID = "@(#) $Header: /u/cvsroot/nsd-modules/dqd_log/dqd_log.c,v 1.8 2004/10/16 14:38:21 mayoff Exp $, compiled: " __DATE__ " " __TIME__;

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ns.h>

extern char **environ;

NS_EXPORT int Ns_ModuleVersion = 1; 		/* Needed for AOLserver */

typedef struct {
    char	   *module;
    Ns_Mutex	    lock;
    int             fd;
    char          **accelAddresses;
    char          **headers;
    char          **outputHeaders;
    int             errorPauseSeconds;
} Log;

static Ns_TraceProc LogTrace;
static void AppendPeerAddress(Log *logPtr, Tcl_DString *ds, Ns_Conn *conn);


/*
 *----------------------------------------------------------------------
 *
 * Ns_ModuleInit --
 *
 *	Module initialization routine.
 *
 * Results:
 *	NS_OK.
 *
 * Side effects:
 *	Log process is spawned and trace routine is registered.
 *
 *----------------------------------------------------------------------
 */

NS_EXPORT int
Ns_ModuleInit(char *server, char *module)
{
    char 	*path;
    Log		*logPtr;
    char        *config;
    char        *p;
    int          argc;

    Ns_Log(Notice, "%s: dqd_log version 2.3", module);
    logPtr = Ns_Calloc(1, sizeof(Log));

    logPtr->module = module;
    Ns_MutexInit(&logPtr->lock);

    path = Ns_ConfigGetPath(server, module, NULL);

    config = Ns_ConfigGetValue(path, "fd");
    if (config == NULL) {
	Ns_Log(Error, "%s: missing config parameter 'fd'",
	    module, path);
	return NS_ERROR;
    }

    logPtr->fd = strtoul(config, &p, 10);
    if (p == config || *p != '\000') {
	Ns_Log(Error, "%s: illegal value '%s' for fd parameter",
	    module, config, path);
	return NS_ERROR;
    }

    if (fcntl(logPtr->fd, F_SETFD, 1) < 0) {
	Ns_Log(Error, "%s: error setting FD_CLOEXEC on %d: %s",
	    module, logPtr->fd, strerror(errno));
	return NS_ERROR;
    }

    config = Ns_ConfigGetValue(path, "headers");
    if (config == NULL)
	config = "referer user-agent";

    if (Tcl_SplitList(NULL, config, &argc, &logPtr->headers) != TCL_OK) {
	Ns_Log(Error, "Could not parse %s/Headers parameter", path);
	return NS_ERROR;
    }

    config = Ns_ConfigGetValue(path, "outputHeaders");
    if (config == NULL)
	config = "";

    if (Tcl_SplitList(NULL, config, &argc, &logPtr->outputHeaders) != TCL_OK) {
	Ns_Log(Error, "Could not parse %s/OutputHeaders parameter", path);
	return NS_ERROR;
    }

    config = Ns_ConfigGetValue(path, "AccelAddresses");
    if (config == NULL)
	config = "";

    if (Tcl_SplitList(NULL, config, &argc, &logPtr->accelAddresses) != TCL_OK) {
	Ns_Log(Error, "Could not parse %s/AccelAddresses parameter", path);
	return NS_ERROR;
    }

    if (Ns_ConfigGetInt(path, "errorpauseseconds",
	&logPtr->errorPauseSeconds) == NS_FALSE) {

	logPtr->errorPauseSeconds = 5;
    }

    Ns_RegisterServerTrace(server, LogTrace, logPtr);
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * LogTrace --
 *
 *	Trace routine for writing a log entry to the log pipe.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Entry is written to the log pipe.
 *
 *----------------------------------------------------------------------
 */

static void
LogTrace(void *arg, Ns_Conn *conn)
{
    Log		  *logPtr = arg;
    Tcl_DString    ds;
    char           buf[100];
    char         **hp;
    char          *bytes;
    int            bytesToWrite, bytesWritten;
    
    Tcl_DStringInit(&ds);

    /* Peer address. */
    AppendPeerAddress(logPtr, &ds, conn);

    /* Auth user. */
    Tcl_DStringAppendElement(&ds,
	(conn->authUser == NULL) ? "" : conn->authUser);

    /* Request line. */
    Tcl_DStringAppendElement(&ds, 
	(conn->request != NULL && conn->request->line != NULL)
	    ? conn->request->line : "");

    /* HTTP status. */
    sprintf(buf, "%d", Ns_ConnResponseStatus(conn));
    Tcl_DStringAppendElement(&ds, buf);

    /* Content bytes transmitted. */
    sprintf(buf, "%d", Ns_ConnContentSent(conn));
    Tcl_DStringAppendElement(&ds, buf);

    for (hp = logPtr->headers; *hp != NULL; hp++) {
	char *headerValue = Ns_SetIGet(conn->headers, *hp);
	Tcl_DStringAppendElement(&ds,
	    (headerValue == NULL) ? "" : headerValue);
    }

    for (hp = logPtr->outputHeaders; *hp != NULL; hp++) {
	char *headerValue = Ns_SetIGet(conn->outputheaders, *hp);
	Tcl_DStringAppendElement(&ds,
	    (headerValue == NULL) ? "" : headerValue);
    }

    Tcl_DStringAppend(&ds, "\n", 1);

    bytes = Tcl_DStringValue(&ds);
    bytesToWrite = Tcl_DStringLength(&ds);

    Ns_MutexLock(&logPtr->lock);

    while (bytesToWrite > 0) {
	bytesWritten = write(logPtr->fd, bytes, bytesToWrite);
	if (bytesWritten < 0) {
	    if (errno == EPIPE) {
		Ns_MutexUnlock(&logPtr->lock);
		Ns_Fatal("%s: log program closed the pipe",
		    logPtr->module);
	    }

	    Ns_Log(Error, "%s: error '%s' writing to log pipe",
		logPtr->module, strerror(errno));
	    sleep(logPtr->errorPauseSeconds);
	}

	else {
	    bytesToWrite -= bytesWritten;
	    bytes += bytesWritten;
	}
    }

    Ns_MutexUnlock(&logPtr->lock);
}


/*
 *----------------------------------------------------------------------
 *
 * AppendPeerAddress --
 *
 *	Append the peer address to the Tcl_DString.  If the peer
 *      address is an accelerator address, then use the last word
 *      of the X-Forwarded-For request header, if available.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
AppendPeerAddress(Log *logPtr, Tcl_DString *ds, Ns_Conn *conn)
{
    int i;
    char *peerAddress;
    char *xff;
    char *p;

    peerAddress = Ns_ConnPeer(conn);
    for (i = 0; logPtr->accelAddresses[i] != NULL; i++) {
	if (STREQ(peerAddress, logPtr->accelAddresses[i])) {

	    xff = Ns_SetIGet(conn->headers, "x-forwarded-for");
	    if (xff != NULL) {
		while (*xff == ' ')
		    xff++;
		if (*xff != '\000') {
		    p = xff + strlen(xff) - 1;
		    while (p > xff && *p == ' ')
			p--;
		    /*
		     * !!!: Yeah, we're not supposed to modify the
		     * header.  Oh well, we're just trimming blanks.
		     */
		    p[1] = '\000';
		    while (p > xff && p[-1] != ' ' && p[-1] != ',')
			p--;

		    Tcl_DStringAppendElement(ds, p);

		    return;
		}
	    }

	    /*
	     * It's an accelerator address, but something was wrong
	     * with the X-Forwarded-For header.  Just log the
	     * perceived peer address.
	     */
	    break;
	}
    }

    Tcl_DStringAppendElement(ds, peerAddress);
}

