1 #include <net-snmp/net-snmp-config.h>
2 #include <net-snmp/net-snmp-features.h>
3 
4 #include <errno.h>
5 #include <signal.h>
6 #include <string.h>
7 #ifdef HAVE_UNISTD_H
8 #include <unistd.h>             /* optind, optarg and optopt */
9 #endif
10 
11 #include <net-snmp/net-snmp-includes.h>
12 #include <net-snmp/agent/ds_agent.h>
13 #include "../agent_global_vars.h"
14 
15 #include "../agent/mibgroup/agentx/agentx_config.h"
16 #include "../agent/mibgroup/agentx/client.h"
17 #include "../agent/mibgroup/agentx/protocol.h"
18 #include "../agent/mibgroup/agentx/subagent.h"
19 
20 netsnmp_feature_require(snmp_split_pdu);
21 netsnmp_feature_require(snmp_reset_var_types);
22 
23 
24 static void
usage(const char * progname)25 usage(const char *progname)
26 {
27     fprintf(stderr,
28             "USAGE: %s [OPTIONS] TRAP-PARAMETERS\n"
29             "\n"
30             "  Version:  %s\n"
31             "  Web:      http://www.net-snmp.org/\n"
32             "  Email:    net-snmp-coders@lists.sourceforge.net\n"
33             "\n"
34             "OPTIONS:\n", progname, netsnmp_get_version());
35 
36     fprintf(stderr,
37             "  -h\t\t\tdisplay this help message\n"
38             "  -V\t\t\tdisplay package version number\n"
39             "  -m MIB[" ENV_SEPARATOR "...]\t\tload given list of MIBs (ALL loads everything)\n"
40             "  -M DIR[" ENV_SEPARATOR "...]\t\tlook in given list of directories for MIBs\n"
41             "  -D[TOKEN[,...]]\tturn on debugging output for the specified "
42             "TOKENs\n"
43             "\t\t\t   (ALL gives extremely verbose debugging output)\n"
44             "  -d\t\t\tdump all traffic\n");
45 #ifndef NETSNMP_DISABLE_MIB_LOADING
46     fprintf(stderr,
47             "  -P MIBOPTS\t\tToggle various defaults controlling mib "
48             "parsing:\n");
49     snmp_mib_toggle_options_usage("\t\t\t  ", stderr);
50 #endif                          /* NETSNMP_DISABLE_MIB_LOADING */
51     fprintf(stderr,
52             "  -L LOGOPTS\t\tToggle various defaults controlling logging:\n");
53     snmp_log_options_usage("\t\t\t  ", stderr);
54 
55     fprintf(stderr,
56             "  -c context\n"
57             "  -U uptime\n"
58             "  -x ADDRESS\t\tuse ADDRESS as AgentX address\n"
59             "\n"
60             "TRAP-PARAMETERS:\n"
61             "  trapoid [OID TYPE VALUE] ...\n");
62 }
63 
64 struct tState_s;
65 typedef const struct tState_s *tState;
66 struct tState_s {
67     void            (*entry)(tState self);
68                                 /**<< State entry action */
69     void            (*exit)(tState self);
70                                 /**<< State exit action */
71     /** Handler for AgentX-Response-PDU's */
72     void            (*response)(tState self, netsnmp_pdu *res);
73     /** State to change to if an AgentX timeout occurs or the timer runs out */
74     tState          timeout;
75     void            (*disconnect)(tState self);
76                                 /**<< Handler for disconnect indications */
77     /** Handler for Close-PDU indications */
78     void            (*close)(tState self, netsnmp_pdu *res);
79     const char     *name;       /**<< Name of the current state */
80     int             is_open;    /**<< If the connection is open in this state */
81 };
82 
83 static tState   state;          /**<< Current state of the state machine */
84 static tState   next_state;     /**<< Next state of the state machine */
85 static const char *context;     /**<< Context that delivers the trap */
86 static size_t   contextLen;     /**<< Length of eventual context */
87 static int      result = 1;     /**<< Program return value */
88 static netsnmp_pdu *pdu;        /**<< The trap pdu that is to be sent */
89 /** The reference number of the next packet */
90 static long     packetid;
91 /** The session id of the session to the master */
92 static long     session;
93 static void    *sessp;          /**<< The current communication session */
94 
95 #define STATE_CALL(method)                                              \
96     if (!state->method) {                                               \
97         snmp_log(LOG_ERR, "No " #method " method in %s, terminating\n", \
98                  state->name);                                          \
99         abort();                                                        \
100     } else                                                              \
101         state->method
102 
103 static void
change_state(tState new_state)104 change_state(tState new_state)
105 {
106     if (next_state && next_state != new_state)
107         DEBUGMSGTL(("process", "Ignore transition to %s\n",
108                     next_state->name));
109     next_state = new_state;
110 }
111 
112 static int
handle_agentx_response(int operation,netsnmp_session * sp,int reqid,netsnmp_pdu * act,void * magic)113 handle_agentx_response(int operation, netsnmp_session *sp, int reqid,
114                        netsnmp_pdu *act, void *magic)
115 {
116     switch (operation) {
117     case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE:
118         if (act->command == AGENTX_MSG_CLEANUPSET) {
119             /* Do nothing - no response and no action as nothing get
120              * allocated in any handler here
121              */
122         } else if (act->command != AGENTX_MSG_RESPONSE) {
123             /* Copy the head to a response */
124             netsnmp_pdu    *res = snmp_split_pdu(act, 0, 0);
125             res->command = AGENTX_MSG_RESPONSE;
126             if (act->sessid != session || !state->is_open)
127                 res->errstat = AGENTX_ERR_NOT_OPEN;
128             if (res->errstat == AGENTX_ERR_NOERROR)
129                 switch (act->command) {
130                 case AGENTX_MSG_GET:
131                     res->variables = snmp_clone_varbind(act->variables);
132                     snmp_reset_var_types(res->variables,
133                                          SNMP_NOSUCHOBJECT);
134                     break;
135                 case AGENTX_MSG_GETNEXT:
136                 case AGENTX_MSG_GETBULK:
137                     res->variables = snmp_clone_varbind(act->variables);
138                     snmp_reset_var_types(res->variables,
139                                          SNMP_ENDOFMIBVIEW);
140                     break;
141                 case AGENTX_MSG_TESTSET:
142                     res->errstat = SNMP_ERR_NOTWRITABLE;
143                     res->errindex = 1;
144                     break;
145                 case AGENTX_MSG_COMMITSET:
146                     res->errstat = SNMP_ERR_COMMITFAILED;
147                     res->errindex = 1;
148                     break;
149                 case AGENTX_MSG_UNDOSET:
150                     /* Success - could undo not setting any value :-) */
151                     break;
152                 case AGENTX_MSG_CLOSE:
153                     /* Always let the master succeed! */
154                     break;
155                 default:
156                     /* Unknown command */
157                     res->errstat = AGENTX_ERR_PARSE_FAILED;
158                     break;
159                 }
160             if (snmp_send(sp, res) == 0)
161                 snmp_free_pdu(res);
162             switch (act->command) {
163             case AGENTX_MSG_CLOSE:
164                 /* Take action once the answer is sent! */
165                 STATE_CALL(close)(state, act);
166                 break;
167             default:
168                 /* Do nothing */
169                 break;
170             }
171         } else
172             /* RESPONSE act->time, act->errstat, act->errindex, varlist */
173             STATE_CALL(response)(state, act);
174         break;
175     case NETSNMP_CALLBACK_OP_TIMED_OUT:
176         change_state(state->timeout);
177         break;
178     case NETSNMP_CALLBACK_OP_DISCONNECT:
179         STATE_CALL(disconnect)(state);
180         break;
181     }
182     return 0;
183 }
184 
185 static const struct tState_s Connecting;
186 static const struct tState_s Opening;
187 static const struct tState_s Notifying;
188 static const struct tState_s Closing;
189 static const struct tState_s Disconnecting;
190 static const struct tState_s Exit;
191 
192 static void
StateDisconnect(tState self)193 StateDisconnect(tState self)
194 {
195     snmp_log(LOG_ERR, "Unexpected disconnect in state %s\n", self->name);
196     change_state(&Disconnecting);
197 }
198 
199 static void
StateClose(tState self,netsnmp_pdu * act)200 StateClose(tState self, netsnmp_pdu *act)
201 {
202     snmp_log(LOG_ERR,
203              "Unexpected close with reason code %ld in state %s\n",
204              act->errstat, self->name);
205     change_state(&Disconnecting);
206 }
207 
208 static void
ConnectingEntry(tState self)209 ConnectingEntry(tState self)
210 {
211     netsnmp_session init;
212     netsnmp_transport *t;
213     void           *sess;
214 
215     if (sessp) {
216         snmp_sess_close(sessp);
217         sessp = NULL;
218     }
219 
220     snmp_sess_init(&init);
221     init.version = AGENTX_VERSION_1;
222     init.retries = 0;           /* Retries are handled by the state machine */
223     init.timeout = SNMP_DEFAULT_TIMEOUT;
224     init.flags |= SNMP_FLAGS_STREAM_SOCKET;
225     init.callback = handle_agentx_response;
226     init.authenticator = NULL;
227 
228     if (!(t = netsnmp_transport_open_client("agentx",
229                                             netsnmp_ds_get_string
230                                             (NETSNMP_DS_APPLICATION_ID,
231                                              NETSNMP_DS_AGENT_X_SOCKET)))) {
232         snmp_log(LOG_ERR, "Failed to connect to AgentX server\n");
233         change_state(&Exit);
234     } else if (!(sess = snmp_sess_add_ex(&init, t, NULL, agentx_parse, NULL,
235                                          NULL, agentx_realloc_build,
236                                          agentx_check_packet, NULL))) {
237         snmp_log(LOG_ERR, "Failed to create session\n");
238         change_state(&Exit);
239     } else {
240         sessp = sess;
241         change_state(&Opening);
242     }
243 }
244 
245 static const struct tState_s Connecting = {
246     ConnectingEntry,
247     NULL,
248     NULL,
249     NULL,
250     StateDisconnect,
251     NULL,
252     "Connnecting",
253     0
254 };
255 
256 static netsnmp_pdu *
pdu_create_opt_context(int command,const char * context,size_t len)257 pdu_create_opt_context(int command, const char *context, size_t len)
258 {
259     netsnmp_pdu    *res = snmp_pdu_create(command);
260     if (res)
261         if (context) {
262             if (snmp_clone_mem((void **) &res->contextName, context, len)) {
263                 snmp_free_pdu(res);
264                 res = NULL;
265             } else
266                 res->contextNameLen = len;
267         }
268     return res;
269 }
270 
271 static void
OpeningEntry(tState self)272 OpeningEntry(tState self)
273 {
274     netsnmp_pdu    *act =
275         pdu_create_opt_context(AGENTX_MSG_OPEN, context, contextLen);
276     if (act) {
277         act->sessid = 0;
278         act->transid = 0;
279         act->reqid = ++packetid;
280         act->time = 0;
281         snmp_pdu_add_variable(act, NULL, 0, ASN_OCTET_STR, NULL, 0);
282         if (snmp_sess_send(sessp, act) == 0)
283             snmp_free_pdu(act);
284     }
285 }
286 
287 static void
OpeningRes(tState self,netsnmp_pdu * act)288 OpeningRes(tState self, netsnmp_pdu *act)
289 {
290     if (act->errstat == AGENTX_ERR_NOERROR) {
291         session = act->sessid;
292         change_state(&Notifying);
293     } else {
294         snmp_log(LOG_ERR, "Failed to open session");
295         change_state(&Exit);
296     }
297 }
298 
299 static const struct tState_s Opening = {
300     OpeningEntry,
301     NULL,
302     OpeningRes,
303     &Disconnecting,
304     StateDisconnect,
305     StateClose,
306     "Opening",
307     0
308 };
309 
310 static void
NotifyingEntry(tState self)311 NotifyingEntry(tState self)
312 {
313     netsnmp_pdu    *act = snmp_clone_pdu(pdu);
314     if (act) {
315         act->sessid = session;
316         act->transid = 0;
317         act->reqid = ++packetid;
318         if (snmp_sess_send(sessp, act) == 0)
319             snmp_free_pdu(act);
320     }
321 }
322 
323 static void
NotifyingRes(tState self,netsnmp_pdu * act)324 NotifyingRes(tState self, netsnmp_pdu *act)
325 {
326     if (act->errstat == AGENTX_ERR_NOERROR)
327         result = 0;
328     else
329         snmp_log(LOG_ERR, "Failed to send notification");
330     /** \todo: Retry handling --- ClosingReconnect??? */
331     change_state(&Closing);
332 }
333 
334 static const struct tState_s Notifying = {
335     NotifyingEntry,
336     NULL,
337     NotifyingRes,
338     NULL,            /** \todo: Retry handling? */
339     StateDisconnect, /** \todo: Retry handling? */
340     StateClose,
341     "Notifying",
342     1
343 };
344 
345 static void
ClosingEntry(tState self)346 ClosingEntry(tState self)
347 {
348     /* CLOSE pdu->errstat */
349     netsnmp_pdu    *act =
350         pdu_create_opt_context(AGENTX_MSG_CLOSE, context, contextLen);
351     if (act) {
352         act->sessid = session;
353         act->transid = 0;
354         act->reqid = ++packetid;
355         act->errstat = AGENTX_CLOSE_SHUTDOWN;
356         if (snmp_sess_send(sessp, act) == 0)
357             snmp_free_pdu(act);
358     }
359 }
360 
361 static void
ClosingRes(tState self,netsnmp_pdu * act)362 ClosingRes(tState self, netsnmp_pdu *act)
363 {
364     if (act->errstat != AGENTX_ERR_NOERROR) {
365         snmp_log(LOG_ERR, "AgentX error status of %ld\n", act->errstat);
366     }
367     change_state(&Disconnecting);
368 }
369 
370 static void
ClosingDisconnect(tState self)371 ClosingDisconnect(tState self)
372 {
373     change_state(&Disconnecting);
374 }
375 
376 static void
ClosingClose(tState self,netsnmp_pdu * act)377 ClosingClose(tState self, netsnmp_pdu *act)
378 {
379     change_state(&Disconnecting);
380 }
381 
382 static const struct tState_s Closing = {
383     ClosingEntry,
384     NULL,
385     ClosingRes,
386     &Disconnecting,
387     ClosingDisconnect,
388     ClosingClose,
389     "Closing",
390     1
391 };
392 
393 static void
DisconnectingEntry(tState self)394 DisconnectingEntry(tState self)
395 {
396     snmp_sess_close(sessp);
397     sessp = NULL;
398     change_state(&Exit);
399 }
400 
401 static const struct tState_s Disconnecting = {
402     DisconnectingEntry,
403     NULL,
404     NULL,
405     NULL,
406     NULL,
407     NULL,
408     "Disconnecting",
409     0
410 };
411 
412 static const struct tState_s Exit = {
413     NULL,
414     NULL,
415     NULL,
416     NULL,
417     NULL,
418     NULL,
419     "Exit",
420     0
421 };
422 
423 int
main(int argc,char * argv[])424 main(int argc, char *argv[])
425 {
426     int             arg;
427     char           *prognam;
428     char           *cp = NULL;
429     const char     *sysUpTime = NULL;
430 
431     /* initialize tcpip, if necessary */
432     SOCK_STARTUP;
433 
434     prognam = strrchr(argv[0], '/');
435     if (prognam)
436         ++prognam;
437     else
438         prognam = argv[0];
439 
440     putenv(strdup("POSIXLY_CORRECT=1"));
441 
442     netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
443                            NETSNMP_DS_LIB_DISABLE_PERSISTENT_LOAD, 1);
444     netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
445                            NETSNMP_DS_LIB_DISABLE_PERSISTENT_SAVE, 1);
446 
447     while ((arg = getopt(argc, argv, ":Vhm:M:D:dP:L:U:c:x:")) != -1) {
448         switch (arg) {
449         case 'h':
450             usage(prognam);
451             result = 0;
452             goto out;
453         case 'm':
454             setenv("MIBS", optarg, 1);
455             break;
456         case 'M':
457             setenv("MIBDIRS", optarg, 1);
458             break;
459         case 'c':
460             context = optarg;
461             contextLen = strlen(context);
462             break;
463         case 'D':
464             debug_register_tokens(optarg);
465             snmp_set_do_debugging(1);
466             break;
467         case 'd':
468             netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
469                                    NETSNMP_DS_LIB_DUMP_PACKET, 1);
470             break;
471         case 'U':
472             sysUpTime = optarg;
473             break;
474         case 'V':
475             fprintf(stderr, "NET-SNMP version: %s\n",
476                     netsnmp_get_version());
477             result = 0;
478             goto out;
479 #ifndef DISABLE_MIB_LOADING
480         case 'P':
481             cp = snmp_mib_toggle_options(optarg);
482             if (cp != NULL) {
483                 fprintf(stderr, "Unknown parser option to -P: %c.\n", *cp);
484                 usage(prognam);
485                 goto out;
486             }
487             break;
488 #endif                          /* DISABLE_MIB_LOADING */
489         case 'L':
490             if (snmp_log_options(optarg, argc, argv) < 0)
491                 goto out;
492             break;
493         case 'x':
494             if (optarg != NULL) {
495                 netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID,
496                                       NETSNMP_DS_AGENT_X_SOCKET, optarg);
497             } else
498                 usage(argv[0]);
499             break;
500 
501         case ':':
502             fprintf(stderr, "Option -%c requires an operand\n", optopt);
503             usage(prognam);
504             goto out;
505         case '?':
506             fprintf(stderr, "Unrecognized option: -%c\n", optopt);
507             usage(prognam);
508             goto out;
509         }
510     }
511 
512     arg = optind;
513 
514     init_snmp(NETSNMP_APPLICATION_CONFIG_TYPE);
515     agentx_config_init();
516 
517     /* NOTIFY varlist */
518     pdu = pdu_create_opt_context(AGENTX_MSG_NOTIFY, context, contextLen);
519 
520     if (sysUpTime)
521         snmp_add_var(pdu, sysuptime_oid, sysuptime_oid_len, 't',
522                      sysUpTime);
523 
524     if (arg == argc) {
525         fprintf(stderr, "Missing trap-oid parameter\n");
526         usage(prognam);
527         goto out;
528     }
529 
530     if (snmp_add_var(pdu, snmptrap_oid, snmptrap_oid_len, 'o', argv[arg])) {
531         snmp_perror(argv[arg]);
532         goto out;
533     }
534     ++arg;
535 
536     while (arg < argc) {
537         oid             name[MAX_OID_LEN];
538         size_t          name_length = MAX_OID_LEN;
539         arg += 3;
540         if (arg > argc) {
541             fprintf(stderr, "%s: Missing type/value for variable\n",
542                     argv[arg - 3]);
543             goto out;
544         }
545         if (!snmp_parse_oid(argv[arg - 3], name, &name_length)) {
546             snmp_perror(argv[arg - 3]);
547             goto out;
548         }
549         if (snmp_add_var(pdu, name, name_length, argv[arg - 2][0],
550                          argv[arg - 1]) != 0) {
551             snmp_perror(argv[arg - 3]);
552             goto out;
553         }
554     }
555 
556     packetid = 0;
557 
558     state = &Connecting;
559     next_state = NULL;
560     if (state->entry)
561         state->entry(state);
562 
563     /* main loop here... */
564     for (;;) {
565         int             block = 1;
566         int             numfds = 0;
567         int             count;
568         fd_set          fdset;
569         struct timeval  timeout;
570         NETSNMP_SELECT_TIMEVAL timeout2;
571 
572         while (next_state) {
573             if (state->exit)
574                 state->exit(state);
575             DEBUGMSGTL(("process", "State transition: %s -> %s\n",
576                         state->name, next_state->name));
577             state = next_state;
578             next_state = NULL;
579             if (state->entry)
580                 state->entry(state);
581         }
582 
583         if (state == &Exit)
584             break;
585 
586         FD_ZERO(&fdset);
587         snmp_sess_select_info(sessp, &numfds, &fdset, &timeout, &block);
588         timeout2.tv_sec = timeout.tv_sec;
589         timeout2.tv_usec = timeout.tv_usec;
590         count = select(numfds, &fdset, NULL, NULL, !block ? &timeout2 : NULL);
591         if (count > 0)
592             snmp_sess_read(sessp, &fdset);
593         else if (count == 0)
594             snmp_sess_timeout(sessp);
595         else if (errno != EINTR) {
596             snmp_log(LOG_ERR, "select error [%s]\n", strerror(errno));
597             change_state(&Exit);
598         }
599     }
600 
601     /* at shutdown time */
602     snmp_free_pdu(pdu);
603     pdu = NULL;
604 
605     snmp_shutdown(NETSNMP_APPLICATION_CONFIG_TYPE);
606 
607 out:
608     SOCK_CLEANUP;
609     return result;
610 }
611