/*
 * tnmUnixSnmp.c --
 *
 *	This file contains all functions that handle UNIX specific
 *	functions for the SNMP engine. This is basically the code
 *	required to receive SNMP traps via the nmtrapd(8) daemon.
 *
 * Copyright (c) 1994-1996 Technical University of Braunschweig.
 * Copyright (c) 1996-1997 University of Twente.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * @(#) $Id: tnmUnixSnmp.c 895 1997-12-08 16:02:43Z schoenw $
 */

#include "tnmSnmp.h"

#include <sys/un.h>

/*
 * Local variables:
 */

extern int hexdump;		/* flag that controls hexdump */
static int trap_sock = -1;	/* socket to receive traps */
static int trap_count = 0;	/* reference counter for trap socket */
static int trapSocket;

static char *serv_path = "/tmp/.nmtrapd-162";

/*
 * The default filename where we will find the nmtrapd binary. This
 * is normally overwritten in the Makefile.
 */

#ifndef NMTRAPD
#define NMTRAPD "/usr/local/bin/nmtrapd"
#endif

/*
 * The following variable holds the channel used to access
 * the pipe to the nmtrapd process.
 */

static Tcl_Channel channel = NULL;

/*
 * Forward declarations for procedures defined later in this file:
 */

static int
xread			_ANSI_ARGS_((int fd, char *buf, int len));

static int
ForkDaemon		_ANSI_ARGS_((Tcl_Interp *interp));

static void
TrapProc		_ANSI_ARGS_((ClientData clientData, int mask));

static int
TrapRecv		_ANSI_ARGS_((Tcl_Interp *interp, 
				     u_char *packet, int *packetlen, 
				     struct sockaddr_in *from));
#ifdef SIGPIPE
static void
IgnorePipe		_ANSI_ARGS_((int dummy));
#endif


/*
 *----------------------------------------------------------------------
 *
 * IgnorePipe --
 *
 *	This procedure is a dummy signal handler to catch SIGPIPE
 *	signals. Restart signalhandler for all these bozo's outside.
 *
 * Results:
 *	None.
 * 
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

#ifdef SIGPIPE
static void
IgnorePipe(dummy)
    int dummy;
{
    signal(SIGPIPE, IgnorePipe);
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * xread --
 *
 *	This procedure reads a buffer from a file descriptor. This 
 *	wrapper is needed on broken SYS V machines to handle 
 *	interrupted system calls.
 *
 * Results:
 *	The number of bytes read.
 * 
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
xread(fd, buf, len)
    int fd;
    char *buf;
    int len;
{
    int rc;
    
    while ((rc = read(fd, buf, len)) < 0 
	   && (errno == EINTR || errno == EAGAIN)) {
	continue;
    }
    
    return rc;
}

/*
 *----------------------------------------------------------------------
 *
 * ForkDaemon --
 *
 *	This procedure starts the trap forwarder daemon named
 *	nmtrapd.
 *
 * Results:
 *	A standard Tcl result.
 * 
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
ForkDaemon(interp)
    Tcl_Interp *interp;
{
    int argc = 1;
    char *argv[2];

    argv[0] = getenv("TNM_NMTRAPD");
    if (! argv[0]) {
	argv[0] = NMTRAPD;
    }
    argv[1] = NULL;

    channel= Tcl_OpenCommandChannel(interp, argc, argv, 0);
    return channel ? TCL_OK : TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpTrapOpen --
 *
 *	This procedure creates a socket used to receive trap messages.
 *	Since traps are send to a privileged port, we start the nmtrapd
 *	trap multiplexer and connect to it via a UNIX domain socket.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TnmSnmpTrapOpen(interp)
    Tcl_Interp *interp;
{
    int i, rc;
    struct sockaddr_un saddr;
    int slen;

    trap_count++;

    if (trap_sock >= 0) {
	return TCL_OK;
    }

    trap_sock = TnmSocket(AF_UNIX, SOCK_STREAM, 0);
    if (trap_sock == TNM_SOCKET_ERROR) {
	Tcl_AppendResult(interp, "can not create nmtrapd socket: ",
			 Tcl_PosixError(interp), (char *) NULL);
	return TCL_ERROR;
    }
    
    memset((char *) &saddr, 0, sizeof(saddr));
    
    saddr.sun_family = AF_UNIX;
    strcpy(saddr.sun_path, serv_path);
    slen = sizeof(saddr) - sizeof(saddr.sun_path) + strlen(saddr.sun_path);
    
    if (connect(trap_sock, (struct sockaddr *) &saddr, slen) < 0) {
	
	/*
	 * Start nmtrapd if not done yet.
	 */

	if (channel == NULL) {
	    if (ForkDaemon(interp) != TCL_OK) {
		return TCL_ERROR;
	    }
	}
	
	for (i = 0; i < 5; i++) {
	    sleep(1);
	    rc = connect(trap_sock, (struct sockaddr *) &saddr, slen);
	    if (rc >= 0) break;
	}
	
	if (rc < 0) {
	    Tcl_AppendResult(interp, "can not connect to nmtrapd: ",
			     Tcl_PosixError(interp), (char *) NULL);
	    TnmSocketClose(trap_sock);
	    trap_sock = -1;    
	    return TCL_ERROR;
	}
    }

#ifdef SIGPIPE
    signal(SIGPIPE, IgnorePipe);
#endif

    trapSocket = trap_sock;
    Tcl_CreateFileHandler(trapSocket, TCL_READABLE, 
			  TrapProc, (ClientData) interp);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpTrapClose --
 *
 *	This procedure closes the socket for incoming traps.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TnmSnmpTrapClose()
{
    if (--trap_count == 0) {
	Tcl_DeleteFileHandler(trapSocket);
	TnmSocketClose(trap_sock);
	trap_sock = -1;
	Tcl_ReapDetachedProcs();
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TrapRecv --
 *
 *	This procedure reads from the trap socket to process incoming
 *	trap messages.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
TrapRecv(interp, packet, packetlen, from)
    Tcl_Interp *interp;
    u_char *packet;
    int *packetlen;
    struct sockaddr_in *from;
{
    int len, rlen;
    char c, version, unused;

    /*
     * Read the message from the nmtrapd daemon. See the message
     * format description in the nmtrapd(8) man page.
     */

    if (xread(trap_sock, (char *) &version, 1) != 1) {
        goto errorExit;
    }
    if (xread(trap_sock, (char *) &unused, 1) != 1) {
        goto errorExit;
    }
    if (xread(trap_sock, (char *) &from->sin_port, 2) != 2) {
	goto errorExit;
    }
    if (xread(trap_sock, (char *) &from->sin_addr.s_addr, 4) != 4) {
	goto errorExit;
    }
    if (xread(trap_sock, (char *) &len, 4) != 4) {
	goto errorExit;
    }
    len = ntohl(len);
    rlen = len < *packetlen ? len : *packetlen;
    if (xread(trap_sock, (char *) packet, rlen) <= 0) {
	goto errorExit;
    }

    /*
     * Eat up any remaining data-bytes.
     */

    while (len > *packetlen) {
        if (xread(trap_sock, &c, 1) != 1) {
	    goto errorExit;
	}
        len--;
    }

    *packetlen = rlen;

    if (hexdump) {
	TnmSnmpDumpPacket(packet, *packetlen, from, NULL);
    }

    /* 
     * Finally, make sure that the socket address belongs to the 
     * INET address family.
     */

    from->sin_family = AF_INET;
    return TCL_OK;

 errorExit:
    TnmSnmpTrapClose();
    Tcl_SetResult(interp, "lost connection to nmtrapd daemon", TCL_STATIC);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * TrapProc --
 *
 *	This procedure is called from the event dispatcher whenever
 *	a trap message is received.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
TrapProc(clientData, mask)
    ClientData clientData;
    int mask;
{
    Tcl_Interp *interp = (Tcl_Interp *) clientData;
    u_char packet[TNM_SNMP_MAXSIZE];
    int code, packetlen = TNM_SNMP_MAXSIZE;
    struct sockaddr_in from;

    Tcl_ResetResult(interp);
    code = TrapRecv(interp, packet, &packetlen, &from);
    if (code != TCL_OK) return;

    code = TnmSnmpDecode(interp, packet, packetlen, &from, NULL, NULL,
			 NULL, NULL);
    if (code == TCL_ERROR) {
	Tcl_AddErrorInfo(interp, "\n    (snmp trap event)");
	Tcl_BackgroundError(interp);
    }
    if (code == TCL_CONTINUE && hexdump) {
	TnmWriteMessage(interp->result);
	TnmWriteMessage("\n");
    }
}
