1108983Simp/*
2108983Simp * Copyright (c) 2004-2005
3108983Simp *	Hartmut Brandt.
4108983Simp *	All rights reserved.
5108983Simp * Copyright (c) 2001-2003
6108983Simp *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
7108983Simp *	All rights reserved.
8108983Simp *
9108983Simp * Author: Harti Brandt <harti@freebsd.org>
10108983Simp *         Kendy Kutzner
11225861Swblock *
12225861Swblock * Redistribution and use in source and binary forms, with or without
13225861Swblock * modification, are permitted provided that the following conditions
14225861Swblock * are met:
15108983Simp * 1. Redistributions of source code must retain the above copyright
16108983Simp *    notice, this list of conditions and the following disclaimer.
17108983Simp * 2. Redistributions in binary form must reproduce the above copyright
18108983Simp *    notice, this list of conditions and the following disclaimer in the
19108983Simp *    documentation and/or other materials provided with the distribution.
20148471Simp *
21108983Simp * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22139027Sbrueffer * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23146969Smarius * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24139027Sbrueffer * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
25108983Simp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26108983Simp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27108983Simp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28108983Simp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29108983Simp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30108983Simp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31159126Sthompsa * SUCH DAMAGE.
32159126Sthompsa *
33108983Simp * $Begemot: bsnmp/lib/snmpclient.c,v 1.36 2005/10/06 07:14:58 brandt_h Exp $
34187743Ssam *
35225861Swblock * Support functions for SNMP clients.
36225861Swblock */
37187743Ssam#include <sys/types.h>
38187743Ssam#include <sys/time.h>
39159126Sthompsa#include <sys/queue.h>
40159126Sthompsa#include <sys/socket.h>
41159126Sthompsa#include <sys/un.h>
42159126Sthompsa#include <stdio.h>
43108983Simp#include <stdlib.h>
44108983Simp#include <stddef.h>
45147088Sbrooks#include <stdarg.h>
46225861Swblock#include <string.h>
47147088Sbrooks#include <errno.h>
48216983Sjpaetzel#include <unistd.h>
49147088Sbrooks#include <fcntl.h>
50147088Sbrooks#include <netdb.h>
51147088Sbrooks#ifdef HAVE_STDINT_H
52147088Sbrooks#include <stdint.h>
53147088Sbrooks#elif defined(HAVE_INTTYPES_H)
54148642Ssam#include <inttypes.h>
55175683Smtm#endif
56147088Sbrooks#include <limits.h>
57147088Sbrooks#ifdef HAVE_ERR_H
58148642Ssam#include <err.h>
59148642Ssam#endif
60148642Ssam
61148642Ssam#include "support.h"
62148642Ssam#include "asn1.h"
63148642Ssam#include "snmp.h"
64148642Ssam#include "snmpclient.h"
65148642Ssam#include "snmppriv.h"
66148642Ssam
67148642Ssam/* global context */
68148642Ssamstruct snmp_client snmp_client;
69148642Ssam
70148642Ssam/* List of all outstanding requests */
71148642Ssamstruct sent_pdu {
72148642Ssam	int		reqid;
73148642Ssam	struct snmp_pdu	*pdu;
74148642Ssam	struct timeval	time;
75175683Smtm	u_int		retrycount;
76148642Ssam	snmp_send_cb_f	callback;
77148642Ssam	void		*arg;
78108983Simp	void		*timeout_id;
79108983Simp	LIST_ENTRY(sent_pdu) entries;
80108983Simp};
81139281SbruefferLIST_HEAD(sent_pdu_list, sent_pdu);
82108983Simp
83108983Simpstatic struct sent_pdu_list sent_pdus;
84108983Simp
85108983Simp/*
86108983Simp * Prototype table entry. All C-structure produced by the table function must
87108983Simp * start with these two fields. This relies on the fact, that all TAILQ_ENTRY
88108983Simp * are compatible with each other in the sense implied by ANSI-C.
89108983Simp */
90225861Swblockstruct entry {
91152326Semax	TAILQ_ENTRY(entry)	link;
92152326Semax	uint64_t		found;
93175683Smtm};
94152326SemaxTAILQ_HEAD(table, entry);
95152326Semax
96152326Semax/*
97175683Smtm * working list entry. This list is used to hold the Index part of the
98152326Semax * table row's. The entry list and the work list parallel each other.
99152326Semax */
100215195Semaxstruct work {
101215195Semax	TAILQ_ENTRY(work)	link;
102215195Semax	struct asn_oid		index;
103215195Semax};
104215195SemaxTAILQ_HEAD(worklist, work);
105215195Semax
106215195Semax/*
107139281Sbrueffer * Table working data
108134584Sbrooks */
109134584Sbrooksstruct tabwork {
110156782Semax	const struct snmp_table *descr;
111134584Sbrooks	struct table	*table;
112134584Sbrooks	struct worklist	worklist;
113134584Sbrooks	uint32_t	last_change;
114156331Semax	int		first;
115134584Sbrooks	u_int		iter;
116134584Sbrooks	snmp_table_cb_f	callback;
117138175Siedowse	void		*arg;
118138175Siedowse	struct snmp_pdu	pdu;
119175683Smtm};
120138175Siedowse
121138175Siedowse/*
122208060Sdougb * Set the error string
123208060Sdougb */
124208060Sdougbstatic void
125208060Sdougbseterr(struct snmp_client *sc, const char *fmt, ...)
126179804Skmacy{
127153300Siedowse	va_list ap;
128225861Swblock
129153300Siedowse	va_start(ap, fmt);
130153300Siedowse	vsnprintf(sc->error, sizeof(sc->error), fmt, ap);
131153300Siedowse	va_end(ap);
132153300Siedowse}
133153300Siedowse
134153300Siedowse/*
135153300Siedowse * Free the entire table and work list. If table is NULL only the worklist
136153300Siedowse * is freed.
137153300Siedowse */
138153300Siedowsestatic void
139153300Siedowsetable_free(struct tabwork *work, int all)
140153300Siedowse{
141153300Siedowse	struct work *w;
142192198Smaxim	struct entry *e;
143153300Siedowse	const struct snmp_table_entry *d;
144153300Siedowse	u_int i;
145153300Siedowse
146153300Siedowse	while ((w = TAILQ_FIRST(&work->worklist)) != NULL) {
147153300Siedowse		TAILQ_REMOVE(&work->worklist, w, link);
148207020Sthompsa		free(w);
149207020Sthompsa	}
150207020Sthompsa
151207020Sthompsa	if (all == 0)
152207020Sthompsa		return;
153207020Sthompsa
154207020Sthompsa	while ((e = TAILQ_FIRST(work->table)) != NULL) {
155207020Sthompsa		for (i = 0; work->descr->entries[i].syntax != SNMP_SYNTAX_NULL;
156153300Siedowse		    i++) {
157153300Siedowse			d = &work->descr->entries[i];
158108983Simp			if (d->syntax == SNMP_SYNTAX_OCTETSTRING &&
159148471Simp			    (e->found & ((uint64_t)1 << i)))
160148471Simp				free(*(void **)(void *)
161108983Simp				    ((u_char *)e + d->offset));
162108983Simp		}
163108983Simp		TAILQ_REMOVE(work->table, e, link);
164131646Simp		free(e);
165108983Simp	}
166108983Simp}
167108983Simp
168114799Simp/*
169166754Simp * Find the correct table entry for the given variable. If non exists,
170166754Simp * create one.
171119254Simp */
172114852Simpstatic struct entry *
173119254Simptable_find(struct tabwork *work, const struct asn_oid *var)
174108983Simp{
175166701Sjoerg	struct entry *e, *e1;
176166701Sjoerg	struct work *w, *w1;
177166701Sjoerg	u_int i, p, j;
178166701Sjoerg	size_t len;
179166701Sjoerg	u_char *ptr;
180166701Sjoerg	struct asn_oid oid;
181166701Sjoerg
182166701Sjoerg	/* get index */
183166701Sjoerg	asn_slice_oid(&oid, var, work->descr->table.len + 2, var->len);
184166701Sjoerg
185166701Sjoerg	e = TAILQ_FIRST(work->table);
186166701Sjoerg	w = TAILQ_FIRST(&work->worklist);
187166701Sjoerg	while (e != NULL) {
188166701Sjoerg		if (asn_compare_oid(&w->index, &oid) == 0)
189166701Sjoerg			return (e);
190166701Sjoerg		e = TAILQ_NEXT(e, link);
191166701Sjoerg		w = TAILQ_NEXT(w, link);
192166701Sjoerg	}
193166701Sjoerg
194166701Sjoerg	/* Not found create new one */
195166701Sjoerg	if ((e = malloc(work->descr->entry_size)) == NULL) {
196166701Sjoerg		seterr(&snmp_client, "no memory for table entry");
197166701Sjoerg		return (NULL);
198166701Sjoerg	}
199166701Sjoerg	if ((w = malloc(sizeof(*w))) == NULL) {
200166701Sjoerg		seterr(&snmp_client, "no memory for table entry");
201166701Sjoerg		free(e);
202166701Sjoerg		return (NULL);
203166701Sjoerg	}
204166701Sjoerg	w->index = oid;
205139281Sbrueffer	memset(e, 0, work->descr->entry_size);
206123626Snjl
207123626Snjl	/* decode index */
208123626Snjl	p = work->descr->table.len + 2;
209125366Snjl	for (i = 0; i < work->descr->index_size; i++) {
210123626Snjl		switch (work->descr->entries[i].syntax) {
211123626Snjl
212125366Snjl		  case SNMP_SYNTAX_INTEGER:
213125366Snjl			if (var->len < p + 1) {
214125366Snjl				seterr(&snmp_client, "bad index: need integer");
215125366Snjl				goto err;
216125366Snjl			}
217125366Snjl			if (var->subs[p] > INT32_MAX) {
218125366Snjl				seterr(&snmp_client,
219125366Snjl				    "bad index: integer too large");
220125366Snjl				goto err;
221125366Snjl			}
222168495Spjd			*(int32_t *)(void *)((u_char *)e +
223168495Spjd			    work->descr->entries[i].offset) = var->subs[p++];
224168495Spjd			break;
225168495Spjd
226168495Spjd		  case SNMP_SYNTAX_OCTETSTRING:
227168495Spjd			if (var->len < p + 1) {
228168495Spjd				seterr(&snmp_client,
229168495Spjd				    "bad index: need string length");
230168495Spjd				goto err;
231168495Spjd			}
232168497Spjd			len = var->subs[p++];
233168495Spjd			if (var->len < p + len) {
234168495Spjd				seterr(&snmp_client,
235168495Spjd				    "bad index: string too short");
236168495Spjd				goto err;
237168495Spjd			}
238168495Spjd			if ((ptr = malloc(len + 1)) == NULL) {
239168495Spjd				seterr(&snmp_client,
240168495Spjd				    "no memory for index string");
241168495Spjd				goto err;
242168495Spjd			}
243168495Spjd			for (j = 0; j < len; j++) {
244168495Spjd				if (var->subs[p] > UCHAR_MAX) {
245168495Spjd					seterr(&snmp_client,
246168495Spjd					    "bad index: char too large");
247168495Spjd					free(ptr);
248168495Spjd					goto err;
249168495Spjd				}
250168495Spjd				ptr[j] = var->subs[p++];
251168495Spjd			}
252168495Spjd			ptr[j] = '\0';
253170976Snjl			*(u_char **)(void *)((u_char *)e +
254170976Snjl			    work->descr->entries[i].offset) = ptr;
255170976Snjl			*(size_t *)(void *)((u_char *)e +
256170976Snjl			    work->descr->entries[i].offset + sizeof(u_char *))
257170976Snjl			    = len;
258170976Snjl			break;
259170976Snjl
260170976Snjl		  case SNMP_SYNTAX_OID:
261170976Snjl			if (var->len < p + 1) {
262170976Snjl				seterr(&snmp_client,
263170976Snjl				    "bad index: need oid length");
264170976Snjl				goto err;
265170976Snjl			}
266108983Simp			oid.len = var->subs[p++];
267108983Simp			if (var->len < p + oid.len) {
268225861Swblock				seterr(&snmp_client,
269225861Swblock				    "bad index: oid too short");
270225861Swblock				goto err;
271225861Swblock			}
272225861Swblock			for (j = 0; j < oid.len; j++)
273108983Simp				oid.subs[j] = var->subs[p++];
274108983Simp			*(struct asn_oid *)(void *)((u_char *)e +
275108983Simp			    work->descr->entries[i].offset) = oid;
276108983Simp			break;
277108983Simp
278108983Simp		  case SNMP_SYNTAX_IPADDRESS:
279108983Simp			if (var->len < p + 4) {
280108983Simp				seterr(&snmp_client,
281108983Simp				    "bad index: need ip-address");
282108983Simp				goto err;
283108983Simp			}
284108983Simp			for (j = 0; j < 4; j++) {
285108983Simp				if (var->subs[p] > 0xff) {
286108983Simp					seterr(&snmp_client,
287108983Simp					    "bad index: ipaddress too large");
288121493Snjl					goto err;
289121493Snjl				}
290225861Swblock				((u_char *)e +
291121493Snjl				    work->descr->entries[i].offset)[j] =
292121493Snjl				    var->subs[p++];
293121493Snjl			}
294121493Snjl			break;
295121493Snjl
296121493Snjl		  case SNMP_SYNTAX_GAUGE:
297121493Snjl			if (var->len < p + 1) {
298121493Snjl				seterr(&snmp_client,
299121493Snjl				    "bad index: need unsigned");
300121493Snjl				goto err;
301121493Snjl			}
302121493Snjl			if (var->subs[p] > UINT32_MAX) {
303220168Strasz				seterr(&snmp_client,
304170976Snjl				    "bad index: unsigned too large");
305121493Snjl				goto err;
306121493Snjl			}
307121493Snjl			*(uint32_t *)(void *)((u_char *)e +
308121493Snjl			    work->descr->entries[i].offset) = var->subs[p++];
309121493Snjl			break;
310121493Snjl
311121493Snjl		  case SNMP_SYNTAX_COUNTER:
312121493Snjl		  case SNMP_SYNTAX_TIMETICKS:
313121493Snjl		  case SNMP_SYNTAX_COUNTER64:
314121493Snjl		  case SNMP_SYNTAX_NULL:
315121493Snjl		  case SNMP_SYNTAX_NOSUCHOBJECT:
316220168Strasz		  case SNMP_SYNTAX_NOSUCHINSTANCE:
317220168Strasz		  case SNMP_SYNTAX_ENDOFMIBVIEW:
318225861Swblock			abort();
319220168Strasz		}
320220168Strasz		e->found |= (uint64_t)1 << i;
321220168Strasz	}
322220168Strasz
323220168Strasz	/* link into the correct place */
324220168Strasz	e1 = TAILQ_FIRST(work->table);
325108983Simp	w1 = TAILQ_FIRST(&work->worklist);
326	while (e1 != NULL) {
327		if (asn_compare_oid(&w1->index, &w->index) > 0)
328			break;
329		e1 = TAILQ_NEXT(e1, link);
330		w1 = TAILQ_NEXT(w1, link);
331	}
332	if (e1 == NULL) {
333		TAILQ_INSERT_TAIL(work->table, e, link);
334		TAILQ_INSERT_TAIL(&work->worklist, w, link);
335	} else {
336		TAILQ_INSERT_BEFORE(e1, e, link);
337		TAILQ_INSERT_BEFORE(w1, w, link);
338	}
339
340	return (e);
341
342  err:
343	/*
344	 * Error happend. Free all octet string index parts and the entry
345	 * itself.
346	 */
347	for (i = 0; i < work->descr->index_size; i++) {
348		if (work->descr->entries[i].syntax == SNMP_SYNTAX_OCTETSTRING &&
349		    (e->found & ((uint64_t)1 << i)))
350			free(*(void **)(void *)((u_char *)e +
351			    work->descr->entries[i].offset));
352	}
353	free(e);
354	free(w);
355	return (NULL);
356}
357
358/*
359 * Assign the value
360 */
361static int
362table_value(const struct snmp_table *descr, struct entry *e,
363    const struct snmp_value *b)
364{
365	u_int i;
366	u_char *ptr;
367
368	for (i = descr->index_size;
369	    descr->entries[i].syntax != SNMP_SYNTAX_NULL; i++)
370		if (descr->entries[i].subid ==
371		    b->var.subs[descr->table.len + 1])
372			break;
373	if (descr->entries[i].syntax == SNMP_SYNTAX_NULL)
374		return (0);
375
376	/* check syntax */
377	if (b->syntax != descr->entries[i].syntax) {
378		seterr(&snmp_client, "bad syntax (%u instead of %u)", b->syntax,
379		    descr->entries[i].syntax);
380		return (-1);
381	}
382
383	switch (b->syntax) {
384
385	  case SNMP_SYNTAX_INTEGER:
386		*(int32_t *)(void *)((u_char *)e + descr->entries[i].offset) =
387		    b->v.integer;
388		break;
389
390	  case SNMP_SYNTAX_OCTETSTRING:
391		if ((ptr = malloc(b->v.octetstring.len + 1)) == NULL) {
392			seterr(&snmp_client, "no memory for string");
393			return (-1);
394		}
395		memcpy(ptr, b->v.octetstring.octets, b->v.octetstring.len);
396		ptr[b->v.octetstring.len] = '\0';
397		*(u_char **)(void *)((u_char *)e + descr->entries[i].offset) =
398		    ptr;
399		*(size_t *)(void *)((u_char *)e + descr->entries[i].offset +
400		    sizeof(u_char *)) = b->v.octetstring.len;
401		break;
402
403	  case SNMP_SYNTAX_OID:
404		*(struct asn_oid *)(void *)((u_char *)e + descr->entries[i].offset) =
405		    b->v.oid;
406		break;
407
408	  case SNMP_SYNTAX_IPADDRESS:
409		memcpy((u_char *)e + descr->entries[i].offset,
410		    b->v.ipaddress, 4);
411		break;
412
413	  case SNMP_SYNTAX_COUNTER:
414	  case SNMP_SYNTAX_GAUGE:
415	  case SNMP_SYNTAX_TIMETICKS:
416		*(uint32_t *)(void *)((u_char *)e + descr->entries[i].offset) =
417		    b->v.uint32;
418		break;
419
420	  case SNMP_SYNTAX_COUNTER64:
421		*(uint64_t *)(void *)((u_char *)e + descr->entries[i].offset) =
422		    b->v.counter64;
423		break;
424
425	  case SNMP_SYNTAX_NULL:
426	  case SNMP_SYNTAX_NOSUCHOBJECT:
427	  case SNMP_SYNTAX_NOSUCHINSTANCE:
428	  case SNMP_SYNTAX_ENDOFMIBVIEW:
429		abort();
430	}
431	e->found |= (uint64_t)1 << i;
432
433	return (0);
434}
435
436/*
437 * Initialize the first PDU to send
438 */
439static void
440table_init_pdu(const struct snmp_table *descr, struct snmp_pdu *pdu)
441{
442	if (snmp_client.version == SNMP_V1)
443		snmp_pdu_create(pdu, SNMP_PDU_GETNEXT);
444	else {
445		snmp_pdu_create(pdu, SNMP_PDU_GETBULK);
446		pdu->error_index = 10;
447	}
448	if (descr->last_change.len != 0) {
449		pdu->bindings[pdu->nbindings].syntax = SNMP_SYNTAX_NULL;
450		pdu->bindings[pdu->nbindings].var = descr->last_change;
451		pdu->nbindings++;
452		if (pdu->version != SNMP_V1)
453			pdu->error_status++;
454	}
455	pdu->bindings[pdu->nbindings].var = descr->table;
456	pdu->bindings[pdu->nbindings].syntax = SNMP_SYNTAX_NULL;
457	pdu->nbindings++;
458}
459
460/*
461 * Return code:
462 *	0  - End Of Table
463 * 	-1 - Error
464 *	-2 - Last change changed - again
465 *	+1 - ok, continue
466 */
467static int
468table_check_response(struct tabwork *work, const struct snmp_pdu *resp)
469{
470	const struct snmp_value *b;
471	struct entry *e;
472
473	if (resp->error_status != SNMP_ERR_NOERROR) {
474		if (snmp_client.version == SNMP_V1 &&
475		    resp->error_status == SNMP_ERR_NOSUCHNAME &&
476		    resp->error_index ==
477		    ((work->descr->last_change.len == 0) ? 1 : 2))
478			/* EOT */
479			return (0);
480		/* Error */
481		seterr(&snmp_client, "error fetching table: status=%d index=%d",
482		    resp->error_status, resp->error_index);
483		return (-1);
484	}
485
486	for (b = resp->bindings; b < resp->bindings + resp->nbindings; b++) {
487		if (work->descr->last_change.len != 0 && b == resp->bindings) {
488			if (!asn_is_suboid(&work->descr->last_change, &b->var) ||
489			    b->var.len != work->descr->last_change.len + 1 ||
490			    b->var.subs[work->descr->last_change.len] != 0) {
491				seterr(&snmp_client,
492				    "last_change: bad response");
493				return (-1);
494			}
495			if (b->syntax != SNMP_SYNTAX_TIMETICKS) {
496				seterr(&snmp_client,
497				    "last_change: bad syntax %u", b->syntax);
498				return (-1);
499			}
500			if (work->first) {
501				work->last_change = b->v.uint32;
502				work->first = 0;
503
504			} else if (work->last_change != b->v.uint32) {
505				if (++work->iter >= work->descr->max_iter) {
506					seterr(&snmp_client,
507					    "max iteration count exceeded");
508					return (-1);
509				}
510				table_free(work, 1);
511				return (-2);
512			}
513
514			continue;
515		}
516		if (!asn_is_suboid(&work->descr->table, &b->var) ||
517		    b->syntax == SNMP_SYNTAX_ENDOFMIBVIEW)
518			return (0);
519
520		if ((e = table_find(work, &b->var)) == NULL)
521			return (-1);
522		if (table_value(work->descr, e, b))
523			return (-1);
524	}
525	return (+1);
526}
527
528/*
529 * Check table consistency
530 */
531static int
532table_check_cons(struct tabwork *work)
533{
534	struct entry *e;
535
536	TAILQ_FOREACH(e, work->table, link)
537		if ((e->found & work->descr->req_mask) !=
538		    work->descr->req_mask) {
539			if (work->descr->last_change.len == 0) {
540				if (++work->iter >= work->descr->max_iter) {
541					seterr(&snmp_client,
542					    "max iteration count exceeded");
543					return (-1);
544				}
545				return (-2);
546			}
547			seterr(&snmp_client, "inconsistency detected %llx %llx",
548			    e->found, work->descr->req_mask);
549			return (-1);
550		}
551	return (0);
552}
553
554/*
555 * Fetch a table. Returns 0 if ok, -1 on errors.
556 * This is the synchronous variant.
557 */
558int
559snmp_table_fetch(const struct snmp_table *descr, void *list)
560{
561	struct snmp_pdu resp;
562	struct tabwork work;
563	int ret;
564
565	work.descr = descr;
566	work.table = (struct table *)list;
567	work.iter = 0;
568	TAILQ_INIT(work.table);
569	TAILQ_INIT(&work.worklist);
570	work.callback = NULL;
571	work.arg = NULL;
572
573  again:
574	/*
575	 * We come to this label when the code detects that the table
576	 * has changed while fetching it.
577	 */
578	work.first = 1;
579	work.last_change = 0;
580	table_init_pdu(descr, &work.pdu);
581
582	for (;;) {
583		if (snmp_dialog(&work.pdu, &resp)) {
584			table_free(&work, 1);
585			return (-1);
586		}
587		if ((ret = table_check_response(&work, &resp)) == 0) {
588			snmp_pdu_free(&resp);
589			break;
590		}
591		if (ret == -1) {
592			snmp_pdu_free(&resp);
593			table_free(&work, 1);
594			return (-1);
595		}
596		if (ret == -2) {
597			snmp_pdu_free(&resp);
598			goto again;
599		}
600
601		work.pdu.bindings[work.pdu.nbindings - 1].var =
602		    resp.bindings[resp.nbindings - 1].var;
603
604		snmp_pdu_free(&resp);
605	}
606
607	if ((ret = table_check_cons(&work)) == -1) {
608		table_free(&work, 1);
609		return (-1);
610	}
611	if (ret == -2) {
612		table_free(&work, 1);
613		goto again;
614	}
615	/*
616	 * Free index list
617	 */
618	table_free(&work, 0);
619	return (0);
620}
621
622/*
623 * Callback for table
624 */
625static void
626table_cb(struct snmp_pdu *req __unused, struct snmp_pdu *resp, void *arg)
627{
628	struct tabwork *work = arg;
629	int ret;
630
631	if (resp == NULL) {
632		/* timeout */
633		seterr(&snmp_client, "no response to fetch table request");
634		table_free(work, 1);
635		work->callback(work->table, work->arg, -1);
636		free(work);
637		return;
638	}
639
640	if ((ret = table_check_response(work, resp)) == 0) {
641		/* EOT */
642		snmp_pdu_free(resp);
643
644		if ((ret = table_check_cons(work)) == -1) {
645			/* error happend */
646			table_free(work, 1);
647			work->callback(work->table, work->arg, -1);
648			free(work);
649			return;
650		}
651		if (ret == -2) {
652			/* restart */
653  again:
654			table_free(work, 1);
655			work->first = 1;
656			work->last_change = 0;
657			table_init_pdu(work->descr, &work->pdu);
658			if (snmp_pdu_send(&work->pdu, table_cb, work) == -1) {
659				work->callback(work->table, work->arg, -1);
660				free(work);
661				return;
662			}
663			return;
664		}
665		/*
666		 * Free index list
667		 */
668		table_free(work, 0);
669		work->callback(work->table, work->arg, 0);
670		free(work);
671		return;
672	}
673
674	if (ret == -1) {
675		/* error */
676		snmp_pdu_free(resp);
677		table_free(work, 1);
678		work->callback(work->table, work->arg, -1);
679		free(work);
680		return;
681	}
682
683	if (ret == -2) {
684		/* again */
685		snmp_pdu_free(resp);
686		goto again;
687	}
688
689	/* next part */
690
691	work->pdu.bindings[work->pdu.nbindings - 1].var =
692	    resp->bindings[resp->nbindings - 1].var;
693
694	snmp_pdu_free(resp);
695
696	if (snmp_pdu_send(&work->pdu, table_cb, work) == -1) {
697		table_free(work, 1);
698		work->callback(work->table, work->arg, -1);
699		free(work);
700		return;
701	}
702}
703
704int
705snmp_table_fetch_async(const struct snmp_table *descr, void *list,
706    snmp_table_cb_f func, void *arg)
707{
708	struct tabwork *work;
709
710	if ((work = malloc(sizeof(*work))) == NULL) {
711		seterr(&snmp_client, "%s", strerror(errno));
712		return (-1);
713	}
714
715	work->descr = descr;
716	work->table = (struct table *)list;
717	work->iter = 0;
718	TAILQ_INIT(work->table);
719	TAILQ_INIT(&work->worklist);
720
721	work->callback = func;
722	work->arg = arg;
723
724	/*
725	 * Start by sending the first PDU
726	 */
727	work->first = 1;
728	work->last_change = 0;
729	table_init_pdu(descr, &work->pdu);
730
731	if (snmp_pdu_send(&work->pdu, table_cb, work) == -1) {
732		free(work);
733		work = NULL;
734		return (-1);
735	}
736	return (0);
737}
738
739/*
740 * Append an index to an oid
741 */
742int
743snmp_oid_append(struct asn_oid *oid, const char *fmt, ...)
744{
745	va_list	va;
746	int	size;
747	char	*nextptr;
748	const u_char *str;
749	size_t	len;
750	struct in_addr ina;
751	int ret;
752
753	va_start(va, fmt);
754
755	size = 0;
756
757	ret = 0;
758	while (*fmt != '\0') {
759		switch (*fmt++) {
760		  case 'i':
761			/* just an integer more */
762			if (oid->len + 1 > ASN_MAXOIDLEN) {
763				warnx("%s: OID too long for integer", __func__);
764				ret = -1;
765				break;
766			}
767			oid->subs[oid->len++] = va_arg(va, asn_subid_t);
768			break;
769
770		  case 'a':
771			/* append an IP address */
772			if (oid->len + 4 > ASN_MAXOIDLEN) {
773				warnx("%s: OID too long for ip-addr", __func__);
774				ret = -1;
775				break;
776			}
777			ina = va_arg(va, struct in_addr);
778			ina.s_addr = ntohl(ina.s_addr);
779			oid->subs[oid->len++] = (ina.s_addr >> 24) & 0xff;
780			oid->subs[oid->len++] = (ina.s_addr >> 16) & 0xff;
781			oid->subs[oid->len++] = (ina.s_addr >> 8) & 0xff;
782			oid->subs[oid->len++] = (ina.s_addr >> 0) & 0xff;
783			break;
784
785		  case 's':
786			/* append a null-terminated string,
787			 * length is computed */
788			str = (const u_char *)va_arg(va, const char *);
789			len = strlen((const char *)str);
790			if (oid->len + len + 1 > ASN_MAXOIDLEN) {
791				warnx("%s: OID too long for string", __func__);
792				ret = -1;
793				break;
794			}
795			oid->subs[oid->len++] = len;
796			while (len--)
797				oid->subs[oid->len++] = *str++;
798			break;
799
800		  case '(':
801			/* the integer value between ( and ) is stored
802			 * in size */
803			size = strtol(fmt, &nextptr, 10);
804			if (*nextptr != ')')
805				abort();
806			fmt = ++nextptr;
807			break;
808
809		  case 'b':
810			/* append `size` characters */
811			str = (const u_char *)va_arg(va, const char *);
812			if (oid->len + size > ASN_MAXOIDLEN) {
813				warnx("%s: OID too long for string", __func__);
814				ret = -1;
815				break;
816			}
817			while (size--)
818				oid->subs[oid->len++] = *str++;
819			break;
820
821		  case 'c':
822			/* get size and the octets from the arguments */
823			size = va_arg(va, size_t);
824			str = va_arg(va, const u_char *);
825			if (oid->len + size + 1 > ASN_MAXOIDLEN) {
826				warnx("%s: OID too long for string", __func__);
827				ret = -1;
828				break;
829			}
830			oid->subs[oid->len++] = size;
831			while (size--)
832				oid->subs[oid->len++] = *str++;
833			break;
834
835		  default:
836			abort();
837		}
838	}
839	va_end(va);
840	return (ret);
841}
842
843/*
844 * Initialize a client structure
845 */
846void
847snmp_client_init(struct snmp_client *c)
848{
849	memset(c, 0, sizeof(*c));
850
851	c->version = SNMP_V2c;
852	c->trans = SNMP_TRANS_UDP;
853	c->chost = NULL;
854	c->cport = NULL;
855
856	strcpy(c->read_community, "public");
857	strcpy(c->write_community, "private");
858
859	c->security_model = SNMP_SECMODEL_USM;
860	strcpy(c->cname, "");
861
862	c->timeout.tv_sec = 3;
863	c->timeout.tv_usec = 0;
864	c->retries = 3;
865	c->dump_pdus = 0;
866	c->txbuflen = c->rxbuflen = 10000;
867
868	c->fd = -1;
869
870	c->max_reqid = INT32_MAX;
871	c->min_reqid = 0;
872	c->next_reqid = 0;
873
874	c->engine.max_msg_size = 1500; /* XXX */
875}
876
877
878/*
879 * Open UDP client socket
880 */
881static int
882open_client_udp(const char *host, const char *port)
883{
884	int error;
885	char *ptr;
886	struct addrinfo hints, *res0, *res;
887
888	/* copy host- and portname */
889	if (snmp_client.chost == NULL) {
890		if ((snmp_client.chost = malloc(1 + sizeof(DEFAULT_HOST)))
891		    == NULL) {
892			seterr(&snmp_client, "%s", strerror(errno));
893			return (-1);
894		}
895		strcpy(snmp_client.chost, DEFAULT_HOST);
896	}
897	if (host != NULL) {
898		if ((ptr = malloc(1 + strlen(host))) == NULL) {
899			seterr(&snmp_client, "%s", strerror(errno));
900			return (-1);
901		}
902		free(snmp_client.chost);
903		snmp_client.chost = ptr;
904		strcpy(snmp_client.chost, host);
905	}
906	if (snmp_client.cport == NULL) {
907		if ((snmp_client.cport = malloc(1 + sizeof(DEFAULT_PORT)))
908		    == NULL) {
909			seterr(&snmp_client, "%s", strerror(errno));
910			return (-1);
911		}
912		strcpy(snmp_client.cport, DEFAULT_PORT);
913	}
914	if (port != NULL) {
915		if ((ptr = malloc(1 + strlen(port))) == NULL) {
916			seterr(&snmp_client, "%s", strerror(errno));
917			return (-1);
918		}
919		free(snmp_client.cport);
920		snmp_client.cport = ptr;
921		strcpy(snmp_client.cport, port);
922	}
923
924	/* open connection */
925	memset(&hints, 0, sizeof(hints));
926	hints.ai_flags = AI_CANONNAME;
927	hints.ai_family = AF_INET;
928	hints.ai_socktype = SOCK_DGRAM;
929	hints.ai_protocol = 0;
930	error = getaddrinfo(snmp_client.chost, snmp_client.cport, &hints, &res0);
931	if (error != 0) {
932		seterr(&snmp_client, "%s: %s", snmp_client.chost,
933		    gai_strerror(error));
934		return (-1);
935	}
936	res = res0;
937	for (;;) {
938		if ((snmp_client.fd = socket(res->ai_family, res->ai_socktype,
939		    res->ai_protocol)) == -1) {
940			if ((res = res->ai_next) == NULL) {
941				seterr(&snmp_client, "%s", strerror(errno));
942				freeaddrinfo(res0);
943				return (-1);
944			}
945		} else if (connect(snmp_client.fd, res->ai_addr,
946		    res->ai_addrlen) == -1) {
947			if ((res = res->ai_next) == NULL) {
948				seterr(&snmp_client, "%s", strerror(errno));
949				freeaddrinfo(res0);
950				(void)close(snmp_client.fd);
951				snmp_client.fd = -1;
952				return (-1);
953			}
954		} else
955			break;
956	}
957	freeaddrinfo(res0);
958	return (0);
959}
960
961static void
962remove_local(void)
963{
964	(void)remove(snmp_client.local_path);
965}
966
967/*
968 * Open local socket
969 */
970static int
971open_client_local(const char *path)
972{
973	struct sockaddr_un sa;
974	char *ptr;
975	int stype;
976
977	if (snmp_client.chost == NULL) {
978		if ((snmp_client.chost = malloc(1 + sizeof(DEFAULT_LOCAL)))
979		    == NULL) {
980			seterr(&snmp_client, "%s", strerror(errno));
981			return (-1);
982		}
983		strcpy(snmp_client.chost, DEFAULT_LOCAL);
984	}
985	if (path != NULL) {
986		if ((ptr = malloc(1 + strlen(path))) == NULL) {
987			seterr(&snmp_client, "%s", strerror(errno));
988			return (-1);
989		}
990		free(snmp_client.chost);
991		snmp_client.chost = ptr;
992		strcpy(snmp_client.chost, path);
993	}
994
995	if (snmp_client.trans == SNMP_TRANS_LOC_DGRAM)
996		stype = SOCK_DGRAM;
997	else
998		stype = SOCK_STREAM;
999
1000	if ((snmp_client.fd = socket(PF_LOCAL, stype, 0)) == -1) {
1001		seterr(&snmp_client, "%s", strerror(errno));
1002		return (-1);
1003	}
1004
1005	snprintf(snmp_client.local_path, sizeof(snmp_client.local_path),
1006	    "%s", SNMP_LOCAL_PATH);
1007
1008	if (mktemp(snmp_client.local_path) == NULL) {
1009		seterr(&snmp_client, "%s", strerror(errno));
1010		(void)close(snmp_client.fd);
1011		snmp_client.fd = -1;
1012		return (-1);
1013	}
1014
1015	sa.sun_family = AF_LOCAL;
1016	sa.sun_len = sizeof(sa);
1017	strcpy(sa.sun_path, snmp_client.local_path);
1018
1019	if (bind(snmp_client.fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
1020		seterr(&snmp_client, "%s", strerror(errno));
1021		(void)close(snmp_client.fd);
1022		snmp_client.fd = -1;
1023		(void)remove(snmp_client.local_path);
1024		return (-1);
1025	}
1026	atexit(remove_local);
1027
1028	sa.sun_family = AF_LOCAL;
1029	sa.sun_len = offsetof(struct sockaddr_un, sun_path) +
1030	    strlen(snmp_client.chost);
1031	strncpy(sa.sun_path, snmp_client.chost, sizeof(sa.sun_path) - 1);
1032	sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
1033
1034	if (connect(snmp_client.fd, (struct sockaddr *)&sa, sa.sun_len) == -1) {
1035		seterr(&snmp_client, "%s", strerror(errno));
1036		(void)close(snmp_client.fd);
1037		snmp_client.fd = -1;
1038		(void)remove(snmp_client.local_path);
1039		return (-1);
1040	}
1041	return (0);
1042}
1043
1044/*
1045 * SNMP_OPEN
1046 */
1047int
1048snmp_open(const char *host, const char *port, const char *readcomm,
1049    const char *writecomm)
1050{
1051	struct timeval tout;
1052
1053	/* still open ? */
1054	if (snmp_client.fd != -1) {
1055		errno = EBUSY;
1056		seterr(&snmp_client, "%s", strerror(errno));
1057		return (-1);
1058	}
1059
1060	/* copy community strings */
1061	if (readcomm != NULL)
1062		strlcpy(snmp_client.read_community, readcomm,
1063		    sizeof(snmp_client.read_community));
1064	if (writecomm != NULL)
1065		strlcpy(snmp_client.write_community, writecomm,
1066		    sizeof(snmp_client.write_community));
1067
1068	switch (snmp_client.trans) {
1069
1070	  case SNMP_TRANS_UDP:
1071		if (open_client_udp(host, port) != 0)
1072			return (-1);
1073		break;
1074
1075	  case SNMP_TRANS_LOC_DGRAM:
1076	  case SNMP_TRANS_LOC_STREAM:
1077		if (open_client_local(host) != 0)
1078			return (-1);
1079		break;
1080
1081	  default:
1082		seterr(&snmp_client, "bad transport mapping");
1083		return (-1);
1084	}
1085	tout.tv_sec = 0;
1086	tout.tv_usec = 0;
1087	if (setsockopt(snmp_client.fd, SOL_SOCKET, SO_SNDTIMEO,
1088	    &tout, sizeof(struct timeval)) == -1) {
1089		seterr(&snmp_client, "%s", strerror(errno));
1090		(void)close(snmp_client.fd);
1091		snmp_client.fd = -1;
1092		if (snmp_client.local_path[0] != '\0')
1093			(void)remove(snmp_client.local_path);
1094		return (-1);
1095	}
1096
1097	/* initialize list */
1098	LIST_INIT(&sent_pdus);
1099
1100	return (0);
1101}
1102
1103
1104/*
1105 * SNMP_CLOSE
1106 *
1107 * closes connection to snmp server
1108 * - function cannot fail
1109 * - clears connection
1110 * - clears list of sent pdus
1111 *
1112 * input:
1113 *  void
1114 * return:
1115 *  void
1116 */
1117void
1118snmp_close(void)
1119{
1120	struct sent_pdu *p1;
1121
1122	if (snmp_client.fd != -1) {
1123		(void)close(snmp_client.fd);
1124		snmp_client.fd = -1;
1125		if (snmp_client.local_path[0] != '\0')
1126			(void)remove(snmp_client.local_path);
1127	}
1128	while(!LIST_EMPTY(&sent_pdus)){
1129		p1 = LIST_FIRST(&sent_pdus);
1130		if (p1->timeout_id != NULL)
1131			snmp_client.timeout_stop(p1->timeout_id);
1132		LIST_REMOVE(p1, entries);
1133		free(p1);
1134	}
1135	free(snmp_client.chost);
1136	free(snmp_client.cport);
1137}
1138
1139/*
1140 * initialize a snmp_pdu structure
1141 */
1142void
1143snmp_pdu_create(struct snmp_pdu *pdu, u_int op)
1144{
1145	memset(pdu, 0, sizeof(struct snmp_pdu));
1146
1147	if (op == SNMP_PDU_SET)
1148		strlcpy(pdu->community, snmp_client.write_community,
1149		    sizeof(pdu->community));
1150	else
1151		strlcpy(pdu->community, snmp_client.read_community,
1152		    sizeof(pdu->community));
1153
1154	pdu->type = op;
1155	pdu->version = snmp_client.version;
1156	pdu->error_status = 0;
1157	pdu->error_index = 0;
1158	pdu->nbindings = 0;
1159
1160	if (snmp_client.version != SNMP_V3)
1161		return;
1162
1163	pdu->identifier = ++snmp_client.identifier;
1164	pdu->engine.max_msg_size = snmp_client.engine.max_msg_size;
1165	pdu->flags = 0;
1166	pdu->security_model = snmp_client.security_model;
1167
1168	if (snmp_client.security_model == SNMP_SECMODEL_USM) {
1169		memcpy(&pdu->engine, &snmp_client.engine, sizeof(pdu->engine));
1170		memcpy(&pdu->user, &snmp_client.user, sizeof(pdu->user));
1171		snmp_pdu_init_secparams(pdu);
1172	} else
1173		seterr(&snmp_client, "unknown security model");
1174
1175	if (snmp_client.clen > 0) {
1176		memcpy(pdu->context_engine, snmp_client.cengine,
1177		    snmp_client.clen);
1178		pdu->context_engine_len = snmp_client.clen;
1179	} else {
1180		memcpy(pdu->context_engine, snmp_client.engine.engine_id,
1181		    snmp_client.engine.engine_len);
1182		pdu->context_engine_len = snmp_client.engine.engine_len;
1183	}
1184
1185	strlcpy(pdu->context_name, snmp_client.cname,
1186	    sizeof(pdu->context_name));
1187}
1188
1189/* add pairs of (struct asn_oid, enum snmp_syntax) to an existing pdu */
1190/* added 10/04/02 by kek: check for MAX_BINDINGS */
1191int
1192snmp_add_binding(struct snmp_v1_pdu *pdu, ...)
1193{
1194	va_list ap;
1195	const struct asn_oid *oid;
1196	u_int ret;
1197
1198	va_start(ap, pdu);
1199
1200	ret = pdu->nbindings;
1201	while ((oid = va_arg(ap, const struct asn_oid *)) != NULL) {
1202		if (pdu->nbindings >= SNMP_MAX_BINDINGS){
1203			va_end(ap);
1204			return (-1);
1205		}
1206		pdu->bindings[pdu->nbindings].var = *oid;
1207		pdu->bindings[pdu->nbindings].syntax =
1208		    va_arg(ap, enum snmp_syntax);
1209		pdu->nbindings++;
1210	}
1211	va_end(ap);
1212	return (ret);
1213}
1214
1215
1216static int32_t
1217snmp_next_reqid(struct snmp_client * c)
1218{
1219	int32_t i;
1220
1221	i = c->next_reqid;
1222	if (c->next_reqid >= c->max_reqid)
1223		c->next_reqid = c->min_reqid;
1224	else
1225		c->next_reqid++;
1226	return (i);
1227}
1228
1229/*
1230 * Send request and return request id.
1231 */
1232static int32_t
1233snmp_send_packet(struct snmp_pdu * pdu)
1234{
1235        u_char *buf;
1236        struct asn_buf b;
1237        ssize_t ret;
1238
1239	if ((buf = calloc(1, snmp_client.txbuflen)) == NULL) {
1240		seterr(&snmp_client, "%s", strerror(errno));
1241		return (-1);
1242	}
1243
1244	pdu->request_id = snmp_next_reqid(&snmp_client);
1245
1246	b.asn_ptr = buf;
1247	b.asn_len = snmp_client.txbuflen;
1248	if (snmp_pdu_encode(pdu, &b)) {
1249		seterr(&snmp_client, "%s", strerror(errno));
1250		free(buf);
1251		return (-1);
1252	}
1253
1254	if (snmp_client.dump_pdus)
1255		snmp_pdu_dump(pdu);
1256
1257	if ((ret = send(snmp_client.fd, buf, b.asn_ptr - buf, 0)) == -1) {
1258		seterr(&snmp_client, "%s", strerror(errno));
1259		free(buf);
1260		return (-1);
1261	}
1262	free(buf);
1263
1264	return (pdu->request_id);
1265}
1266
1267/*
1268 * to be called when a snmp request timed out
1269 */
1270static void
1271snmp_timeout(void * listentry_ptr)
1272{
1273	struct sent_pdu *listentry = listentry_ptr;
1274
1275#if 0
1276	warnx("snmp request %i timed out, attempt (%i/%i)",
1277	    listentry->reqid, listentry->retrycount, snmp_client.retries);
1278#endif
1279
1280	listentry->retrycount++;
1281	if (listentry->retrycount > snmp_client.retries) {
1282		/* there is no answer at all */
1283		LIST_REMOVE(listentry, entries);
1284		listentry->callback(listentry->pdu, NULL, listentry->arg);
1285		free(listentry);
1286	} else {
1287		/* try again */
1288		/* new request with new request ID */
1289		listentry->reqid = snmp_send_packet(listentry->pdu);
1290		listentry->timeout_id =
1291		    snmp_client.timeout_start(&snmp_client.timeout,
1292		    snmp_timeout, listentry);
1293	}
1294}
1295
1296int32_t
1297snmp_pdu_send(struct snmp_pdu *pdu, snmp_send_cb_f func, void *arg)
1298{
1299	struct sent_pdu *listentry;
1300	int32_t id;
1301
1302	if ((listentry = malloc(sizeof(struct sent_pdu))) == NULL) {
1303		seterr(&snmp_client, "%s", strerror(errno));
1304		return (-1);
1305	}
1306
1307	/* here we really send */
1308	if ((id = snmp_send_packet(pdu)) == -1) {
1309		free(listentry);
1310		return (-1);
1311	}
1312
1313	/* add entry to list of sent PDUs */
1314	listentry->pdu = pdu;
1315	if (gettimeofday(&listentry->time, NULL) == -1)
1316		warn("gettimeofday() failed");
1317
1318	listentry->reqid = pdu->request_id;
1319	listentry->callback = func;
1320	listentry->arg = arg;
1321	listentry->retrycount=1;
1322	listentry->timeout_id =
1323	    snmp_client.timeout_start(&snmp_client.timeout, snmp_timeout,
1324	    listentry);
1325
1326	LIST_INSERT_HEAD(&sent_pdus, listentry, entries);
1327
1328	return (id);
1329}
1330
1331/*
1332 * Receive an SNMP packet.
1333 *
1334 * tv controls how we wait for a packet: if tv is a NULL pointer,
1335 * the receive blocks forever, if tv points to a structure with all
1336 * members 0 the socket is polled, in all other cases tv specifies the
1337 * maximum time to wait for a packet.
1338 *
1339 * Return:
1340 *	-1 on errors
1341 *	0 on timeout
1342 *	+1 if packet received
1343 */
1344static int
1345snmp_receive_packet(struct snmp_pdu *pdu, struct timeval *tv)
1346{
1347	int dopoll, setpoll;
1348	int flags;
1349	int saved_errno;
1350	u_char *buf;
1351	int ret;
1352	struct asn_buf abuf;
1353	int32_t ip;
1354#ifdef bsdi
1355	int optlen;
1356#else
1357	socklen_t optlen;
1358#endif
1359
1360	if ((buf = calloc(1, snmp_client.rxbuflen)) == NULL) {
1361		seterr(&snmp_client, "%s", strerror(errno));
1362		return (-1);
1363	}
1364	dopoll = setpoll = 0;
1365	flags = 0;
1366	if (tv != NULL) {
1367		/* poll or timeout */
1368		if (tv->tv_sec != 0 || tv->tv_usec != 0) {
1369			/* wait with timeout */
1370			if (setsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO,
1371			    tv, sizeof(*tv)) == -1) {
1372				seterr(&snmp_client, "setsockopt: %s",
1373				    strerror(errno));
1374				free(buf);
1375				return (-1);
1376			}
1377			optlen = sizeof(*tv);
1378			if (getsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO,
1379			    tv, &optlen) == -1) {
1380				seterr(&snmp_client, "getsockopt: %s",
1381				    strerror(errno));
1382				free(buf);
1383				return (-1);
1384			}
1385			/* at this point tv_sec and tv_usec may appear
1386			 * as 0. This happens for timeouts lesser than
1387			 * the clock granularity. The kernel rounds these to
1388			 * 0 and this would result in a blocking receive.
1389			 * Instead of an else we check tv_sec and tv_usec
1390			 * again below and if this rounding happens,
1391			 * switch to a polling receive. */
1392		}
1393		if (tv->tv_sec == 0 && tv->tv_usec == 0) {
1394			/* poll */
1395			dopoll = 1;
1396			if ((flags = fcntl(snmp_client.fd, F_GETFL, 0)) == -1) {
1397				seterr(&snmp_client, "fcntl: %s",
1398				    strerror(errno));
1399				free(buf);
1400				return (-1);
1401			}
1402			if (!(flags & O_NONBLOCK)) {
1403				setpoll = 1;
1404				flags |= O_NONBLOCK;
1405				if (fcntl(snmp_client.fd, F_SETFL, flags) == -1) {
1406					seterr(&snmp_client, "fcntl: %s",
1407					    strerror(errno));
1408					free(buf);
1409					return (-1);
1410				}
1411			}
1412		}
1413	}
1414	ret = recv(snmp_client.fd, buf, snmp_client.rxbuflen, 0);
1415	saved_errno = errno;
1416	if (tv != NULL) {
1417		if (dopoll) {
1418			if (setpoll) {
1419				flags &= ~O_NONBLOCK;
1420				(void)fcntl(snmp_client.fd, F_SETFL, flags);
1421			}
1422		} else {
1423			tv->tv_sec = 0;
1424			tv->tv_usec = 0;
1425			(void)setsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO,
1426			    tv, sizeof(*tv));
1427		}
1428	}
1429	if (ret == -1) {
1430		free(buf);
1431		if (errno == EAGAIN || errno == EWOULDBLOCK)
1432			return (0);
1433		seterr(&snmp_client, "recv: %s", strerror(saved_errno));
1434		return (-1);
1435	}
1436	if (ret == 0) {
1437		/* this happens when we have a streaming socket and the
1438		 * remote side has closed it */
1439		free(buf);
1440		seterr(&snmp_client, "recv: socket closed by peer");
1441		errno = EPIPE;
1442		return (-1);
1443	}
1444
1445	abuf.asn_ptr = buf;
1446	abuf.asn_len = ret;
1447
1448	memset(pdu, 0, sizeof(*pdu));
1449	if (snmp_client.security_model == SNMP_SECMODEL_USM) {
1450		memcpy(&pdu->engine, &snmp_client.engine, sizeof(pdu->engine));
1451		memcpy(&pdu->user, &snmp_client.user, sizeof(pdu->user));
1452		snmp_pdu_init_secparams(pdu);
1453	}
1454
1455	if (SNMP_CODE_OK != (ret = snmp_pdu_decode(&abuf, pdu, &ip))) {
1456		seterr(&snmp_client, "snmp_decode_pdu: failed %d", ret);
1457		free(buf);
1458		return (-1);
1459	}
1460
1461	free(buf);
1462	if (snmp_client.dump_pdus)
1463		snmp_pdu_dump(pdu);
1464
1465	snmp_client.engine.engine_time = pdu->engine.engine_time;
1466	snmp_client.engine.engine_boots = pdu->engine.engine_boots;
1467
1468	return (+1);
1469}
1470
1471static int
1472snmp_deliver_packet(struct snmp_pdu * resp)
1473{
1474	struct sent_pdu *listentry;
1475
1476	if (resp->type != SNMP_PDU_RESPONSE) {
1477		warn("ignoring snmp pdu %u", resp->type);
1478		return (-1);
1479	}
1480
1481	LIST_FOREACH(listentry, &sent_pdus, entries)
1482		if (listentry->reqid == resp->request_id)
1483			break;
1484	if (listentry == NULL)
1485		return (-1);
1486
1487	LIST_REMOVE(listentry, entries);
1488	listentry->callback(listentry->pdu, resp, listentry->arg);
1489
1490	snmp_client.timeout_stop(listentry->timeout_id);
1491
1492	free(listentry);
1493	return (0);
1494}
1495
1496int
1497snmp_receive(int blocking)
1498{
1499	int ret;
1500
1501	struct timeval tv;
1502	struct snmp_pdu * resp;
1503
1504	memset(&tv, 0, sizeof(tv));
1505
1506	resp = malloc(sizeof(struct snmp_pdu));
1507	if (resp == NULL) {
1508		seterr(&snmp_client, "no memory for returning PDU");
1509		return (-1) ;
1510	}
1511
1512	if ((ret = snmp_receive_packet(resp, blocking ? NULL : &tv)) <= 0) {
1513		free(resp);
1514		return (ret);
1515	}
1516	ret = snmp_deliver_packet(resp);
1517	snmp_pdu_free(resp);
1518	free(resp);
1519	return (ret);
1520}
1521
1522
1523/*
1524 * Check a GETNEXT response. Here we have three possible outcomes: -1 an
1525 * unexpected error happened. +1 response is ok and is within the table 0
1526 * response is ok, but is behind the table or error is NOSUCHNAME. The req
1527 * should point to a template PDU which contains the base OIDs and the
1528 * syntaxes. This is really only useful to sweep non-sparse tables.
1529 */
1530static int
1531ok_getnext(const struct snmp_pdu * req, const struct snmp_pdu * resp)
1532{
1533	u_int i;
1534
1535	if (resp->version != req->version) {
1536		warnx("SNMP GETNEXT: response has wrong version");
1537		return (-1);
1538	}
1539
1540	if (resp->error_status == SNMP_ERR_NOSUCHNAME)
1541		return (0);
1542
1543	if (resp->error_status != SNMP_ERR_NOERROR) {
1544		warnx("SNMP GETNEXT: error %d", resp->error_status);
1545		return (-1);
1546	}
1547	if (resp->nbindings != req->nbindings) {
1548		warnx("SNMP GETNEXT: bad number of bindings in response");
1549		return (-1);
1550	}
1551	for (i = 0; i < req->nbindings; i++) {
1552		if (!asn_is_suboid(&req->bindings[i].var,
1553		    &resp->bindings[i].var)) {
1554			if (i != 0)
1555				warnx("SNMP GETNEXT: inconsistent table "
1556				    "response");
1557			return (0);
1558		}
1559		if (resp->version != SNMP_V1 &&
1560		    resp->bindings[i].syntax == SNMP_SYNTAX_ENDOFMIBVIEW)
1561			return (0);
1562
1563		if (resp->bindings[i].syntax != req->bindings[i].syntax) {
1564			warnx("SNMP GETNEXT: bad syntax in response");
1565			return (0);
1566		}
1567	}
1568	return (1);
1569}
1570
1571/*
1572 * Check a GET response. Here we have three possible outcomes: -1 an
1573 * unexpected error happened. +1 response is ok. 0 NOSUCHNAME The req should
1574 * point to a template PDU which contains the OIDs and the syntaxes. This
1575 * is only useful for SNMPv1 or single object GETS.
1576 */
1577static int
1578ok_get(const struct snmp_pdu * req, const struct snmp_pdu * resp)
1579{
1580	u_int i;
1581
1582	if (resp->version != req->version) {
1583		warnx("SNMP GET: response has wrong version");
1584		return (-1);
1585	}
1586
1587	if (resp->error_status == SNMP_ERR_NOSUCHNAME)
1588		return (0);
1589
1590	if (resp->error_status != SNMP_ERR_NOERROR) {
1591		warnx("SNMP GET: error %d", resp->error_status);
1592		return (-1);
1593	}
1594
1595	if (resp->nbindings != req->nbindings) {
1596		warnx("SNMP GET: bad number of bindings in response");
1597		return (-1);
1598	}
1599	for (i = 0; i < req->nbindings; i++) {
1600		if (asn_compare_oid(&req->bindings[i].var,
1601		    &resp->bindings[i].var) != 0) {
1602			warnx("SNMP GET: bad OID in response");
1603			return (-1);
1604		}
1605		if (snmp_client.version != SNMP_V1 &&
1606		    (resp->bindings[i].syntax == SNMP_SYNTAX_NOSUCHOBJECT ||
1607		    resp->bindings[i].syntax == SNMP_SYNTAX_NOSUCHINSTANCE))
1608			return (0);
1609		if (resp->bindings[i].syntax != req->bindings[i].syntax) {
1610			warnx("SNMP GET: bad syntax in response");
1611			return (-1);
1612		}
1613	}
1614	return (1);
1615}
1616
1617/*
1618 * Check the response to a SET PDU. We check: - the error status must be 0 -
1619 * the number of bindings must be equal in response and request - the
1620 * syntaxes must be the same in response and request - the OIDs must be the
1621 * same in response and request
1622 */
1623static int
1624ok_set(const struct snmp_pdu * req, const struct snmp_pdu * resp)
1625{
1626	u_int i;
1627
1628	if (resp->version != req->version) {
1629		warnx("SNMP SET: response has wrong version");
1630		return (-1);
1631	}
1632
1633	if (resp->error_status == SNMP_ERR_NOSUCHNAME) {
1634		warnx("SNMP SET: error %d", resp->error_status);
1635		return (0);
1636	}
1637	if (resp->error_status != SNMP_ERR_NOERROR) {
1638		warnx("SNMP SET: error %d", resp->error_status);
1639		return (-1);
1640	}
1641
1642	if (resp->nbindings != req->nbindings) {
1643		warnx("SNMP SET: bad number of bindings in response");
1644		return (-1);
1645	}
1646	for (i = 0; i < req->nbindings; i++) {
1647		if (asn_compare_oid(&req->bindings[i].var,
1648		    &resp->bindings[i].var) != 0) {
1649			warnx("SNMP SET: wrong OID in response to SET");
1650			return (-1);
1651		}
1652		if (resp->bindings[i].syntax != req->bindings[i].syntax) {
1653			warnx("SNMP SET: bad syntax in response");
1654			return (-1);
1655		}
1656	}
1657	return (1);
1658}
1659
1660/*
1661 * Simple checks for response PDUs against request PDUs. Return values: 1=ok,
1662 * 0=nosuchname or similar, -1=failure, -2=no response at all
1663 */
1664int
1665snmp_pdu_check(const struct snmp_pdu *req,
1666    const struct snmp_pdu *resp)
1667{
1668	if (resp == NULL)
1669		return (-2);
1670
1671	switch (req->type) {
1672
1673	  case SNMP_PDU_GET:
1674		return (ok_get(req, resp));
1675
1676	  case SNMP_PDU_SET:
1677		return (ok_set(req, resp));
1678
1679	  case SNMP_PDU_GETNEXT:
1680		return (ok_getnext(req, resp));
1681
1682	}
1683	errx(1, "%s: bad pdu type %i", __func__, req->type);
1684}
1685
1686int
1687snmp_dialog(struct snmp_v1_pdu *req, struct snmp_v1_pdu *resp)
1688{
1689	struct timeval tv = snmp_client.timeout;
1690	struct timeval end;
1691	struct snmp_pdu pdu;
1692	int ret;
1693	int32_t reqid;
1694	u_int i;
1695
1696	/*
1697	 * Make a copy of the request and replace the syntaxes by NULL
1698	 * if this is a GET,GETNEXT or GETBULK.
1699	 */
1700	pdu = *req;
1701	if (pdu.type == SNMP_PDU_GET || pdu.type == SNMP_PDU_GETNEXT ||
1702	    pdu.type == SNMP_PDU_GETBULK) {
1703		for (i = 0; i < pdu.nbindings; i++)
1704			pdu.bindings[i].syntax = SNMP_SYNTAX_NULL;
1705	}
1706
1707	for (i = 0; i <= snmp_client.retries; i++) {
1708		(void)gettimeofday(&end, NULL);
1709		timeradd(&end, &snmp_client.timeout, &end);
1710		if ((reqid = snmp_send_packet(&pdu)) == -1)
1711			return (-1);
1712		for (;;) {
1713			(void)gettimeofday(&tv, NULL);
1714			if (timercmp(&end, &tv, <=))
1715				break;
1716			timersub(&end, &tv, &tv);
1717			if ((ret = snmp_receive_packet(resp, &tv)) == 0)
1718				/* timeout */
1719				break;
1720
1721			if (ret > 0) {
1722				if (reqid == resp->request_id)
1723					return (0);
1724				/* not for us */
1725				(void)snmp_deliver_packet(resp);
1726			}
1727			if (ret < 0 && errno == EPIPE)
1728				/* stream closed */
1729				return (-1);
1730		}
1731	}
1732	errno = ETIMEDOUT;
1733	seterr(&snmp_client, "retry count exceeded");
1734	return (-1);
1735}
1736
1737int
1738snmp_discover_engine(char *passwd)
1739{
1740	char cname[SNMP_ADM_STR32_SIZ];
1741	enum snmp_authentication cap;
1742	enum snmp_privacy cpp;
1743	struct snmp_pdu req, resp;
1744
1745	if (snmp_client.version != SNMP_V3)
1746		seterr(&snmp_client, "wrong version");
1747
1748	strlcpy(cname, snmp_client.user.sec_name, sizeof(cname));
1749	cap = snmp_client.user.auth_proto;
1750	cpp = snmp_client.user.priv_proto;
1751
1752	snmp_client.engine.engine_len = 0;
1753	snmp_client.engine.engine_boots = 0;
1754	snmp_client.engine.engine_time = 0;
1755	snmp_client.user.auth_proto = SNMP_AUTH_NOAUTH;
1756	snmp_client.user.priv_proto = SNMP_PRIV_NOPRIV;
1757	memset(snmp_client.user.sec_name, 0, sizeof(snmp_client.user.sec_name));
1758
1759	snmp_pdu_create(&req, SNMP_PDU_GET);
1760
1761	if (snmp_dialog(&req, &resp) == -1)
1762		 return (-1);
1763
1764	if (resp.version != req.version) {
1765		seterr(&snmp_client, "wrong version");
1766		return (-1);
1767	}
1768
1769	if (resp.error_status != SNMP_ERR_NOERROR) {
1770		seterr(&snmp_client, "Error %d in responce", resp.error_status);
1771		return (-1);
1772	}
1773
1774	snmp_client.engine.engine_len = resp.engine.engine_len;
1775	snmp_client.engine.max_msg_size = resp.engine.max_msg_size;
1776	memcpy(snmp_client.engine.engine_id, resp.engine.engine_id,
1777	    resp.engine.engine_len);
1778
1779	strlcpy(snmp_client.user.sec_name, cname,
1780	    sizeof(snmp_client.user.sec_name));
1781	snmp_client.user.auth_proto = cap;
1782	snmp_client.user.priv_proto = cpp;
1783
1784	if (snmp_client.user.auth_proto == SNMP_AUTH_NOAUTH)
1785		return (0);
1786
1787	if (passwd == NULL ||
1788	    snmp_passwd_to_keys(&snmp_client.user, passwd) != SNMP_CODE_OK ||
1789	    snmp_get_local_keys(&snmp_client.user, snmp_client.engine.engine_id,
1790	    snmp_client.engine.engine_len) != SNMP_CODE_OK)
1791		return (-1);
1792
1793	if (resp.engine.engine_boots != 0)
1794		snmp_client.engine.engine_boots = resp.engine.engine_boots;
1795
1796	if (resp.engine.engine_time != 0) {
1797		snmp_client.engine.engine_time = resp.engine.engine_time;
1798		return (0);
1799	}
1800
1801	snmp_pdu_free(&req);
1802
1803	snmp_pdu_create(&req, SNMP_PDU_GET);
1804	req.engine.engine_boots = 0;
1805	req.engine.engine_time = 0;
1806
1807	if (snmp_dialog(&req, &resp) == -1)
1808		return (-1);
1809
1810	if (resp.version != req.version) {
1811		seterr(&snmp_client, "wrong version");
1812		return (-1);
1813	}
1814
1815	if (resp.error_status != SNMP_ERR_NOERROR) {
1816		seterr(&snmp_client, "Error %d in responce", resp.error_status);
1817		return (-1);
1818	}
1819
1820	snmp_client.engine.engine_boots = resp.engine.engine_boots;
1821	snmp_client.engine.engine_time = resp.engine.engine_time;
1822
1823	snmp_pdu_free(&req);
1824	snmp_pdu_free(&resp);
1825
1826	return (0);
1827}
1828
1829int
1830snmp_client_set_host(struct snmp_client *cl, const char *h)
1831{
1832	char *np;
1833
1834	if (h == NULL) {
1835		if (cl->chost != NULL)
1836			free(cl->chost);
1837		cl->chost = NULL;
1838	} else {
1839		if ((np = malloc(strlen(h) + 1)) == NULL)
1840			return (-1);
1841		strcpy(np, h);
1842		if (cl->chost != NULL)
1843			free(cl->chost);
1844		cl->chost = np;
1845	}
1846	return (0);
1847}
1848
1849int
1850snmp_client_set_port(struct snmp_client *cl, const char *p)
1851{
1852	char *np;
1853
1854	if (p == NULL) {
1855		if (cl->cport != NULL)
1856			free(cl->cport);
1857		cl->cport = NULL;
1858	} else {
1859		if ((np = malloc(strlen(p) + 1)) == NULL)
1860			return (-1);
1861		strcpy(np, p);
1862		if (cl->cport != NULL)
1863			free(cl->cport);
1864		cl->cport = np;
1865	}
1866	return (0);
1867}
1868
1869/*
1870 * parse a server specification
1871 *
1872 * [trans::][community@][server][:port]
1873 */
1874int
1875snmp_parse_server(struct snmp_client *sc, const char *str)
1876{
1877	const char *p, *s = str;
1878
1879	/* look for a double colon */
1880	for (p = s; *p != '\0'; p++) {
1881		if (*p == '\\' && p[1] != '\0') {
1882			p++;
1883			continue;
1884		}
1885		if (*p == ':' && p[1] == ':')
1886			break;
1887	}
1888	if (*p != '\0') {
1889		if (p > s) {
1890			if (p - s == 3 && strncmp(s, "udp", 3) == 0)
1891				sc->trans = SNMP_TRANS_UDP;
1892			else if (p - s == 6 && strncmp(s, "stream", 6) == 0)
1893				sc->trans = SNMP_TRANS_LOC_STREAM;
1894			else if (p - s == 5 && strncmp(s, "dgram", 5) == 0)
1895				sc->trans = SNMP_TRANS_LOC_DGRAM;
1896			else {
1897				seterr(sc, "unknown SNMP transport '%.*s'",
1898				    (int)(p - s), s);
1899				return (-1);
1900			}
1901		}
1902		s = p + 2;
1903	}
1904
1905	/* look for a @ */
1906	for (p = s; *p != '\0'; p++) {
1907		if (*p == '\\' && p[1] != '\0') {
1908			p++;
1909			continue;
1910		}
1911		if (*p == '@')
1912			break;
1913	}
1914
1915	if (*p != '\0') {
1916		if (p - s > SNMP_COMMUNITY_MAXLEN) {
1917			seterr(sc, "community string too long");
1918			return (-1);
1919		}
1920		strncpy(sc->read_community, s, p - s);
1921		sc->read_community[p - s] = '\0';
1922		strncpy(sc->write_community, s, p - s);
1923		sc->write_community[p - s] = '\0';
1924		s = p + 1;
1925	}
1926
1927	/* look for a colon */
1928	for (p = s; *p != '\0'; p++) {
1929		if (*p == '\\' && p[1] != '\0') {
1930			p++;
1931			continue;
1932		}
1933		if (*p == ':')
1934			break;
1935	}
1936
1937	if (*p == ':') {
1938		if (p > s) {
1939			/* host:port */
1940			free(sc->chost);
1941			if ((sc->chost = malloc(p - s + 1)) == NULL) {
1942				seterr(sc, "%s", strerror(errno));
1943				return (-1);
1944			}
1945			strncpy(sc->chost, s, p - s);
1946			sc->chost[p - s] = '\0';
1947		}
1948		/* port */
1949		free(sc->cport);
1950		if ((sc->cport = strdup(p + 1)) == NULL) {
1951			seterr(sc, "%s", strerror(errno));
1952			return (-1);
1953		}
1954
1955	} else if (p > s) {
1956		/* host */
1957		free(sc->chost);
1958		if ((sc->chost = strdup(s)) == NULL) {
1959			seterr(sc, "%s", strerror(errno));
1960			return (-1);
1961		}
1962	}
1963	return (0);
1964}
1965