Annotation of src/usr.bin/snmp/snmp.c, Revision 1.6
1.6 ! martijn 1: /* $OpenBSD: snmp.c,v 1.5 2019/09/18 09:52:47 martijn Exp $ */
1.1 martijn 2:
3: /*
4: * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
5: * Copyright (c) 2013 Reyk Floeter <reyk@openbsd.org>
6: *
7: * Permission to use, copy, modify, and distribute this software for any
8: * purpose with or without fee is hereby granted, provided that the above
9: * copyright notice and this permission notice appear in all copies.
10: *
11: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18: */
19:
20: #include <sys/socket.h>
21:
22: #include <errno.h>
23: #include <poll.h>
24: #include <stdlib.h>
25: #include <string.h>
26: #include <stdio.h>
27: #include <time.h>
28:
29: #include "ber.h"
30: #include "smi.h"
31: #include "snmp.h"
32:
1.4 martijn 33: #define UDP_MAXPACKET 65535
34:
1.1 martijn 35: static struct ber_element *
36: snmp_resolve(struct snmp_agent *, struct ber_element *, int);
1.3 martijn 37: static char *
38: snmp_package(struct snmp_agent *, struct ber_element *, size_t *);
39: static struct ber_element *
40: snmp_unpackage(struct snmp_agent *, char *, size_t);
1.4 martijn 41: static void snmp_v3_free(struct snmp_v3 *);
1.5 martijn 42: static void snmp_v3_secparamsoffset(void *, size_t);
1.4 martijn 43:
44: struct snmp_v3 *
45: snmp_v3_init(int level, const char *ctxname, size_t ctxnamelen,
46: struct snmp_sec *sec)
47: {
48: struct snmp_v3 *v3;
49:
50: if ((level & (SNMP_MSGFLAG_SECMASK | SNMP_MSGFLAG_REPORT)) != level ||
51: sec == NULL) {
52: errno = EINVAL;
53: return NULL;
54: }
55: if ((v3 = calloc(1, sizeof(*v3))) == NULL)
56: return NULL;
57:
58: v3->level = level | SNMP_MSGFLAG_REPORT;
59: v3->ctxnamelen = ctxnamelen;
60: if (ctxnamelen != 0) {
61: if ((v3->ctxname = malloc(ctxnamelen)) == NULL) {
62: free(v3);
63: return NULL;
64: }
65: memcpy(v3->ctxname, ctxname, ctxnamelen);
66: }
67: v3->sec = sec;
68: return v3;
69: }
70:
71: int
72: snmp_v3_setengineid(struct snmp_v3 *v3, char *engineid, size_t engineidlen)
73: {
74: if (v3->engineidset)
75: free(v3->engineid);
76: if ((v3->engineid = malloc(engineidlen)) == NULL)
77: return -1;
78: memcpy(v3->engineid, engineid, engineidlen);
79: v3->engineidlen = engineidlen;
80: v3->engineidset = 1;
81: return 0;
82: }
1.1 martijn 83:
84: struct snmp_agent *
85: snmp_connect_v12(int fd, enum snmp_version version, const char *community)
86: {
87: struct snmp_agent *agent;
88:
89: if (version != SNMP_V1 && version != SNMP_V2C) {
90: errno = EINVAL;
91: return NULL;
92: }
93: if ((agent = malloc(sizeof(*agent))) == NULL)
94: return NULL;
95: agent->fd = fd;
96: agent->version = version;
97: if ((agent->community = strdup(community)) == NULL)
98: goto fail;
99: agent->timeout = 1;
100: agent->retries = 5;
1.4 martijn 101: agent->v3 = NULL;
1.1 martijn 102: return agent;
103:
104: fail:
105: free(agent);
106: return NULL;
107: }
108:
1.4 martijn 109: struct snmp_agent *
110: snmp_connect_v3(int fd, struct snmp_v3 *v3)
111: {
112: struct snmp_agent *agent;
113:
114: if ((agent = malloc(sizeof(*agent))) == NULL)
115: return NULL;
116: agent->fd = fd;
117: agent->version = SNMP_V3;
118: agent->v3 = v3;
119: agent->timeout = 1;
120: agent->retries = 5;
121: agent->community = NULL;
122:
123: if (v3->sec->init(agent) == -1) {
124: snmp_free_agent(agent);
125: return NULL;
126: }
127: return agent;
128: }
129:
1.1 martijn 130: void
131: snmp_free_agent(struct snmp_agent *agent)
132: {
133: free(agent->community);
1.4 martijn 134: if (agent->v3 != NULL)
135: snmp_v3_free(agent->v3);
1.1 martijn 136: free(agent);
137: }
138:
1.4 martijn 139: static void
140: snmp_v3_free(struct snmp_v3 *v3)
141: {
142: v3->sec->free(v3->sec->data);
143: free(v3->sec);
144: free(v3->ctxname);
145: free(v3->engineid);
146: free(v3);
147: }
148:
1.1 martijn 149: struct ber_element *
150: snmp_get(struct snmp_agent *agent, struct ber_oid *oid, size_t len)
151: {
152: struct ber_element *pdu, *varbind;
153: size_t i;
154:
155: if ((pdu = ber_add_sequence(NULL)) == NULL)
156: return NULL;
157: if ((varbind = ber_printf_elements(pdu, "tddd{", BER_CLASS_CONTEXT,
158: SNMP_C_GETREQ, arc4random() & 0x7fffffff, 0, 0)) == NULL)
159: goto fail;
160: for (i = 0; i < len; i++)
161: varbind = ber_printf_elements(varbind, "{O0}", &oid[i]);
162: if (varbind == NULL)
163: goto fail;
164:
165: return snmp_resolve(agent, pdu, 1);
166: fail:
167: ber_free_elements(pdu);
168: return NULL;
169: }
170:
171: struct ber_element *
172: snmp_getnext(struct snmp_agent *agent, struct ber_oid *oid, size_t len)
173: {
174: struct ber_element *pdu, *varbind;
175: size_t i;
176:
177: if ((pdu = ber_add_sequence(NULL)) == NULL)
178: return NULL;
179: if ((varbind = ber_printf_elements(pdu, "tddd{", BER_CLASS_CONTEXT,
180: SNMP_C_GETNEXTREQ, arc4random() & 0x7fffffff, 0, 0)) == NULL)
181: goto fail;
182: for (i = 0; i < len; i++)
183: varbind = ber_printf_elements(varbind, "{O0}", &oid[i]);
184: if (varbind == NULL)
185: goto fail;
186:
187: return snmp_resolve(agent, pdu, 1);
188: fail:
189: ber_free_elements(pdu);
190: return NULL;
191: }
192:
193: int
194: snmp_trap(struct snmp_agent *agent, struct timespec *uptime,
195: struct ber_oid *oid, struct ber_element *custvarbind)
196: {
197: struct ber_element *pdu, *varbind;
198: struct ber_oid sysuptime, trap;
199: long long ticks;
200:
201: if ((pdu = ber_add_sequence(NULL)) == NULL)
202: return -1;
203: if ((varbind = ber_printf_elements(pdu, "tddd{", BER_CLASS_CONTEXT,
204: SNMP_C_TRAPV2, arc4random() & 0x7fffffff, 0, 0)) == NULL)
205: goto fail;
206:
207: ticks = uptime->tv_sec * 100;
208: ticks += uptime->tv_nsec / 10000000;
209: if (smi_string2oid("sysUpTime.0", &sysuptime) == -1)
210: goto fail;
211: if ((varbind = ber_printf_elements(varbind, "{Oit}", &sysuptime, ticks,
212: BER_CLASS_APPLICATION, SNMP_T_TIMETICKS)) == NULL)
213: goto fail;
214: if (smi_string2oid("snmpTrapOID.0", &trap) == -1)
215: goto fail;
216: if ((varbind = ber_printf_elements(varbind, "{OO}", &trap, oid)) == NULL)
217: goto fail;
218: if (custvarbind != NULL)
219: ber_link_elements(varbind, custvarbind);
220:
221: snmp_resolve(agent, pdu, 0);
222: return 0;
223: fail:
224: ber_free_elements(pdu);
225: return -1;
226: }
227:
228: struct ber_element *
229: snmp_getbulk(struct snmp_agent *agent, struct ber_oid *oid, size_t len,
230: int non_repeaters, int max_repetitions)
231: {
232: struct ber_element *pdu, *varbind;
233: size_t i;
234:
235: if ((pdu = ber_add_sequence(NULL)) == NULL)
236: return NULL;
237: if ((varbind = ber_printf_elements(pdu, "tddd{", BER_CLASS_CONTEXT,
238: SNMP_C_GETBULKREQ, arc4random() & 0x7fffffff, non_repeaters,
239: max_repetitions)) == NULL)
240: goto fail;
241: for (i = 0; i < len; i++)
242: varbind = ber_printf_elements(varbind, "{O0}", &oid[i]);
243: if (varbind == NULL)
244: goto fail;
245:
246: return snmp_resolve(agent, pdu, 1);
247: fail:
248: ber_free_elements(pdu);
249: return NULL;
250: }
251:
252: static struct ber_element *
253: snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
254: {
1.3 martijn 255: struct ber_element *varbind;
1.1 martijn 256: struct ber_oid oid;
257: struct timespec start, now;
258: struct pollfd pfd;
1.3 martijn 259: char *message;
1.1 martijn 260: ssize_t len;
261: long long reqid, rreqid;
262: short direction;
263: int to, nfds, ret;
264: int tries;
265: char buf[READ_BUF_SIZE];
266:
267: if (ber_scanf_elements(pdu, "{i", &reqid) != 0) {
268: errno = EINVAL;
269: ber_free_elements(pdu);
270: return NULL;
271: }
272:
1.3 martijn 273: if ((message = snmp_package(agent, pdu, &len)) == NULL)
1.1 martijn 274: return NULL;
275:
276: clock_gettime(CLOCK_MONOTONIC, &start);
277: memcpy(&now, &start, sizeof(now));
278: direction = POLLOUT;
279: tries = agent->retries + 1;
280: while (tries) {
281: pfd.fd = agent->fd;
282: pfd.events = direction;
283: if (agent->timeout > 0) {
284: to = (agent->timeout - (now.tv_sec - start.tv_sec)) * 1000;
285: to -= (now.tv_nsec - start.tv_nsec) / 1000000;
286: } else
287: to = INFTIM;
288: nfds = poll(&pfd, 1, to);
289: if (nfds == 0) {
290: errno = ETIMEDOUT;
291: direction = POLLOUT;
292: tries--;
293: continue;
294: }
295: if (nfds == -1) {
296: if (errno == EINTR)
297: continue;
298: else
299: goto fail;
300: }
301: if (direction == POLLOUT) {
1.3 martijn 302: ret = send(agent->fd, message, len, MSG_DONTWAIT);
1.1 martijn 303: if (ret == -1)
304: goto fail;
305: if (ret < len) {
306: errno = EBADMSG;
307: goto fail;
308: }
309: if (!reply)
310: return NULL;
311: direction = POLLIN;
312: continue;
313: }
314: ret = recv(agent->fd, buf, sizeof(buf), MSG_DONTWAIT);
315: if (ret == 0)
316: errno = ECONNRESET;
317: if (ret <= 0)
318: goto fail;
1.3 martijn 319: if ((pdu = snmp_unpackage(agent, buf, ret)) == NULL) {
1.2 martijn 320: tries--;
321: direction = POLLOUT;
322: errno = EPROTO;
1.1 martijn 323: continue;
1.2 martijn 324: }
1.1 martijn 325: /* Validate pdu format and check request id */
326: if (ber_scanf_elements(pdu, "{iSSe", &rreqid, &varbind) != 0 ||
1.2 martijn 327: varbind->be_encoding != BER_TYPE_SEQUENCE) {
328: errno = EPROTO;
329: direction = POLLOUT;
330: tries--;
331: continue;
332: }
1.4 martijn 333: if (rreqid != reqid && rreqid != 0) {
1.2 martijn 334: errno = EPROTO;
335: direction = POLLOUT;
336: tries--;
1.1 martijn 337: continue;
1.2 martijn 338: }
1.1 martijn 339: for (varbind = varbind->be_sub; varbind != NULL;
340: varbind = varbind->be_next) {
1.2 martijn 341: if (ber_scanf_elements(varbind, "{oS}", &oid) != 0) {
342: errno = EPROTO;
343: direction = POLLOUT;
344: tries--;
1.4 martijn 345: continue;
1.2 martijn 346: }
1.1 martijn 347: }
348:
1.3 martijn 349: free(message);
350: return pdu;
351: }
352:
353: fail:
354: free(message);
355: return NULL;
356: }
357:
358: static char *
359: snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
360: {
361: struct ber ber;
1.6 ! martijn 362: struct ber_element *message, *scopedpdu = NULL, *secparams, *encpdu;
1.4 martijn 363: ssize_t securitysize, ret;
1.5 martijn 364: size_t secparamsoffset;
1.4 martijn 365: char *securityparams = NULL, *packet = NULL;
366: long long msgid;
1.5 martijn 367: void *cookie = NULL;
1.3 martijn 368:
369: bzero(&ber, sizeof(ber));
370: ber_set_application(&ber, smi_application);
371:
372: if ((message = ber_add_sequence(NULL)) == NULL) {
373: ber_free_elements(pdu);
374: goto fail;
375: }
376:
377: switch (agent->version) {
378: case SNMP_V1:
379: case SNMP_V2C:
380: if (ber_printf_elements(message, "dse", agent->version,
381: agent->community, pdu) == NULL) {
382: ber_free_elements(pdu);
383: goto fail;
384: }
385: break;
386: case SNMP_V3:
1.4 martijn 387: msgid = arc4random_uniform(2147483647);
388: if ((scopedpdu = ber_add_sequence(NULL)) == NULL) {
389: ber_free_elements(pdu);
390: goto fail;
391: }
392: if (ber_printf_elements(scopedpdu, "xxe",
393: agent->v3->engineid, agent->v3->engineidlen,
394: agent->v3->ctxname, agent->v3->ctxnamelen, pdu) == NULL) {
395: ber_free_elements(pdu);
396: ber_free_elements(scopedpdu);
397: goto fail;
398: }
399: pdu = NULL;
400: if ((securityparams = agent->v3->sec->genparams(agent,
1.5 martijn 401: &securitysize, &cookie)) == NULL) {
1.4 martijn 402: ber_free_elements(scopedpdu);
403: goto fail;
404: }
1.6 ! martijn 405: if (agent->v3->level & SNMP_MSGFLAG_PRIV) {
! 406: if ((encpdu = agent->v3->sec->encpdu(agent, scopedpdu,
! 407: cookie)) == NULL)
! 408: goto fail;
! 409: ber_free_elements(scopedpdu);
! 410: scopedpdu = encpdu;
! 411: }
1.4 martijn 412: if (ber_printf_elements(message, "d{idxd}xe",
413: agent->version, msgid, UDP_MAXPACKET, &(agent->v3->level),
414: (size_t) 1, agent->v3->sec->model, securityparams,
415: securitysize, scopedpdu) == NULL)
416: goto fail;
1.5 martijn 417: if (ber_scanf_elements(message, "{SSe", &secparams) == -1)
418: goto fail;
419: ber_set_writecallback(secparams, snmp_v3_secparamsoffset,
420: &secparamsoffset);
1.3 martijn 421: break;
422: }
423:
424: if (ber_write_elements(&ber, message) == -1)
425: goto fail;
426: ret = ber_copy_writebuf(&ber, (void **)&packet);
427:
428: *len = (size_t) ret;
429: ber_free(&ber);
430:
1.5 martijn 431: if (agent->version == SNMP_V3 && packet != NULL) {
432: if (agent->v3->sec->finalparams(agent, packet,
433: ret, secparamsoffset, cookie) == -1) {
434: free(packet);
435: packet = NULL;
436: }
437: }
438:
1.3 martijn 439: fail:
1.5 martijn 440: if (agent->version == SNMP_V3)
441: agent->v3->sec->freecookie(cookie);
1.3 martijn 442: ber_free_elements(message);
1.4 martijn 443: free(securityparams);
1.3 martijn 444: return packet;
445: }
446:
447: static struct ber_element *
448: snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen)
449: {
450: struct ber ber;
451: enum snmp_version version;
452: char *community;
453: struct ber_element *pdu;
1.4 martijn 454: long long msgid, model;
455: int msgsz;
456: char *msgflags, *secparams;
457: size_t msgflagslen, secparamslen;
458: struct ber_element *message = NULL, *payload, *scopedpdu, *ctxname;
459: off_t secparamsoffset;
1.6 ! martijn 460: char *encpdu, *engineid;
! 461: size_t encpdulen, engineidlen;
! 462: void *cookie = NULL;
1.3 martijn 463:
464: bzero(&ber, sizeof(ber));
465: ber_set_application(&ber, smi_application);
466:
467: ber_set_readbuf(&ber, buf, buflen);
468: if ((message = ber_read_elements(&ber, NULL)) == NULL)
469: return NULL;
470: ber_free(&ber);
471:
472: if (ber_scanf_elements(message, "{de", &version, &payload) != 0)
473: goto fail;
474:
475: if (version != agent->version)
476: goto fail;
477:
1.4 martijn 478: switch (version) {
1.3 martijn 479: case SNMP_V1:
480: case SNMP_V2C:
481: if (ber_scanf_elements(payload, "se", &community, &pdu) == -1)
482: goto fail;
483: if (strcmp(community, agent->community) != 0)
484: goto fail;
485: ber_unlink_elements(payload);
1.1 martijn 486: ber_free_elements(message);
487: return pdu;
1.3 martijn 488: case SNMP_V3:
1.4 martijn 489: if (ber_scanf_elements(payload, "{idxi}pxe", &msgid, &msgsz,
490: &msgflags, &msgflagslen, &model, &secparamsoffset,
491: &secparams, &secparamslen, &scopedpdu) == -1)
492: goto fail;
493: if (msgflagslen != 1)
494: goto fail;
495: if (agent->v3->sec->parseparams(agent, buf, buflen,
1.6 ! martijn 496: secparamsoffset, secparams, secparamslen, msgflags[0],
! 497: &cookie) == -1) {
! 498: cookie = NULL;
1.4 martijn 499: goto fail;
1.6 ! martijn 500: }
! 501: if (msgflags[0] & SNMP_MSGFLAG_PRIV) {
! 502: if (ber_scanf_elements(scopedpdu, "x", &encpdu,
! 503: &encpdulen) == -1)
! 504: goto fail;
! 505: if ((scopedpdu = agent->v3->sec->decpdu(agent, encpdu,
! 506: encpdulen, cookie)) == NULL)
! 507: goto fail;
! 508: }
1.4 martijn 509: if (ber_scanf_elements(scopedpdu, "{xeS{", &engineid,
510: &engineidlen, &ctxname) == -1)
511: goto fail;
512: if (!agent->v3->engineidset) {
513: if (snmp_v3_setengineid(agent->v3, engineid,
514: engineidlen) == -1)
515: goto fail;
516: }
517: pdu = ber_unlink_elements(ctxname);
518: /* Accept reports, so we can continue if possible */
519: if (pdu->be_type != SNMP_C_REPORT) {
520: if ((msgflags[0] & SNMP_MSGFLAG_SECMASK) !=
521: (agent->v3->level & SNMP_MSGFLAG_SECMASK))
522: goto fail;
523: }
524:
525: ber_free_elements(message);
1.6 ! martijn 526: agent->v3->sec->freecookie(cookie);
1.4 martijn 527: return pdu;
1.1 martijn 528: }
1.3 martijn 529: /* NOTREACHED */
1.1 martijn 530:
531: fail:
1.6 ! martijn 532: if (version == SNMP_V3)
! 533: agent->v3->sec->freecookie(cookie);
1.1 martijn 534: ber_free_elements(message);
535: return NULL;
1.5 martijn 536: }
537:
538: static void
539: snmp_v3_secparamsoffset(void *cookie, size_t offset)
540: {
541: size_t *spoffset = cookie;
542:
543: *spoffset = offset;
1.3 martijn 544: }
545:
546: ssize_t
547: ber_copy_writebuf(struct ber *ber, void **buf)
548: {
549: char *bbuf;
550: ssize_t ret;
551:
552: *buf = NULL;
553: if ((ret = ber_get_writebuf(ber, (void **)&bbuf)) == -1)
554: return -1;
555: if ((*buf = malloc(ret)) == NULL)
556: return -1;
557: memcpy(*buf, bbuf, ret);
558: return ret;
1.1 martijn 559: }