/*
 *	snmp.c from Access Point SNMP Utils for Linux
 *	basic snmp packets assembly/disassembly and send/receive functions
 *
 * Copyright (c) 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 <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "ap-utils.h"

extern char *community;
extern short ap_type;
extern struct in_addr ap_ip;

int sockfd = 0, snmp_quit_by_keypress = 0;

void close_sockfd()
{
    if (sockfd)
	close(sockfd);
}

int open_sockfd()
{
    struct sockaddr_in client;

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
	return -1;

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

    if (bind(sockfd, (struct sockaddr *) &client, SIZE) == -1)
	return -1;

    return 0;
}

int reopen_sockfd()
{
    close_sockfd();
    return (open_sockfd());
}

/*
unsigned int ber_decode_uint(unsigned char *bevp, int len)
{
    unsigned int out = 0;

    while (len--) {
	out = (out << 7) | (*bevp & (*bevp & 0x80 ? 0x7f : 0xff));
	bevp++;
    }

    return out;
}
*/

int ber(char *message, varbind * varbindlist, int num, int type)
{

    char pdu1[1024], pdu[1024], *list = pdu1;
    int len_var = 0, lenp, len_tmp, len = 0, community_len =
	strlen(community), i;
    char snmp_ver[] = { 0x02, 0x01, 0x00 };
    const char req_id[] = { 0x02, 0x01 }, err[] = {
    0x02, 0x01, 0x00};

    for (i = 0; i < num; i++) {
	*(pdu1 + len++) = ASN_HEADER;
	len_tmp =
	    (varbindlist + i)->len_oid + (varbindlist + i)->len_val + 4;
	if (!(ap_type == NWN && type == SET)) {
	    *(pdu1 + len++) = 0x82;
	    *(pdu1 + len++) = (len_tmp - (len_tmp % 256)) / 256;
	}
	*(pdu1 + len++) = len_tmp % 256;
	*(pdu1 + len++) = OID_VALUE;
	*(pdu1 + len++) = (varbindlist + i)->len_oid;
	memcpy(pdu1 + len, (varbindlist + i)->oid,
	       (varbindlist + i)->len_oid);
	len += (varbindlist + i)->len_oid;
	*(pdu1 + len++) = (varbindlist + i)->type;
	*(pdu1 + len++) = (varbindlist + i)->len_val;
	memcpy(pdu1 + len, (varbindlist + i)->value,
	       (varbindlist + i)->len_val);
	len += (varbindlist + i)->len_val;
    }

    len_var = len;

    lenp = 0;
    len_tmp = len_var + ((ap_type == NWN && type == SET) ? 11 : 13);
    *(pdu + lenp++) = type;
    if (!(ap_type == NWN && type == SET)) {
	*(pdu + lenp++) = 0x82;
	*(pdu + lenp++) = (len_tmp - (len_tmp % 256)) / 256;
    }
    *(pdu + lenp++) = len_tmp % 256;

    memcpy(pdu + lenp, req_id, sizeof(req_id));
    lenp += sizeof(req_id);
    *(pdu + lenp++) =
	(1 + (int) (255.0 * rand() / (RAND_MAX + 1.0))) & 0xFF;
    memcpy(pdu + lenp, err, sizeof(err));
    lenp += sizeof(err);
    memcpy(pdu + lenp, err, sizeof(err));
    lenp += sizeof(err);
    *(pdu + lenp++) = ASN_HEADER;
    len_tmp = len_var;
    if (!(ap_type == NWN && type == SET)) {
	*(pdu + lenp++) = 0x82;
	*(pdu + lenp++) = (len_tmp - (len_tmp % 256)) / 256;
    }
    *(pdu + lenp++) = len_tmp % 256;
    memcpy(pdu + lenp, list, len_var);
    lenp += len_var;

    *message = ASN_HEADER;
    len = 1;

    i = lenp + community_len + 5;
    if (!(ap_type == NWN && type == SET)) {
	*(message + len++) = 0x82;
	*(message + len++) = (i - (i % 256)) / 256;
    }
    *(message + len++) = i % 256;

    memcpy(message + len, snmp_ver, 3);
    len += 3;
    *(message + len++) = STRING_VALUE;
    *(message + len++) = community_len & 0xFF;
    memcpy(message + len, community, community_len);
    len += community_len;
    memcpy(message + len, pdu, lenp);
    len += lenp;

    return len;
}

int snmp(varbind * varbindlist, int num, int type)
{
    static char buf[1024];
    unsigned char *start;
    unsigned int num_reply;
    int len = 0, tries = 5;
    struct sockaddr_in server;
    struct timeval timeout;
    fd_set rds;

    if (num == 0)
	return 1;

    /*
     * Flush sockfd by reopening. This prevents various 'something received/
     * available on sockfd prior snmp() call' desync problems.
     */
    if (reopen_sockfd() == -1)
	return 0;

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

    while (tries--) {
	len = ber(buf, varbindlist, num, type);
	if (sendto(sockfd, buf, len, 0, (struct sockaddr *) &server, SIZE)
	    == -1) {
	    return 0;
	}

	FD_ZERO(&rds);
	FD_SET(sockfd, &rds);
	timeout.tv_sec = 1;
	timeout.tv_usec = 0;

	if (select(sockfd + 1, &rds, NULL, NULL, &timeout) == -1)
	    return 0;

	if (FD_ISSET(sockfd, &rds)) {
	    if ((len = recv(sockfd, buf, sizeof buf, 0)) <= 0)
		return 0;
	    else
		break;
	}

	/* timeout => next try, as long as no key has been pressed */

	/*
	 * Allow for quick 'last resort' escape using q/Q. Note:
	 * we may not use one select() for checking both fd 0 and sockfd, since
	 * something may appear on sockfd later than key has been pressed =>
	 * give gratuitous 1sec delay for response arrival to sockfd, and THEN
	 * just poll for anything on fd 0.
	 */
	if (snmp_quit_by_keypress && type == GET) {
	    FD_ZERO(&rds);
	    FD_SET(0, &rds);
	    timeout.tv_sec = 0;
	    timeout.tv_usec = 0;
	    if (select(1, &rds, NULL, NULL, &timeout) == -1)
		return 0;

	    if (FD_ISSET(0, &rds))
		return 0;
	}
    }

    if (!tries)
	return 0;

    start = buf;
    num_reply = 0;
    if (*start != ASN_HEADER) {
	return 0;
    }

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

    len -= *(start + 4) + 5;
    start += *(start + 4) + 5;

    if (*(start) != RESPONSE) {
	return 0;
    }



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

    if (*(start + 5))
	return -*(start + 8);

    start += 9;
    len -= 9;
    if (*(start) != ASN_HEADER) {
	return 0;
    }


    if (start[1] & 0x80) {
	start += (start[1] & 0x7F) + 2;
	len -= ((start[1] & 0x7F) + 2);
    } else {
	start += 2;
	len -= 2;
    }
    while (len) {
	if (*(start) != ASN_HEADER) {
	    return num_reply;
	}
	if (start[1] & 0x80) {
	    start += (start[1] & 0x7F) + 2;
	    len -= ((start[1] & 0x7F) + 2);
	} else {
	    start += 2;
	    len -= 2;
	}


	varbindlist[num_reply].len_oid = start[1];
/*	if(varbindlist[num_reply].oid)
	    free(varbindlist[num_reply].oid);
	varbindlist[num_reply].oid =
	    (char *) malloc(varbindlist[num_reply].len_oid);
	memcpy(varbindlist[num_reply].oid, start + 2,
	       varbindlist[num_reply].len_oid);
*/
	varbindlist[num_reply].oid = start + 2;
	len -= *(start + 1) + 2;
	start += *(start + 1) + 2;
	varbindlist[num_reply].type = *(start);

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

/*	if(varbindlist[num_reply].value)
	    free(varbindlist[num_reply].value);
	varbindlist[num_reply].value =
	    (char *) malloc(varbindlist[num_reply].len_val);
	memcpy(varbindlist[num_reply].value, start,
	       varbindlist[num_reply].len_val);
*/
	varbindlist[num_reply].value = start;
	len -= varbindlist[num_reply].len_val;
	start += varbindlist[num_reply].len_val;
	num_reply++;
    }

    return num_reply;
}
