]> git.proxmox.com Git - mirror_frr.git/blob - lib/smux.c
[cleanup] Make command nodes static
[mirror_frr.git] / lib / smux.c
1 /* SNMP support
2 * Copyright (C) 1999 Kunihiro Ishiguro <kunihiro@zebra.org>
3 *
4 * This file is part of GNU Zebra.
5 *
6 * GNU Zebra is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2, or (at your option) any
9 * later version.
10 *
11 * GNU Zebra is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with GNU Zebra; see the file COPYING. If not, write to the Free
18 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 * 02111-1307, USA.
20 */
21
22 #include <zebra.h>
23
24 #ifdef HAVE_SNMP
25 #ifdef HAVE_NETSNMP
26 #include <net-snmp/net-snmp-config.h>
27 #include <net-snmp/net-snmp-includes.h>
28 #else
29 #include <asn1.h>
30 #include <snmp.h>
31 #include <snmp_impl.h>
32 #endif
33
34 #include "log.h"
35 #include "thread.h"
36 #include "linklist.h"
37 #include "command.h"
38 #include <lib/version.h>
39 #include "memory.h"
40 #include "sockunion.h"
41 #include "smux.h"
42
43 #define min(A,B) ((A) < (B) ? (A) : (B))
44
45 enum smux_event {SMUX_SCHEDULE, SMUX_CONNECT, SMUX_READ};
46
47 void smux_event (enum smux_event, int);
48 \f
49
50 /* SMUX socket. */
51 int smux_sock = -1;
52
53 /* SMUX subtree list. */
54 struct list *treelist;
55
56 /* SMUX oid. */
57 oid *smux_oid = NULL;
58 size_t smux_oid_len;
59
60 /* SMUX password. */
61 char *smux_passwd = NULL;
62
63 /* SMUX read threads. */
64 struct thread *smux_read_thread;
65
66 /* SMUX connect thrads. */
67 struct thread *smux_connect_thread;
68
69 /* SMUX debug flag. */
70 int debug_smux = 0;
71
72 /* SMUX failure count. */
73 int fail = 0;
74
75 /* SMUX node. */
76 static struct cmd_node smux_node =
77 {
78 SMUX_NODE,
79 "" /* SMUX has no interface. */
80 };
81
82 /* thread master */
83 static struct thread_master *master;
84 \f
85 void *
86 oid_copy (void *dest, void *src, size_t size)
87 {
88 return memcpy (dest, src, size * sizeof (oid));
89 }
90
91 void
92 oid2in_addr (oid oid[], int len, struct in_addr *addr)
93 {
94 int i;
95 u_char *pnt;
96
97 if (len == 0)
98 return;
99
100 pnt = (u_char *) addr;
101
102 for (i = 0; i < len; i++)
103 *pnt++ = oid[i];
104 }
105
106 void
107 oid_copy_addr (oid oid[], struct in_addr *addr, int len)
108 {
109 int i;
110 u_char *pnt;
111
112 if (len == 0)
113 return;
114
115 pnt = (u_char *) addr;
116
117 for (i = 0; i < len; i++)
118 oid[i] = *pnt++;
119 }
120
121 int
122 oid_compare (oid *o1, int o1_len, oid *o2, int o2_len)
123 {
124 int i;
125
126 for (i = 0; i < min (o1_len, o2_len); i++)
127 {
128 if (o1[i] < o2[i])
129 return -1;
130 else if (o1[i] > o2[i])
131 return 1;
132 }
133 if (o1_len < o2_len)
134 return -1;
135 if (o1_len > o2_len)
136 return 1;
137
138 return 0;
139 }
140
141 static int
142 oid_compare_part (oid *o1, int o1_len, oid *o2, int o2_len)
143 {
144 int i;
145
146 for (i = 0; i < min (o1_len, o2_len); i++)
147 {
148 if (o1[i] < o2[i])
149 return -1;
150 else if (o1[i] > o2[i])
151 return 1;
152 }
153 if (o1_len < o2_len)
154 return -1;
155
156 return 0;
157 }
158 \f
159 static void
160 smux_oid_dump (const char *prefix, oid *oid, size_t oid_len)
161 {
162 unsigned int i;
163 int first = 1;
164 char buf[MAX_OID_LEN * 3];
165
166 buf[0] = '\0';
167
168 for (i = 0; i < oid_len; i++)
169 {
170 sprintf (buf + strlen (buf), "%s%d", first ? "" : ".", (int) oid[i]);
171 first = 0;
172 }
173 zlog_debug ("%s: %s", prefix, buf);
174 }
175
176 static int
177 smux_socket ()
178 {
179 int ret;
180 #ifdef HAVE_IPV6
181 struct addrinfo hints, *res0, *res;
182 int gai;
183 #else
184 struct sockaddr_in serv;
185 struct servent *sp;
186 #endif
187 int sock = 0;
188
189 #ifdef HAVE_IPV6
190 memset(&hints, 0, sizeof(hints));
191 hints.ai_family = PF_UNSPEC;
192 hints.ai_socktype = SOCK_STREAM;
193 gai = getaddrinfo(NULL, "smux", &hints, &res0);
194 if (gai == EAI_SERVICE)
195 {
196 char servbuf[NI_MAXSERV];
197 sprintf(servbuf,"%d",SMUX_PORT_DEFAULT);
198 servbuf[sizeof (servbuf) - 1] = '\0';
199 gai = getaddrinfo(NULL, servbuf, &hints, &res0);
200 }
201 if (gai)
202 {
203 zlog_warn("Cannot locate loopback service smux");
204 return -1;
205 }
206 for(res=res0; res; res=res->ai_next)
207 {
208 if (res->ai_family != AF_INET
209 #ifdef HAVE_IPV6
210 && res->ai_family != AF_INET6
211 #endif /* HAVE_IPV6 */
212 )
213 continue;
214
215 sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
216 if (sock < 0)
217 continue;
218 sockopt_reuseaddr (sock);
219 sockopt_reuseport (sock);
220 ret = connect (sock, res->ai_addr, res->ai_addrlen);
221 if (ret < 0)
222 {
223 close(sock);
224 sock = -1;
225 continue;
226 }
227 break;
228 }
229 freeaddrinfo(res0);
230 if (sock < 0)
231 zlog_warn ("Can't connect to SNMP agent with SMUX");
232 #else
233 sock = socket (AF_INET, SOCK_STREAM, 0);
234 if (sock < 0)
235 {
236 zlog_warn ("Can't make socket for SNMP");
237 return -1;
238 }
239
240 memset (&serv, 0, sizeof (struct sockaddr_in));
241 serv.sin_family = AF_INET;
242 #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
243 serv.sin_len = sizeof (struct sockaddr_in);
244 #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
245
246 sp = getservbyname ("smux", "tcp");
247 if (sp != NULL)
248 serv.sin_port = sp->s_port;
249 else
250 serv.sin_port = htons (SMUX_PORT_DEFAULT);
251
252 serv.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
253
254 sockopt_reuseaddr (sock);
255 sockopt_reuseport (sock);
256
257 ret = connect (sock, (struct sockaddr *) &serv, sizeof (struct sockaddr_in));
258 if (ret < 0)
259 {
260 close (sock);
261 smux_sock = -1;
262 zlog_warn ("Can't connect to SNMP agent with SMUX");
263 return -1;
264 }
265 #endif
266 return sock;
267 }
268
269 static void
270 smux_getresp_send (oid objid[], size_t objid_len, long reqid, long errstat,
271 long errindex, u_char val_type, void *arg, size_t arg_len)
272 {
273 int ret;
274 u_char buf[BUFSIZ];
275 u_char *ptr, *h1, *h1e, *h2, *h2e;
276 size_t len, length;
277
278 ptr = buf;
279 len = BUFSIZ;
280 length = len;
281
282 if (debug_smux)
283 {
284 zlog_debug ("SMUX GETRSP send");
285 zlog_debug ("SMUX GETRSP reqid: %ld", reqid);
286 }
287
288 h1 = ptr;
289 /* Place holder h1 for complete sequence */
290 ptr = asn_build_sequence (ptr, &len, (u_char) SMUX_GETRSP, 0);
291 h1e = ptr;
292
293 ptr = asn_build_int (ptr, &len,
294 (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
295 &reqid, sizeof (reqid));
296
297 if (debug_smux)
298 zlog_debug ("SMUX GETRSP errstat: %ld", errstat);
299
300 ptr = asn_build_int (ptr, &len,
301 (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
302 &errstat, sizeof (errstat));
303 if (debug_smux)
304 zlog_debug ("SMUX GETRSP errindex: %ld", errindex);
305
306 ptr = asn_build_int (ptr, &len,
307 (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
308 &errindex, sizeof (errindex));
309
310 h2 = ptr;
311 /* Place holder h2 for one variable */
312 ptr = asn_build_sequence (ptr, &len,
313 (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR),
314 0);
315 h2e = ptr;
316
317 ptr = snmp_build_var_op (ptr, objid, &objid_len,
318 val_type, arg_len, arg, &len);
319
320 /* Now variable size is known, fill in size */
321 asn_build_sequence(h2,&length,(u_char)(ASN_SEQUENCE|ASN_CONSTRUCTOR),ptr-h2e);
322
323 /* Fill in size of whole sequence */
324 asn_build_sequence(h1,&length,(u_char)SMUX_GETRSP,ptr-h1e);
325
326 if (debug_smux)
327 zlog_debug ("SMUX getresp send: %td", (ptr - buf));
328
329 ret = send (smux_sock, buf, (ptr - buf), 0);
330 }
331
332 static u_char *
333 smux_var (u_char *ptr, size_t len, oid objid[], size_t *objid_len,
334 size_t *var_val_len,
335 u_char *var_val_type,
336 void **var_value)
337 {
338 u_char type;
339 u_char val_type;
340 size_t val_len;
341 u_char *val;
342
343 if (debug_smux)
344 zlog_debug ("SMUX var parse: len %zd", len);
345
346 /* Parse header. */
347 ptr = asn_parse_header (ptr, &len, &type);
348
349 if (debug_smux)
350 {
351 zlog_debug ("SMUX var parse: type %d len %zd", type, len);
352 zlog_debug ("SMUX var parse: type must be %d",
353 (ASN_SEQUENCE | ASN_CONSTRUCTOR));
354 }
355
356 /* Parse var option. */
357 *objid_len = MAX_OID_LEN;
358 ptr = snmp_parse_var_op(ptr, objid, objid_len, &val_type,
359 &val_len, &val, &len);
360
361 if (var_val_len)
362 *var_val_len = val_len;
363
364 if (var_value)
365 *var_value = (void*) val;
366
367 if (var_val_type)
368 *var_val_type = val_type;
369
370 /* Requested object id length is objid_len. */
371 if (debug_smux)
372 smux_oid_dump ("Request OID", objid, *objid_len);
373
374 if (debug_smux)
375 zlog_debug ("SMUX val_type: %d", val_type);
376
377 /* Check request value type. */
378 if (debug_smux)
379 switch (val_type)
380 {
381 case ASN_NULL:
382 /* In case of SMUX_GET or SMUX_GET_NEXT val_type is set to
383 ASN_NULL. */
384 zlog_debug ("ASN_NULL");
385 break;
386
387 case ASN_INTEGER:
388 zlog_debug ("ASN_INTEGER");
389 break;
390 case ASN_COUNTER:
391 case ASN_GAUGE:
392 case ASN_TIMETICKS:
393 case ASN_UINTEGER:
394 zlog_debug ("ASN_COUNTER");
395 break;
396 case ASN_COUNTER64:
397 zlog_debug ("ASN_COUNTER64");
398 break;
399 case ASN_IPADDRESS:
400 zlog_debug ("ASN_IPADDRESS");
401 break;
402 case ASN_OCTET_STR:
403 zlog_debug ("ASN_OCTET_STR");
404 break;
405 case ASN_OPAQUE:
406 case ASN_NSAP:
407 case ASN_OBJECT_ID:
408 zlog_debug ("ASN_OPAQUE");
409 break;
410 case SNMP_NOSUCHOBJECT:
411 zlog_debug ("SNMP_NOSUCHOBJECT");
412 break;
413 case SNMP_NOSUCHINSTANCE:
414 zlog_debug ("SNMP_NOSUCHINSTANCE");
415 break;
416 case SNMP_ENDOFMIBVIEW:
417 zlog_debug ("SNMP_ENDOFMIBVIEW");
418 break;
419 case ASN_BIT_STR:
420 zlog_debug ("ASN_BIT_STR");
421 break;
422 default:
423 zlog_debug ("Unknown type");
424 break;
425 }
426 return ptr;
427 }
428
429 /* NOTE: all 3 functions (smux_set, smux_get & smux_getnext) are based on
430 ucd-snmp smux and as such suppose, that the peer receives in the message
431 only one variable. Fortunately, IBM seems to do the same in AIX. */
432
433 static int
434 smux_set (oid *reqid, size_t *reqid_len,
435 u_char val_type, void *val, size_t val_len, int action)
436 {
437 int j;
438 struct subtree *subtree;
439 struct variable *v;
440 int subresult;
441 oid *suffix;
442 size_t suffix_len;
443 int result;
444 u_char *statP = NULL;
445 WriteMethod *write_method = NULL;
446 struct listnode *node, *nnode;
447
448 /* Check */
449 for (ALL_LIST_ELEMENTS (treelist, node, nnode, subtree))
450 {
451 subresult = oid_compare_part (reqid, *reqid_len,
452 subtree->name, subtree->name_len);
453
454 /* Subtree matched. */
455 if (subresult == 0)
456 {
457 /* Prepare suffix. */
458 suffix = reqid + subtree->name_len;
459 suffix_len = *reqid_len - subtree->name_len;
460 result = subresult;
461
462 /* Check variables. */
463 for (j = 0; j < subtree->variables_num; j++)
464 {
465 v = &subtree->variables[j];
466
467 /* Always check suffix */
468 result = oid_compare_part (suffix, suffix_len,
469 v->name, v->namelen);
470
471 /* This is exact match so result must be zero. */
472 if (result == 0)
473 {
474 if (debug_smux)
475 zlog_debug ("SMUX function call index is %d", v->magic);
476
477 statP = (*v->findVar) (v, suffix, &suffix_len, 1,
478 &val_len, &write_method);
479
480 if (write_method)
481 {
482 return (*write_method)(action, val, val_type, val_len,
483 statP, suffix, suffix_len, v);
484 }
485 else
486 {
487 return SNMP_ERR_READONLY;
488 }
489 }
490
491 /* If above execution is failed or oid is small (so
492 there is no further match). */
493 if (result < 0)
494 return SNMP_ERR_NOSUCHNAME;
495 }
496 }
497 }
498 return SNMP_ERR_NOSUCHNAME;
499 }
500
501 static int
502 smux_get (oid *reqid, size_t *reqid_len, int exact,
503 u_char *val_type,void **val, size_t *val_len)
504 {
505 int j;
506 struct subtree *subtree;
507 struct variable *v;
508 int subresult;
509 oid *suffix;
510 size_t suffix_len;
511 int result;
512 WriteMethod *write_method=NULL;
513 struct listnode *node, *nnode;
514
515 /* Check */
516 for (ALL_LIST_ELEMENTS (treelist, node, nnode,subtree))
517 {
518 subresult = oid_compare_part (reqid, *reqid_len,
519 subtree->name, subtree->name_len);
520
521 /* Subtree matched. */
522 if (subresult == 0)
523 {
524 /* Prepare suffix. */
525 suffix = reqid + subtree->name_len;
526 suffix_len = *reqid_len - subtree->name_len;
527 result = subresult;
528
529 /* Check variables. */
530 for (j = 0; j < subtree->variables_num; j++)
531 {
532 v = &subtree->variables[j];
533
534 /* Always check suffix */
535 result = oid_compare_part (suffix, suffix_len,
536 v->name, v->namelen);
537
538 /* This is exact match so result must be zero. */
539 if (result == 0)
540 {
541 if (debug_smux)
542 zlog_debug ("SMUX function call index is %d", v->magic);
543
544 *val = (*v->findVar) (v, suffix, &suffix_len, exact,
545 val_len, &write_method);
546
547 /* There is no instance. */
548 if (*val == NULL)
549 return SNMP_NOSUCHINSTANCE;
550
551 /* Call is suceed. */
552 *val_type = v->type;
553
554 return 0;
555 }
556
557 /* If above execution is failed or oid is small (so
558 there is no further match). */
559 if (result < 0)
560 return SNMP_ERR_NOSUCHNAME;
561 }
562 }
563 }
564 return SNMP_ERR_NOSUCHNAME;
565 }
566
567 static int
568 smux_getnext (oid *reqid, size_t *reqid_len, int exact,
569 u_char *val_type,void **val, size_t *val_len)
570 {
571 int j;
572 oid save[MAX_OID_LEN];
573 int savelen = 0;
574 struct subtree *subtree;
575 struct variable *v;
576 int subresult;
577 oid *suffix;
578 size_t suffix_len;
579 int result;
580 WriteMethod *write_method=NULL;
581 struct listnode *node, *nnode;
582
583
584 /* Save incoming request. */
585 oid_copy (save, reqid, *reqid_len);
586 savelen = *reqid_len;
587
588 /* Check */
589 for (ALL_LIST_ELEMENTS (treelist, node, nnode, subtree))
590 {
591 subresult = oid_compare_part (reqid, *reqid_len,
592 subtree->name, subtree->name_len);
593
594 /* If request is in the tree. The agent has to make sure we
595 only receive requests we have registered for. */
596 /* Unfortunately, that's not true. In fact, a SMUX subagent has to
597 behave as if it manages the whole SNMP MIB tree itself. It's the
598 duty of the master agent to collect the best answer and return it
599 to the manager. See RFC 1227 chapter 3.1.6 for the glory details
600 :-). ucd-snmp really behaves bad here as it actually might ask
601 multiple times for the same GETNEXT request as it throws away the
602 answer when it expects it in a different subtree and might come
603 back later with the very same request. --jochen */
604
605 if (subresult <= 0)
606 {
607 /* Prepare suffix. */
608 suffix = reqid + subtree->name_len;
609 suffix_len = *reqid_len - subtree->name_len;
610 if (subresult < 0)
611 {
612 oid_copy(reqid, subtree->name, subtree->name_len);
613 *reqid_len = subtree->name_len;
614 }
615 for (j = 0; j < subtree->variables_num; j++)
616 {
617 result = subresult;
618 v = &subtree->variables[j];
619
620 /* Next then check result >= 0. */
621 if (result == 0)
622 result = oid_compare_part (suffix, suffix_len,
623 v->name, v->namelen);
624
625 if (result <= 0)
626 {
627 if (debug_smux)
628 zlog_debug ("SMUX function call index is %d", v->magic);
629 if(result<0)
630 {
631 oid_copy(suffix, v->name, v->namelen);
632 suffix_len = v->namelen;
633 }
634 *val = (*v->findVar) (v, suffix, &suffix_len, exact,
635 val_len, &write_method);
636 *reqid_len = suffix_len + subtree->name_len;
637 if (*val)
638 {
639 *val_type = v->type;
640 return 0;
641 }
642 }
643 }
644 }
645 }
646 memcpy (reqid, save, savelen * sizeof(oid));
647 *reqid_len = savelen;
648
649 return SNMP_ERR_NOSUCHNAME;
650 }
651
652 /* GET message header. */
653 static u_char *
654 smux_parse_get_header (u_char *ptr, size_t *len, long *reqid)
655 {
656 u_char type;
657 long errstat;
658 long errindex;
659
660 /* Request ID. */
661 ptr = asn_parse_int (ptr, len, &type, reqid, sizeof (*reqid));
662
663 if (debug_smux)
664 zlog_debug ("SMUX GET reqid: %d len: %d", (int) *reqid, (int) *len);
665
666 /* Error status. */
667 ptr = asn_parse_int (ptr, len, &type, &errstat, sizeof (errstat));
668
669 if (debug_smux)
670 zlog_debug ("SMUX GET errstat %ld len: %zd", errstat, *len);
671
672 /* Error index. */
673 ptr = asn_parse_int (ptr, len, &type, &errindex, sizeof (errindex));
674
675 if (debug_smux)
676 zlog_debug ("SMUX GET errindex %ld len: %zd", errindex, *len);
677
678 return ptr;
679 }
680
681 static void
682 smux_parse_set (u_char *ptr, size_t len, int action)
683 {
684 long reqid;
685 oid oid[MAX_OID_LEN];
686 size_t oid_len;
687 u_char val_type;
688 void *val;
689 size_t val_len;
690 int ret;
691
692 if (debug_smux)
693 zlog_debug ("SMUX SET(%s) message parse: len %zd",
694 (RESERVE1 == action) ? "RESERVE1" : ((FREE == action) ? "FREE" : "COMMIT"),
695 len);
696
697 /* Parse SET message header. */
698 ptr = smux_parse_get_header (ptr, &len, &reqid);
699
700 /* Parse SET message object ID. */
701 ptr = smux_var (ptr, len, oid, &oid_len, &val_len, &val_type, &val);
702
703 ret = smux_set (oid, &oid_len, val_type, val, val_len, action);
704 if (debug_smux)
705 zlog_debug ("SMUX SET ret %d", ret);
706
707 /* Return result. */
708 if (RESERVE1 == action)
709 smux_getresp_send (oid, oid_len, reqid, ret, 3, ASN_NULL, NULL, 0);
710 }
711
712 static void
713 smux_parse_get (u_char *ptr, size_t len, int exact)
714 {
715 long reqid;
716 oid oid[MAX_OID_LEN];
717 size_t oid_len;
718 u_char val_type;
719 void *val;
720 size_t val_len;
721 int ret;
722
723 if (debug_smux)
724 zlog_debug ("SMUX GET message parse: len %zd", len);
725
726 /* Parse GET message header. */
727 ptr = smux_parse_get_header (ptr, &len, &reqid);
728
729 /* Parse GET message object ID. We needn't the value come */
730 ptr = smux_var (ptr, len, oid, &oid_len, NULL, NULL, NULL);
731
732 /* Traditional getstatptr. */
733 if (exact)
734 ret = smux_get (oid, &oid_len, exact, &val_type, &val, &val_len);
735 else
736 ret = smux_getnext (oid, &oid_len, exact, &val_type, &val, &val_len);
737
738 /* Return result. */
739 if (ret == 0)
740 smux_getresp_send (oid, oid_len, reqid, 0, 0, val_type, val, val_len);
741 else
742 smux_getresp_send (oid, oid_len, reqid, ret, 3, ASN_NULL, NULL, 0);
743 }
744
745 /* Parse SMUX_CLOSE message. */
746 static void
747 smux_parse_close (u_char *ptr, int len)
748 {
749 long reason = 0;
750
751 while (len--)
752 {
753 reason = (reason << 8) | (long) *ptr;
754 ptr++;
755 }
756 zlog_info ("SMUX_CLOSE with reason: %ld", reason);
757 }
758
759 /* SMUX_RRSP message. */
760 static void
761 smux_parse_rrsp (u_char *ptr, size_t len)
762 {
763 u_char val;
764 long errstat;
765
766 ptr = asn_parse_int (ptr, &len, &val, &errstat, sizeof (errstat));
767
768 if (debug_smux)
769 zlog_debug ("SMUX_RRSP value: %d errstat: %ld", val, errstat);
770 }
771
772 /* Parse SMUX message. */
773 static int
774 smux_parse (u_char *ptr, size_t len)
775 {
776 /* This buffer we'll use for SOUT message. We could allocate it with
777 malloc and save only static pointer/lenght, but IMHO static
778 buffer is a faster solusion. */
779 static u_char sout_save_buff[SMUXMAXPKTSIZE];
780 static int sout_save_len = 0;
781
782 int len_income = len; /* see note below: YYY */
783 u_char type;
784 u_char rollback;
785
786 rollback = ptr[2]; /* important only for SMUX_SOUT */
787
788 process_rest: /* see note below: YYY */
789
790 /* Parse SMUX message type and subsequent length. */
791 ptr = asn_parse_header (ptr, &len, &type);
792
793 if (debug_smux)
794 zlog_debug ("SMUX message received type: %d rest len: %zd", type, len);
795
796 switch (type)
797 {
798 case SMUX_OPEN:
799 /* Open must be not send from SNMP agent. */
800 zlog_warn ("SMUX_OPEN received: resetting connection.");
801 return -1;
802 break;
803 case SMUX_RREQ:
804 /* SMUX_RREQ message is invalid for us. */
805 zlog_warn ("SMUX_RREQ received: resetting connection.");
806 return -1;
807 break;
808 case SMUX_SOUT:
809 /* SMUX_SOUT message is now valied for us. */
810 if (debug_smux)
811 zlog_debug ("SMUX_SOUT(%s)", rollback ? "rollback" : "commit");
812
813 if (sout_save_len > 0)
814 {
815 smux_parse_set (sout_save_buff, sout_save_len, rollback ? FREE : COMMIT);
816 sout_save_len = 0;
817 }
818 else
819 zlog_warn ("SMUX_SOUT sout_save_len=%d - invalid", (int) sout_save_len);
820
821 if (len_income > 3)
822 {
823 /* YYY: this strange code has to solve the "slow peer"
824 problem: When agent sends SMUX_SOUT message it doesn't
825 wait any responce and may send some next message to
826 subagent. Then the peer in 'smux_read()' will recieve
827 from socket the 'concatenated' buffer, contaning both
828 SMUX_SOUT message and the next one
829 (SMUX_GET/SMUX_GETNEXT/SMUX_GET). So we should check: if
830 the buffer is longer than 3 ( length of SMUX_SOUT ), we
831 must process the rest of it. This effect may be observed
832 if 'debug_smux' is set to '1' */
833 ptr++;
834 len = len_income - 3;
835 goto process_rest;
836 }
837 break;
838 case SMUX_GETRSP:
839 /* SMUX_GETRSP message is invalid for us. */
840 zlog_warn ("SMUX_GETRSP received: resetting connection.");
841 return -1;
842 break;
843 case SMUX_CLOSE:
844 /* Close SMUX connection. */
845 if (debug_smux)
846 zlog_debug ("SMUX_CLOSE");
847 smux_parse_close (ptr, len);
848 return -1;
849 break;
850 case SMUX_RRSP:
851 /* This is response for register message. */
852 if (debug_smux)
853 zlog_debug ("SMUX_RRSP");
854 smux_parse_rrsp (ptr, len);
855 break;
856 case SMUX_GET:
857 /* Exact request for object id. */
858 if (debug_smux)
859 zlog_debug ("SMUX_GET");
860 smux_parse_get (ptr, len, 1);
861 break;
862 case SMUX_GETNEXT:
863 /* Next request for object id. */
864 if (debug_smux)
865 zlog_debug ("SMUX_GETNEXT");
866 smux_parse_get (ptr, len, 0);
867 break;
868 case SMUX_SET:
869 /* SMUX_SET is supported with some limitations. */
870 if (debug_smux)
871 zlog_debug ("SMUX_SET");
872
873 /* save the data for future SMUX_SOUT */
874 memcpy (sout_save_buff, ptr, len);
875 sout_save_len = len;
876 smux_parse_set (ptr, len, RESERVE1);
877 break;
878 default:
879 zlog_info ("Unknown type: %d", type);
880 break;
881 }
882 return 0;
883 }
884
885 /* SMUX message read function. */
886 static int
887 smux_read (struct thread *t)
888 {
889 int sock;
890 int len;
891 u_char buf[SMUXMAXPKTSIZE];
892 int ret;
893
894 /* Clear thread. */
895 sock = THREAD_FD (t);
896 smux_read_thread = NULL;
897
898 if (debug_smux)
899 zlog_debug ("SMUX read start");
900
901 /* Read message from SMUX socket. */
902 len = recv (sock, buf, SMUXMAXPKTSIZE, 0);
903
904 if (len < 0)
905 {
906 zlog_warn ("Can't read all SMUX packet: %s", safe_strerror (errno));
907 close (sock);
908 smux_sock = -1;
909 smux_event (SMUX_CONNECT, 0);
910 return -1;
911 }
912
913 if (len == 0)
914 {
915 zlog_warn ("SMUX connection closed: %d", sock);
916 close (sock);
917 smux_sock = -1;
918 smux_event (SMUX_CONNECT, 0);
919 return -1;
920 }
921
922 if (debug_smux)
923 zlog_debug ("SMUX read len: %d", len);
924
925 /* Parse the message. */
926 ret = smux_parse (buf, len);
927
928 if (ret < 0)
929 {
930 close (sock);
931 smux_sock = -1;
932 smux_event (SMUX_CONNECT, 0);
933 return -1;
934 }
935
936 /* Regiser read thread. */
937 smux_event (SMUX_READ, sock);
938
939 return 0;
940 }
941
942 static int
943 smux_open (int sock)
944 {
945 u_char buf[BUFSIZ];
946 u_char *ptr;
947 size_t len;
948 long version;
949 u_char progname[] = QUAGGA_PROGNAME "-" QUAGGA_VERSION;
950
951 if (debug_smux)
952 {
953 smux_oid_dump ("SMUX open oid", smux_oid, smux_oid_len);
954 zlog_debug ("SMUX open progname: %s", progname);
955 zlog_debug ("SMUX open password: %s", smux_passwd);
956 }
957
958 ptr = buf;
959 len = BUFSIZ;
960
961 /* SMUX Header. As placeholder. */
962 ptr = asn_build_header (ptr, &len, (u_char) SMUX_OPEN, 0);
963
964 /* SMUX Open. */
965 version = 0;
966 ptr = asn_build_int (ptr, &len,
967 (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
968 &version, sizeof (version));
969
970 /* SMUX connection oid. */
971 ptr = asn_build_objid (ptr, &len,
972 (u_char)
973 (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID),
974 smux_oid, smux_oid_len);
975
976 /* SMUX connection description. */
977 ptr = asn_build_string (ptr, &len,
978 (u_char)
979 (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR),
980 progname, strlen (progname));
981
982 /* SMUX connection password. */
983 ptr = asn_build_string (ptr, &len,
984 (u_char)
985 (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR),
986 (u_char *)smux_passwd, strlen (smux_passwd));
987
988 /* Fill in real SMUX header. We exclude ASN header size (2). */
989 len = BUFSIZ;
990 asn_build_header (buf, &len, (u_char) SMUX_OPEN, (ptr - buf) - 2);
991
992 return send (sock, buf, (ptr - buf), 0);
993 }
994
995 int
996 smux_trap (oid *name, size_t namelen,
997 oid *iname, size_t inamelen,
998 struct trap_object *trapobj, size_t trapobjlen,
999 unsigned int tick, u_char sptrap)
1000 {
1001 unsigned int i;
1002 u_char buf[BUFSIZ];
1003 u_char *ptr;
1004 size_t len, length;
1005 struct in_addr addr;
1006 unsigned long val;
1007 u_char *h1, *h1e;
1008
1009 ptr = buf;
1010 len = BUFSIZ;
1011 length = len;
1012
1013 /* When SMUX connection is not established. */
1014 if (smux_sock < 0)
1015 return 0;
1016
1017 /* SMUX header. */
1018 ptr = asn_build_header (ptr, &len, (u_char) SMUX_TRAP, 0);
1019
1020 /* Sub agent enterprise oid. */
1021 ptr = asn_build_objid (ptr, &len,
1022 (u_char)
1023 (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID),
1024 smux_oid, smux_oid_len);
1025
1026 /* IP address. */
1027 addr.s_addr = 0;
1028 ptr = asn_build_string (ptr, &len,
1029 (u_char)
1030 (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_IPADDRESS),
1031 (u_char *)&addr, sizeof (addr));
1032
1033 /* Generic trap integer. */
1034 val = SNMP_TRAP_ENTERPRISESPECIFIC;
1035 ptr = asn_build_int (ptr, &len,
1036 (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
1037 (long *)&val, sizeof (val));
1038
1039 /* Specific trap integer. */
1040 val = sptrap;
1041 ptr = asn_build_int (ptr, &len,
1042 (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
1043 (long *)&val, sizeof (val));
1044
1045 /* Timeticks timestamp. */
1046 val = 0;
1047 ptr = asn_build_unsigned_int (ptr, &len,
1048 (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_TIMETICKS),
1049 &val, sizeof (val));
1050
1051 /* Variables. */
1052 h1 = ptr;
1053 ptr = asn_build_sequence (ptr, &len,
1054 (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR),
1055 0);
1056
1057
1058 /* Iteration for each objects. */
1059 h1e = ptr;
1060 for (i = 0; i < trapobjlen; i++)
1061 {
1062 int ret;
1063 oid oid[MAX_OID_LEN];
1064 size_t oid_len;
1065 void *val;
1066 size_t val_len;
1067 u_char val_type;
1068
1069 /* Make OID. */
1070 if (trapobj[i].namelen > 0)
1071 {
1072 oid_copy (oid, name, namelen);
1073 oid_copy (oid + namelen, trapobj[i].name, trapobj[i].namelen);
1074 oid_copy (oid + namelen + trapobj[i].namelen, iname, inamelen);
1075 oid_len = namelen + trapobj[i].namelen + inamelen;
1076 }
1077 else
1078 {
1079 oid_copy (oid, name, namelen);
1080 oid_copy (oid + namelen, trapobj[i].name, trapobj[i].namelen * (-1));
1081 oid_len = namelen + trapobj[i].namelen * (-1) ;
1082 }
1083
1084 if (debug_smux)
1085 {
1086 smux_oid_dump ("Trap", name, namelen);
1087 if (trapobj[i].namelen < 0)
1088 smux_oid_dump ("Trap",
1089 trapobj[i].name, (- 1) * (trapobj[i].namelen));
1090 else
1091 {
1092 smux_oid_dump ("Trap", trapobj[i].name, (trapobj[i].namelen));
1093 smux_oid_dump ("Trap", iname, inamelen);
1094 }
1095 smux_oid_dump ("Trap", oid, oid_len);
1096 zlog_info ("BUFSIZ: %d // oid_len: %lu", BUFSIZ, (u_long)oid_len);
1097 }
1098
1099 ret = smux_get (oid, &oid_len, 1, &val_type, &val, &val_len);
1100
1101 if (debug_smux)
1102 zlog_debug ("smux_get result %d", ret);
1103
1104 if (ret == 0)
1105 ptr = snmp_build_var_op (ptr, oid, &oid_len,
1106 val_type, val_len, val, &len);
1107 }
1108
1109 /* Now variable size is known, fill in size */
1110 asn_build_sequence(h1, &length,
1111 (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR),
1112 ptr - h1e);
1113
1114 /* Fill in size of whole sequence */
1115 len = BUFSIZ;
1116 asn_build_header (buf, &len, (u_char) SMUX_TRAP, (ptr - buf) - 2);
1117
1118 return send (smux_sock, buf, (ptr - buf), 0);
1119 }
1120
1121 static int
1122 smux_register (int sock)
1123 {
1124 u_char buf[BUFSIZ];
1125 u_char *ptr;
1126 int ret;
1127 size_t len;
1128 long priority;
1129 long operation;
1130 struct subtree *subtree;
1131 struct listnode *node, *nnode;
1132
1133 ret = 0;
1134
1135 for (ALL_LIST_ELEMENTS (treelist, node, nnode, subtree))
1136 {
1137 ptr = buf;
1138 len = BUFSIZ;
1139
1140 /* SMUX RReq Header. */
1141 ptr = asn_build_header (ptr, &len, (u_char) SMUX_RREQ, 0);
1142
1143 /* Register MIB tree. */
1144 ptr = asn_build_objid (ptr, &len,
1145 (u_char)
1146 (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID),
1147 subtree->name, subtree->name_len);
1148
1149 /* Priority. */
1150 priority = -1;
1151 ptr = asn_build_int (ptr, &len,
1152 (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
1153 &priority, sizeof (priority));
1154
1155 /* Operation. */
1156 operation = 2; /* Register R/W */
1157 ptr = asn_build_int (ptr, &len,
1158 (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
1159 &operation, sizeof (operation));
1160
1161 if (debug_smux)
1162 {
1163 smux_oid_dump ("SMUX register oid", subtree->name, subtree->name_len);
1164 zlog_debug ("SMUX register priority: %ld", priority);
1165 zlog_debug ("SMUX register operation: %ld", operation);
1166 }
1167
1168 len = BUFSIZ;
1169 asn_build_header (buf, &len, (u_char) SMUX_RREQ, (ptr - buf) - 2);
1170 ret = send (sock, buf, (ptr - buf), 0);
1171 if (ret < 0)
1172 return ret;
1173 }
1174 return ret;
1175 }
1176
1177 /* Try to connect to SNMP agent. */
1178 static int
1179 smux_connect (struct thread *t)
1180 {
1181 int ret;
1182
1183 if (debug_smux)
1184 zlog_debug ("SMUX connect try %d", fail + 1);
1185
1186 /* Clear thread poner of myself. */
1187 smux_connect_thread = NULL;
1188
1189 /* Make socket. Try to connect. */
1190 smux_sock = smux_socket ();
1191 if (smux_sock < 0)
1192 {
1193 if (++fail < SMUX_MAX_FAILURE)
1194 smux_event (SMUX_CONNECT, 0);
1195 return 0;
1196 }
1197
1198 /* Send OPEN PDU. */
1199 ret = smux_open (smux_sock);
1200 if (ret < 0)
1201 {
1202 zlog_warn ("SMUX open message send failed: %s", safe_strerror (errno));
1203 close (smux_sock);
1204 smux_sock = -1;
1205 if (++fail < SMUX_MAX_FAILURE)
1206 smux_event (SMUX_CONNECT, 0);
1207 return -1;
1208 }
1209
1210 /* Send any outstanding register PDUs. */
1211 ret = smux_register (smux_sock);
1212 if (ret < 0)
1213 {
1214 zlog_warn ("SMUX register message send failed: %s", safe_strerror (errno));
1215 close (smux_sock);
1216 smux_sock = -1;
1217 if (++fail < SMUX_MAX_FAILURE)
1218 smux_event (SMUX_CONNECT, 0);
1219 return -1;
1220 }
1221
1222 /* Everything goes fine. */
1223 smux_event (SMUX_READ, smux_sock);
1224
1225 return 0;
1226 }
1227
1228 /* Clear all SMUX related resources. */
1229 static void
1230 smux_stop ()
1231 {
1232 if (smux_read_thread)
1233 {
1234 thread_cancel (smux_read_thread);
1235 smux_read_thread = NULL;
1236 }
1237
1238 if (smux_connect_thread)
1239 {
1240 thread_cancel (smux_connect_thread);
1241 smux_connect_thread = NULL;
1242 }
1243
1244 if (smux_sock >= 0)
1245 {
1246 close (smux_sock);
1247 smux_sock = -1;
1248 }
1249 }
1250 \f
1251
1252
1253 void
1254 smux_event (enum smux_event event, int sock)
1255 {
1256 switch (event)
1257 {
1258 case SMUX_SCHEDULE:
1259 smux_connect_thread = thread_add_event (master, smux_connect, NULL, 0);
1260 break;
1261 case SMUX_CONNECT:
1262 smux_connect_thread = thread_add_timer (master, smux_connect, NULL, 10);
1263 break;
1264 case SMUX_READ:
1265 smux_read_thread = thread_add_read (master, smux_read, NULL, sock);
1266 break;
1267 default:
1268 break;
1269 }
1270 }
1271 \f
1272 static int
1273 smux_str2oid (const char *str, oid *oid, size_t *oid_len)
1274 {
1275 int len;
1276 int val;
1277
1278 len = 0;
1279 val = 0;
1280 *oid_len = 0;
1281
1282 if (*str == '.')
1283 str++;
1284 if (*str == '\0')
1285 return 0;
1286
1287 while (1)
1288 {
1289 if (! isdigit (*str))
1290 return -1;
1291
1292 while (isdigit (*str))
1293 {
1294 val *= 10;
1295 val += (*str - '0');
1296 str++;
1297 }
1298
1299 if (*str == '\0')
1300 break;
1301 if (*str != '.')
1302 return -1;
1303
1304 oid[len++] = val;
1305 val = 0;
1306 str++;
1307 }
1308
1309 oid[len++] = val;
1310 *oid_len = len;
1311
1312 return 0;
1313 }
1314
1315 static oid *
1316 smux_oid_dup (oid *objid, size_t objid_len)
1317 {
1318 oid *new;
1319
1320 new = XMALLOC (MTYPE_TMP, sizeof (oid) * objid_len);
1321 oid_copy (new, objid, objid_len);
1322
1323 return new;
1324 }
1325
1326 static int
1327 smux_peer_oid (struct vty *vty, const char *oid_str, const char *passwd_str)
1328 {
1329 int ret;
1330 oid oid[MAX_OID_LEN];
1331 size_t oid_len;
1332
1333 ret = smux_str2oid (oid_str, oid, &oid_len);
1334 if (ret != 0)
1335 {
1336 vty_out (vty, "object ID malformed%s", VTY_NEWLINE);
1337 return CMD_WARNING;
1338 }
1339
1340 if (smux_oid)
1341 {
1342 free (smux_oid);
1343 smux_oid = NULL;
1344 }
1345
1346 /* careful, smux_passwd might point to string constant */
1347 if (smux_passwd)
1348 {
1349 free (smux_passwd);
1350 smux_passwd = NULL;
1351 }
1352
1353 smux_oid = smux_oid_dup (oid, oid_len);
1354 smux_oid_len = oid_len;
1355
1356 if (passwd_str)
1357 smux_passwd = strdup (passwd_str);
1358 else
1359 smux_passwd = strdup ("");
1360
1361 return 0;
1362 }
1363
1364 int
1365 smux_header_generic (struct variable *v, oid *name, size_t *length, int exact,
1366 size_t *var_len, WriteMethod **write_method)
1367 {
1368 oid fulloid[MAX_OID_LEN];
1369 int ret;
1370
1371 oid_copy (fulloid, v->name, v->namelen);
1372 fulloid[v->namelen] = 0;
1373 /* Check against full instance. */
1374 ret = oid_compare (name, *length, fulloid, v->namelen + 1);
1375
1376 /* Check single instance. */
1377 if ((exact && (ret != 0)) || (!exact && (ret >= 0)))
1378 return MATCH_FAILED;
1379
1380 /* In case of getnext, fill in full instance. */
1381 memcpy (name, fulloid, (v->namelen + 1) * sizeof (oid));
1382 *length = v->namelen + 1;
1383
1384 *write_method = 0;
1385 *var_len = sizeof(long); /* default to 'long' results */
1386
1387 return MATCH_SUCCEEDED;
1388 }
1389
1390 static int
1391 smux_peer_default ()
1392 {
1393 if (smux_oid)
1394 {
1395 free (smux_oid);
1396 smux_oid = NULL;
1397 }
1398
1399 /* careful, smux_passwd might be pointing at string constant */
1400 if (smux_passwd)
1401 {
1402 free (smux_passwd);
1403 smux_passwd = NULL;
1404 }
1405
1406 return CMD_SUCCESS;
1407 }
1408
1409 DEFUN (smux_peer,
1410 smux_peer_cmd,
1411 "smux peer OID",
1412 "SNMP MUX protocol settings\n"
1413 "SNMP MUX peer settings\n"
1414 "Object ID used in SMUX peering\n")
1415 {
1416 if (smux_peer_oid (vty, argv[0], NULL) == 0)
1417 {
1418 smux_start();
1419 return CMD_SUCCESS;
1420 }
1421 else
1422 return CMD_WARNING;
1423 }
1424
1425 DEFUN (smux_peer_password,
1426 smux_peer_password_cmd,
1427 "smux peer OID PASSWORD",
1428 "SNMP MUX protocol settings\n"
1429 "SNMP MUX peer settings\n"
1430 "SMUX peering object ID\n"
1431 "SMUX peering password\n")
1432 {
1433 if (smux_peer_oid (vty, argv[0], argv[1]) == 0)
1434 {
1435 smux_start();
1436 return CMD_SUCCESS;
1437 }
1438 else
1439 return CMD_WARNING;
1440 }
1441
1442 DEFUN (no_smux_peer,
1443 no_smux_peer_cmd,
1444 "no smux peer",
1445 NO_STR
1446 "SNMP MUX protocol settings\n"
1447 "SNMP MUX peer settings\n")
1448 {
1449 smux_stop();
1450 return smux_peer_default ();
1451 }
1452
1453 ALIAS (no_smux_peer,
1454 no_smux_peer_oid_cmd,
1455 "no smux peer OID",
1456 NO_STR
1457 "SNMP MUX protocol settings\n"
1458 "SNMP MUX peer settings\n"
1459 "SMUX peering object ID\n")
1460
1461 ALIAS (no_smux_peer,
1462 no_smux_peer_oid_password_cmd,
1463 "no smux peer OID PASSWORD",
1464 NO_STR
1465 "SNMP MUX protocol settings\n"
1466 "SNMP MUX peer settings\n"
1467 "SMUX peering object ID\n"
1468 "SMUX peering password\n")
1469
1470 static int
1471 config_write_smux (struct vty *vty)
1472 {
1473 int first = 1;
1474 unsigned int i;
1475
1476 if (smux_oid)
1477 {
1478 vty_out (vty, "smux peer ");
1479 for (i = 0; i < smux_oid_len; i++)
1480 {
1481 vty_out (vty, "%s%d", first ? "" : ".", (int) smux_oid[i]);
1482 first = 0;
1483 }
1484 vty_out (vty, " %s%s", smux_passwd, VTY_NEWLINE);
1485 }
1486 return 0;
1487 }
1488
1489 /* Register subtree to smux master tree. */
1490 void
1491 smux_register_mib (const char *descr, struct variable *var,
1492 size_t width, int num,
1493 oid name[], size_t namelen)
1494 {
1495 struct subtree *tree;
1496
1497 tree = (struct subtree *)malloc(sizeof(struct subtree));
1498 oid_copy (tree->name, name, namelen);
1499 tree->name_len = namelen;
1500 tree->variables = var;
1501 tree->variables_num = num;
1502 tree->variables_width = width;
1503 tree->registered = 0;
1504 listnode_add_sort(treelist, tree);
1505 }
1506
1507 /* Compare function to keep treelist sorted */
1508 static int
1509 smux_tree_cmp(struct subtree *tree1, struct subtree *tree2)
1510 {
1511 return oid_compare(tree1->name, tree1->name_len,
1512 tree2->name, tree2->name_len);
1513 }
1514
1515 /* Initialize some values then schedule first SMUX connection. */
1516 void
1517 smux_init (struct thread_master *tm)
1518 {
1519 /* copy callers thread master */
1520 master = tm;
1521
1522 /* Make MIB tree. */
1523 treelist = list_new();
1524 treelist->cmp = (int (*)(void *, void *))smux_tree_cmp;
1525
1526 /* Install commands. */
1527 install_node (&smux_node, config_write_smux);
1528
1529 install_element (CONFIG_NODE, &smux_peer_cmd);
1530 install_element (CONFIG_NODE, &smux_peer_password_cmd);
1531 install_element (CONFIG_NODE, &no_smux_peer_cmd);
1532 install_element (CONFIG_NODE, &no_smux_peer_oid_cmd);
1533 install_element (CONFIG_NODE, &no_smux_peer_oid_password_cmd);
1534 }
1535
1536 void
1537 smux_start(void)
1538 {
1539 /* Close any existing connections. */
1540 smux_stop();
1541
1542 /* Schedule first connection. */
1543 smux_event (SMUX_SCHEDULE, 0);
1544 }
1545 #endif /* HAVE_SNMP */