/src/net-snmp/snmplib/transports/snmpUDPIPv6Domain.c
Line | Count | Source |
1 | | /* |
2 | | * Portions of this file are copyrighted by: |
3 | | * Copyright (c) 2016 VMware, Inc. All rights reserved. |
4 | | * Use is subject to license terms specified in the COPYING file |
5 | | * distributed with the Net-SNMP package. |
6 | | */ |
7 | | #include <net-snmp/net-snmp-config.h> |
8 | | |
9 | | #include "snmpIPBaseDomain.h" |
10 | | #include <net-snmp/library/snmpUDPIPv6Domain.h> |
11 | | #include <net-snmp/library/system.h> |
12 | | |
13 | | #include <net-snmp/types.h> |
14 | | |
15 | | #ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN |
16 | | |
17 | | #include <stdio.h> |
18 | | #include <sys/types.h> |
19 | | #include <ctype.h> |
20 | | #include <errno.h> |
21 | | |
22 | | #ifdef HAVE_STRING_H |
23 | | #include <string.h> |
24 | | #else |
25 | | #include <strings.h> |
26 | | #endif |
27 | | #include <stddef.h> |
28 | | #ifdef HAVE_STDLIB_H |
29 | | #include <stdlib.h> |
30 | | #endif |
31 | | #ifdef HAVE_UNISTD_H |
32 | | #include <unistd.h> |
33 | | #endif |
34 | | #ifdef HAVE_SYS_SOCKET_H |
35 | | #include <sys/socket.h> |
36 | | #endif |
37 | | #ifdef HAVE_NETINET_IN_H |
38 | | #include <netinet/in.h> |
39 | | #endif |
40 | | #ifdef HAVE_ARPA_INET_H |
41 | | #include <arpa/inet.h> |
42 | | #endif |
43 | | #ifdef HAVE_NETDB_H |
44 | | #include <netdb.h> |
45 | | #endif |
46 | | #ifdef HAVE_NET_IF_H |
47 | | #include <net/if.h> |
48 | | #endif |
49 | | |
50 | | #ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY |
51 | | #define SS_FAMILY ss_family |
52 | | #elif defined(HAVE_STRUCT_SOCKADDR_STORAGE___SS_FAMILY) |
53 | | #define SS_FAMILY __ss_family |
54 | | #endif |
55 | | |
56 | | #include <net-snmp/types.h> |
57 | | #include <net-snmp/output_api.h> |
58 | | #include <net-snmp/config_api.h> |
59 | | |
60 | | #include <net-snmp/library/snmp_impl.h> |
61 | | #include <net-snmp/library/snmp_transport.h> |
62 | | #include <net-snmp/library/snmpSocketBaseDomain.h> |
63 | | #include <net-snmp/library/tools.h> |
64 | | #include <net-snmp/library/snmp_assert.h> |
65 | | |
66 | | #ifndef NETSNMP_NO_SYSTEMD |
67 | | #include <net-snmp/library/sd-daemon.h> |
68 | | #endif |
69 | | |
70 | | #include "inet_ntop.h" |
71 | | #include "inet_pton.h" |
72 | | |
73 | | const oid netsnmp_UDPIPv6Domain[] = { TRANSPORT_DOMAIN_UDP_IPV6 }; |
74 | | static netsnmp_tdomain udp6Domain; |
75 | | |
76 | | /* |
77 | | * Return a string representing the address in data, or else the "far end" |
78 | | * address if data is NULL. |
79 | | */ |
80 | | |
81 | | static char * |
82 | | netsnmp_udp6_fmtaddr(netsnmp_transport *t, const void *data, int len) |
83 | 0 | { |
84 | 0 | return netsnmp_ipv6_fmtaddr("UDP/IPv6", t, data, len); |
85 | 0 | } |
86 | | |
87 | | /* |
88 | | * You can write something into opaque that will subsequently get passed back |
89 | | * to your send function if you like. For instance, you might want to |
90 | | * remember where a PDU came from, so that you can send a reply there... |
91 | | */ |
92 | | |
93 | | static int |
94 | | netsnmp_udp6_recv(netsnmp_transport *t, void *buf, int size, |
95 | | void **opaque, int *olength) |
96 | 0 | { |
97 | 0 | int rc = -1; |
98 | 0 | socklen_t fromlen = sizeof(struct sockaddr_in6); |
99 | 0 | struct sockaddr *from; |
100 | |
|
101 | 0 | if (t != NULL && t->sock >= 0) { |
102 | 0 | from = (struct sockaddr *) malloc(sizeof(struct sockaddr_in6)); |
103 | 0 | if (from == NULL) { |
104 | 0 | *opaque = NULL; |
105 | 0 | *olength = 0; |
106 | 0 | return -1; |
107 | 0 | } else { |
108 | 0 | memset(from, 0, fromlen); |
109 | 0 | } |
110 | | |
111 | 0 | while (rc < 0) { |
112 | 0 | rc = recvfrom(t->sock, buf, size, 0, from, &fromlen); |
113 | 0 | if (rc < 0 && errno != EINTR) { |
114 | 0 | break; |
115 | 0 | } |
116 | 0 | } |
117 | |
|
118 | 0 | if (rc >= 0) { |
119 | 0 | DEBUGIF("netsnmp_udp6") { |
120 | 0 | char *str = netsnmp_udp6_fmtaddr(NULL, from, fromlen); |
121 | 0 | DEBUGMSGTL(("netsnmp_udp6", |
122 | 0 | "recvfrom fd %d got %d bytes (from %s)\n", t->sock, |
123 | 0 | rc, str)); |
124 | 0 | free(str); |
125 | 0 | } |
126 | 0 | } else { |
127 | 0 | DEBUGMSGTL(("netsnmp_udp6", "recvfrom fd %d err %d (\"%s\")\n", |
128 | 0 | t->sock, errno, strerror(errno))); |
129 | 0 | } |
130 | 0 | *opaque = (void *) from; |
131 | 0 | *olength = sizeof(struct sockaddr_in6); |
132 | 0 | } |
133 | 0 | return rc; |
134 | 0 | } |
135 | | |
136 | | |
137 | | |
138 | | static int |
139 | | netsnmp_udp6_send(netsnmp_transport *t, const void *buf, int size, |
140 | | void **opaque, int *olength) |
141 | 0 | { |
142 | 0 | int rc = -1; |
143 | 0 | const struct sockaddr *to = NULL; |
144 | |
|
145 | 0 | if (opaque != NULL && *opaque != NULL && |
146 | 0 | *olength == sizeof(struct sockaddr_in6)) { |
147 | 0 | to = (const struct sockaddr *) (*opaque); |
148 | 0 | } else if (t != NULL && t->data != NULL && |
149 | 0 | ((t->data_length == sizeof(struct sockaddr_in6)) || |
150 | 0 | (t->data_length == sizeof(netsnmp_indexed_addr_pair)))) { |
151 | 0 | to = (const struct sockaddr *) (t->data); |
152 | 0 | } |
153 | |
|
154 | 0 | if (to != NULL && t != NULL && t->sock >= 0) { |
155 | 0 | DEBUGIF("netsnmp_udp6") { |
156 | 0 | char *str = netsnmp_udp6_fmtaddr(NULL, to, |
157 | 0 | sizeof(struct sockaddr_in6)); |
158 | 0 | DEBUGMSGTL(("netsnmp_udp6", |
159 | 0 | "send %d bytes from %p to %s on fd %d\n", |
160 | 0 | size, buf, str, t->sock)); |
161 | 0 | free(str); |
162 | 0 | } |
163 | 0 | while (rc < 0) { |
164 | 0 | rc = sendto(t->sock, buf, size, 0, to,sizeof(struct sockaddr_in6)); |
165 | 0 | if (rc < 0 && errno != EINTR) { |
166 | 0 | break; |
167 | 0 | } |
168 | 0 | } |
169 | 0 | } |
170 | 0 | return rc; |
171 | 0 | } |
172 | | |
173 | | |
174 | | /* |
175 | | * Initialize a UDP/IPv6-based transport for SNMP. Local is TRUE if addr is the |
176 | | * local address to bind to (i.e. this is a server-type session); otherwise |
177 | | * addr is the remote address to send things to. |
178 | | */ |
179 | | |
180 | | netsnmp_transport * |
181 | | netsnmp_udp6_transport_init(const struct netsnmp_ep *ep, int flags) |
182 | 0 | { |
183 | 0 | const struct sockaddr_in6 *addr = &ep->a.sin6; |
184 | 0 | netsnmp_transport *t = NULL; |
185 | 0 | int local = flags & NETSNMP_TSPEC_LOCAL; |
186 | 0 | u_char *addr_ptr; |
187 | |
|
188 | | #ifdef NETSNMP_NO_LISTEN_SUPPORT |
189 | | if (local) |
190 | | return NULL; |
191 | | #endif /* NETSNMP_NO_LISTEN_SUPPORT */ |
192 | |
|
193 | 0 | if (addr == NULL || addr->sin6_family != AF_INET6) { |
194 | 0 | return NULL; |
195 | 0 | } |
196 | | |
197 | 0 | t = SNMP_MALLOC_TYPEDEF(netsnmp_transport); |
198 | 0 | if (t == NULL) { |
199 | 0 | return NULL; |
200 | 0 | } |
201 | | |
202 | 0 | t->sock = -1; |
203 | |
|
204 | 0 | addr_ptr = netsnmp_memdup(addr, sizeof(*addr)); |
205 | 0 | if (addr_ptr == NULL) { |
206 | 0 | free(t); |
207 | 0 | return NULL; |
208 | 0 | } |
209 | 0 | if (local) { |
210 | | /** This is a server session. */ |
211 | 0 | t->local_length = sizeof(*addr); |
212 | 0 | t->local = addr_ptr; |
213 | 0 | } else { |
214 | | /** This is a client session. */ |
215 | 0 | t->remote = addr_ptr; |
216 | 0 | t->remote_length = sizeof(*addr); |
217 | 0 | } |
218 | |
|
219 | 0 | DEBUGIF("netsnmp_udp6") { |
220 | 0 | char *str = netsnmp_udp6_fmtaddr(NULL, addr, sizeof(*addr)); |
221 | 0 | DEBUGMSGTL(("netsnmp_udp6", "open %s %s\n", local ? "local" : "remote", |
222 | 0 | str)); |
223 | 0 | free(str); |
224 | 0 | } |
225 | |
|
226 | 0 | if (!local) { |
227 | 0 | netsnmp_indexed_addr_pair *addr_pair; |
228 | | |
229 | | /* |
230 | | * allocate space to save the (remote) address in the |
231 | | * transport-specific data pointer for later use by netsnmp_udp_send. |
232 | | */ |
233 | 0 | t->data = calloc(1, sizeof(netsnmp_indexed_addr_pair)); |
234 | 0 | if (NULL == t->data) { |
235 | 0 | netsnmp_transport_free(t); |
236 | 0 | return NULL; |
237 | 0 | } |
238 | 0 | t->data_length = sizeof(netsnmp_indexed_addr_pair); |
239 | |
|
240 | 0 | addr_pair = (netsnmp_indexed_addr_pair *)t->data; |
241 | 0 | memcpy(&addr_pair->remote_addr, addr, sizeof(*addr)); |
242 | 0 | } |
243 | | |
244 | | /* |
245 | | * 16-bit length field, 8 byte UDP header, 40 byte IPv6 header. |
246 | | */ |
247 | | |
248 | 0 | t->msgMaxSize = 0xffff - 8 - 40; |
249 | 0 | t->f_recv = netsnmp_udp6_recv; |
250 | 0 | t->f_send = netsnmp_udp6_send; |
251 | 0 | t->f_close = netsnmp_socketbase_close; |
252 | 0 | t->f_accept = NULL; |
253 | 0 | t->f_setup_session = netsnmp_ipbase_session_init; |
254 | 0 | t->f_fmtaddr = netsnmp_udp6_fmtaddr; |
255 | 0 | t->f_get_taddr = netsnmp_ipv6_get_taddr; |
256 | |
|
257 | 0 | t->domain = netsnmp_UDPIPv6Domain; |
258 | 0 | t->domain_length = |
259 | 0 | sizeof(netsnmp_UDPIPv6Domain) / sizeof(netsnmp_UDPIPv6Domain[0]); |
260 | |
|
261 | 0 | return t; |
262 | 0 | } |
263 | | |
264 | | static void set_ipv6_v6only_sockopt(int sd) |
265 | 0 | { |
266 | 0 | #ifdef IPV6_V6ONLY |
267 | | /* Try to restrict PF_INET6 socket to IPv6 communications only. */ |
268 | 0 | int optval = 1; |
269 | |
|
270 | 0 | if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&optval, |
271 | 0 | sizeof(optval)) != 0) { |
272 | 0 | DEBUGMSGTL(("netsnmp_udp6", "couldn't set IPV6_V6ONLY: %s\n", |
273 | 0 | strerror(errno))); |
274 | 0 | } |
275 | 0 | #endif |
276 | 0 | } |
277 | | |
278 | | int |
279 | | netsnmp_udp6_transport_bind(netsnmp_transport *t, |
280 | | const struct netsnmp_ep *ep, |
281 | | int flags) |
282 | 0 | { |
283 | 0 | const struct sockaddr_in6 *addr = &ep->a.sin6; |
284 | 0 | int local = flags & NETSNMP_TSPEC_LOCAL; |
285 | 0 | int rc = 0; |
286 | |
|
287 | 0 | if (local) { |
288 | 0 | #ifndef NETSNMP_NO_LISTEN_SUPPORT |
289 | | /* |
290 | | * This session is intended as a server, so we must bind on to the |
291 | | * given IP address, which may include an interface address, or could |
292 | | * be INADDR_ANY, but certainly includes a port number. |
293 | | */ |
294 | |
|
295 | 0 | set_ipv6_v6only_sockopt(t->sock); |
296 | | #else /* NETSNMP_NO_LISTEN_SUPPORT */ |
297 | | return -1; |
298 | | #endif /* NETSNMP_NO_LISTEN_SUPPORT */ |
299 | 0 | } |
300 | |
|
301 | 0 | DEBUGIF("netsnmp_udp6") { |
302 | 0 | char *str; |
303 | 0 | str = netsnmp_udp6_fmtaddr(NULL, addr, sizeof(*addr)); |
304 | 0 | DEBUGMSGTL(("netsnmp_udp6", "binding socket: %d to %s\n", |
305 | 0 | t->sock, str)); |
306 | 0 | free(str); |
307 | 0 | } |
308 | 0 | if (flags & NETSNMP_TSPEC_PREBOUND) { |
309 | 0 | DEBUGMSGTL(("netsnmp_udp6", "socket %d is prebound, nothing to do\n", |
310 | 0 | t->sock)); |
311 | 0 | return 0; |
312 | 0 | } |
313 | 0 | rc = netsnmp_bindtodevice(t->sock, ep->iface); |
314 | 0 | if (rc != 0) { |
315 | 0 | DEBUGMSGTL(("netsnmp_udp6", "failed to bind to iface %s: %s\n", |
316 | 0 | ep->iface, strerror(errno))); |
317 | 0 | goto err; |
318 | 0 | } |
319 | 0 | rc = bind(t->sock, (const struct sockaddr *)addr, sizeof(*addr)); |
320 | 0 | if (rc != 0) { |
321 | 0 | DEBUGMSGTL(("netsnmp_udp6", "failed to bind for clientaddr: %d %s\n", |
322 | 0 | errno, strerror(errno))); |
323 | 0 | goto err; |
324 | 0 | } |
325 | | |
326 | 0 | return 0; |
327 | | |
328 | 0 | err: |
329 | 0 | netsnmp_socketbase_close(t); |
330 | 0 | return -1; |
331 | 0 | } |
332 | | |
333 | | int |
334 | | netsnmp_udp6_transport_socket(int flags) |
335 | 0 | { |
336 | 0 | int local = flags & NETSNMP_TSPEC_LOCAL; |
337 | 0 | int sock = socket(PF_INET6, SOCK_DGRAM, 0); |
338 | |
|
339 | 0 | DEBUGMSGTL(("UDPBase", "opened socket %d as local=%d\n", sock, local)); |
340 | 0 | if (sock < 0) |
341 | 0 | return -1; |
342 | | |
343 | 0 | _netsnmp_udp_sockopt_set(sock, local); |
344 | |
|
345 | 0 | return sock; |
346 | 0 | } |
347 | | |
348 | | void |
349 | | netsnmp_udp6_transport_get_bound_addr(netsnmp_transport *t) |
350 | 0 | { |
351 | 0 | netsnmp_indexed_addr_pair *addr_pair; |
352 | 0 | socklen_t local_addr_len = sizeof(addr_pair->local_addr); |
353 | 0 | int rc; |
354 | | |
355 | | /** only for client transports: must have data and not local */ |
356 | 0 | if (NULL == t || NULL != t->local || NULL == t->data || |
357 | 0 | t->data_length < local_addr_len) { |
358 | 0 | snmp_log(LOG_ERR, "bad parameters for get bound addr\n"); |
359 | 0 | return; |
360 | 0 | } |
361 | | |
362 | 0 | addr_pair = (netsnmp_indexed_addr_pair *)t->data; |
363 | | |
364 | | /** get local socket address for client session */ |
365 | 0 | local_addr_len = sizeof(addr_pair->local_addr); |
366 | 0 | rc = getsockname(t->sock, (struct sockaddr*)&addr_pair->local_addr, |
367 | 0 | &local_addr_len); |
368 | 0 | netsnmp_assert(rc == 0); |
369 | 0 | DEBUGIF("netsnmp_udpbase") { |
370 | 0 | char *str = netsnmp_udp6_fmtaddr(NULL, (void *)&addr_pair->local_addr, |
371 | 0 | sizeof(addr_pair->local_addr)); |
372 | 0 | DEBUGMSGTL(("netsnmp_udpbase", "socket %d bound to %s\n", |
373 | 0 | t->sock, str)); |
374 | 0 | free(str); |
375 | 0 | } |
376 | 0 | } |
377 | | |
378 | | netsnmp_transport * |
379 | | netsnmp_udpipv6base_tspec_transport(netsnmp_tdomain_spec *tspec) |
380 | 0 | { |
381 | 0 | struct netsnmp_ep ep; |
382 | 0 | int local; |
383 | |
|
384 | 0 | if (NULL == tspec) |
385 | 0 | return NULL; |
386 | | |
387 | 0 | local = tspec->flags & NETSNMP_TSPEC_LOCAL; |
388 | | |
389 | | /** get address from target */ |
390 | 0 | if (!netsnmp_sockaddr_in6_3(&ep, tspec->target, tspec->default_target)) |
391 | 0 | return NULL; |
392 | | |
393 | 0 | if (NULL != tspec->source) { |
394 | 0 | struct netsnmp_ep src_addr; |
395 | | |
396 | | /** get sockaddr from source */ |
397 | 0 | if (!netsnmp_sockaddr_in6_3(&src_addr, tspec->source, ":0")) |
398 | 0 | return NULL; |
399 | 0 | return netsnmp_udp6_transport_with_source(&ep, local, &src_addr); |
400 | 0 | } |
401 | | |
402 | | /** no source and default client address ok */ |
403 | 0 | return netsnmp_udp6_transport(&ep, local); |
404 | 0 | } |
405 | | |
406 | | netsnmp_transport * |
407 | | netsnmp_udp6_transport_with_source(const struct netsnmp_ep *ep, |
408 | | int local, const struct netsnmp_ep *src_addr) |
409 | 0 | { |
410 | 0 | netsnmp_transport *t = NULL; |
411 | 0 | const struct netsnmp_ep *bind_addr; |
412 | 0 | int rc, flags = 0; |
413 | |
|
414 | 0 | t = netsnmp_udp6_transport_init(ep, local); |
415 | 0 | if (NULL == t) |
416 | 0 | return NULL; |
417 | | |
418 | 0 | if (local) { |
419 | 0 | bind_addr = ep; |
420 | 0 | flags |= NETSNMP_TSPEC_LOCAL; |
421 | |
|
422 | 0 | #ifndef NETSNMP_NO_SYSTEMD |
423 | | /* |
424 | | * Maybe the socket was already provided by systemd... |
425 | | */ |
426 | 0 | t->sock = netsnmp_sd_find_inet_socket(PF_INET6, SOCK_DGRAM, -1, |
427 | 0 | ntohs(ep->a.sin6.sin6_port)); |
428 | 0 | if (t->sock >= 0) |
429 | 0 | flags |= NETSNMP_TSPEC_PREBOUND; |
430 | 0 | #endif |
431 | 0 | } |
432 | 0 | else |
433 | 0 | bind_addr = src_addr; |
434 | |
|
435 | 0 | if (-1 == t->sock) |
436 | 0 | t->sock = netsnmp_udp6_transport_socket(flags); |
437 | 0 | if (t->sock < 0) { |
438 | 0 | netsnmp_transport_free(t); |
439 | 0 | return NULL; |
440 | 0 | } |
441 | | |
442 | | /* |
443 | | * If we've been given an address to bind to, then bind to it. |
444 | | * Otherwise the OS will use "something sensible". |
445 | | */ |
446 | 0 | if (NULL == bind_addr) |
447 | 0 | return t; |
448 | | |
449 | 0 | rc = netsnmp_udp6_transport_bind(t, bind_addr, flags); |
450 | 0 | if (rc) { |
451 | 0 | netsnmp_transport_free(t); |
452 | 0 | t = NULL; |
453 | 0 | } |
454 | 0 | else if (!local) |
455 | 0 | netsnmp_udp6_transport_get_bound_addr(t); |
456 | |
|
457 | 0 | return t; |
458 | 0 | } |
459 | | |
460 | | /* |
461 | | * Open a UDP/IPv6-based transport for SNMP. Local is TRUE if addr is the |
462 | | * local address to bind to (i.e. this is a server-type session); otherwise |
463 | | * addr is the remote address to send things to. |
464 | | */ |
465 | | |
466 | | netsnmp_transport * |
467 | | netsnmp_udp6_transport(const struct netsnmp_ep *ep, int local) |
468 | 0 | { |
469 | 0 | struct netsnmp_ep client_ep; |
470 | 0 | const char *client_addr; |
471 | |
|
472 | 0 | memset(&client_ep, 0, sizeof(client_ep)); |
473 | 0 | client_ep.a.sin6.sin6_family = AF_INET6; |
474 | |
|
475 | 0 | if (local) |
476 | 0 | goto out; |
477 | | |
478 | 0 | client_addr = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, |
479 | 0 | NETSNMP_DS_LIB_CLIENT_ADDR); |
480 | 0 | if (!client_addr) |
481 | 0 | goto out; |
482 | | |
483 | 0 | if (netsnmp_sockaddr_in6_3(&client_ep, client_addr, ":0") < 0) |
484 | 0 | snmp_log(LOG_ERR, "Parsing clientaddr %s failed\n", client_addr); |
485 | |
|
486 | 0 | out: |
487 | 0 | return netsnmp_udp6_transport_with_source(ep, local, &client_ep); |
488 | 0 | } |
489 | | |
490 | | |
491 | | #if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) |
492 | | /* |
493 | | * The following functions provide the "com2sec6" configuration token |
494 | | * functionality for compatibility. |
495 | | */ |
496 | | |
497 | 1.02k | #define EXAMPLE_NETWORK "NETWORK" |
498 | 643 | #define EXAMPLE_COMMUNITY "COMMUNITY" |
499 | | |
500 | | typedef struct com2Sec6Entry_s { |
501 | | const char *secName; |
502 | | const char *contextName; |
503 | | struct com2Sec6Entry_s *next; |
504 | | struct in6_addr network; |
505 | | struct in6_addr mask; |
506 | | int negate; |
507 | | const char community[1]; |
508 | | } com2Sec6Entry; |
509 | | |
510 | | static com2Sec6Entry *com2Sec6List = NULL, *com2Sec6ListLast = NULL; |
511 | | |
512 | | |
513 | | NETSNMP_STATIC_INLINE int |
514 | | create_com2Sec6Entry(const struct addrinfo* const run, |
515 | | const struct in6_addr* const mask, |
516 | | const char* const secName, |
517 | | const size_t secNameLen, |
518 | | const char* const contextName, |
519 | | const size_t contextNameLen, |
520 | | const char* const community, |
521 | | const size_t communityLen, |
522 | | int negate, |
523 | | com2Sec6Entry** const begin, |
524 | | com2Sec6Entry** const end) |
525 | 23 | { |
526 | 23 | const struct sockaddr_in6 * const run_addr = |
527 | 23 | (const struct sockaddr_in6*)run->ai_addr; |
528 | 23 | int i; |
529 | | |
530 | | /* Check that the network and mask are consistent. */ |
531 | 269 | for (i = 0; i < 16; ++i) { |
532 | 258 | if (run_addr->sin6_addr.s6_addr[i] & ~mask->s6_addr[i]) { |
533 | 12 | config_perror("source/mask mismatch"); |
534 | 12 | return 1; |
535 | 12 | } |
536 | 258 | } |
537 | | |
538 | 11 | { |
539 | 11 | char buf1[INET6_ADDRSTRLEN]; |
540 | 11 | char buf2[INET6_ADDRSTRLEN]; |
541 | 11 | DEBUGMSGTL(("netsnmp_udp6_parse_security", |
542 | 11 | "<\"%s\", %s/%s> => \"%s\"\n", |
543 | 11 | community, |
544 | 11 | inet_ntop(AF_INET6, &run_addr->sin6_addr, |
545 | 11 | buf1, sizeof(buf1)), |
546 | 11 | inet_ntop(AF_INET6, mask, buf2, sizeof(buf2)), |
547 | 11 | secName)); |
548 | 11 | } |
549 | | |
550 | 11 | { |
551 | | /* Allocate all the needed chunks */ |
552 | 11 | void * const v = |
553 | 11 | malloc(offsetof(com2Sec6Entry, community) + communityLen + |
554 | 11 | secNameLen + contextNameLen); |
555 | | |
556 | 11 | com2Sec6Entry* const e = (com2Sec6Entry*)v; |
557 | 11 | char *last = ((char*)v) + offsetof(com2Sec6Entry, community); |
558 | | |
559 | 11 | if (v == NULL) { |
560 | 0 | config_perror("memory error"); |
561 | 0 | return 1; |
562 | 0 | } |
563 | | |
564 | 11 | memcpy(last, community, communityLen); |
565 | 11 | last += communityLen; |
566 | | |
567 | 11 | memcpy(last, secName, secNameLen); |
568 | 11 | e->secName = last; |
569 | 11 | last += secNameLen; |
570 | | |
571 | 11 | if (contextNameLen) { |
572 | 8 | memcpy(last, contextName, contextNameLen); |
573 | 8 | e->contextName = last; |
574 | 8 | } else |
575 | 3 | e->contextName = last - 1; |
576 | | |
577 | 11 | memcpy(&e->network, &run_addr->sin6_addr, sizeof(struct in6_addr)); |
578 | 11 | memcpy(&e->mask, mask, sizeof(struct in6_addr)); |
579 | | |
580 | 11 | e->negate = negate; |
581 | 11 | e->next = NULL; |
582 | 11 | if (*end != NULL) { |
583 | 0 | (*end)->next = e; |
584 | 0 | *end = e; |
585 | 11 | } else { |
586 | 11 | *end = *begin = e; |
587 | 11 | } |
588 | 11 | } |
589 | 0 | return 0; |
590 | 11 | } |
591 | | |
592 | | void |
593 | | netsnmp_udp6_parse_security(const char *token, char *param) |
594 | 756 | { |
595 | | /** copy_nword does null term, so we need vars of max size + 2. */ |
596 | | /** (one for null, one to detect param too long */ |
597 | 756 | char secName[VACMSTRINGLEN]; /* == VACM_MAX_STRING + 2 */ |
598 | 756 | size_t secNameLen; |
599 | 756 | char contextName[VACMSTRINGLEN]; |
600 | 756 | size_t contextNameLen; |
601 | 756 | char community[COMMUNITY_MAX_LEN + 2];/* overflow + null char */ |
602 | 756 | size_t communityLen; |
603 | 756 | char source[301]; /* !(1)+dns-name(253)+/(1)+mask(45)+\0(1) */ |
604 | 756 | char *sourcep; |
605 | 756 | struct in6_addr mask; |
606 | 756 | int negate; |
607 | | |
608 | | /* |
609 | | * Get security, source address/netmask and community strings. |
610 | | */ |
611 | | |
612 | 756 | param = copy_nword( param, secName, sizeof(secName)); |
613 | 756 | if (strcmp(secName, "-Cn") == 0) { |
614 | 31 | if (!param) { |
615 | 3 | config_perror("missing CONTEXT_NAME parameter"); |
616 | 3 | return; |
617 | 3 | } |
618 | 28 | param = copy_nword( param, contextName, sizeof(contextName)); |
619 | 28 | contextNameLen = strlen(contextName); |
620 | 28 | if (contextNameLen > VACM_MAX_STRING) { |
621 | 5 | config_perror("context name too long"); |
622 | 5 | return; |
623 | 5 | } |
624 | 23 | if (!param) { |
625 | 3 | config_perror("missing NAME parameter"); |
626 | 3 | return; |
627 | 3 | } |
628 | 20 | ++contextNameLen; /* null termination */ |
629 | 20 | param = copy_nword( param, secName, sizeof(secName)); |
630 | 725 | } else { |
631 | 725 | contextNameLen = 0; |
632 | 725 | } |
633 | | |
634 | 745 | secNameLen = strlen(secName); |
635 | 745 | if (secNameLen == 0) { |
636 | 60 | config_perror("empty NAME parameter"); |
637 | 60 | return; |
638 | 685 | } else if (secNameLen > VACM_MAX_STRING) { |
639 | 64 | config_perror("security name too long"); |
640 | 64 | return; |
641 | 64 | } |
642 | 621 | ++secNameLen; /* null termination */ |
643 | | |
644 | 621 | if (!param) { |
645 | 101 | config_perror("missing SOURCE parameter"); |
646 | 101 | return; |
647 | 101 | } |
648 | 520 | param = copy_nword( param, source, sizeof(source)); |
649 | 520 | if (source[0] == '\0') { |
650 | 6 | config_perror("empty SOURCE parameter"); |
651 | 6 | return; |
652 | 6 | } |
653 | 514 | if (strncmp(source, EXAMPLE_NETWORK, strlen(EXAMPLE_NETWORK)) == 0) { |
654 | 3 | config_perror("example config NETWORK not properly configured"); |
655 | 3 | return; |
656 | 3 | } |
657 | | |
658 | 511 | if (!param) { |
659 | 46 | config_perror("missing COMMUNITY parameter"); |
660 | 46 | return; |
661 | 46 | } |
662 | 465 | param = copy_nword( param, community, sizeof(community)); |
663 | 465 | if (community[0] == '\0') { |
664 | 8 | config_perror("empty COMMUNITY parameter"); |
665 | 8 | return; |
666 | 8 | } |
667 | 457 | communityLen = strlen(community); |
668 | 457 | if (communityLen > COMMUNITY_MAX_LEN) { |
669 | 0 | config_perror("community name too long"); |
670 | 0 | return; |
671 | 0 | } |
672 | 457 | ++communityLen; /* null termination */ |
673 | 457 | if (communityLen == sizeof(EXAMPLE_COMMUNITY) && |
674 | 93 | memcmp(community, EXAMPLE_COMMUNITY, sizeof(EXAMPLE_COMMUNITY)) == 0) { |
675 | 6 | config_perror("example config COMMUNITY not properly configured"); |
676 | 6 | return; |
677 | 6 | } |
678 | | |
679 | | /* Possible mask cases |
680 | | * "default" <=> 0::0/0 |
681 | | * <hostname>[/] <=> <hostname>/128 |
682 | | * <hostname>/number <=> <hostname>/number |
683 | | * <hostname>/<mask> <=> <hostname>/<mask> |
684 | | */ |
685 | 451 | { |
686 | | /* Deal with the "default" case first. */ |
687 | 451 | const int isdefault = strcmp(source, "default") == 0; |
688 | | |
689 | 451 | if (isdefault) { |
690 | 9 | memset(mask.s6_addr, '\0', sizeof(mask.s6_addr)); |
691 | 9 | negate = 0; |
692 | 9 | sourcep = NULL; /* gcc gets confused about sourcep being used */ |
693 | 442 | } else { |
694 | 442 | char *strmask; |
695 | 442 | if (*source == '!') { |
696 | 14 | negate = 1; |
697 | 14 | sourcep = source + 1; |
698 | 428 | } else { |
699 | 428 | negate = 0; |
700 | 428 | sourcep = source; |
701 | 428 | } |
702 | | |
703 | | /* Split the source/netmask parts */ |
704 | 442 | strmask = strchr(sourcep, '/'); |
705 | 442 | if (strmask != NULL) |
706 | | /* Mask given. */ |
707 | 262 | *strmask++ = '\0'; |
708 | | |
709 | | /* Try to interpret the mask */ |
710 | 442 | if (strmask == NULL || *strmask == '\0') { |
711 | | /* No mask was given. Assume /128 */ |
712 | 187 | memset(mask.s6_addr, 0xff, sizeof(mask.s6_addr)); |
713 | 255 | } else { |
714 | | /* Try to interpret mask as a "number of 1 bits". */ |
715 | 255 | char* cp; |
716 | 255 | long masklength = strtol(strmask, &cp, 10); |
717 | 255 | if (*cp == '\0') { |
718 | 215 | if (0 <= masklength && masklength <= 128) { |
719 | 23 | const int j = masklength / 8; |
720 | 23 | const int jj = masklength % 8; |
721 | | |
722 | 23 | memset(mask.s6_addr, 0xff, j); |
723 | 23 | if (j < 16) { |
724 | 21 | mask.s6_addr[j] = (0xffu << (8 - jj)); |
725 | 21 | memset(mask.s6_addr + j + 1, '\0', 15 - j); |
726 | 21 | } |
727 | 192 | } else { |
728 | 192 | config_perror("bad mask length"); |
729 | 192 | return; |
730 | 192 | } |
731 | 215 | } |
732 | | /* Try to interpret mask numerically. */ |
733 | 40 | else if (inet_pton(AF_INET6, strmask, &mask) != 1) { |
734 | 38 | config_perror("bad mask"); |
735 | 38 | return; |
736 | 38 | } |
737 | 255 | } |
738 | 442 | } |
739 | | |
740 | 221 | { |
741 | 221 | struct sockaddr_in6 pton_addr; |
742 | 221 | struct addrinfo hints, *res = NULL; |
743 | 221 | memset(&hints, '\0', sizeof(hints)); |
744 | | |
745 | | /* First check if default, otherwise try to parse as a numeric |
746 | | * address, if that also fails try to lookup the address */ |
747 | 221 | if (isdefault) { |
748 | 9 | memset(&pton_addr.sin6_addr.s6_addr, '\0', |
749 | 9 | sizeof(struct in6_addr)); |
750 | 212 | } else if (inet_pton(AF_INET6, sourcep, &pton_addr.sin6_addr) != 1) { |
751 | | /* Nope, wasn't a numeric IPv6 address. Must be IPv4 or a hostname. */ |
752 | | |
753 | | /* Try interpreting as dotted quad - IPv4 */ |
754 | 202 | struct in_addr network; |
755 | 202 | if (inet_pton(AF_INET, sourcep, &network) > 0){ |
756 | | /* Yes, it's IPv4 - so it's already parsed and we can return. */ |
757 | 1 | DEBUGMSGTL(("com2sec6", "IPv4 detected for IPv6 parser. Skipping.\n")); |
758 | 1 | return; |
759 | 1 | } |
760 | 201 | #ifdef HAVE_GETADDRINFO |
761 | 201 | { |
762 | 201 | int gai_error; |
763 | | |
764 | 201 | hints.ai_family = AF_INET6; |
765 | 201 | hints.ai_socktype = SOCK_DGRAM; |
766 | 201 | gai_error = netsnmp_getaddrinfo(sourcep, NULL, &hints, |
767 | 201 | &res); |
768 | 201 | if (gai_error != 0) { |
769 | 197 | config_perror(gai_strerror(gai_error)); |
770 | 197 | return; |
771 | 197 | } |
772 | 201 | } |
773 | | #else |
774 | | config_perror("getaddrinfo() not available"); |
775 | | return; |
776 | | #endif |
777 | 201 | } |
778 | 23 | if (res == NULL) { |
779 | 19 | hints.ai_addrlen = sizeof(pton_addr); |
780 | 19 | hints.ai_addr = (struct sockaddr*)&pton_addr; |
781 | 19 | hints.ai_next = NULL; |
782 | 19 | res = &hints; |
783 | 19 | } |
784 | | |
785 | 23 | { |
786 | 23 | struct addrinfo *run; |
787 | 23 | int failed = 0; |
788 | 23 | com2Sec6Entry *begin = NULL, *end = NULL; |
789 | | |
790 | 46 | for (run = res; run && !failed; run = run->ai_next) |
791 | 23 | failed = |
792 | 23 | create_com2Sec6Entry(run, &mask, |
793 | 23 | secName, secNameLen, |
794 | 23 | contextName, contextNameLen, |
795 | 23 | community, communityLen, negate, |
796 | 23 | &begin, &end); |
797 | | |
798 | 23 | if (failed) { |
799 | | /* Free eventually allocated chunks */ |
800 | 12 | while (begin) { |
801 | 0 | end = begin; |
802 | 0 | begin = begin->next; |
803 | 0 | free(end); |
804 | 0 | } |
805 | 12 | } else if (com2Sec6ListLast != NULL) { |
806 | 10 | com2Sec6ListLast->next = begin; |
807 | 10 | com2Sec6ListLast = end; |
808 | 10 | } else { |
809 | 1 | com2Sec6List = begin; |
810 | 1 | com2Sec6ListLast = end; |
811 | 1 | } |
812 | 23 | } |
813 | 23 | #ifdef HAVE_GETADDRINFO |
814 | 23 | if (res != &hints) |
815 | 4 | freeaddrinfo(res); |
816 | 23 | #endif |
817 | 23 | } |
818 | 23 | } |
819 | 23 | } |
820 | | |
821 | | void |
822 | | netsnmp_udp6_com2Sec6List_free(void) |
823 | 6.65k | { |
824 | 6.65k | com2Sec6Entry *e = com2Sec6List; |
825 | 6.65k | while (e != NULL) { |
826 | 0 | com2Sec6Entry *tmp = e; |
827 | 0 | e = e->next; |
828 | 0 | free(tmp); |
829 | 0 | } |
830 | 6.65k | com2Sec6List = com2Sec6ListLast = NULL; |
831 | 6.65k | } |
832 | | |
833 | | #endif /* support for community based SNMP */ |
834 | | |
835 | | void |
836 | | netsnmp_udp6_agent_config_tokens_register(void) |
837 | 3.32k | { |
838 | 3.32k | #if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) |
839 | 3.32k | register_app_config_handler("com2sec6", netsnmp_udp6_parse_security, |
840 | 3.32k | netsnmp_udp6_com2Sec6List_free, |
841 | 3.32k | "[-Cn CONTEXT] secName IPv6-network-address[/netmask] community"); |
842 | 3.32k | #endif /* support for community based SNMP */ |
843 | 3.32k | } |
844 | | |
845 | | |
846 | | |
847 | | #if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) |
848 | | |
849 | | /* |
850 | | * Return 0 if there are no com2sec entries, or return 1 if there ARE com2sec |
851 | | * entries. On return, if a com2sec entry matched the passed parameters, |
852 | | * then *secName points at the appropriate security name, or is NULL if the |
853 | | * parameters did not match any com2sec entry. |
854 | | */ |
855 | | |
856 | | int |
857 | | netsnmp_udp6_getSecName(void *opaque, int olength, |
858 | | const char *community, |
859 | | int community_len, |
860 | | const char **secName, const char **contextName) |
861 | 0 | { |
862 | 0 | const com2Sec6Entry *c; |
863 | 0 | struct sockaddr_in6 *from = (struct sockaddr_in6 *) opaque; |
864 | 0 | char *ztcommunity = NULL; |
865 | 0 | char str6[INET6_ADDRSTRLEN]; |
866 | |
|
867 | 0 | if (secName != NULL) { |
868 | 0 | *secName = NULL; /* Haven't found anything yet */ |
869 | 0 | } |
870 | | |
871 | | /* |
872 | | * Special case if there are NO entries (as opposed to no MATCHING |
873 | | * entries). |
874 | | */ |
875 | |
|
876 | 0 | if (com2Sec6List == NULL) { |
877 | 0 | DEBUGMSGTL(("netsnmp_udp6_getSecName", "no com2sec entries\n")); |
878 | 0 | return 0; |
879 | 0 | } |
880 | | |
881 | | /* |
882 | | * If there is no IPv6 source address, then there can be no valid security |
883 | | * name. |
884 | | */ |
885 | | |
886 | 0 | if (opaque == NULL || olength != sizeof(struct sockaddr_in6) |
887 | 0 | || from->sin6_family != PF_INET6) { |
888 | 0 | DEBUGMSGTL(("netsnmp_udp6_getSecName", |
889 | 0 | "no IPv6 source address in PDU?\n")); |
890 | 0 | return 1; |
891 | 0 | } |
892 | | |
893 | 0 | ztcommunity = (char *) malloc(community_len + 1); |
894 | 0 | if (ztcommunity != NULL) { |
895 | 0 | memcpy(ztcommunity, community, community_len); |
896 | 0 | ztcommunity[community_len] = '\0'; |
897 | 0 | } |
898 | |
|
899 | 0 | inet_ntop(AF_INET6, &from->sin6_addr, str6, sizeof(str6)); |
900 | 0 | DEBUGMSGTL(("netsnmp_udp6_getSecName", "resolve <\"%s\", %s>\n", |
901 | 0 | ztcommunity ? ztcommunity : "<malloc error>", str6)); |
902 | |
|
903 | 0 | for (c = com2Sec6List; c != NULL; c = c->next) { |
904 | 0 | { |
905 | 0 | char buf1[INET6_ADDRSTRLEN]; |
906 | 0 | char buf2[INET6_ADDRSTRLEN]; |
907 | 0 | DEBUGMSGTL(("netsnmp_udp6_getSecName", |
908 | 0 | "compare <\"%s\", %s/%s>", c->community, |
909 | 0 | inet_ntop(AF_INET6, &c->network, buf1, sizeof(buf1)), |
910 | 0 | inet_ntop(AF_INET6, &c->mask, buf2, sizeof(buf2)))); |
911 | 0 | } |
912 | 0 | if ((community_len == (int)strlen(c->community)) && |
913 | 0 | (memcmp(community, c->community, community_len) == 0)) { |
914 | 0 | int i, ok = 1; |
915 | 0 | for (i = 0; ok && i < 16; ++i) |
916 | 0 | if ((from->sin6_addr.s6_addr[i] & c->mask.s6_addr[i]) != |
917 | 0 | c->network.s6_addr[i]) |
918 | 0 | ok = 0; |
919 | 0 | if (ok) { |
920 | 0 | DEBUGMSG(("netsnmp_udp6_getSecName", "... SUCCESS\n")); |
921 | 0 | if (c->negate) { |
922 | | /* |
923 | | * If we matched a negative entry, then we are done - claim that we |
924 | | * matched nothing. |
925 | | */ |
926 | 0 | DEBUGMSG(("netsnmp_udp6_getSecName", "... <negative entry>\n")); |
927 | 0 | break; |
928 | 0 | } |
929 | 0 | if (secName != NULL) { |
930 | 0 | *secName = c->secName; |
931 | 0 | *contextName = c->contextName; |
932 | 0 | } |
933 | 0 | break; |
934 | 0 | } |
935 | 0 | } |
936 | 0 | else { |
937 | 0 | DEBUGMSG(("netsnmp_udp6_getSecName", "... nope\n")); |
938 | 0 | } |
939 | 0 | } |
940 | |
|
941 | 0 | if (ztcommunity != NULL) { |
942 | 0 | free(ztcommunity); |
943 | 0 | } |
944 | 0 | return 1; |
945 | 0 | } |
946 | | #endif /* support for community based SNMP */ |
947 | | |
948 | | netsnmp_transport * |
949 | | netsnmp_udp6_create_tstring(const char *str, int local, |
950 | | const char *default_target) |
951 | 0 | { |
952 | 0 | struct netsnmp_ep ep; |
953 | |
|
954 | 0 | if (netsnmp_sockaddr_in6_3(&ep, str, default_target)) { |
955 | 0 | return netsnmp_udp6_transport(&ep, local); |
956 | 0 | } else { |
957 | 0 | return NULL; |
958 | 0 | } |
959 | 0 | } |
960 | | |
961 | | netsnmp_transport * |
962 | | netsnmp_udp6_create_tspec(netsnmp_tdomain_spec *tspec) |
963 | 0 | { |
964 | 0 | netsnmp_transport *t = netsnmp_udpipv6base_tspec_transport(tspec); |
965 | 0 | return t; |
966 | |
|
967 | 0 | } |
968 | | |
969 | | |
970 | | /* |
971 | | * See: |
972 | | * |
973 | | * http://www.ietf.org/internet-drafts/draft-ietf-ops-taddress-mib-01.txt |
974 | | * |
975 | | * (or newer equivalent) for details of the TC which we are using for |
976 | | * the mapping here. |
977 | | */ |
978 | | |
979 | | netsnmp_transport * |
980 | | netsnmp_udp6_create_ostring(const void *o, size_t o_len, int local) |
981 | 0 | { |
982 | 0 | struct netsnmp_ep ep; |
983 | |
|
984 | 0 | memset(&ep, 0, sizeof(ep)); |
985 | 0 | if (netsnmp_ipv6_ostring_to_sockaddr(&ep.a.sin6, o, o_len)) |
986 | 0 | return netsnmp_udp6_transport(&ep, local); |
987 | 0 | return NULL; |
988 | 0 | } |
989 | | |
990 | | |
991 | | void |
992 | | netsnmp_udpipv6_ctor(void) |
993 | 4.16k | { |
994 | 4.16k | udp6Domain.name = netsnmp_UDPIPv6Domain; |
995 | 4.16k | udp6Domain.name_length = OID_LENGTH(netsnmp_UDPIPv6Domain); |
996 | 4.16k | udp6Domain.f_create_from_tstring_new = netsnmp_udp6_create_tstring; |
997 | 4.16k | udp6Domain.f_create_from_tspec = netsnmp_udp6_create_tspec; |
998 | 4.16k | udp6Domain.f_create_from_ostring = netsnmp_udp6_create_ostring; |
999 | 4.16k | udp6Domain.prefix = calloc(5, sizeof(char *)); |
1000 | 4.16k | if (!udp6Domain.prefix) { |
1001 | 0 | snmp_log(LOG_ERR, "calloc() failed - out of memory\n"); |
1002 | 0 | return; |
1003 | 0 | } |
1004 | 4.16k | udp6Domain.prefix[0] = "udp6"; |
1005 | 4.16k | udp6Domain.prefix[1] = "ipv6"; |
1006 | 4.16k | udp6Domain.prefix[2] = "udpv6"; |
1007 | 4.16k | udp6Domain.prefix[3] = "udpipv6"; |
1008 | | |
1009 | 4.16k | netsnmp_tdomain_register(&udp6Domain); |
1010 | 4.16k | } |
1011 | | |
1012 | | #else |
1013 | | |
1014 | | #ifdef NETSNMP_DLL |
1015 | | /* need this hook for win32 MSVC++ DLL build */ |
1016 | | void |
1017 | | netsnmp_udp6_agent_config_tokens_register(void) |
1018 | | { } |
1019 | | #endif |
1020 | | |
1021 | | #endif /* NETSNMP_TRANSPORT_UDPIPV6_DOMAIN */ |
1022 | | |