snmpclient.c (295685c5c1f1b55e360c6328f6323a72a199c160) snmpclient.c (04d1781439aa9bf5c34c0eee606a3909f3503fe4)
1/*
1/*
2 * Copyright (c) 2004-2005
2 * Copyright (c) 2004-2005,2018
3 * Hartmut Brandt.
4 * All rights reserved.
5 * Copyright (c) 2001-2003
6 * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
7 * All rights reserved.
8 *
9 * Author: Harti Brandt <harti@freebsd.org>
10 * Kendy Kutzner

--- 18 unchanged lines hidden (view full) ---

29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 *
33 * $Begemot: bsnmp/lib/snmpclient.c,v 1.36 2005/10/06 07:14:58 brandt_h Exp $
34 *
35 * Support functions for SNMP clients.
36 */
3 * Hartmut Brandt.
4 * All rights reserved.
5 * Copyright (c) 2001-2003
6 * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
7 * All rights reserved.
8 *
9 * Author: Harti Brandt <harti@freebsd.org>
10 * Kendy Kutzner

--- 18 unchanged lines hidden (view full) ---

29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 *
33 * $Begemot: bsnmp/lib/snmpclient.c,v 1.36 2005/10/06 07:14:58 brandt_h Exp $
34 *
35 * Support functions for SNMP clients.
36 */
37#include <sys/types.h>
37#include <sys/param.h>
38#include <sys/time.h>
39#include <sys/queue.h>
40#include <sys/socket.h>
41#include <sys/un.h>
38#include <sys/time.h>
39#include <sys/queue.h>
40#include <sys/socket.h>
41#include <sys/un.h>
42#include <net/if.h>
43#include <ctype.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <stddef.h>
45#include <stdarg.h>
46#include <string.h>
47#include <errno.h>
48#include <unistd.h>
49#include <fcntl.h>
50#include <netdb.h>
51#ifdef HAVE_STDINT_H
52#include <stdint.h>
53#elif defined(HAVE_INTTYPES_H)
54#include <inttypes.h>
55#endif
56#include <limits.h>
57#ifdef HAVE_ERR_H
58#include <err.h>
59#endif
60
44#include <stdio.h>
45#include <stdlib.h>
46#include <stddef.h>
47#include <stdarg.h>
48#include <string.h>
49#include <errno.h>
50#include <unistd.h>
51#include <fcntl.h>
52#include <netdb.h>
53#ifdef HAVE_STDINT_H
54#include <stdint.h>
55#elif defined(HAVE_INTTYPES_H)
56#include <inttypes.h>
57#endif
58#include <limits.h>
59#ifdef HAVE_ERR_H
60#include <err.h>
61#endif
62
63#include <arpa/inet.h>
64
61#include "support.h"
62#include "asn1.h"
63#include "snmp.h"
64#include "snmpclient.h"
65#include "snmppriv.h"
66
65#include "support.h"
66#include "asn1.h"
67#include "snmp.h"
68#include "snmpclient.h"
69#include "snmppriv.h"
70
71#define DEBUG_PARSE 0
72
67/* global context */
68struct snmp_client snmp_client;
69
70/* List of all outstanding requests */
71struct sent_pdu {
72 int reqid;
73 struct snmp_pdu *pdu;
74 struct timeval time;

--- 844 unchanged lines hidden (view full) ---

919 free(snmp_client.cport);
920 snmp_client.cport = ptr;
921 strcpy(snmp_client.cport, port);
922 }
923
924 /* open connection */
925 memset(&hints, 0, sizeof(hints));
926 hints.ai_flags = AI_CANONNAME;
73/* global context */
74struct snmp_client snmp_client;
75
76/* List of all outstanding requests */
77struct sent_pdu {
78 int reqid;
79 struct snmp_pdu *pdu;
80 struct timeval time;

--- 844 unchanged lines hidden (view full) ---

925 free(snmp_client.cport);
926 snmp_client.cport = ptr;
927 strcpy(snmp_client.cport, port);
928 }
929
930 /* open connection */
931 memset(&hints, 0, sizeof(hints));
932 hints.ai_flags = AI_CANONNAME;
927 hints.ai_family = AF_INET;
933 hints.ai_family = snmp_client.trans == SNMP_TRANS_UDP ? AF_INET:
934 AF_INET6;
928 hints.ai_socktype = SOCK_DGRAM;
929 hints.ai_protocol = 0;
930 error = getaddrinfo(snmp_client.chost, snmp_client.cport, &hints, &res0);
931 if (error != 0) {
932 seterr(&snmp_client, "%s: %s", snmp_client.chost,
933 gai_strerror(error));
934 return (-1);
935 }

--- 127 unchanged lines hidden (view full) ---

1063 sizeof(snmp_client.read_community));
1064 if (writecomm != NULL)
1065 strlcpy(snmp_client.write_community, writecomm,
1066 sizeof(snmp_client.write_community));
1067
1068 switch (snmp_client.trans) {
1069
1070 case SNMP_TRANS_UDP:
935 hints.ai_socktype = SOCK_DGRAM;
936 hints.ai_protocol = 0;
937 error = getaddrinfo(snmp_client.chost, snmp_client.cport, &hints, &res0);
938 if (error != 0) {
939 seterr(&snmp_client, "%s: %s", snmp_client.chost,
940 gai_strerror(error));
941 return (-1);
942 }

--- 127 unchanged lines hidden (view full) ---

1070 sizeof(snmp_client.read_community));
1071 if (writecomm != NULL)
1072 strlcpy(snmp_client.write_community, writecomm,
1073 sizeof(snmp_client.write_community));
1074
1075 switch (snmp_client.trans) {
1076
1077 case SNMP_TRANS_UDP:
1078 case SNMP_TRANS_UDP6:
1071 if (open_client_udp(host, port) != 0)
1072 return (-1);
1073 break;
1074
1075 case SNMP_TRANS_LOC_DGRAM:
1076 case SNMP_TRANS_LOC_STREAM:
1077 if (open_client_local(host) != 0)
1078 return (-1);

--- 782 unchanged lines hidden (view full) ---

1861 strcpy(np, p);
1862 if (cl->cport != NULL)
1863 free(cl->cport);
1864 cl->cport = np;
1865 }
1866 return (0);
1867}
1868
1079 if (open_client_udp(host, port) != 0)
1080 return (-1);
1081 break;
1082
1083 case SNMP_TRANS_LOC_DGRAM:
1084 case SNMP_TRANS_LOC_STREAM:
1085 if (open_client_local(host) != 0)
1086 return (-1);

--- 782 unchanged lines hidden (view full) ---

1869 strcpy(np, p);
1870 if (cl->cport != NULL)
1871 free(cl->cport);
1872 cl->cport = np;
1873 }
1874 return (0);
1875}
1876
1869/*
1870 * parse a server specification
1877/**
1878 * Try to get a transport identifier which is a leading alphanumeric string
1879 * (starting with '_' or a letter and including also '_') terminated by
1880 * a double colon. The string may not be empty. The transport identifier
1881 * is optional.
1871 *
1882 *
1872 * [trans::][community@][server][:port]
1883 * \param sc client struct to set errors
1884 * \param strp possible start of transport; updated to point to
1885 * the next character to parse
1886 *
1887 * \return end of transport; equals *strp if there is none; NULL if there
1888 * was an error
1873 */
1889 */
1874int
1875snmp_parse_server(struct snmp_client *sc, const char *str)
1890static inline const char *
1891get_transp(struct snmp_client *sc, const char **strp)
1876{
1892{
1877 const char *p, *s = str;
1893 const char *p = *strp;
1878
1894
1879 /* look for a double colon */
1880 for (p = s; *p != '\0'; p++) {
1881 if (*p == '\\' && p[1] != '\0') {
1895 if (isascii(*p) && (isalpha(*p) || *p == '_')) {
1896 p++;
1897 while (isascii(*p) && (isalnum(*p) || *p == '_'))
1882 p++;
1898 p++;
1883 continue;
1899 if (p[0] == ':' && p[1] == ':') {
1900 *strp = p + 2;
1901 return (p);
1884 }
1902 }
1885 if (*p == ':' && p[1] == ':')
1886 break;
1887 }
1903 }
1888 if (*p != '\0') {
1889 if (p > s) {
1890 if (p - s == 3 && strncmp(s, "udp", 3) == 0)
1891 sc->trans = SNMP_TRANS_UDP;
1892 else if (p - s == 6 && strncmp(s, "stream", 6) == 0)
1893 sc->trans = SNMP_TRANS_LOC_STREAM;
1894 else if (p - s == 5 && strncmp(s, "dgram", 5) == 0)
1895 sc->trans = SNMP_TRANS_LOC_DGRAM;
1896 else {
1897 seterr(sc, "unknown SNMP transport '%.*s'",
1898 (int)(p - s), s);
1899 return (-1);
1900 }
1901 }
1902 s = p + 2;
1904 if (p[0] == ':' && p[1] == ':') {
1905 seterr(sc, "empty transport specifier");
1906 return (NULL);
1903 }
1907 }
1908 return (*strp);
1909}
1904
1910
1905 /* look for a @ */
1906 for (p = s; *p != '\0'; p++) {
1907 if (*p == '\\' && p[1] != '\0') {
1908 p++;
1909 continue;
1910 }
1911 if (*p == '@')
1912 break;
1911/**
1912 * Try to get community string. Eat everything up to the last @ (if there is
1913 * any) but only if it is not longer than SNMP_COMMUNITY_MAXLEN. Empty
1914 * community strings are legal.
1915 *
1916 * \param sc client struct to set errors
1917 * \param strp possible start of community; updated to the point to
1918 * the next character to parse
1919 *
1920 * \return end of community; equals *strp if there is none; NULL if there
1921 * was an error
1922 */
1923static inline const char *
1924get_comm(struct snmp_client *sc, const char **strp)
1925{
1926 const char *p = strrchr(*strp, '@');
1927
1928 if (p == NULL)
1929 /* no community string */
1930 return (*strp);
1931
1932 if (p - *strp > SNMP_COMMUNITY_MAXLEN) {
1933 seterr(sc, "community string too long '%.*s'",
1934 p - *strp, *strp);
1935 return (NULL);
1913 }
1914
1936 }
1937
1915 if (*p != '\0') {
1916 if (p - s > SNMP_COMMUNITY_MAXLEN) {
1917 seterr(sc, "community string too long");
1918 return (-1);
1938 *strp = p + 1;
1939 return (p);
1940}
1941
1942/**
1943 * Try to get an IPv6 address. This starts with an [ and should end with an ]
1944 * and everything between should be not longer than INET6_ADDRSTRLEN and
1945 * parseable by inet_pton().
1946 *
1947 * \param sc client struct to set errors
1948 * \param strp possible start of IPv6 address (the '['); updated to point to
1949 * the next character to parse (the one after the closing ']')
1950 *
1951 * \return end of address (equals *strp + 1 if there is none) or NULL
1952 * on errors
1953 */
1954static inline const char *
1955get_ipv6(struct snmp_client *sc, const char **strp)
1956{
1957 char str[INET6_ADDRSTRLEN + IF_NAMESIZE];
1958 struct addrinfo hints, *res;
1959 int error;
1960
1961 if (**strp != '[')
1962 return (*strp + 1);
1963
1964 const char *p = *strp + 1;
1965 while (*p != ']' ) {
1966 if (*p == '\0') {
1967 seterr(sc, "unterminated IPv6 address '%.*s'",
1968 p - *strp, *strp);
1969 return (NULL);
1919 }
1970 }
1920 strncpy(sc->read_community, s, p - s);
1921 sc->read_community[p - s] = '\0';
1922 strncpy(sc->write_community, s, p - s);
1923 sc->write_community[p - s] = '\0';
1924 s = p + 1;
1971 p++;
1925 }
1926
1972 }
1973
1927 /* look for a colon */
1928 for (p = s; *p != '\0'; p++) {
1929 if (*p == '\\' && p[1] != '\0') {
1930 p++;
1931 continue;
1932 }
1933 if (*p == ':')
1934 break;
1974 if (p - *strp > INET6_ADDRSTRLEN + IF_NAMESIZE) {
1975 seterr(sc, "IPv6 address too long '%.*s'", p - *strp, *strp);
1976 return (NULL);
1935 }
1936
1977 }
1978
1937 if (*p == ':') {
1938 if (p > s) {
1939 /* host:port */
1940 free(sc->chost);
1941 if ((sc->chost = malloc(p - s + 1)) == NULL) {
1942 seterr(sc, "%s", strerror(errno));
1943 return (-1);
1944 }
1945 strncpy(sc->chost, s, p - s);
1946 sc->chost[p - s] = '\0';
1979 strncpy(str, *strp + 1, p - (*strp + 1));
1980 str[p - (*strp + 1)] = '\0';
1981
1982 memset(&hints, 0, sizeof(hints));
1983 hints.ai_flags = AI_CANONNAME | AI_NUMERICHOST;
1984 hints.ai_family = AF_INET6;
1985 hints.ai_socktype = SOCK_DGRAM;
1986 hints.ai_protocol = IPPROTO_UDP;
1987 error = getaddrinfo(str, NULL, &hints, &res);
1988 if (error != 0) {
1989 seterr(sc, "%s: %s", str, gai_strerror(error));
1990 return (NULL);
1991 }
1992 freeaddrinfo(res);
1993 *strp = p + 1;
1994 return (p);
1995}
1996
1997/**
1998 * Try to get an IPv4 address. This starts with a digit and consists of digits
1999 * and dots, is not longer INET_ADDRSTRLEN and must be parseable by
2000 * inet_aton().
2001 *
2002 * \param sc client struct to set errors
2003 * \param strp possible start of IPv4 address; updated to point to the
2004 * next character to parse
2005 *
2006 * \return end of address (equals *strp if there is none) or NULL
2007 * on errors
2008 */
2009static inline const char *
2010get_ipv4(struct snmp_client *sc, const char **strp)
2011{
2012 const char *p = *strp;
2013
2014 while (isascii(*p) && (isdigit(*p) || *p == '.'))
2015 p++;
2016
2017 if (p - *strp > INET_ADDRSTRLEN) {
2018 seterr(sc, "IPv4 address too long '%.*s'", p - *strp, *strp);
2019 return (NULL);
2020 }
2021 if (*strp == p)
2022 return *strp;
2023
2024 char str[INET_ADDRSTRLEN + 1];
2025 strncpy(str, *strp, p - *strp);
2026 str[p - *strp] = '\0';
2027
2028 struct in_addr addr;
2029 if (inet_aton(str, &addr) != 1) {
2030 seterr(sc, "illegal IPv4 address '%s'", str);
2031 return (NULL);
2032 }
2033
2034 *strp = p;
2035 return (p);
2036}
2037
2038/**
2039 * Try to get a hostname. This includes everything up to but not including
2040 * the last colon (if any). There is no length restriction.
2041 *
2042 * \param sc client struct to set errors
2043 * \param strp possible start of hostname; updated to point to the next
2044 * character to parse (the trailing NUL character or the last
2045 * colon)
2046 *
2047 * \return end of address (equals *strp if there is none)
2048 */
2049static inline const char *
2050get_host(struct snmp_client *sc __unused, const char **strp)
2051{
2052 const char *p = strrchr(*strp, ':');
2053
2054 if (p == NULL) {
2055 *strp += strlen(*strp);
2056 return (*strp);
2057 }
2058
2059 *strp = p;
2060 return (p);
2061}
2062
2063/**
2064 * Try to get a port number. This start with a colon and extends to the end
2065 * of string. The port number must not be empty.
2066 *
2067 * \param sc client struct to set errors
2068 * \param strp possible start of port specification; if this points to a
2069 * colon there is a port specification
2070 *
2071 * \return end of port number (equals *strp if there is none); NULL
2072 * if there is no port number
2073 */
2074static inline const char *
2075get_port(struct snmp_client *sc, const char **strp)
2076{
2077 if (**strp != ':')
2078 return (*strp + 1);
2079
2080 if ((*strp)[1] == '\0') {
2081 seterr(sc, "empty port name");
2082 return (NULL);
2083 }
2084
2085 *strp += strlen(*strp);
2086 return (*strp);
2087}
2088
2089/**
2090 * Save the string in the range given by two pointers.
2091 *
2092 * \param sc client struct to set errors
2093 * \param s begin and end pointers
2094 *
2095 * \return freshly allocated copy of the string between s[0] and s[1]
2096 */
2097static inline char *
2098save_str(struct snmp_client *sc, const char *const s[2])
2099{
2100 char *m;
2101
2102 if ((m = malloc(s[1] - s[0] + 1)) == NULL) {
2103 seterr(sc, "%s: %s", __func__, strerror(errno));
2104 return (NULL);
2105 }
2106 strncpy(m, s[0], s[1] - s[0]);
2107 m[s[1] - s[0]] = '\0';
2108
2109 return (m);
2110}
2111
2112/**
2113 * Parse a server specification. All parts are optional:
2114 *
2115 * [<trans>::][<comm>@][<host-or-ip>][:<port>]
2116 *
2117 * The transport string consists of letters, digits or '_' and starts with
2118 * a letter or digit. It is terminated by two colons and may not be empty.
2119 *
2120 * The community string is terminated by the last '@' and does not exceed
2121 * SNMP_COMMUNITY_MAXLEN. It may be empty.
2122 *
2123 * The host or ip is either an IPv4 address (as parsed by inet_pton()), an
2124 * IPv6 address in '[' and ']' and parseable by inet_aton() or a hostname
2125 * terminated by the last colon or by the NUL character.
2126 *
2127 * The port number may be specified numerically or symbolically and starts
2128 * with the last colon.
2129 *
2130 * The functions sets the chost, cport, trans, read_community and
2131 * write_community fields on success and the error field on errors.
2132 * The chost and cport fields are allocated by malloc(3), their previous
2133 * content is deallocated by free(3).
2134 *
2135 * The function explicitly allows mismatches between the transport and
2136 * the address type in order to support IPv4 in IPv6 addresses.
2137 *
2138 * \param sc client struct to fill
2139 * \param str string to parse
2140 *
2141 * \return 0 on success and -1 on errors
2142 */
2143int
2144snmp_parse_server(struct snmp_client *sc, const char *str)
2145{
2146#if DEBUG_PARSE
2147 const char *const orig = str;
2148#endif
2149
2150 const char *const trans_list[] = {
2151 [SNMP_TRANS_UDP] = "udp",
2152 [SNMP_TRANS_LOC_DGRAM] = "dgram",
2153 [SNMP_TRANS_LOC_STREAM] = "stream",
2154 [SNMP_TRANS_UDP6] = "udp6",
2155 };
2156
2157 /* parse input */
2158 const char *const transp[2] = {
2159 str,
2160 get_transp(sc, &str),
2161 };
2162 if (transp[1] == NULL)
2163 return (-1);
2164
2165 const char *const comm[2] = {
2166 str,
2167 get_comm(sc, &str),
2168 };
2169 if (comm[1] == NULL)
2170 return (-1);
2171
2172 const char *const ipv6[2] = {
2173 str + 1,
2174 get_ipv6(sc, &str),
2175 };
2176 if (ipv6[1] == NULL)
2177 return (-1);
2178
2179 const char *ipv4[2] = {
2180 str,
2181 str,
2182 };
2183
2184 const char *host[2] = {
2185 str,
2186 str,
2187 };
2188
2189 if (ipv6[0] == ipv6[1]) {
2190 ipv4[1] = get_ipv4(sc, &str);
2191
2192 if (ipv4[0] == ipv4[1])
2193 host[1] = get_host(sc, &str);
2194 }
2195
2196 const char *port[2] = {
2197 str + 1,
2198 get_port(sc, &str),
2199 };
2200 if (port[1] == NULL)
2201 return (-1);
2202
2203 if (*str != '\0') {
2204 seterr(sc, "junk at end of server specification '%s'", str);
2205 return (-1);
2206 }
2207
2208#if DEBUG_PARSE
2209 printf("transp: %zu %zu\n", transp[0] - orig, transp[1] - orig);
2210 printf("comm: %zu %zu\n", comm[0] - orig, comm[1] - orig);
2211 printf("ipv6: %zu %zu\n", ipv6[0] - orig, ipv6[1] - orig);
2212 printf("ipv4: %zu %zu\n", ipv4[0] - orig, ipv4[1] - orig);
2213 printf("host: %zu %zu\n", host[0] - orig, host[1] - orig);
2214 printf("port: %zu %zu\n", port[0] - orig, port[1] - orig);
2215#endif
2216
2217 /* analyse and allocate */
2218 int i = -1;
2219 if (transp[0] != transp[1]) {
2220 for (i = 0; i < (int)nitems(trans_list); i++) {
2221 if (trans_list[i] != NULL &&
2222 strlen(trans_list[i]) == (size_t)(transp[1] -
2223 transp[0]) && !strncmp(trans_list[i], transp[0],
2224 transp[1] - transp[0]))
2225 break;
1947 }
2226 }
1948 /* port */
1949 free(sc->cport);
1950 if ((sc->cport = strdup(p + 1)) == NULL) {
1951 seterr(sc, "%s", strerror(errno));
2227
2228 if (i == (int)nitems(trans_list)) {
2229 seterr(sc, "unknown transport specifier '%.*s'",
2230 transp[1] - transp[0], transp[0]);
1952 return (-1);
1953 }
2231 return (-1);
2232 }
2233 }
1954
2234
1955 } else if (p > s) {
1956 /* host */
1957 free(sc->chost);
1958 if ((sc->chost = strdup(s)) == NULL) {
1959 seterr(sc, "%s", strerror(errno));
2235 char *chost;
2236
2237 if (ipv6[0] != ipv6[1]) {
2238 if ((chost = save_str(sc, ipv6)) == NULL)
1960 return (-1);
2239 return (-1);
2240 if (i == -1)
2241 i = SNMP_TRANS_UDP6;
2242 } else if (ipv4[0] != ipv4[1]) {
2243 if ((chost = save_str(sc, ipv4)) == NULL)
2244 return (-1);
2245 if (i == -1)
2246 i = SNMP_TRANS_UDP;
2247 } else {
2248 if ((chost = save_str(sc, host)) == NULL)
2249 return (-1);
2250
2251 if (i == -1) {
2252 /* Default transport is UDP unless the host contains
2253 * a slash in which case we default to DGRAM. */
2254 i = SNMP_TRANS_UDP;
2255 for (const char *p = host[0]; p < host[1]; p++)
2256 if (*p == '/') {
2257 i = SNMP_TRANS_LOC_DGRAM;
2258 break;
2259 }
1961 }
1962 }
2260 }
2261 }
2262
2263 char *cport = save_str(sc, port);
2264 if (cport == NULL) {
2265 free(chost);
2266 return (-1);
2267 }
2268
2269 /* commit */
2270 sc->trans = i;
2271
2272 strncpy(sc->read_community, comm[0], comm[1] - comm[0]);
2273 sc->read_community[comm[1] - comm[0]] = '\0';
2274 strncpy(sc->write_community, comm[0], comm[1] - comm[0]);
2275 sc->write_community[comm[1] - comm[0]] = '\0';
2276
2277 free(sc->chost);
2278 sc->chost = chost;
2279 free(sc->cport);
2280 sc->cport = cport;
2281
1963 return (0);
1964}
2282 return (0);
2283}