/*\
 *	DISTRIBUTION: HNMS v2.0
 *	FILE: hnmstool/hnmstool.c
 *
 *	HNMS Plain-Text UI ("ptui") Module.
 *
 *	Jude George
 *	NAS Facility, NASA Ames Research Center
 *
 *	Copyright (c) 1994 Jude George
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 1, or (at your option)
 *	any later version.
 *
 *	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., 675 Mass Ave, Cambridge, MA 02139, USA.
\*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <netdb.h>
#include <sys/param.h>

#include "stdhnms.h"

#define IF_STATS_INTERVAL	300
#define PROC_ROUTE_INTERVAL	60

static char		filename[MAXPATHLEN];
static FILE		*outfile;
static PE		nullpe = NULL;
static int		connected = 0;
static VarBindList	subscriptions[MAX_OBJECTS + 1];
static int		sub_all_reach = 0;
static int		sub_if_stats = 0;
static int		sub_pr_reach = 0;
static int		sub_pr_route = 0;
static VarBindList	vbl_reach = NULL;
static VarBindList	vbl_route = NULL;

void showusage(bname)
    const char		*bname;
{
    printf("\tHNMS Plain-Text UI Module\n\n");
    printf("Usage: %s [-a|-i|-p|-r] [-d] [-f file]\n\n", bname);
    printf("     -a        -- subscribe to reachability, all objects\n");
    printf("     -i        -- subscribe to statistics, interfaces\n");
    printf("     -p        -- subscribe to reachability, processors\n");
    printf("     -r        -- subscribe to default route, processors\n");
    printf("     -d        -- operate in background\n");
    printf("     -f file   -- send output to file (variable data only)\n");
}

/*\
 *  Send the UName of one object to stdout.
\*/
void PTUI_list_obj(id)
    const int		id;
{
    static char		*cp;

    if (!OBJ_exists(id))
	return;
    cp = OBJ_get_uname(id);
    puts(cp);
}

/*\
 *  Send a list of my objects to stdout.
\*/
void PTUI_list_objs()
{
    OBJ_iterate(PTUI_list_obj, NULL);
}

/*\
 *  Process one user command.
\*/
void PTUI_process_command(buf)
    const char		*buf;
{
    static char		lbuf[STRBUFLEN];
    char		*cp, *cp2;
    static char		ws[STRBUFLEN];
    static char		uname[STRBUFLEN];
    static char		oid_str[STRBUFLEN];
    static char		val_str[STRBUFLEN];
    static char		hostname[STRBUFLEN];
    static char		community[STRBUFLEN];
    int			id;
    int			interval, type;
    Message		msg;
    VarBindList		vbl;
    OID			oid;
    PE			pe;
    struct hostent	*host;
    int			i;

    if (!buf)
	return;

    if (buf[0] == 0)
	return;

    bzero(lbuf, STRBUFLEN);
    bzero(uname, STRBUFLEN);
    bzero(oid_str, STRBUFLEN);
    bzero(val_str, STRBUFLEN);
    bcopy(buf, lbuf, strlen(buf));
    /* more wierd casting... thorpej */
#ifdef __sparc
    cp = (char *)strchr((char *)lbuf, (int)' ');
#else
    cp = strchr(lbuf, ' ');
#endif
    if (!cp) {
	/*
	 *  L I S T.
	 */
	if (!strcmp(lbuf, "list"))
	    PTUI_list_objs();
	/*
	 *  E X I T.
	 */
	else if (!strcmp(lbuf, "exit") ||
		 !strcmp(lbuf, "quit") ||
		 !strcmp(lbuf, "q")) {
	    if (connected) {
		msg = Message_create(HNMP_GOODBYE);
		HNMP_Message_enqueue(msg, 0);
		HNMP_close(0);
	    }
	    exit(0);
	}
    }
    else {
	*cp++ = 0;
	if (!cp)
	    return;
	/*
	 *  A N N O U N C E.
	 */
	if (!strcmp(lbuf, "announce")) {
	    if (sscanf(cp, "%[\t \"]%[^\"]", ws, uname) != 2)
		return;
	    vbl = NULL;
	    VarBindList_set(&vbl, oid_hnmsObjUName, uname, strlen(uname),
			    MIB_octetstring, 0, 0);
	    msg = Message_create(HNMP_ANNOUNCE);
	    id = 0;
	    Message_set(msg, HNMP_object_id, &id);
	    Message_set(msg, HNMP_variables, vbl);
	    HNMP_Message_enqueue(msg, 0);
	    VarBindList_free(vbl);
	}
	/*
	 *  D E L E T E.
	 */
	else if (!strcmp(lbuf, "delete")) {
	    if (sscanf(cp, "%[\t \"]%[^\"]", ws, uname) != 2)
		return;
	    if ((id = OBJ_find(uname)) == 0)
		return;
	    msg = Message_create(HNMP_DELETE);
	    Message_set(msg, HNMP_object_id, &id);
	    HNMP_Message_enqueue(msg, 0);
	}
	/*
	 *  S E T.
	 */
	else if (!strcmp(lbuf, "set")) {
	    if (sscanf(cp, "%[\t \"]%[^\"]\" %s %[^\n]",
		       ws, uname, oid_str, val_str) != 4)
		return;
	    if ((id = OBJ_find(uname)) == 0)
		return;
	    type = 0;
	    if ((oid = MIB_ode2oid(oid_str, &type)) == NULL)
		return;
	    switch (type) {
		case MIB_integer:
		case MIB_enum:
		case MIB_timeticks:
		case MIB_counter:
		case MIB_gauge:
		    pe = int2prim(atoi(val_str));
		    break;
		case MIB_ipaddr:
		    pe = ipaddr_int2pe(inet_addr(val_str));
		    break;
		case MIB_octetstring:
		case MIB_displaystring:
		    pe = oct2prim(val_str, strlen(val_str));
		    break;
		default:;
		}
	    if (!pe)
		return;
	    vbl = NULL;
	    VarBindList_set(&vbl, oid, pe, 0, 0, 0, 0, 0);
	    msg = Message_create(HNMP_SET_DATA);
	    Message_set(msg, HNMP_object_id, &id);
	    Message_set(msg, HNMP_variables, vbl);
	    HNMP_Message_enqueue(msg, 0);
	    VarBindList_free(vbl);
	}
	/*
	 *  G E T.
	 */
	else if (!strcmp(lbuf, "get")) {
	    if (sscanf(cp, "%[\t \"]%[^\"]\" %s", ws, uname, oid_str) != 3)
		return;
	    if ((id = OBJ_find(uname)) == 0)
		return;
	    if ((oid = MIB_ode2oid(oid_str, NULL)) == NULL)
		return;
	    vbl = NULL;
	    VarBindList_set(&vbl, oid, nullpe, 0, 0, 0, 0, 0);
	    msg = Message_create(HNMP_GET_DATA);
	    Message_set(msg, HNMP_object_id, &id);
	    Message_set(msg, HNMP_variables, vbl);
	    HNMP_Message_enqueue(msg, 0);
	    VarBindList_free(vbl);
	}
	/*
	 *  G E T N E X T.
	 */
	else if (!strcmp(lbuf, "getnext")) {
	    if (sscanf(cp, "%[\t \"]%[^\"]\" %s", ws, uname, oid_str) != 3)
		return;
	    if ((id = OBJ_find(uname)) == 0)
		return;
	    if ((oid = MIB_ode2oid(oid_str, NULL)) == NULL)
		return;
	    vbl = NULL;
	    VarBindList_set(&vbl, oid, nullpe, 0, 0, 0, 0, 0);
	    msg = Message_create(HNMP_GET_NEXT_DATA);
	    Message_set(msg, HNMP_object_id, &id);
	    Message_set(msg, HNMP_variables, vbl);
	    HNMP_Message_enqueue(msg, 0);
	    VarBindList_free(vbl);
	}
	/*
	 *  W A L K.
	 */
	else if (!strcmp(lbuf, "walk")) {
	    if (sscanf(cp, "%[\t \"]%[^\"]\" %s ", ws, uname, oid_str) != 3)
		return;
	    if ((id = OBJ_find(uname)) == 0)
		return;
	    if ((oid = MIB_ode2oid(oid_str, NULL)) == NULL)
		return;
	    vbl = NULL;
	    VarBindList_set(&vbl, oid, nullpe, 0, 0, 0, 0, 0);
	    msg = Message_create(HNMP_GET_WALK_DATA);
	    Message_set(msg, HNMP_object_id, &id);
	    Message_set(msg, HNMP_variables, vbl);
	    HNMP_Message_enqueue(msg, 0);
	    VarBindList_free(vbl);

	}
	/*
	 *  S U B S C R I B E.
	 */
	else if (!strcmp(lbuf, "subscribe")) {
	    if (sscanf(cp, "%[\t \"]%[^\"]", ws, uname) != 2)
		return;
	    id = OBJ_find(uname);
	    if (!id)
		return;
	    vbl = NULL;
	    /* wierd casting - thorpej */
#ifdef __sparc
	    cp = (char *)strrchr((char *)cp, (int)'\"');
#else
	    cp = strrchr(cp, '\"');
#endif
	    for (;;) {
#ifdef __sparc
		cp = (char *)strchr((char *)cp, (int)' ');
#else
		cp = strchr(cp, ' ');
#endif
		if (!cp)
		    break;
		cp++;
		if (sscanf(cp, "%[^@]@%d", oid_str, &interval) != 2)
		    break;
		if ((oid = MIB_ode2oid(oid_str, NULL)) == NULL)
		    break;
		VarBindList_set(&vbl, oid, nullpe, 0, 0, &interval, 0);
	    }
	    if (vbl) {
		if (subscriptions[id]) {
		    if (!VarBindList_cmplists(vbl, subscriptions[id])) {
			VarBindList_free(vbl);
			return;
		    }
		    else
			VarBindList_free(subscriptions[id]);
		}
		subscriptions[id] = vbl;
		msg = Message_create(HNMP_SUB_DATA);
		Message_set(msg, HNMP_object_id, &id);
		Message_set(msg, HNMP_variables, vbl);
		HNMP_Message_enqueue(msg, 0);
	    }
	}
	/*
	 *  U N S U B S C R I B E.
	 */
	else if (!strcmp(lbuf, "unsubscribe")) {
	    if (sscanf(cp, "%[\t \"]%[^\"]", ws, uname) != 2)
		return;
	    id = OBJ_find(uname);
	    if (!id)
		return;
	    if (subscriptions[id]) {
		VarBindList_free(subscriptions[id]);
		subscriptions[id] = NULL;
	    }
	    msg = Message_create(HNMP_UNSUB_DATA);
	    Message_set(msg, HNMP_object_id, &id);
	    HNMP_Message_enqueue(msg, 0);
	}
	/*
	 * H I N T.
	 */
	else if (!strcmp(lbuf, "hint")) {
	    bzero(hostname, STRBUFLEN);
	    bzero(community, STRBUFLEN);
	    sscanf(cp, "%s %s", hostname, community);
	    if (hostname[0] == 0)
		return;
	    if (community[0] == 0)
		strcpy(community, "public");
	    host = gethostbyname(hostname);
	    if (!host)
		return;
	    cp = ipaddr_int2str(*(int *)(host->h_addr_list[0]));
	    if (!cp)
		return;
	    cp2 = OBJ_make_uname(OBJ_agent, cp);
	    if (!cp2)
		return;
	    id = OBJ_find(cp2);
	    if (id) {
		/*
		 * If the Agent exists, just reset it's discovery time
		 * and community name.
		 */
		vbl = NULL;
		i = 0;
		VarBindList_set(&vbl, oid_hnmsAgentLastDiscoverTime, &i,
				0, MIB_integer, 0, 0);
		VarBindList_set(&vbl, oid_hnmsAgentCommunity, community,
				strlen(community), MIB_octetstring, 0, 0);
		msg = Message_create(HNMP_SET_DATA);
		Message_set(msg, HNMP_object_id, &id);
		Message_set(msg, HNMP_variables, vbl);
		HNMP_Message_enqueue(msg, 0);
		VarBindList_free(vbl);
	    }
	    else {
		/*
		 * If the Agent doesn't yet exist, announce, it.
		 */
		vbl = NULL;
		VarBindList_set(&vbl, oid_hnmsObjUName, cp2, strlen(cp2),
				MIB_octetstring, 0, 0);
		VarBindList_set(&vbl, oid_hnmsAgentIpaddr,
				(int *)(host->h_addr_list[0]),
				0, MIB_ipaddr, 0, 0);
		VarBindList_set(&vbl, oid_hnmsAgentCommunity, community,
				strlen(community), MIB_octetstring, 0, 0);
		msg = Message_create(HNMP_ANNOUNCE);
		id = 0;
		Message_set(msg, HNMP_object_id, &id);
		Message_set(msg, HNMP_variables, vbl);
		HNMP_Message_enqueue(msg, 0);
		VarBindList_free(vbl);
	    }
	}
	/*
	 * S H O W.
	 */
	else if (!strcmp(lbuf, "show")) {
	    if (sscanf(cp, "%[\t \"]%[^\"]", ws, uname) != 2)
		return;
	    if ((id = OBJ_find(uname)) == 0)
		return;
	    vbl = OBJ_get_vbl(id);
	    VarBindList_print(vbl);
	}
	/*
	 *  T R A N S L A T E.
	 */
	else if (!strcmp(lbuf, "translate")) {
	    if (sscanf(cp, "%s", oid_str) != 1)
		return;
	    if ((oid = MIB_ode2oid(oid_str, NULL)) == NULL)
		return;
	    vbl = NULL;
	    VarBindList_set(&vbl, oid, nullpe, 0, 0, 0, 0, 0);
	    VarBindList_print(vbl);
	    VarBindList_free(vbl);
	}
    }
    return;
}

/*\
 *  Read commands from stdin.
\*/
int PTUI_read()
{
    fd_set		read_ready;
    int			ready_descs;
    struct timeval	select_time;
    char		c;
    int			rval;
    static int		i = -1;
    static char		*cp, buf[STRBUFLEN];
    
    if (i == -1) {
	i = 0;
	bzero(buf, STRBUFLEN);
	cp = buf;
    }
    while (1) {
	select_time.tv_sec = 0;
	select_time.tv_usec = 0;
	FD_ZERO(&read_ready);
	FD_SET(0, &read_ready);
	if (select(1, &read_ready, NULL, NULL, &select_time) == 1) {
	    if (!FD_ISSET(0, &read_ready))
		break;
	    bzero(buf, STRBUFLEN);
	    gets(buf);
	    if (buf[0] != 0)
		PTUI_process_command(buf);
	}
	else
	    break;
    }
    return 0;
}


/*\
 *  Write a line report to stdout.
\*/
void PTUI_write(id, vbl)
    const int		id;
    const VarBindList	vbl;
{
    VarBindList		v;
    char		*cp, *uname;
    int			type;
    int			len;
    int			n;
    FILE		*fp;

    if (outfile)
	fp = outfile;
    else
	fp = stdout;

    uname = OBJ_get_uname(id);
    if (!uname)
	return;

    for (v = vbl; v; v = v->next) {
	cp = MIB_oid2ode(v->VarBind->name, &type);
	if (!cp)
	    continue;
	fprintf(fp, "%d\t%s\t%s\t", v->VarBind->timestamp->parm, uname, cp);
	switch (type) {
	case MIB_integer:
	case MIB_enum:
	case MIB_timeticks:
	case MIB_gauge:
	    n = prim2num(v->VarBind->value);
	    fprintf(fp, "%d\n", n);
	    break;
	case MIB_counter:
	    n = fixed_prim2num(v->VarBind->value);
	    fprintf(fp, "%u\n", (unsigned int)n);
	    break;
	case MIB_ipaddr:
	    fprintf(fp, "%s\n", ipaddr_pe2str(v->VarBind->value));
	    break;
	case MIB_octetstring:
	case MIB_displaystring:
	    cp = prim2str(v->VarBind->value, &len);
	    fprintf(fp, "%s\n", cp);
	    free(cp);
	    break;
	default:;
	    fprintf(fp, "*UNKNOWN*\n");
	}
    }
    fflush(fp);
}

/*\
 *  Callback routine called when HNMP closes my server connection.
\*/
void HNMP_close_callback(module_id)
    const int		module_id;
{
    connected = 0;
    OBJ_iterate(OBJ_delete, NULL);
}

/*\
 *  Callback routine called when HNMP opens my server connection.
\*/
void HNMP_open_callback(module_id)
    const int		module_id;
{
    connected = 1;
}

/*\
 *  Process an incoming HNMP message.
\*/
void HNMP_Message_process(msg)
    const Message	msg;
{
    VarBindList		vbl, vbl2;
    Message		msg2;
    int			id, interval;
    char		*cp, uname[STRBUFLEN], vname[STRBUFLEN];

    switch (msg->data->offset) {
    case HNMP_ANNOUNCE:
	vbl = (VarBindList)Message_get(msg, HNMP_variables);
	id = *(int *)Message_get(msg, HNMP_object_id);
	bzero(uname, STRBUFLEN);
	VarBindList_get(vbl, oid_hnmsObjUName, uname, 0, 0, 0, 0);
	OBJ_add_by_uname(id, uname);
	OBJ_set_list(id, vbl);
	/*
	 *	Pre-set some variables I expect to be using.
	 */
	if (vbl_reach == NULL) {
	    interval = PROC_ROUTE_INTERVAL;
	    VarBindList_set(&vbl_route,
			    MIB_ode2oid("ipRouteNextHop.0.0.0.0", NULL),
			    nullpe, 0, 0, &interval, 0);
	    interval = 0;
	    VarBindList_set(&vbl_reach, oid_hnmsObjReachStatus,
			    nullpe, 0, 0, &interval, 0);
	}
	/*
	 *	FOR -a FLAG: subscribe to reach status on all objects
	 */
	if (sub_all_reach) {
	    msg2 = Message_create(HNMP_SUB_DATA);
	    Message_set(msg2, HNMP_object_id, &id);
	    Message_set(msg2, HNMP_variables, vbl_reach);
	    HNMP_Message_enqueue(msg2, 0);
	}
	/*
	 *	FOR -i FLAG: subscribe to reach status & stats on Interfaces
	 */
	if ((!strncmp(uname, "interface:", 10)) && (sub_if_stats == 1)) {
	    msg2 = Message_create(HNMP_SUB_DATA);
	    Message_set(msg2, HNMP_object_id, &id);
	    vbl2 = 0;
	    interval = IF_STATS_INTERVAL;
	    cp = strrchr(uname, (int)'_');
	    if (!cp) {
		Message_free(msg2);
		break;
	    }
	    cp++;
	    sprintf(vname, "ifInOctets.%s", cp);
	    VarBindList_set(&vbl2, MIB_ode2oid(vname, NULL),
			    nullpe, 0, 0, &interval, 0);
	    sprintf(vname, "ifOutOctets.%s", cp);
	    VarBindList_set(&vbl2, MIB_ode2oid(vname, NULL),
			    nullpe, 0, 0, &interval, 0);
	    sprintf(vname, "ifInErrors.%s", cp);
	    VarBindList_set(&vbl2, MIB_ode2oid(vname, NULL),
			    nullpe, 0, 0, &interval, 0);
	    sprintf(vname, "ifOutErrors.%s", cp);
	    VarBindList_set(&vbl2, MIB_ode2oid(vname, NULL),
			    nullpe, 0, 0, &interval, 0);
	    VarBindList_set(&vbl2, oid_hnmsObjReachStatus,
			    nullpe, 0, 0, &interval, 0);
	    Message_set(msg2, HNMP_variables, vbl2);
	    HNMP_Message_enqueue(msg2, 0);
	    VarBindList_free(vbl2);
	}
	/*
	 *	FOR -p FLAG: subscribe to reachability status on Processors
	 */
	else if ((!strncmp(uname, "processor:", 10)) && (sub_pr_reach == 1)) {
	    msg2 = Message_create(HNMP_SUB_DATA);
	    Message_set(msg2, HNMP_object_id, &id);
	    Message_set(msg2, HNMP_variables, vbl_reach);
	    HNMP_Message_enqueue(msg2, 0);
	}
	/*
	 *	FOR -r FLAG: subscribe to default route on Processors
	 */
	else if ((!strncmp(uname, "processor:", 10)) && (sub_pr_route == 1)) {
	    msg2 = Message_create(HNMP_SUB_DATA);
	    Message_set(msg2, HNMP_object_id, &id);
	    Message_set(msg2, HNMP_variables, vbl_route);
	    HNMP_Message_enqueue(msg2, 0);
	}
	break;

    case HNMP_DELETE:
	id = *(int *)Message_get(msg, HNMP_object_id);
	OBJ_delete(id);
	break;

    case HNMP_SEND_DATA:
    case HNMP_SEND_RELATIONS:
	vbl = (VarBindList)Message_get(msg, HNMP_variables);
	id = *(int *)Message_get(msg, HNMP_object_id);
	OBJ_set_list(id, vbl);
	PTUI_write(id, vbl);
	break;

    default:;
    }
}

/*\
 *  Read in the auxiliary MIB file, if there is one,
 *  from $HNMS_HOME/mibtable.
\*/
void PTUI_read_mib()
{
    char	*cp;
    char	buf[MAXPATHLEN];

    cp = PARAM_get_str(oid_hnmsModuleHnmsHome);
    bzero(buf, MAXPATHLEN);
    sprintf(buf, "%s/mibtable", cp);
    MIB_read_file(buf);
}

/*\
 *  Top level.
\*/
main(argc, argv)
    int			argc;
    char		*argv[];
{
    int			interval;
    int			c;
    int			daemon = 0;
    extern char		*optarg;
    extern int		optind, opterr;

#ifdef sgi
    mallopt(M_FREEHD, 1);
#endif

    bzero(filename, MAXPATHLEN);
    while ((c = getopt(argc, argv, "aiprdf:")) != -1) {
	switch (c) {
	case 'a':
	    sub_all_reach = 1;
	    break;
	case 'i':
	    sub_if_stats = 1;
	    break;
	case 'p':
	    sub_pr_reach = 1;
	    break;
	case 'r':
	    sub_pr_route = 1;
	    break;
	case 'd':
	    daemon = 1;
	    close(0);
	    close(1);
	    break;
	case 'f':
	    strcpy(filename, optarg);
	    outfile = fopen(filename, "a");
	    if (!outfile)
		exit(1);
	    break;
	default:
	    showusage(argv[0]);
	    exit(0);
	}
    }

    if (daemon)
	if (fork() != 0)
	    exit(0);

    bzero(subscriptions, (MAX_OBJECTS + 1) * sizeof (void *));

    nullpe = pe_create_null();
	
    HNMS_init(MODULE_UI);
    HNMS_create_task("Read MIB file", PTUI_read_mib, NULL, 0);
    HNMS_create_task("Connect to Server", NULL, PEER_connect_server, 30);
    if (!daemon)
	HNMS_create_task("Read From stdin", NULL, PTUI_read, 1);

    for (;;) {
	HNMS_process();
	sleep(1);
    }
}
