Annotation of src/usr.bin/snmp/snmp.c, Revision 1.7
1.7 ! martijn 1: /* $OpenBSD: snmp.c,v 1.6 2019/09/18 09:54:36 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;
1.7 ! martijn 250: }
! 251:
! 252: struct ber_element *
! 253: snmp_set(struct snmp_agent *agent, struct ber_element *vblist)
! 254: {
! 255: struct ber_element *pdu;
! 256:
! 257: if ((pdu = ber_add_sequence(NULL)) == NULL)
! 258: return NULL;
! 259: if (ber_printf_elements(pdu, "tddd{e", BER_CLASS_CONTEXT,
! 260: SNMP_C_SETREQ, arc4random() & 0x7fffffff, 0, 0, vblist) == NULL) {
! 261: ber_free_elements(pdu);
! 262: return NULL;
! 263: }
! 264:
! 265: return snmp_resolve(agent, pdu, 1);
1.1 martijn 266: }
267:
268: static struct ber_element *
269: snmp_resolve(struct snmp_agent *agent, struct ber_element *pdu, int reply)
270: {
1.3 martijn 271: struct ber_element *varbind;
1.1 martijn 272: struct ber_oid oid;
273: struct timespec start, now;
274: struct pollfd pfd;
1.3 martijn 275: char *message;
1.1 martijn 276: ssize_t len;
277: long long reqid, rreqid;
278: short direction;
279: int to, nfds, ret;
280: int tries;
281: char buf[READ_BUF_SIZE];
282:
283: if (ber_scanf_elements(pdu, "{i", &reqid) != 0) {
284: errno = EINVAL;
285: ber_free_elements(pdu);
286: return NULL;
287: }
288:
1.3 martijn 289: if ((message = snmp_package(agent, pdu, &len)) == NULL)
1.1 martijn 290: return NULL;
291:
292: clock_gettime(CLOCK_MONOTONIC, &start);
293: memcpy(&now, &start, sizeof(now));
294: direction = POLLOUT;
295: tries = agent->retries + 1;
296: while (tries) {
297: pfd.fd = agent->fd;
298: pfd.events = direction;
299: if (agent->timeout > 0) {
300: to = (agent->timeout - (now.tv_sec - start.tv_sec)) * 1000;
301: to -= (now.tv_nsec - start.tv_nsec) / 1000000;
302: } else
303: to = INFTIM;
304: nfds = poll(&pfd, 1, to);
305: if (nfds == 0) {
306: errno = ETIMEDOUT;
307: direction = POLLOUT;
308: tries--;
309: continue;
310: }
311: if (nfds == -1) {
312: if (errno == EINTR)
313: continue;
314: else
315: goto fail;
316: }
317: if (direction == POLLOUT) {
1.3 martijn 318: ret = send(agent->fd, message, len, MSG_DONTWAIT);
1.1 martijn 319: if (ret == -1)
320: goto fail;
321: if (ret < len) {
322: errno = EBADMSG;
323: goto fail;
324: }
325: if (!reply)
326: return NULL;
327: direction = POLLIN;
328: continue;
329: }
330: ret = recv(agent->fd, buf, sizeof(buf), MSG_DONTWAIT);
331: if (ret == 0)
332: errno = ECONNRESET;
333: if (ret <= 0)
334: goto fail;
1.3 martijn 335: if ((pdu = snmp_unpackage(agent, buf, ret)) == NULL) {
1.2 martijn 336: tries--;
337: direction = POLLOUT;
338: errno = EPROTO;
1.1 martijn 339: continue;
1.2 martijn 340: }
1.1 martijn 341: /* Validate pdu format and check request id */
342: if (ber_scanf_elements(pdu, "{iSSe", &rreqid, &varbind) != 0 ||
1.2 martijn 343: varbind->be_encoding != BER_TYPE_SEQUENCE) {
344: errno = EPROTO;
345: direction = POLLOUT;
346: tries--;
347: continue;
348: }
1.4 martijn 349: if (rreqid != reqid && rreqid != 0) {
1.2 martijn 350: errno = EPROTO;
351: direction = POLLOUT;
352: tries--;
1.1 martijn 353: continue;
1.2 martijn 354: }
1.1 martijn 355: for (varbind = varbind->be_sub; varbind != NULL;
356: varbind = varbind->be_next) {
1.2 martijn 357: if (ber_scanf_elements(varbind, "{oS}", &oid) != 0) {
358: errno = EPROTO;
359: direction = POLLOUT;
360: tries--;
1.4 martijn 361: continue;
1.2 martijn 362: }
1.1 martijn 363: }
364:
1.3 martijn 365: free(message);
366: return pdu;
367: }
368:
369: fail:
370: free(message);
371: return NULL;
372: }
373:
374: static char *
375: snmp_package(struct snmp_agent *agent, struct ber_element *pdu, size_t *len)
376: {
377: struct ber ber;
1.6 martijn 378: struct ber_element *message, *scopedpdu = NULL, *secparams, *encpdu;
1.4 martijn 379: ssize_t securitysize, ret;
1.5 martijn 380: size_t secparamsoffset;
1.4 martijn 381: char *securityparams = NULL, *packet = NULL;
382: long long msgid;
1.5 martijn 383: void *cookie = NULL;
1.3 martijn 384:
385: bzero(&ber, sizeof(ber));
386: ber_set_application(&ber, smi_application);
387:
388: if ((message = ber_add_sequence(NULL)) == NULL) {
389: ber_free_elements(pdu);
390: goto fail;
391: }
392:
393: switch (agent->version) {
394: case SNMP_V1:
395: case SNMP_V2C:
396: if (ber_printf_elements(message, "dse", agent->version,
397: agent->community, pdu) == NULL) {
398: ber_free_elements(pdu);
399: goto fail;
400: }
401: break;
402: case SNMP_V3:
1.4 martijn 403: msgid = arc4random_uniform(2147483647);
404: if ((scopedpdu = ber_add_sequence(NULL)) == NULL) {
405: ber_free_elements(pdu);
406: goto fail;
407: }
408: if (ber_printf_elements(scopedpdu, "xxe",
409: agent->v3->engineid, agent->v3->engineidlen,
410: agent->v3->ctxname, agent->v3->ctxnamelen, pdu) == NULL) {
411: ber_free_elements(pdu);
412: ber_free_elements(scopedpdu);
413: goto fail;
414: }
415: pdu = NULL;
416: if ((securityparams = agent->v3->sec->genparams(agent,
1.5 martijn 417: &securitysize, &cookie)) == NULL) {
1.4 martijn 418: ber_free_elements(scopedpdu);
419: goto fail;
420: }
1.6 martijn 421: if (agent->v3->level & SNMP_MSGFLAG_PRIV) {
422: if ((encpdu = agent->v3->sec->encpdu(agent, scopedpdu,
423: cookie)) == NULL)
424: goto fail;
425: ber_free_elements(scopedpdu);
426: scopedpdu = encpdu;
427: }
1.4 martijn 428: if (ber_printf_elements(message, "d{idxd}xe",
429: agent->version, msgid, UDP_MAXPACKET, &(agent->v3->level),
430: (size_t) 1, agent->v3->sec->model, securityparams,
431: securitysize, scopedpdu) == NULL)
432: goto fail;
1.5 martijn 433: if (ber_scanf_elements(message, "{SSe", &secparams) == -1)
434: goto fail;
435: ber_set_writecallback(secparams, snmp_v3_secparamsoffset,
436: &secparamsoffset);
1.3 martijn 437: break;
438: }
439:
440: if (ber_write_elements(&ber, message) == -1)
441: goto fail;
442: ret = ber_copy_writebuf(&ber, (void **)&packet);
443:
444: *len = (size_t) ret;
445: ber_free(&ber);
446:
1.5 martijn 447: if (agent->version == SNMP_V3 && packet != NULL) {
448: if (agent->v3->sec->finalparams(agent, packet,
449: ret, secparamsoffset, cookie) == -1) {
450: free(packet);
451: packet = NULL;
452: }
453: }
454:
1.3 martijn 455: fail:
1.5 martijn 456: if (agent->version == SNMP_V3)
457: agent->v3->sec->freecookie(cookie);
1.3 martijn 458: ber_free_elements(message);
1.4 martijn 459: free(securityparams);
1.3 martijn 460: return packet;
461: }
462:
463: static struct ber_element *
464: snmp_unpackage(struct snmp_agent *agent, char *buf, size_t buflen)
465: {
466: struct ber ber;
467: enum snmp_version version;
468: char *community;
469: struct ber_element *pdu;
1.4 martijn 470: long long msgid, model;
471: int msgsz;
472: char *msgflags, *secparams;
473: size_t msgflagslen, secparamslen;
474: struct ber_element *message = NULL, *payload, *scopedpdu, *ctxname;
475: off_t secparamsoffset;
1.6 martijn 476: char *encpdu, *engineid;
477: size_t encpdulen, engineidlen;
478: void *cookie = NULL;
1.3 martijn 479:
480: bzero(&ber, sizeof(ber));
481: ber_set_application(&ber, smi_application);
482:
483: ber_set_readbuf(&ber, buf, buflen);
484: if ((message = ber_read_elements(&ber, NULL)) == NULL)
485: return NULL;
486: ber_free(&ber);
487:
488: if (ber_scanf_elements(message, "{de", &version, &payload) != 0)
489: goto fail;
490:
491: if (version != agent->version)
492: goto fail;
493:
1.4 martijn 494: switch (version) {
1.3 martijn 495: case SNMP_V1:
496: case SNMP_V2C:
497: if (ber_scanf_elements(payload, "se", &community, &pdu) == -1)
498: goto fail;
499: if (strcmp(community, agent->community) != 0)
500: goto fail;
501: ber_unlink_elements(payload);
1.1 martijn 502: ber_free_elements(message);
503: return pdu;
1.3 martijn 504: case SNMP_V3:
1.4 martijn 505: if (ber_scanf_elements(payload, "{idxi}pxe", &msgid, &msgsz,
506: &msgflags, &msgflagslen, &model, &secparamsoffset,
507: &secparams, &secparamslen, &scopedpdu) == -1)
508: goto fail;
509: if (msgflagslen != 1)
510: goto fail;
511: if (agent->v3->sec->parseparams(agent, buf, buflen,
1.6 martijn 512: secparamsoffset, secparams, secparamslen, msgflags[0],
513: &cookie) == -1) {
514: cookie = NULL;
1.4 martijn 515: goto fail;
1.6 martijn 516: }
517: if (msgflags[0] & SNMP_MSGFLAG_PRIV) {
518: if (ber_scanf_elements(scopedpdu, "x", &encpdu,
519: &encpdulen) == -1)
520: goto fail;
521: if ((scopedpdu = agent->v3->sec->decpdu(agent, encpdu,
522: encpdulen, cookie)) == NULL)
523: goto fail;
524: }
1.4 martijn 525: if (ber_scanf_elements(scopedpdu, "{xeS{", &engineid,
526: &engineidlen, &ctxname) == -1)
527: goto fail;
528: if (!agent->v3->engineidset) {
529: if (snmp_v3_setengineid(agent->v3, engineid,
530: engineidlen) == -1)
531: goto fail;
532: }
533: pdu = ber_unlink_elements(ctxname);
534: /* Accept reports, so we can continue if possible */
535: if (pdu->be_type != SNMP_C_REPORT) {
536: if ((msgflags[0] & SNMP_MSGFLAG_SECMASK) !=
537: (agent->v3->level & SNMP_MSGFLAG_SECMASK))
538: goto fail;
539: }
540:
541: ber_free_elements(message);
1.6 martijn 542: agent->v3->sec->freecookie(cookie);
1.4 martijn 543: return pdu;
1.1 martijn 544: }
1.3 martijn 545: /* NOTREACHED */
1.1 martijn 546:
547: fail:
1.6 martijn 548: if (version == SNMP_V3)
549: agent->v3->sec->freecookie(cookie);
1.1 martijn 550: ber_free_elements(message);
551: return NULL;
1.5 martijn 552: }
553:
554: static void
555: snmp_v3_secparamsoffset(void *cookie, size_t offset)
556: {
557: size_t *spoffset = cookie;
558:
559: *spoffset = offset;
1.3 martijn 560: }
561:
562: ssize_t
563: ber_copy_writebuf(struct ber *ber, void **buf)
564: {
565: char *bbuf;
566: ssize_t ret;
567:
568: *buf = NULL;
569: if ((ret = ber_get_writebuf(ber, (void **)&bbuf)) == -1)
570: return -1;
571: if ((*buf = malloc(ret)) == NULL)
572: return -1;
573: memcpy(*buf, bbuf, ret);
574: return ret;
1.1 martijn 575: }