snmpagent.c revision 122394
1179260Sjb/* 2179260Sjb * Copyright (c) 2001-2003 3179260Sjb * Fraunhofer Institute for Open Communication Systems (FhG Fokus). 4179260Sjb * All rights reserved. 5179260Sjb * 6179260Sjb * Author: Harti Brandt <harti@freebsd.org> 7179260Sjb * 8179260Sjb * Redistribution of this software and documentation and use in source and 9179260Sjb * binary forms, with or without modification, are permitted provided that 10179260Sjb * the following conditions are met: 11179260Sjb * 12179260Sjb * 1. Redistributions of source code or documentation must retain the above 13179260Sjb * copyright notice, this list of conditions and the following disclaimer. 14179260Sjb * 2. Redistributions in binary form must reproduce the above copyright 15179260Sjb * notice, this list of conditions and the following disclaimer in the 16179260Sjb * documentation and/or other materials provided with the distribution. 17179260Sjb * 3. Neither the name of the Institute nor the names of its contributors 18179260Sjb * may be used to endorse or promote products derived from this software 19179260Sjb * without specific prior written permission. 20179260Sjb * 21179260Sjb * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS 22179260Sjb * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 23179260Sjb * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 24179260Sjb * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 25179260Sjb * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26179260Sjb * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27179260Sjb * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 28179260Sjb * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 29179260Sjb * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30179260Sjb * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 31179260Sjb * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32179260Sjb * 33179260Sjb * $Begemot: bsnmp/lib/snmpagent.c,v 1.14 2003/01/30 11:23:00 hbb Exp $ 34179260Sjb * 35179260Sjb * SNMP Agent functions 36179260Sjb */ 37179260Sjb#include <sys/types.h> 38179260Sjb#include <sys/queue.h> 39179260Sjb#include <stdio.h> 40179260Sjb#include <stdlib.h> 41179260Sjb#include <stddef.h> 42179260Sjb#include <stdarg.h> 43179260Sjb#include <string.h> 44179260Sjb 45179260Sjb#include "asn1.h" 46179260Sjb#include "snmp.h" 47179260Sjb#include "snmppriv.h" 48179260Sjb#include "snmpagent.h" 49179260Sjb 50179260Sjbstatic void snmp_debug_func(const char *fmt, ...); 51179260Sjb 52179260Sjbvoid (*snmp_debug)(const char *fmt, ...) = snmp_debug_func; 53179260Sjb 54179260Sjbstruct snmp_node *tree; 55179260Sjbu_int tree_size; 56179260Sjb 57179260Sjb/* 58179260Sjb * Structure to hold dependencies during SET processing 59179260Sjb * The last two members of this structure must be the 60179260Sjb * dependency visible by the user and the user data. 61179260Sjb */ 62179260Sjbstruct depend { 63179260Sjb TAILQ_ENTRY(depend) link; 64179260Sjb size_t len; /* size of data part */ 65179260Sjb snmp_depop_t func; 66179260Sjb struct snmp_dependency dep; 67179260Sjb u_char data[]; 68179260Sjb}; 69179260SjbTAILQ_HEAD(depend_list, depend); 70179260Sjb 71179260Sjb/* 72179260Sjb * Structure to hold atfinish functions during SET processing. 73179260Sjb */ 74179260Sjbstruct finish { 75179260Sjb STAILQ_ENTRY(finish) link; 76179260Sjb snmp_set_finish_t func; 77179260Sjb void *arg; 78179260Sjb}; 79179260SjbSTAILQ_HEAD(finish_list, finish); 80179260Sjb 81179260Sjb/* 82179260Sjb * Set context 83179260Sjb */ 84179260Sjbstruct context { 85179260Sjb struct snmp_context ctx; 86179260Sjb struct depend_list dlist; 87179260Sjb struct finish_list flist; 88179260Sjb const struct snmp_node *node[SNMP_MAX_BINDINGS]; 89179260Sjb struct snmp_scratch scratch[SNMP_MAX_BINDINGS]; 90179260Sjb struct depend *depend; 91179260Sjb}; 92179260Sjb 93179260Sjb#define TR(W) (snmp_trace & SNMP_TRACE_##W) 94179260Sjbu_int snmp_trace = 0; 95179260Sjb 96179260Sjbstatic char oidbuf[ASN_OIDSTRLEN]; 97179260Sjb 98179260Sjb/* 99179260Sjb * Allocate a context 100179260Sjb */ 101179260Sjbstruct snmp_context * 102179260Sjbsnmp_init_context(void) 103179260Sjb{ 104179260Sjb struct context *context; 105179260Sjb 106179260Sjb if ((context = malloc(sizeof(*context))) == NULL) 107179260Sjb return (NULL); 108179260Sjb 109179260Sjb memset(context, 0, sizeof(*context)); 110179260Sjb TAILQ_INIT(&context->dlist); 111179260Sjb STAILQ_INIT(&context->flist); 112179260Sjb 113179260Sjb return (&context->ctx); 114179260Sjb} 115179260Sjb 116179260Sjb/* 117179260Sjb * Find a variable for SET/GET and the first GETBULK pass. 118179260Sjb * Return the node pointer. If the search fails, set the errp to 119179260Sjb * the correct SNMPv2 GET exception code. 120179260Sjb */ 121179260Sjbstatic struct snmp_node * 122179260Sjbfind_node(const struct snmp_value *value, enum snmp_syntax *errp) 123179260Sjb{ 124179260Sjb struct snmp_node *tp; 125179260Sjb 126179260Sjb if (TR(FIND)) 127179260Sjb snmp_debug("find: searching %s", 128179260Sjb asn_oid2str_r(&value->var, oidbuf)); 129179260Sjb 130179260Sjb /* 131179260Sjb * If we have an exact match (the entry in the table is a 132179260Sjb * sub-oid from the variable) we have found what we are for. 133179260Sjb * If the table oid is higher than the variable, there is no match. 134179260Sjb */ 135179260Sjb for (tp = tree; tp < tree + tree_size; tp++) { 136179260Sjb if (asn_is_suboid(&tp->oid, &value->var)) 137179260Sjb goto found; 138179260Sjb if (asn_compare_oid(&tp->oid, &value->var) >= 0) 139179260Sjb break; 140179260Sjb } 141179260Sjb 142179260Sjb if (TR(FIND)) 143179260Sjb snmp_debug("find: no match"); 144179260Sjb *errp = SNMP_SYNTAX_NOSUCHOBJECT; 145179260Sjb return (NULL); 146179260Sjb 147179260Sjb found: 148179260Sjb /* leafs must have a 0 instance identifier */ 149179260Sjb if (tp->type == SNMP_NODE_LEAF && 150179260Sjb (value->var.len != tp->oid.len + 1 || 151179260Sjb value->var.subs[tp->oid.len] != 0)) { 152179260Sjb if (TR(FIND)) 153179260Sjb snmp_debug("find: bad leaf index"); 154179260Sjb *errp = SNMP_SYNTAX_NOSUCHINSTANCE; 155179260Sjb return (NULL); 156179260Sjb } 157179260Sjb if (TR(FIND)) 158179260Sjb snmp_debug("find: found %s", 159179260Sjb asn_oid2str_r(&value->var, oidbuf)); 160179260Sjb return (tp); 161179260Sjb} 162179260Sjb 163179260Sjbstatic struct snmp_node * 164179260Sjbfind_subnode(const struct snmp_value *value) 165179260Sjb{ 166179260Sjb struct snmp_node *tp; 167179260Sjb 168179260Sjb for (tp = tree; tp < tree + tree_size; tp++) { 169179260Sjb if (asn_is_suboid(&value->var, &tp->oid)) 170179260Sjb return (tp); 171179260Sjb } 172179260Sjb return (NULL); 173179260Sjb} 174179260Sjb 175179260Sjb/* 176179260Sjb * Execute a GET operation. The tree is rooted at the global 'root'. 177179260Sjb * Build the response PDU on the fly. If the return code is SNMP_RET_ERR 178179260Sjb * the pdu error status and index will be set. 179179260Sjb */ 180179260Sjbenum snmp_ret 181179260Sjbsnmp_get(struct snmp_pdu *pdu, struct asn_buf *resp_b, 182179260Sjb struct snmp_pdu *resp, void *data) 183179260Sjb{ 184179260Sjb int ret; 185179260Sjb u_int i; 186179260Sjb struct snmp_node *tp; 187179260Sjb enum snmp_syntax except; 188179260Sjb struct context context; 189179260Sjb enum asn_err err; 190179260Sjb 191179260Sjb memset(&context, 0, sizeof(context)); 192179260Sjb context.ctx.data = data; 193179260Sjb 194179260Sjb memset(resp, 0, sizeof(*resp)); 195179260Sjb strcpy(resp->community, pdu->community); 196179260Sjb resp->version = pdu->version; 197179260Sjb resp->type = SNMP_PDU_RESPONSE; 198179260Sjb resp->request_id = pdu->request_id; 199179260Sjb resp->version = pdu->version; 200179260Sjb 201179260Sjb if (snmp_pdu_encode_header(resp_b, resp) != SNMP_CODE_OK) 202179260Sjb /* cannot even encode header - very bad */ 203179260Sjb return (SNMP_RET_IGN); 204179260Sjb 205179260Sjb for (i = 0; i < pdu->nbindings; i++) { 206179260Sjb resp->bindings[i].var = pdu->bindings[i].var; 207179260Sjb if ((tp = find_node(&pdu->bindings[i], &except)) == NULL) { 208179260Sjb if (pdu->version == SNMP_V1) { 209179260Sjb if (TR(GET)) 210179260Sjb snmp_debug("get: nosuchname"); 211179260Sjb pdu->error_status = SNMP_ERR_NOSUCHNAME; 212179260Sjb pdu->error_index = i + 1; 213179260Sjb snmp_pdu_free(resp); 214179260Sjb return (SNMP_RET_ERR); 215179260Sjb } 216179260Sjb if (TR(GET)) 217179260Sjb snmp_debug("get: exception %u", except); 218179260Sjb resp->bindings[i].syntax = except; 219179260Sjb 220179260Sjb } else { 221179260Sjb /* call the action to fetch the value. */ 222179260Sjb resp->bindings[i].syntax = tp->syntax; 223179260Sjb ret = (*tp->op)(&context.ctx, &resp->bindings[i], 224179260Sjb tp->oid.len, tp->index, SNMP_OP_GET); 225179260Sjb if (TR(GET)) 226179260Sjb snmp_debug("get: action returns %d", ret); 227179260Sjb 228179260Sjb if (ret == SNMP_ERR_NOSUCHNAME) { 229179260Sjb if (pdu->version == SNMP_V1) { 230179260Sjb pdu->error_status = SNMP_ERR_NOSUCHNAME; 231179260Sjb pdu->error_index = i + 1; 232179260Sjb snmp_pdu_free(resp); 233179260Sjb return (SNMP_RET_ERR); 234179260Sjb } 235179260Sjb if (TR(GET)) 236179260Sjb snmp_debug("get: exception noSuchInstance"); 237179260Sjb resp->bindings[i].syntax = SNMP_SYNTAX_NOSUCHINSTANCE; 238179260Sjb 239179260Sjb } else if (ret != SNMP_ERR_NOERROR) { 240179260Sjb pdu->error_status = SNMP_ERR_GENERR; 241179260Sjb pdu->error_index = i + 1; 242179260Sjb snmp_pdu_free(resp); 243179260Sjb return (SNMP_RET_ERR); 244179260Sjb } 245179260Sjb } 246179260Sjb resp->nbindings++; 247179260Sjb 248179260Sjb err = snmp_binding_encode(resp_b, &resp->bindings[i]); 249179260Sjb 250179260Sjb if (err == ASN_ERR_EOBUF) { 251179260Sjb pdu->error_status = SNMP_ERR_TOOBIG; 252179260Sjb pdu->error_index = 0; 253179260Sjb snmp_pdu_free(resp); 254179260Sjb return (SNMP_RET_ERR); 255179260Sjb } 256179260Sjb if (err != ASN_ERR_OK) { 257179260Sjb if (TR(GET)) 258179260Sjb snmp_debug("get: binding encoding: %u", err); 259179260Sjb pdu->error_status = SNMP_ERR_GENERR; 260179260Sjb pdu->error_index = i + 1; 261179260Sjb snmp_pdu_free(resp); 262179260Sjb return (SNMP_RET_ERR); 263179260Sjb } 264179260Sjb } 265179260Sjb 266179260Sjb return (snmp_fix_encoding(resp_b, resp)); 267179260Sjb} 268179260Sjb 269179260Sjbstatic struct snmp_node * 270179260Sjbnext_node(const struct snmp_value *value, int *pnext) 271179260Sjb{ 272179260Sjb struct snmp_node *tp; 273179260Sjb 274179260Sjb if (TR(FIND)) 275179260Sjb snmp_debug("next: searching %s", 276179260Sjb asn_oid2str_r(&value->var, oidbuf)); 277179260Sjb 278179260Sjb *pnext = 0; 279179260Sjb for (tp = tree; tp < tree + tree_size; tp++) { 280179260Sjb if (asn_is_suboid(&tp->oid, &value->var)) { 281179260Sjb /* the tree OID is a sub-oid of the requested OID. */ 282179260Sjb if (tp->type == SNMP_NODE_LEAF) { 283179260Sjb if (tp->oid.len == value->var.len) { 284179260Sjb /* request for scalar type */ 285179260Sjb if (TR(FIND)) 286179260Sjb snmp_debug("next: found scalar %s", 287179260Sjb asn_oid2str_r(&tp->oid, oidbuf)); 288179260Sjb return (tp); 289179260Sjb } 290179260Sjb /* try next */ 291179260Sjb } else { 292179260Sjb if (TR(FIND)) 293179260Sjb snmp_debug("next: found column %s", 294179260Sjb asn_oid2str_r(&tp->oid, oidbuf)); 295179260Sjb return (tp); 296179260Sjb } 297179260Sjb } else if (asn_is_suboid(&value->var, &tp->oid) || 298179260Sjb asn_compare_oid(&tp->oid, &value->var) >= 0) { 299179260Sjb if (TR(FIND)) 300179260Sjb snmp_debug("next: found %s", 301179260Sjb asn_oid2str_r(&tp->oid, oidbuf)); 302179260Sjb *pnext = 1; 303179260Sjb return (tp); 304179260Sjb } 305179260Sjb } 306179260Sjb 307179260Sjb if (TR(FIND)) 308179260Sjb snmp_debug("next: failed"); 309179260Sjb 310179260Sjb return (NULL); 311179260Sjb} 312179260Sjb 313179260Sjbstatic enum snmp_ret 314179260Sjbdo_getnext(struct context *context, const struct snmp_value *inb, 315179260Sjb struct snmp_value *outb, struct snmp_pdu *pdu) 316179260Sjb{ 317179260Sjb const struct snmp_node *tp; 318179260Sjb int ret, next; 319179260Sjb 320179260Sjb if ((tp = next_node(inb, &next)) == NULL) 321179260Sjb goto eofMib; 322179260Sjb 323179260Sjb /* retain old variable if we are doing a GETNEXT on an exact 324179260Sjb * matched leaf only */ 325179260Sjb if (tp->type == SNMP_NODE_LEAF || next) 326179260Sjb outb->var = tp->oid; 327179260Sjb else 328179260Sjb outb->var = inb->var; 329179260Sjb 330179260Sjb for (;;) { 331179260Sjb outb->syntax = tp->syntax; 332179260Sjb if (tp->type == SNMP_NODE_LEAF) { 333179260Sjb /* make a GET operation */ 334179260Sjb outb->var.subs[outb->var.len++] = 0; 335179260Sjb ret = (*tp->op)(&context->ctx, outb, tp->oid.len, 336179260Sjb tp->index, SNMP_OP_GET); 337179260Sjb } else { 338179260Sjb /* make a GETNEXT */ 339179260Sjb ret = (*tp->op)(&context->ctx, outb, tp->oid.len, 340179260Sjb tp->index, SNMP_OP_GETNEXT); 341179260Sjb } 342179260Sjb if (ret != SNMP_ERR_NOSUCHNAME) { 343179260Sjb /* got something */ 344179260Sjb if (ret != SNMP_ERR_NOERROR && TR(GETNEXT)) 345179260Sjb snmp_debug("getnext: %s returns %u", 346179260Sjb asn_oid2str(&outb->var), ret); 347179260Sjb break; 348179260Sjb } 349179260Sjb 350179260Sjb /* object has no data - try next */ 351179260Sjb if (++tp == tree + tree_size) 352179260Sjb break; 353179260Sjb outb->var = tp->oid; 354179260Sjb } 355179260Sjb 356179260Sjb if (ret == SNMP_ERR_NOSUCHNAME) { 357179260Sjb eofMib: 358179260Sjb outb->var = inb->var; 359179260Sjb if (pdu->version == SNMP_V1) { 360179260Sjb pdu->error_status = SNMP_ERR_NOSUCHNAME; 361179260Sjb return (SNMP_RET_ERR); 362179260Sjb } 363179260Sjb outb->syntax = SNMP_SYNTAX_ENDOFMIBVIEW; 364179260Sjb 365179260Sjb } else if (ret != SNMP_ERR_NOERROR) { 366179260Sjb pdu->error_status = SNMP_ERR_GENERR; 367179260Sjb return (SNMP_RET_ERR); 368179260Sjb } 369179260Sjb return (SNMP_RET_OK); 370179260Sjb} 371179260Sjb 372179260Sjb 373179260Sjb/* 374179260Sjb * Execute a GETNEXT operation. The tree is rooted at the global 'root'. 375179260Sjb * Build the response PDU on the fly. The return is: 376179260Sjb */ 377179260Sjbenum snmp_ret 378179260Sjbsnmp_getnext(struct snmp_pdu *pdu, struct asn_buf *resp_b, 379179260Sjb struct snmp_pdu *resp, void *data) 380179260Sjb{ 381179260Sjb struct context context; 382179260Sjb u_int i; 383179260Sjb enum asn_err err; 384179260Sjb enum snmp_ret result; 385179260Sjb 386179260Sjb memset(&context, 0, sizeof(context)); 387179260Sjb context.ctx.data = data; 388179260Sjb 389179260Sjb memset(resp, 0, sizeof(*resp)); 390179260Sjb strcpy(resp->community, pdu->community); 391179260Sjb resp->type = SNMP_PDU_RESPONSE; 392179260Sjb resp->request_id = pdu->request_id; 393179260Sjb resp->version = pdu->version; 394179260Sjb 395179260Sjb if (snmp_pdu_encode_header(resp_b, resp)) 396179260Sjb return (SNMP_RET_IGN); 397179260Sjb 398179260Sjb for (i = 0; i < pdu->nbindings; i++) { 399179260Sjb result = do_getnext(&context, &pdu->bindings[i], 400179260Sjb &resp->bindings[i], pdu); 401179260Sjb 402179260Sjb if (result != SNMP_RET_OK) { 403179260Sjb pdu->error_index = i + 1; 404179260Sjb snmp_pdu_free(resp); 405179260Sjb return (result); 406179260Sjb } 407179260Sjb 408179260Sjb resp->nbindings++; 409179260Sjb 410179260Sjb err = snmp_binding_encode(resp_b, &resp->bindings[i]); 411179260Sjb 412179260Sjb if (err == ASN_ERR_EOBUF) { 413179260Sjb pdu->error_status = SNMP_ERR_TOOBIG; 414179260Sjb pdu->error_index = 0; 415179260Sjb snmp_pdu_free(resp); 416179260Sjb return (SNMP_RET_ERR); 417179260Sjb } 418179260Sjb if (err != ASN_ERR_OK) { 419179260Sjb if (TR(GET)) 420179260Sjb snmp_debug("getnext: binding encoding: %u", err); 421179260Sjb pdu->error_status = SNMP_ERR_GENERR; 422179260Sjb pdu->error_index = i + 1; 423179260Sjb snmp_pdu_free(resp); 424179260Sjb return (SNMP_RET_ERR); 425179260Sjb } 426179260Sjb } 427179260Sjb return (snmp_fix_encoding(resp_b, resp)); 428179260Sjb} 429179260Sjb 430179260Sjbenum snmp_ret 431179260Sjbsnmp_getbulk(struct snmp_pdu *pdu, struct asn_buf *resp_b, 432179260Sjb struct snmp_pdu *resp, void *data) 433179260Sjb{ 434179260Sjb struct context context; 435179260Sjb u_int i; 436179260Sjb int cnt; 437179260Sjb u_int non_rep; 438179260Sjb int eomib; 439179260Sjb enum snmp_ret result; 440179260Sjb enum asn_err err; 441179260Sjb 442179260Sjb memset(&context, 0, sizeof(context)); 443179260Sjb context.ctx.data = data; 444179260Sjb 445179260Sjb memset(resp, 0, sizeof(*resp)); 446179260Sjb strcpy(resp->community, pdu->community); 447179260Sjb resp->version = pdu->version; 448179260Sjb resp->type = SNMP_PDU_RESPONSE; 449179260Sjb resp->request_id = pdu->request_id; 450179260Sjb resp->version = pdu->version; 451179260Sjb 452179260Sjb if (snmp_pdu_encode_header(resp_b, resp) != SNMP_CODE_OK) 453179260Sjb /* cannot even encode header - very bad */ 454179260Sjb return (SNMP_RET_IGN); 455179260Sjb 456179260Sjb if ((non_rep = pdu->error_status) > pdu->nbindings) 457179260Sjb non_rep = pdu->nbindings; 458179260Sjb 459179260Sjb /* non-repeaters */ 460179260Sjb for (i = 0; i < non_rep; i++) { 461179260Sjb result = do_getnext(&context, &pdu->bindings[i], 462179260Sjb &resp->bindings[resp->nbindings], pdu); 463179260Sjb 464179260Sjb if (result != SNMP_RET_OK) { 465179260Sjb pdu->error_index = i + 1; 466179260Sjb snmp_pdu_free(resp); 467179260Sjb return (result); 468179260Sjb } 469179260Sjb 470179260Sjb err = snmp_binding_encode(resp_b, 471179260Sjb &resp->bindings[resp->nbindings++]); 472179260Sjb 473179260Sjb if (err == ASN_ERR_EOBUF) 474179260Sjb goto done; 475179260Sjb 476179260Sjb if (err != ASN_ERR_OK) { 477179260Sjb if (TR(GET)) 478179260Sjb snmp_debug("getnext: binding encoding: %u", err); 479179260Sjb pdu->error_status = SNMP_ERR_GENERR; 480179260Sjb pdu->error_index = i + 1; 481179260Sjb snmp_pdu_free(resp); 482179260Sjb return (SNMP_RET_ERR); 483179260Sjb } 484179260Sjb } 485179260Sjb 486179260Sjb if (non_rep == pdu->nbindings) 487179260Sjb goto done; 488179260Sjb 489179260Sjb /* repeates */ 490179260Sjb for (cnt = 0; cnt < pdu->error_index; cnt++) { 491179260Sjb eomib = 1; 492179260Sjb for (i = non_rep; i < pdu->nbindings; i++) { 493179260Sjb if (cnt == 0) 494179260Sjb result = do_getnext(&context, &pdu->bindings[i], 495179260Sjb &resp->bindings[resp->nbindings], pdu); 496179260Sjb else 497179260Sjb result = do_getnext(&context, 498179260Sjb &resp->bindings[resp->nbindings - 499179260Sjb (pdu->nbindings - non_rep)], 500179260Sjb &resp->bindings[resp->nbindings], pdu); 501179260Sjb 502179260Sjb if (result != SNMP_RET_OK) { 503179260Sjb pdu->error_index = i + 1; 504179260Sjb snmp_pdu_free(resp); 505179260Sjb return (result); 506179260Sjb } 507179260Sjb if (resp->bindings[resp->nbindings].syntax != 508179260Sjb SNMP_SYNTAX_ENDOFMIBVIEW) 509179260Sjb eomib = 0; 510179260Sjb 511179260Sjb err = snmp_binding_encode(resp_b, 512179260Sjb &resp->bindings[resp->nbindings++]); 513179260Sjb 514179260Sjb if (err == ASN_ERR_EOBUF) 515179260Sjb goto done; 516179260Sjb 517179260Sjb if (err != ASN_ERR_OK) { 518179260Sjb if (TR(GET)) 519179260Sjb snmp_debug("getnext: binding encoding: %u", err); 520179260Sjb pdu->error_status = SNMP_ERR_GENERR; 521179260Sjb pdu->error_index = i + 1; 522179260Sjb snmp_pdu_free(resp); 523179260Sjb return (SNMP_RET_ERR); 524179260Sjb } 525179260Sjb } 526179260Sjb if (eomib) 527179260Sjb break; 528179260Sjb } 529179260Sjb 530179260Sjb done: 531179260Sjb return (snmp_fix_encoding(resp_b, resp)); 532179260Sjb} 533179260Sjb 534179260Sjb/* 535179260Sjb * Rollback a SET operation. Failed index is 'i'. 536179260Sjb */ 537179260Sjbstatic void 538179260Sjbrollback(struct context *context, struct snmp_pdu *pdu, u_int i) 539179260Sjb{ 540179260Sjb struct snmp_value *b; 541179260Sjb const struct snmp_node *np; 542179260Sjb int ret; 543179260Sjb 544179260Sjb while (i-- > 0) { 545179260Sjb b = &pdu->bindings[i]; 546179260Sjb np = context->node[i]; 547179260Sjb 548179260Sjb context->ctx.scratch = &context->scratch[i]; 549179260Sjb 550179260Sjb ret = (*np->op)(&context->ctx, b, np->oid.len, np->index, 551179260Sjb SNMP_OP_ROLLBACK); 552179260Sjb 553179260Sjb if (ret != SNMP_ERR_NOERROR) { 554179260Sjb snmp_error("set: rollback failed (%d) on variable %s " 555179260Sjb "index %u", ret, asn_oid2str(&b->var), i); 556179260Sjb if (pdu->version != SNMP_V1) { 557179260Sjb pdu->error_status = SNMP_ERR_UNDO_FAILED; 558179260Sjb pdu->error_index = 0; 559179260Sjb } 560179260Sjb } 561179260Sjb } 562179260Sjb} 563179260Sjb 564179260Sjb/* 565179260Sjb * Commit dependencies. 566179260Sjb */ 567179260Sjbint 568179260Sjbsnmp_dep_commit(struct snmp_context *ctx) 569179260Sjb{ 570179260Sjb struct context *context = (struct context *)ctx; 571179260Sjb int ret; 572179260Sjb 573179260Sjb TAILQ_FOREACH(context->depend, &context->dlist, link) { 574179260Sjb ctx->dep = &context->depend->dep; 575179260Sjb 576179260Sjb if (TR(SET)) 577179260Sjb snmp_debug("set: dependency commit %s", 578179260Sjb asn_oid2str(&ctx->dep->obj)); 579179260Sjb 580179260Sjb ret = context->depend->func(ctx, ctx->dep, SNMP_DEPOP_COMMIT); 581179260Sjb 582179260Sjb if (ret != SNMP_ERR_NOERROR) { 583179260Sjb if (TR(SET)) 584179260Sjb snmp_debug("set: dependency failed %d", ret); 585179260Sjb return (ret); 586179260Sjb } 587179260Sjb } 588179260Sjb return (SNMP_ERR_NOERROR); 589179260Sjb} 590179260Sjb 591179260Sjb/* 592179260Sjb * Rollback dependencies 593179260Sjb */ 594179260Sjbint 595179260Sjbsnmp_dep_rollback(struct snmp_context *ctx) 596179260Sjb{ 597179260Sjb struct context *context = (struct context *)ctx; 598179260Sjb int ret, ret1; 599179260Sjb char objbuf[ASN_OIDSTRLEN]; 600179260Sjb char idxbuf[ASN_OIDSTRLEN]; 601179260Sjb 602179260Sjb ret1 = SNMP_ERR_NOERROR; 603179260Sjb while ((context->depend = 604179260Sjb TAILQ_PREV(context->depend, depend_list, link)) != NULL) { 605179260Sjb ctx->dep = &context->depend->dep; 606179260Sjb 607179260Sjb if (TR(SET)) 608179260Sjb snmp_debug("set: dependency rollback %s", 609179260Sjb asn_oid2str(&ctx->dep->obj)); 610179260Sjb 611179260Sjb ret = context->depend->func(ctx, ctx->dep, SNMP_DEPOP_ROLLBACK); 612179260Sjb 613179260Sjb if (ret != SNMP_ERR_NOERROR) { 614179260Sjb snmp_debug("set: dep rollback returns %u: %s %s", ret, 615179260Sjb asn_oid2str_r(&ctx->dep->obj, objbuf), 616179260Sjb asn_oid2str_r(&ctx->dep->idx, idxbuf)); 617179260Sjb if (ret1 == SNMP_ERR_NOERROR) 618179260Sjb ret1 = ret; 619179260Sjb } 620179260Sjb } 621179260Sjb return (ret1); 622179260Sjb} 623179260Sjb 624179260Sjb/* 625179260Sjb * Do a SET operation. 626179260Sjb */ 627179260Sjbenum snmp_ret 628179260Sjbsnmp_set(struct snmp_pdu *pdu, struct asn_buf *resp_b, 629179260Sjb struct snmp_pdu *resp, void *data) 630179260Sjb{ 631179260Sjb int ret; 632179260Sjb u_int i; 633179260Sjb enum snmp_ret code; 634179260Sjb enum asn_err asnerr; 635179260Sjb struct context context; 636179260Sjb const struct snmp_node *np; 637179260Sjb struct finish *f; 638179260Sjb struct depend *d; 639179260Sjb struct snmp_value *b; 640179260Sjb enum snmp_syntax except; 641179260Sjb 642179260Sjb memset(&context, 0, sizeof(context)); 643179260Sjb TAILQ_INIT(&context.dlist); 644179260Sjb STAILQ_INIT(&context.flist); 645179260Sjb context.ctx.data = data; 646179260Sjb 647179260Sjb memset(resp, 0, sizeof(*resp)); 648179260Sjb strcpy(resp->community, pdu->community); 649179260Sjb resp->type = SNMP_PDU_RESPONSE; 650179260Sjb resp->request_id = pdu->request_id; 651179260Sjb resp->version = pdu->version; 652179260Sjb 653179260Sjb if (snmp_pdu_encode_header(resp_b, resp)) 654179260Sjb return (SNMP_RET_IGN); 655179260Sjb 656179260Sjb /* 657179260Sjb * 1. Find all nodes, check that they are writeable and 658179260Sjb * that the syntax is ok, copy over the binding to the response. 659179260Sjb */ 660179260Sjb for (i = 0; i < pdu->nbindings; i++) { 661179260Sjb b = &pdu->bindings[i]; 662179260Sjb 663179260Sjb if ((np = context.node[i] = find_node(b, &except)) == NULL) { 664179260Sjb /* not found altogether or LEAF with wrong index */ 665179260Sjb if (TR(SET)) 666179260Sjb snmp_debug("set: node not found %s", 667179260Sjb asn_oid2str_r(&b->var, oidbuf)); 668179260Sjb if (pdu->version == SNMP_V1) { 669179260Sjb pdu->error_index = i + 1; 670179260Sjb pdu->error_status = SNMP_ERR_NOSUCHNAME; 671179260Sjb } else if ((np = find_subnode(b)) != NULL) { 672179260Sjb /* 2. intermediate object */ 673179260Sjb pdu->error_index = i + 1; 674179260Sjb pdu->error_status = SNMP_ERR_NOT_WRITEABLE; 675179260Sjb } else if (except == SNMP_SYNTAX_NOSUCHOBJECT) { 676179260Sjb pdu->error_index = i + 1; 677179260Sjb pdu->error_status = SNMP_ERR_NO_ACCESS; 678179260Sjb } else { 679179260Sjb pdu->error_index = i + 1; 680179260Sjb pdu->error_status = SNMP_ERR_NO_CREATION; 681179260Sjb } 682179260Sjb snmp_pdu_free(resp); 683179260Sjb return (SNMP_RET_ERR); 684179260Sjb } 685179260Sjb /* 686179260Sjb * 2. write/createable? 687179260Sjb * Can check this for leafs only, because in v2 we have 688179260Sjb * to differentiate between NOT_WRITEABLE and NO_CREATION 689179260Sjb * and only the action routine for COLUMNS knows, whether 690179260Sjb * a column exists. 691179260Sjb */ 692179260Sjb if (np->type == SNMP_NODE_LEAF && 693179260Sjb !(np->flags & SNMP_NODE_CANSET)) { 694179260Sjb if (pdu->version == SNMP_V1) { 695179260Sjb pdu->error_index = i + 1; 696179260Sjb pdu->error_status = SNMP_ERR_NOSUCHNAME; 697179260Sjb } else { 698179260Sjb pdu->error_index = i + 1; 699179260Sjb pdu->error_status = SNMP_ERR_NOT_WRITEABLE; 700179260Sjb } 701179260Sjb snmp_pdu_free(resp); 702179260Sjb return (SNMP_RET_ERR); 703179260Sjb } 704179260Sjb /* 705179260Sjb * 3. Ensure the right syntax 706179260Sjb */ 707179260Sjb if (np->syntax != b->syntax) { 708179260Sjb if (pdu->version == SNMP_V1) { 709179260Sjb pdu->error_index = i + 1; 710179260Sjb pdu->error_status = SNMP_ERR_BADVALUE; /* v2: wrongType */ 711179260Sjb } else { 712179260Sjb pdu->error_index = i + 1; 713179260Sjb pdu->error_status = SNMP_ERR_WRONG_TYPE; 714179260Sjb } 715179260Sjb snmp_pdu_free(resp); 716179260Sjb return (SNMP_RET_ERR); 717179260Sjb } 718179260Sjb /* 719179260Sjb * 4. Copy binding 720179260Sjb */ 721179260Sjb if (snmp_value_copy(&resp->bindings[i], b)) { 722179260Sjb pdu->error_index = i + 1; 723179260Sjb pdu->error_status = SNMP_ERR_GENERR; 724179260Sjb snmp_pdu_free(resp); 725179260Sjb return (SNMP_RET_ERR); 726179260Sjb } 727179260Sjb asnerr = snmp_binding_encode(resp_b, &resp->bindings[i]); 728179260Sjb if (asnerr == ASN_ERR_EOBUF) { 729179260Sjb pdu->error_index = i + 1; 730179260Sjb pdu->error_status = SNMP_ERR_TOOBIG; 731179260Sjb snmp_pdu_free(resp); 732179260Sjb return (SNMP_RET_ERR); 733179260Sjb } else if (asnerr != ASN_ERR_OK) { 734179260Sjb pdu->error_index = i + 1; 735179260Sjb pdu->error_status = SNMP_ERR_GENERR; 736179260Sjb snmp_pdu_free(resp); 737179260Sjb return (SNMP_RET_ERR); 738179260Sjb } 739179260Sjb resp->nbindings++; 740179260Sjb } 741179260Sjb 742179260Sjb code = SNMP_RET_OK; 743179260Sjb 744179260Sjb /* 745179260Sjb * 2. Call the SET method for each node. If a SET fails, rollback 746179260Sjb * everything. Map error codes depending on the version. 747179260Sjb */ 748179260Sjb for (i = 0; i < pdu->nbindings; i++) { 749179260Sjb b = &pdu->bindings[i]; 750179260Sjb np = context.node[i]; 751179260Sjb 752179260Sjb context.ctx.var_index = i + 1; 753179260Sjb context.ctx.scratch = &context.scratch[i]; 754179260Sjb 755179260Sjb ret = (*np->op)(&context.ctx, b, np->oid.len, np->index, 756179260Sjb SNMP_OP_SET); 757179260Sjb 758179260Sjb if (TR(SET)) 759179260Sjb snmp_debug("set: action %s returns %d", np->name, ret); 760179260Sjb 761179260Sjb if (pdu->version == SNMP_V1) { 762179260Sjb switch (ret) { 763179260Sjb case SNMP_ERR_NO_ACCESS: 764179260Sjb ret = SNMP_ERR_NOSUCHNAME; 765179260Sjb break; 766179260Sjb case SNMP_ERR_WRONG_TYPE: 767179260Sjb /* should no happen */ 768179260Sjb ret = SNMP_ERR_BADVALUE; 769179260Sjb break; 770179260Sjb case SNMP_ERR_WRONG_LENGTH: 771179260Sjb ret = SNMP_ERR_BADVALUE; 772179260Sjb break; 773179260Sjb case SNMP_ERR_WRONG_ENCODING: 774179260Sjb /* should not happen */ 775179260Sjb ret = SNMP_ERR_BADVALUE; 776179260Sjb break; 777179260Sjb case SNMP_ERR_WRONG_VALUE: 778179260Sjb ret = SNMP_ERR_BADVALUE; 779179260Sjb break; 780179260Sjb case SNMP_ERR_NO_CREATION: 781179260Sjb ret = SNMP_ERR_NOSUCHNAME; 782179260Sjb break; 783179260Sjb case SNMP_ERR_INCONS_VALUE: 784179260Sjb ret = SNMP_ERR_BADVALUE; 785179260Sjb break; 786179260Sjb case SNMP_ERR_RES_UNAVAIL: 787179260Sjb ret = SNMP_ERR_GENERR; 788179260Sjb break; 789179260Sjb case SNMP_ERR_COMMIT_FAILED: 790179260Sjb ret = SNMP_ERR_GENERR; 791179260Sjb break; 792179260Sjb case SNMP_ERR_UNDO_FAILED: 793179260Sjb ret = SNMP_ERR_GENERR; 794179260Sjb break; 795179260Sjb case SNMP_ERR_AUTH_ERR: 796179260Sjb /* should not happen */ 797179260Sjb ret = SNMP_ERR_GENERR; 798179260Sjb break; 799179260Sjb case SNMP_ERR_NOT_WRITEABLE: 800179260Sjb ret = SNMP_ERR_NOSUCHNAME; 801179260Sjb break; 802179260Sjb case SNMP_ERR_INCONS_NAME: 803179260Sjb ret = SNMP_ERR_BADVALUE; 804179260Sjb break; 805179260Sjb } 806179260Sjb } 807179260Sjb if (ret != SNMP_ERR_NOERROR) { 808179260Sjb pdu->error_index = i + 1; 809179260Sjb pdu->error_status = ret; 810179260Sjb 811179260Sjb rollback(&context, pdu, i); 812179260Sjb snmp_pdu_free(resp); 813179260Sjb 814179260Sjb code = SNMP_RET_ERR; 815179260Sjb 816179260Sjb goto errout; 817179260Sjb } 818179260Sjb } 819179260Sjb 820179260Sjb /* 821179260Sjb * 3. Call dependencies 822179260Sjb */ 823179260Sjb if (TR(SET)) 824179260Sjb snmp_debug("set: set operations ok"); 825179260Sjb 826179260Sjb if ((ret = snmp_dep_commit(&context.ctx)) != SNMP_ERR_NOERROR) { 827179260Sjb pdu->error_status = ret; 828179260Sjb pdu->error_index = context.ctx.var_index; 829179260Sjb 830179260Sjb if ((ret = snmp_dep_rollback(&context.ctx)) != SNMP_ERR_NOERROR) { 831179260Sjb if (pdu->version != SNMP_V1) { 832179260Sjb pdu->error_status = SNMP_ERR_UNDO_FAILED; 833179260Sjb pdu->error_index = 0; 834179260Sjb } 835179260Sjb } 836179260Sjb rollback(&context, pdu, i); 837179260Sjb snmp_pdu_free(resp); 838179260Sjb 839179260Sjb code = SNMP_RET_ERR; 840179260Sjb 841179260Sjb goto errout; 842179260Sjb } 843179260Sjb 844179260Sjb /* 845179260Sjb * 4. Commit and copy values from the original packet to the response. 846179260Sjb * This is not the commit operation from RFC 1905 but rather an 847179260Sjb * 'FREE RESOURCES' operation. It shouldn't fail. 848179260Sjb */ 849179260Sjb if (TR(SET)) 850179260Sjb snmp_debug("set: commiting"); 851179260Sjb 852179260Sjb for (i = 0; i < pdu->nbindings; i++) { 853179260Sjb b = &resp->bindings[i]; 854179260Sjb np = context.node[i]; 855179260Sjb 856179260Sjb context.ctx.var_index = i + 1; 857179260Sjb context.ctx.scratch = &context.scratch[i]; 858179260Sjb 859179260Sjb ret = (*np->op)(&context.ctx, b, np->oid.len, np->index, 860179260Sjb SNMP_OP_COMMIT); 861179260Sjb 862179260Sjb if (ret != SNMP_ERR_NOERROR) 863179260Sjb snmp_error("set: commit failed (%d) on" 864179260Sjb " variable %s index %u", ret, 865179260Sjb asn_oid2str_r(&b->var, oidbuf), i); 866179260Sjb } 867179260Sjb 868179260Sjb if (snmp_fix_encoding(resp_b, resp) != SNMP_CODE_OK) { 869179260Sjb snmp_error("set: fix_encoding failed"); 870179260Sjb snmp_pdu_free(resp); 871179260Sjb code = SNMP_RET_IGN; 872179260Sjb } 873179260Sjb 874179260Sjb /* 875179260Sjb * Done 876179260Sjb */ 877179260Sjb errout: 878179260Sjb while ((d = TAILQ_FIRST(&context.dlist)) != NULL) { 879179260Sjb TAILQ_REMOVE(&context.dlist, d, link); 880179260Sjb free(d); 881179260Sjb } 882179260Sjb 883179260Sjb /* 884179260Sjb * call finish function 885179260Sjb */ 886179260Sjb while ((f = STAILQ_FIRST(&context.flist)) != NULL) { 887179260Sjb STAILQ_REMOVE_HEAD(&context.flist, link); 888179260Sjb (*f->func)(&context.ctx, code != SNMP_RET_OK, f->arg); 889179260Sjb free(f); 890179260Sjb } 891179260Sjb 892179260Sjb if (TR(SET)) 893179260Sjb snmp_debug("set: returning %d", code); 894179260Sjb 895179260Sjb return (code); 896179260Sjb} 897179260Sjb/* 898179260Sjb * Lookup a dependency. If it doesn't exist, create one 899179260Sjb */ 900179260Sjbstruct snmp_dependency * 901179260Sjbsnmp_dep_lookup(struct snmp_context *ctx, const struct asn_oid *obj, 902179260Sjb const struct asn_oid *idx, size_t len, snmp_depop_t func) 903179260Sjb{ 904179260Sjb struct context *context; 905179260Sjb struct depend *d; 906179260Sjb 907179260Sjb context = (struct context *)(void *) 908179260Sjb ((char *)ctx - offsetof(struct context, ctx)); 909179260Sjb if (TR(DEPEND)) { 910179260Sjb snmp_debug("depend: looking for %s", asn_oid2str(obj)); 911179260Sjb if (idx) 912179260Sjb snmp_debug("depend: index is %s", asn_oid2str(idx)); 913179260Sjb } 914179260Sjb TAILQ_FOREACH(d, &context->dlist, link) 915179260Sjb if (asn_compare_oid(obj, &d->dep.obj) == 0 && 916179260Sjb ((idx == NULL && d->dep.idx.len == 0) || 917179260Sjb (idx != NULL && asn_compare_oid(idx, &d->dep.idx) == 0))) { 918179260Sjb if(TR(DEPEND)) 919179260Sjb snmp_debug("depend: found"); 920179260Sjb return (&d->dep); 921179260Sjb } 922179260Sjb 923179260Sjb if(TR(DEPEND)) 924179260Sjb snmp_debug("depend: creating"); 925179260Sjb 926179260Sjb if ((d = malloc(offsetof(struct depend, dep) + len)) == NULL) 927179260Sjb return (NULL); 928179260Sjb memset(&d->dep, 0, len); 929179260Sjb 930179260Sjb d->dep.obj = *obj; 931179260Sjb if (idx == NULL) 932179260Sjb d->dep.idx.len = 0; 933179260Sjb else 934179260Sjb d->dep.idx = *idx; 935179260Sjb d->len = len; 936179260Sjb d->func = func; 937179260Sjb 938179260Sjb TAILQ_INSERT_TAIL(&context->dlist, d, link); 939179260Sjb 940179260Sjb return (&d->dep); 941179260Sjb} 942179260Sjb 943179260Sjb/* 944179260Sjb * Register a finish function. 945179260Sjb */ 946179260Sjbint 947179260Sjbsnmp_set_atfinish(struct snmp_context *ctx, snmp_set_finish_t func, void *arg) 948179260Sjb{ 949179260Sjb struct context *context; 950179260Sjb struct finish *f; 951179260Sjb 952179260Sjb context = (struct context *)(void *) 953179260Sjb ((char *)ctx - offsetof(struct context, ctx)); 954179260Sjb if ((f = malloc(sizeof(struct finish))) == NULL) 955179260Sjb return (-1); 956179260Sjb f->func = func; 957179260Sjb f->arg = arg; 958179260Sjb STAILQ_INSERT_TAIL(&context->flist, f, link); 959179260Sjb 960179260Sjb return (0); 961179260Sjb} 962179260Sjb 963179260Sjb/* 964179260Sjb * Make an error response from a PDU. We do this without decoding the 965179260Sjb * variable bindings. This means we can sent the junk back to a caller 966179260Sjb * that has sent us junk in the first place. 967179260Sjb */ 968179260Sjbenum snmp_ret 969179260Sjbsnmp_make_errresp(const struct snmp_pdu *pdu, struct asn_buf *pdu_b, 970179260Sjb struct asn_buf *resp_b) 971179260Sjb{ 972179260Sjb asn_len_t len; 973179260Sjb struct snmp_pdu resp; 974179260Sjb enum asn_err err; 975179260Sjb enum snmp_code code; 976179260Sjb 977179260Sjb memset(&resp, 0, sizeof(resp)); 978179260Sjb 979179260Sjb /* Message sequence */ 980179260Sjb if (asn_get_sequence(pdu_b, &len) != ASN_ERR_OK) 981179260Sjb return (SNMP_RET_IGN); 982179260Sjb if (pdu_b->asn_len < len) 983179260Sjb return (SNMP_RET_IGN); 984179260Sjb 985179260Sjb err = snmp_parse_message_hdr(pdu_b, &resp, &len); 986179260Sjb if (ASN_ERR_STOPPED(err)) 987179260Sjb return (SNMP_RET_IGN); 988179260Sjb if (pdu_b->asn_len < len) 989179260Sjb return (SNMP_RET_IGN); 990179260Sjb pdu_b->asn_len = len; 991179260Sjb 992179260Sjb err = snmp_parse_pdus_hdr(pdu_b, &resp, &len); 993179260Sjb if (ASN_ERR_STOPPED(err)) 994179260Sjb return (SNMP_RET_IGN); 995179260Sjb if (pdu_b->asn_len < len) 996179260Sjb return (SNMP_RET_IGN); 997179260Sjb pdu_b->asn_len = len; 998179260Sjb 999179260Sjb /* now we have the bindings left - construct new message */ 1000179260Sjb resp.error_status = pdu->error_status; 1001179260Sjb resp.error_index = pdu->error_index; 1002179260Sjb resp.type = SNMP_PDU_RESPONSE; 1003179260Sjb 1004179260Sjb code = snmp_pdu_encode_header(resp_b, &resp); 1005179260Sjb if (code != SNMP_CODE_OK) 1006179260Sjb return (SNMP_RET_IGN); 1007179260Sjb 1008179260Sjb if (pdu_b->asn_len > resp_b->asn_len) 1009179260Sjb /* too short */ 1010179260Sjb return (SNMP_RET_IGN); 1011179260Sjb (void)memcpy(resp_b->asn_ptr, pdu_b->asn_cptr, pdu_b->asn_len); 1012179260Sjb resp_b->asn_len -= pdu_b->asn_len; 1013179260Sjb resp_b->asn_ptr += pdu_b->asn_len; 1014179260Sjb 1015179260Sjb code = snmp_fix_encoding(resp_b, &resp); 1016179260Sjb if (code != SNMP_CODE_OK) 1017179260Sjb return (SNMP_RET_IGN); 1018179260Sjb 1019179260Sjb return (SNMP_RET_OK); 1020179260Sjb} 1021179260Sjb 1022179260Sjbstatic void 1023179260Sjbsnmp_debug_func(const char *fmt, ...) 1024179260Sjb{ 1025179260Sjb va_list ap; 1026179260Sjb 1027179260Sjb va_start(ap, fmt); 1028179260Sjb vfprintf(stderr, fmt, ap); 1029179260Sjb va_end(ap); 1030179260Sjb fprintf(stderr, "\n"); 1031179260Sjb} 1032179260Sjb