Plan 9 from Bell Labs’s /usr/web/sources/contrib/gabidiaz/root/sys/src/cmd/snmpfs/snmp.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <ip.h>
#include "ber.h"
#include "snmp.h"


int alarmflag;
int readflag;

char *pduerror[] = {
	"noError",
	"tooBig",
	"noSuchName",
	"badValue",
	"readOnly",
	"genErr",
	"noAccess",
	"wrongType",
	"wrongLength",
	"wrongEncoding",
	"wrongValue",
	"noCreation",
	"inconsistenValue",
	"resourceUnavailable",
	"commitFailed",
	"undoFailed",
	"authorizationError",
	"notWritable",
	"inconsistentName"
};
 


/* debug or error */
int
error(int level, char *format, ... )
{
	va_list args;
	char lasterror[ERRMAX];

	va_start(args, format);
	vsnprint(lasterror,ERRMAX, format, args);
	va_end(args);

	werrstr(lasterror);
	switch(level) {
		case 1:
			fprint(2,"Debug:%r\n");
			return 0;
			break;
		case 2:
			fprint(2,"Error: %r\n");
			abort();
			break;
	}

	return 0;
}


/* creates a new snmp request id */
/* based on rsc code */
int
mkreqid(void) 
{
	return (nsec() & 0x7FFE) + 1;
}

void
freesession(Session *s)
{
	free(s->host);
	free(s->port);
	free(s->rocomm);
	free(s->rwcomm);
	free(s);
}

void
freevarbind(Tuple *t, int len)
{
	int i;

	if ( t == nil || len < 1 )
		return;

	for(i=0;i<len;i++) {
		free(t[i].oid);
		free(t[i].value);
	}

	free(t);
}
		
void
freepacket(Packet *p)
{
	if ( p == nil )
		return;
	freevarbind(p->pdu.varbind,p->pdu.nbind);

	if ( p->comm != nil )
		free(p->comm);

	free(p);
}

int
mkwalk(Packet * p, Packet *o)
{

	int i;
	
	p->ver = o->ver;
	p->comm = o->comm;
	p->pdu.type = GetNextRequest;
	p->pdu.reqid = o->pdu.reqid;
	p->pdu.errstat = 0;
	p->pdu.erridx = 0;
	p->pdu.nbind= o->pdu.nbind;

	freevarbind(p->pdu.varbind,p->pdu.nbind);

	p->pdu.varbind = (Tuple*)emalloc(sizeof(Tuple)*p->pdu.nbind);

	for(i=0;i<p->pdu.nbind;i++){
		p->pdu.varbind[i].oid = smprint("%s",o->pdu.varbind[i].oid);
		p->pdu.varbind[i].value = nil;
	}

	return 1;
}



/* The End-Of-MIB message is not always supported */
/* so we need some heuristics to do correct walks */
/* All oids of a packet must complain to be able to walk */
int
canwalk(Packet *orig, Packet *p1, Packet *p2)
{

	int origlen, oid1len;
	int i;
	
	if ( p1 == nil || p2 == nil ) {
		error(DEBUG,"canwalk(): if no packet we can walk to get one");
		return 1;
	}

	for (i=0;i<p1->pdu.nbind;i++) {

		oid1len = strlen(p1->pdu.varbind[i].oid);
		origlen = strlen(orig->pdu.varbind[i].oid);

		if ( strncmp(p1->pdu.varbind[i].oid, p2->pdu.varbind[i].oid, oid1len) == 0 ) {
			error(DEBUG,"canwalk(): error: p1 and p2 are the same");
			return 0;
		}

		if ( strncmp(orig->pdu.varbind[i].oid,p2->pdu.varbind[i].oid,origlen) != 0 ) {
			error(DEBUG,"canwalk(): error: p2 is out of branch: %s -- %s",orig->pdu.varbind[i].oid,p2->pdu.varbind[i].oid);
			return 0;
		}
	}

	/* if i am here, i can walk other packet */
	return 1;

}

/* PDU is an IMPLICIT SEQUENCE
  * and that means the SEQUENCE id
  * is changed using one of those
  * specified and them should be encoded as they
  * should appear in the packet, ie, no more
  * transformations are done */

Elem
mkpdu(int type, Elist *el)
{
	Elem e;

	e.tag.class = type;
	e.tag.num = SEQUENCE;
	e.val.tag = VSeq;
	e.val.u.seqval = el;

	return e;
}

/* this is used to compose set-request message */
/* only int and octectstring */
Elem
char2asn(char *data)
{
	/* for normal requests */
	if ( data == nil ) {
		error(DEBUG,"char2asn(): data = nil");
		return Null();
	}

	/* for set-request only octect-strings and integers */
	if ( data[0] < '0' || data[0] > '9' ) 
		return mkoctet((uchar*)data,strlen(data));
	else 
		return mkint(atoi(data));
	

}

Elem
oid2asn(char *s)
{
	Elem e;
	Ints *o;
	char *f[MAX_ELEM];
	int i, n;
	char *aux;

	aux = strdup(s);
	n = getfields(aux, f, MAX_ELEM, 0, ".");

	if ( n < 0 || atoi(f[0]) < 1 ) {
		free(aux);
		return e;
	}

	o = (Ints*) emalloc9p(sizeof(Ints)*n);
	o->len = n;
	for(i=0;i<n;i++)
		o->data[i] = atoi(f[i]);
	
	e = mkoid(o);

	error(DEBUG,"oid2asn(): oid = %s o->len = %d",s,o->len);
	free(aux);
	freeints(o);
	return e;

}

char*
asn2char(Elem e)
{
	Value v;
	int i;
	Fmt fmt;
	char *aux;
	mpint *m;

	fmtstrinit(&fmt);

	v = e.val;

	/* chek if i have a valid type before trying to decode it */
	if ( e.tag.class > 30 ) {
		switch(e.tag.class) {
		case 	IpAddress: fmtprint(&fmt,"%V",v.u.octetsval->data); break;
		case Counter:
		case Gauge:  
		case TimeTicks:
		case Opaque:
		case NsapAddress:
		case Counter64:
		case UIInteger32:
			m = betomp(v.u.octetsval->data, v.u.octetsval->len, nil);
			aux = mptoa(m,10,nil,0);
			fmtprint(&fmt,"%s",aux);
			mpfree(m);
			free(aux);
			break;
		case EndOfMIB:
			fmtprint(&fmt,"End of MIB");
			break;
		default:
			fmtprint(&fmt,"unknown class type %x and val tag %x",e.tag.class,e.val.tag);
			break;
		}

		return fmtstrflush(&fmt); 
	}

	/* TODO: complete all known types */
	if ( e.tag.class == Universal )
		switch(v.tag){
		case VBool: fmtprint(&fmt,"BOOLEAN");break;
		case VInt: fmtprint(&fmt,"%d",v.u.intval); break;
		case VOctets: /* heuristic is need to know if it is a printable string or not */	
			if ( isalnum(v.u.octetsval->data[0]) ) {
				aux = (char*)emalloc(v.u.octetsval->len+1);
				memcpy(aux,v.u.octetsval->data, v.u.octetsval->len);
				aux[v.u.octetsval->len] = '\0';
				fmtprint(&fmt,"%s",aux);
				free(aux);
			} else 
				for(i=0;i<v.u.octetsval->len;i++)
					fmtprint(&fmt,"%.2x",v.u.octetsval->data[i]);
			break;
		case VBigInt: fmtprint(&fmt,"BINGINT");break;
		case VReal: fmtprint(&fmt,"REAL");break;	
		case VOther: fmtprint(&fmt,"OTHER"); break;
		case VBitString: fmtprint(&fmt,"BITSTRING");break;
		case VNull: fmtprint(&fmt,"NULL");break;
		case VEOC: fmtprint(&fmt,"EOC");break;
		case VObjId:
			for(i = 0; i<v.u.objidval->len; i++)
				fmtprint(&fmt,"%d%s", v.u.objidval->data[i],(i != (v.u.objidval->len-1)) ? ".":"");	
			break; 
		case VString: fmtprint(&fmt,"%s",v.u.stringval); break;
		case VSeq: fmtprint(&fmt,"SEQUENCE");break;
		case VSet: fmtprint(&fmt,"SET");break;
		default:
			fmtprint(&fmt,"unknown class type %x and val tag %x",e.tag.class,e.val.tag);
			break;
		}

	return fmtstrflush(&fmt);
}

/* ----
Var-Bind-List =SEQUENCE 		
	Var-Bind = SEQUENCE		
		oid	OBJECT IDENTIFIER	
		value	NULL
		oid	OBJECT IDENTIFIER
		value NULL
---*/
Elem
mkvarbind(Packet *p)
{
	Elist *z=nil;
	int i;
	Elem oid;
	Elem value;
	
	for(i=0; i < p->pdu.nbind; i++) {	
		value = char2asn(p->pdu.varbind[i].value);
		oid = oid2asn(p->pdu.varbind[i].oid);
		z = mkel(mkseq(mkel(oid, mkel(value, nil))), z);
	}

	return mkseq(z);
}


/* ----
getBulkRequest PDU ASN1
	Request-ID INTEGER
	Non-Repeaters INTEGER
	Max-Repetition INTEGER
	Var-Bind-List =SEQUENCE 		
---- */

Elist *
mkgetbulkreq(Packet *p)
{
	Elist *e;

	e = mkel(mkpdu((int)p->pdu.type,
			mkel(mkint(p->pdu.reqid),
			mkel(mkint(p->pdu.nrep),
			mkel(mkint(p->pdu.maxrep),
			mkel(mkvarbind(p),nil))))),nil);

	return e;

}

/* -----
getNextRequest PDU ASN1				
		Request-ID INTEGER			
		Error-Status INTEGER			
		Error-Index INTEGER			
		Var-Bind-List =SEQUENCE 		
-----*/	
			
Elist *
mkgetnreq(Packet *p)
{
	Elist *e;
	e =  mkel(mkpdu((int)p->pdu.type,
			mkel(mkint(p->pdu.reqid),
			mkel(mkint(p->pdu.errstat),
			mkel(mkint(p->pdu.erridx),
			mkel(mkvarbind(p),nil))))),nil);
	return e;
}


/* ASN.1 snmp packet encoding 	*/
/* message = SEQUECE	*/
/*		version INTEGER	*/	
/*		community OCTECT_STRING	*/
/*		PDU = IMPLICIT SEQUENCE	*/

int
encpkt(Packet *p, Bytes **pbytes)
{
	int err;
	Bytes *bsnmp;
	Elem snmp;
	Elem ver;
	Elem comm;
	Elist * pdu;
	
	switch(p->pdu.type){
	case GetRequest: 
	case SetRequest:
	case GetNextRequest: pdu = mkgetnreq(p); break;
	case GetBulkRequest: pdu = mkgetbulkreq(p); break;
	default: error(DEBUG,"encpkt(): unknown op\n");return 0; break;
	}
	
	if ( p->comm == nil ) {
		error(DEBUG,"encpkt(): community not found");
		return 0;
	}
	ver = mkint(p->ver);
	comm = mkoctet((uchar*)p->comm,strlen(p->comm));
	snmp = mkseq(mkel(ver, mkel(comm, pdu)));

	err = encode(snmp, &bsnmp);
	if ( err != ASN_OK ) {
		error(DEBUG,"encpkt(): cannot encode the pkt: %d %r\n",err);
		freevalfields(&snmp.val);
		return 0;
	}
	
	*pbytes = bsnmp;
	freevalfields(&snmp.val);
	return 1;
}

/* translates an asn1 stream to the pdu struct */
int
dec_pdu(Elist *el, Pdu * p)
{
	Elem *e, *last;
	Elist *pel;
	Elist *elvar = nil;
	Elist *elbind = nil;
	int tabsize, i;

	e = &el->hd;

	if( is_int(e,&p->reqid) ) {
		pel = el->tl; 
		e = &pel->hd;
		if ( is_int(e,&p->errstat) ) {
			pel = pel->tl; 
			e= &pel->hd;
			if ( is_int(e,&p->erridx) ) {
				pel = pel->tl;
				e = &pel->hd;
			} else {
				error(DEBUG,"dec_pdu(): reqid not found");
				return 0;
			}
		} else {
			error(DEBUG,"dec_pdu(): errstat not found");
			return 0;
		}
	}else {
		error(DEBUG,"dec_pdu(): erridx not found");
		return 0;
	}
	p->nbind = 0;

	if ( is_seq(e,&elvar) ) {
		e = &elvar->hd;
		tabsize = elistlen(elvar);
		if ( tabsize <= 0 ) {
			error(DEBUG,"dec_pdu(): varbind length %d",tabsize);
			return 0;
		}
		p->varbind = (Tuple*)emalloc(sizeof(Tuple)*tabsize);
		for(i=0;i<tabsize;i++) {
			if ( is_seq(e,&elbind) ) {
				pel=elbind;
				last = &pel->hd;
				pel = pel->tl;
				if ( last->val.tag == VObjId ) {
					/* store them in reverse order to correct what the ber code did */
					p->varbind[tabsize-1-i].oid = asn2char(*last);
					p->varbind[tabsize-1-i].value = asn2char(pel->hd);
					p->nbind++;
				} else {
					free(p->varbind);
					error(DEBUG,"dec_pdu(): Error decoding var list");
					return 0;
					}
			} else {
				free(p->varbind);
				error(DEBUG,"dec_pdu(): Error decoding bind list");
				return 0;
				}
			elvar = elvar->tl;
			e = &elvar->hd;
		}
	} else {
		error(DEBUG,"dec_pdu(): Error decoding var list");
		return 0;
		}

	return 1;
}

/* decode a pdu with a quick hack: */
/* the asn1 code treats the unknown objects as octetstring; */
/* so we can encode our pdu with the octet-string tag */
/* and change that tag with the sequence-type that is the correct */
/* ( pdu definition says it is IMPLICIT SEQUENCE and that is a */
/* sequence with the correspondant pdu id 0xa1..0xa8*/

int
pdu2asn(Elem *epdu, Elem *e)
{
	int err;
	Bytes *ocpdu;
 
	if ( encode(*epdu,&ocpdu) == ASN_OK)
		*ocpdu->data = 0x30; /* pdu is a sequence, so we make it that */
	else {
		error(DEBUG,"pdu2asn(): error encoding octet-pdu: ");
		freebytes(ocpdu);
		return 0;
	}
	
	 
	/* decode what we encoded, to obtain the pdu */
	err = decode(ocpdu->data,ocpdu->len,e);
	freebytes(ocpdu);
	
	if ( err != ASN_OK ) {
		error(DEBUG,"pdu2asn(): error decoding pdu: ");
		return 0;
	}

	return 1;
}

int
decpkt(uchar *buff, int n, Packet *p) 
{
	Bytes *bbuff, *bcomm;
	Elem emess, *ever, *ecomm, pdu, *epdu;
	Elist *elmess, *el, *elpdu;
	int pver;

	bbuff = makebytes(buff, n);

	if (decode(bbuff->data,bbuff->len,&emess) != ASN_OK) {
		error(DEBUG,"decpkt: error in ASN.1 decode");
		freebytes(bbuff);
		return 0;
	}

	freebytes(bbuff);

	if( !is_seq(&emess,&elmess)  ) {
		error(DEBUG,"decpkt: cannot recognize message");
		freevalfields(&emess.val);
		return 0;
	}
	if ( elistlen(elmess) != 3 ) {
		error(DEBUG,"decpkt: cannot recognize message: n = %d",elistlen(elmess));
		freevalfields(&emess.val);
		return 0;
	}


	ever = &elmess->hd; el = elmess->tl;
	ecomm = &el->hd; el = el->tl;
	epdu = &el->hd;

	if ( !is_int(ever,&pver) ) {
		error(DEBUG,"decpkt: error decoding version field");
		freevalfields(&emess.val);
		return 0;
	}

	p->ver = pver;
	

	if ( !is_octetstring(ecomm,&bcomm) ) {
		error(DEBUG,"decpkt: error decoding community field");
		freevalfields(&emess.val);
		return 0;
	}
	

	p->comm =  (char*)emalloc9p(bcomm->len);
	memcpy(p->comm,(char*)bcomm->data,bcomm->len);
	p->comm[bcomm->len-1]='\0';

	if ( ! pdu2asn(epdu,&pdu) ) {
		error(DEBUG,"decpkt: error in pdu2asn");
		freevalfields(&pdu.val);
		freevalfields(&emess.val);
		return 0;
	}

	if ( !is_seq(&pdu,&elpdu) ){
		error(DEBUG,"decpkt: error decoding pdu");
		freevalfields(&pdu.val);
		freevalfields(&emess.val);
		return 0;
	}

	if ( ! dec_pdu(elpdu,&p->pdu) ) {
		error(DEBUG,"decpkt(): error decoding pdu field");
		freevalfields(&emess.val);
		return 0;
	}

	freevalfields(&emess.val);
	freevalfields(&pdu.val);
	return 1;
}

/* timeout
  * Code from rsc */
int
alarmtr(void*,  char *why)
{

	if(!readflag)
		return 0;

	if(strcmp(why, "alarm") == 0) {
		alarmflag++;
		return 1;
	}
	return 0;
}



int 
talk(Session *s, Packet *req, Packet *recv)
{
	int n, reqid,fd;
	uchar recvbuf[DMAX_PKT];
	Bytes * snmp = nil;
	
	if ( req->pdu.type == SetRequest )
		req->comm = strdup(s->rwcomm);
	else
		req->comm = strdup(s->rocomm);

	if (! encpkt(req, &snmp)) {
		error(DEBUG,"talk(): Error encoding Packet to ASN1");
		freebytes(snmp);
		return 0;
	}
	reqid = req->pdu.reqid;


	if ((fd = dial(netmkaddr(s->host, "udp", s->port), 0, 0, 0)) < 0) {
		error(DEBUG,"talk(): error dialing %s",s->host);
		freebytes(snmp);
		return 0;
	}

	
	do {	
		n = write(fd, snmp->data, snmp->len);
		
		if (n != snmp->len) {
			error(DEBUG,"talk(): Error writing to %d",fd);
			freebytes(snmp);
			return 0;
		}

		freebytes(snmp);

		/* set alarm to exit the while when timeout reached */
		alarmflag = 0;
		readflag = 1;
		threadnotify(alarmtr, 1);
		alarm(s->timeout*1000);

		do {
			n = read(fd, recvbuf, DMAX_PKT);

			if (n > 0) {
				if ( ! decpkt(recvbuf, n, recv) )
					continue;
				else if ( reqid == recv->pdu.reqid ) {
					close(fd);
					alarm(0);
					return 1;
				}
			}
		} while(!alarmflag);

		if (alarmflag) {
				s->retries--;
				error(DEBUG,"talk(): Timeout %d s. Wil retry %d times",s->timeout,s->retries);
		}
		

	} while (s->retries > 0);
	
	close(fd);
	
	alarm(0);

	return 0;
}


/* from rsc */
char * 
dumppkt(Packet *p) 
{

	int i,e;
	Fmt fmt;

	fmtstrinit(&fmt);
	
	for(i=0;i<p->pdu.nbind;i++) {
		e = p->pdu.errstat;
		if ( p->pdu.erridx != i+1 )
			e=noError;
		fmtprint(&fmt,"((%s) (%s) (%s))\n", p->pdu.varbind[i].oid, p->pdu.varbind[i].value,pduerror[e]);
	}

	return fmtstrflush(&fmt);
}


char *
doget(Session *s, Packet * req)
{
	char *buff;
	Packet *recv;
	recv = (Packet*)emalloc(sizeof(Packet));

	if ( talk(s, req, recv) ) {
		buff = dumppkt(recv);
		freepacket(recv);
		return buff;
		
	} else {
		error(DEBUG,"doget(): error talking snmp: %r");
		freepacket(recv);
		return nil;
	}
	
}


char *
dowalk(Session *s, Packet *orig)
{
	Packet *recv;
	Packet *req;
	Packet *last;
	char *aux;
	Fmt fmt;

	fmtstrinit(&fmt);

	recv= (Packet*)emalloc9p(sizeof(Packet));

	if ( ! talk(s, orig, recv) ) {
			error(DEBUG,"dowalk(): error talking snmp");
			freepacket(recv);
			return nil;
	}
	aux = dumppkt(recv);
	fmtprint(&fmt,"%s", aux);
	free(aux);
	last = nil;

	while ( canwalk(orig,last, recv) ) {
		
		req = (Packet*)emalloc9p(sizeof(Packet));

		mkwalk(req,recv);

		freepacket(last);
	
		last = recv;
		recv = (Packet*)emalloc9p(sizeof(Packet));
		

		if ( ! talk(s, req, recv) ) {
			error(DEBUG,"dowalk(): error talking snmp");
			freepacket(recv);
			return nil;
		}
		aux = dumppkt(recv);
		fmtprint(&fmt,"%s", aux);
		free(aux);
		freepacket(req);
	
	}
	freepacket(last);
	freepacket(recv);
	return fmtstrflush(&fmt);
}

char *
dobulk(Session *s, Packet * req)
{
	return dowalk(s, req);
	
}

char *
doset(Session *s, Packet * req)
{
	return doget(s, req);

}

char*
dogetn(Session *s, Packet *req)
{
	return doget(s,req);
}

char *
dosnmp(Session *s, Packet *req)
{
	char *buff;

	/* GetNextRequest is used to walk the snmp tree, */
	/* while WalkRequest is used to identify the operation */
	switch(req->pdu.type) {
		case GetRequest:
			buff = doget(s, req);
			break;
		case GetNextRequest:
			buff = dogetn(s,req);
			break;
		case WalkRequest:
			req->pdu.type = GetNextRequest;
			buff = dowalk(s,req);
			break;
		case GetBulkRequest:
			buff = dobulk(s, req);
			break;
		case SetRequest:
			buff = doset(s, req);
			break;
		default:
			buff = smprint("(dosnmp(): Unknown PDU type %X)\n",req->pdu.type);
			break;
	}

	if ( buff == nil )
		buff =smprint("(%r)\n");

	return buff;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.