/*
 *      ap-trapd.c from Access Point SNMP Utils for Linux
 *	SNMP traps processing daemon code
 *
 * Copyright (c) 2002 Roman Festchook <roma at polesye dot net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 from
 * June 1991 as published by the Free Software Foundation.
 *
 * 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 General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pwd.h>
#include <syslog.h>
#include <stdio.h>
#include "ap-utils.h"

#define TRAPD_USER "nobody"

int main(int argc, char **argv)
{
    extern char *optarg;
    extern int optind;
    extern int opterr;
    extern int optopt;
    int opt = 0;

    char *user=NULL, *device=NULL;
    

    pid_t pid;
    struct passwd *pwd;
    struct sockaddr_in server;
    struct sockaddr_in client;
    int sockfd, len;
    unsigned int client_len = SIZE, hand, sec, min;
    unsigned char buf[512], *start, snmp_version,
	*community = NULL, generic_trap, specific_trap;
    unsigned int i, atmel_ap_type = 0;
    size_t time_stamp;
    char mac[6], *enterprise_oid = NULL, c, mac_info[31], mac_flag = 0;
    struct in_addr agent_addr;
    char enterprise_410[] = { 0x2b, 0x06, 1, 4, 1, 0x83, 0x1a, 1, 1 };
    char enterprise_12350[] = { 0x2b, 0x06, 1, 4, 1, 0xe0, 0x3e, 1, 1 };
    char *generic_traps[8] = {
	"ColdStart",
	"WarmStart",
	"LinkDown",
	"LinkUp",
	"AuthenticationFailure",
	"EgpNeighborLoss",
	"EnterpriseSpecific",
	"unknown"
    };
    char *specific_traps[22] = {
	"Reassociation",
	"RoamOut",
	"Association",
	"Disassociation",
	"AssociationExpire",
	"Reset",
	"SettingPingIPAddress",
	"StartUp",
	"FailedToEraseFlash",
	"APClientScanning",
	"APClientAuthenticating",
	"APClientAssociating",
	"APClientReAssociating",
	"APClientAuthenticationFailed",
	"APClientAssociationFailed",
	"APClientConnected",
	"APClientDisconnected",
	"APClientScanFailed",
	"APClientJoinFailed",
	"APClientJoining",
	"unknown",
	"APClientScanFinished" /* I think:) /roma */
    };

    char *specific_traps_sb[22] = {
	"Reassociation",
	"RoamOut",
	"Association",
	"Disassociation",
	"AssociationExpire",
	"Reset",
	"SettingPingIPAddress",
	"StartUp",
	"FailedToEraseFlash",
	"APClientAssociating",
	"APClientScanning",
	"MultiAttachedStation",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown",
	"unknown"
};

    memset(&server, 0, sizeof server);
    server.sin_family = AF_INET;
    server.sin_port = htons(162);
    server.sin_addr.s_addr = INADDR_ANY;

#ifdef HAVE_GETTEXT
    /* locale support init */
    setlocale(LC_ALL, "");
    bindtextdomain("ap-utils", LOCALEDIR);
    textdomain("ap-utils");
#endif

    if (argc > 1)
     do {
	opterr = 0;
	switch (opt = getopt(argc, argv, "u:i:s")) {
	case 'i':
	    device = malloc(strlen(optarg) + 1);
	    strncpy(device, optarg, strlen(optarg) + 1);
	    break;
	case 'u':
	    user = malloc(strlen(optarg) + 1);
	    strncpy(user, optarg, strlen(optarg) + 1);
	    break;
	case 's':
	    atmel_ap_type = 1;
	    break;
	}
    } while (opt != -1);


    openlog("ap-trapd", LOG_PID, LOG_LOCAL0);
    syslog(LOG_INFO, _("ap-trapd %s started%s%s."), VERSION,
	   (device) ? _(" on ") : "", (device) ? device : "");

    pid = fork();
    if (pid > 0) {		/* parent */
	return 0;
    } else if (pid < 0) {	/* failed */
	syslog(LOG_ERR, _("Unable to fork. Exiting."));
	return 1;
    }
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
	syslog(LOG_ERR, _("Can't create socket. Exiting."));
	return 1;
    }


    if (bind(sockfd, (struct sockaddr *) &server, SIZE) == -1) {
	syslog(LOG_ERR, _("Can't bind socket. Exiting."));
	return 1;
    }
#ifdef OS_LINUX
    if (device) {
	if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, device,
		       strlen(device) + 1) == -1) {
	    syslog(LOG_ERR, _("Can't bind to device %s. Exiting."),
		   device);
	    return 1;
	}
    }
#endif

    if (user == NULL)
	user = TRAPD_USER;
    /* after opening socket change userid  */
    if ((pwd = getpwnam(user)) == NULL) {
	syslog(LOG_ERR, _("Unable to process username %s. Error: %m."),
	       user);
	return 1;
    }
    if (setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
	syslog(LOG_ERR, _("Unable to change to uid %d."), pwd->pw_uid);
	return 1;
    }

    while (1) {
	if ((len =
	     recvfrom(sockfd, buf, 512, 0, (struct sockaddr *) &client,
		      &client_len)) == -1)
	    continue;
	if (buf[0] != ASN_HEADER) {
	    continue;
	}
	start = buf;
	if (buf[1] & 0x80) {
	    start += (buf[1] & 0x7F) + 2;
	} else {
	    start += 2;
	}
	snmp_version = start[2];

	if (community)
	    free(community);
	community = malloc(start[4] + 1);
	memcpy(community, start + 5, start[4]);
	community[start[4]] = '\0';
	start += *(start + 4) + 5;

	if (start[0] != TRAP) {
	    continue;
	}

	if (start[1] & 0x80) {
	    start += (start[1] & 0x7F) + 2;
	} else {
	    start += 2;
	}

	if (enterprise_oid)
	    free(enterprise_oid);
	enterprise_oid = malloc(start[1] + 1);
	memcpy(enterprise_oid, start + 2, start[1]);
	enterprise_oid[start[1]] = '\0';
	start += start[1] + 2;
	memcpy(&agent_addr.s_addr, start + 2, 4);
	if (memcmp(enterprise_oid, enterprise_410, sizeof(enterprise_410)) &&
	    memcmp(enterprise_oid, enterprise_12350, sizeof(enterprise_12350)))
	{
	    syslog(LOG_INFO,
		   _
		   ("Received unknown SNMP ver %d trap. From %s:%d. Agent: %s. Community: %s."),
		   snmp_version + 1, inet_ntoa(client.sin_addr),
		   ntohs(client.sin_port), inet_ntoa(agent_addr),
		   community);
	    continue;
	}

	start += 6;
	generic_trap = start[2];
	if (generic_trap > 6)
	    generic_trap = 7;
	specific_trap = start[5];
	if (specific_trap > 22)
	    specific_trap = 21;

	time_stamp = 0;
	c = 0;
	i = 1;
	while (c < start[7]) {
	    time_stamp += (unsigned char) *(start + 7 + start[7] - c) * i;
	    i *= 256;
	    c++;
	}
	hand = time_stamp % 100;
	time_stamp = time_stamp / 100;
	sec = time_stamp % 60;
	time_stamp = time_stamp / 60;
	min = time_stamp % 60;
	time_stamp = time_stamp / 60;

	start += start[7] + 8;


	mac_flag = 0;
	if ((start - buf < len) && *(start) == ASN_HEADER) {

	    if (start[1] & 0x80) {
		start += (start[1] & 0x7F) + 2;
		len -= ((start[1] & 0x7F) + 2);
	    } else {
		start += 2;
		len -= 2;
	    }

	    if (*(start) == ASN_HEADER) {
		if (start[1] & 0x80) {
		    start += (start[1] & 0x7F) + 2;
		    len -= ((start[1] & 0x7F) + 2);
		} else {
		    start += 2;
		    len -= 2;
		}
		start += start[1] + 4;
		memcpy(mac, start, 6);
		if (generic_trap == 6 && specific_trap >= 1
		    && (specific_trap <= 5 || specific_trap >= 9)) {
		    sprintf(mac_info, "%02X%02X%02X%02X%02X%02X",
			    mac[0] & 0xFF, mac[1] & 0xFF, mac[2] & 0xFF,
			    mac[3] & 0xFF, mac[4] & 0xFF, mac[5] & 0xFF);
		    mac_flag = 1;
		}
		if (generic_trap == 6 && specific_trap == 7) {
		    sprintf(mac_info,
			    "%d.%d.%d.%d",
			    mac[0] & 0xFF, mac[1] & 0xFF, mac[2] & 0xFF,
			    mac[3] & 0xFF);
		    mac_flag = 1;
		}
	    }
	}
	syslog(LOG_INFO,
	       _
	       ("Agent:v%d %s (%s@%s:%d) Trap %i: %s%s%s. "
		"SysUptime %d:%02d:%02d.%02d"),
	       snmp_version + 1,  inet_ntoa(agent_addr),
	       community, inet_ntoa(client.sin_addr), ntohs(client.sin_port),
	       (generic_trap == 6) ? specific_trap: generic_trap + 1,
	       (generic_trap == 6) ?
		(atmel_ap_type == 1) ? specific_traps_sb[specific_trap-1] : specific_traps[specific_trap-1] :
	       generic_traps[generic_trap], (mac_flag) ? " " : "",
	       (mac_flag) ? mac_info : "", time_stamp, min, sec, hand);
    }

    /* not reachable */
    return 0;
}
