Fix possible nil pointer deref in interface check
[ric-plt/lib/rmr.git] / src / rmr / common / src / tools_static.c
1 // :vi sw=4 ts=4 noet:
2 /*
3 ==================================================================================
4         Copyright (c) 2019 Nokia
5         Copyright (c) 2018-2019 AT&T Intellectual Property.
6
7    Licensed under the Apache License, Version 2.0 (the "License");
8    you may not use this file except in compliance with the License.
9    You may obtain a copy of the License at
10
11            http://www.apache.org/licenses/LICENSE-2.0
12
13    Unless required by applicable law or agreed to in writing, software
14    distributed under the License is distributed on an "AS IS" BASIS,
15    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16    See the License for the specific language governing permissions and
17    limitations under the License.
18 ==================================================================================
19 */
20
21 /*
22         Mnemonic:       tools_static.c
23         Abstract:       A small set of very simple tools to support Uta == RMR.
24                                         uta_tokenise -- simple string tokeniser
25                                         uta_rmip_tokenise -- tokenise and remove ip addresses from the list
26                                         uta_h2ip        -- look up host name and return an ip address
27                                         uta_lookup_rtg  -- looks in env for rtg host:port
28                                         uta_has_str     -- searches buffer of tokens for a string
29
30                                         uta_link2       -- establish a nanomsg connection to a host
31
32         Author:         E. Scott Daniels
33         Date:           30 November 2018
34 */
35
36 #ifndef _tools_static_c
37 #define _tools_static_c
38
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <netdb.h>
42 #include <errno.h>
43 #include <string.h>
44 #include <errno.h>
45 #include <pthread.h>
46 #include <ctype.h>
47
48 #include <sys/types.h>          // these are needed to suss out ip addresses from interfaces
49 #include <ifaddrs.h>
50 #include <arpa/inet.h>
51 #include <sys/socket.h>
52 #include <netdb.h>
53
54 // --- some protos needed for better organisation --------
55 int is_this_myip( if_addrs_t* l, char* addr );
56
57
58 // ----------------------------------------------------------------------------------
59
60 /*
61         Simple tokeniser. Split a null terminated string into tokens recording the
62         pointers in the tokens array provided.  Tokens MUST be large enough. Max is
63         the max number of tokens to split into.  Returns the actual number of tokens
64         recorded in the pointer array.
65
66         CAUTION: this modifies the string passed in!!
67 */
68 static int uta_tokenise( char* buf, char** tokens, int max, char sep ) {
69         char* end;                                      // end of token
70         int     n = 0;
71
72         if( !buf || ! tokens || !(*buf) ) {
73                 return 0;
74         }
75
76         tokens[n++] = buf;
77         end = buf;
78         while( n < max && *end && (end = strchr( end, sep )) != NULL ) {
79                 *end = 0;
80                 tokens[n++] = ++end;
81         }
82
83         return n;
84 }
85
86 /*
87         Tokenise and remove matches.
88         Given a buffer of 'sep' separated tokens, and a list of things,
89         return up to max tokens with any tokens that matched things in
90         the list. Toks is the user supplied array of char* which we will
91         fill in (up to max) with pointers to tokens from buf.  This 
92         damages buf, so the caller must dup the string if it must be 
93         preserved for later, original, use.  The pointers returned in 
94         toks will reference portions of bufs.
95
96         Returns the number of tokens referenced by toks.
97 */
98 static int uta_rmip_tokenise( char* buf, if_addrs_t* iplist, char** toks, int max, char sep ) {
99         int     ntoks = 0;                      // total toks in the original buffer
100         int             pcount = 0;                     // count after prune
101         char**  all_toks;
102         int i;
103         int j;
104
105         
106         all_toks = malloc( sizeof( char * ) * max );                                    // refernce to all tokens; we'll prune
107         pcount = ntoks = uta_tokenise( buf, all_toks, max, sep );               // split them up
108         j = 0;
109         if( ntoks > 0 ) {
110                 for( i = 0; i < ntoks; i++ ) {
111                         if( is_this_myip( iplist, all_toks[i] ) ) {
112                                 pcount--;                                                                       // ours, prune
113                         } else {
114                                 toks[j++] = all_toks[i];                                        // not one of ours, keep it
115                         }
116                 }
117         }
118
119         free( all_toks );
120         return pcount;
121 }
122
123 /*
124         Xlate hostname (expected to be name:port) to an IP address that nano will tolerate.
125         We'll use the first address from the list to keep it simple. If the first character
126         of the name is a digit, we assume it's really an IP address and just return that.
127
128         Return is a string which the caller must free. Even if the string passed in is already
129         an IP address, a duplicate will be returend so that it can always be freed.
130         On error a nil pointer is returned.
131 */
132 static char* uta_h2ip( char const* hname ) {
133         char                    buf[120];
134         struct hostent* hent;
135         unsigned int    octs[4];
136         unsigned int    a;
137         int                             i;
138         char*                   tok;
139         char*                   dname;          // duplicated name for distruction
140
141         dname = strdup( hname );
142
143         if( isdigit( *dname ) || *dname == '[' ) {              // hostnames can't start with digit, or ipv6 [; assume ip address
144                 return dname;
145         }
146
147         if( (tok = strchr( dname, ':' )) != NULL ) {
148                 *(tok++) = 0;
149         }
150
151         hent = gethostbyname( dname );
152         if( hent == NULL || hent->h_addr_list == NULL ) {
153                 //rmr_vlog( RMR_VL_WARN, "h2ip: dns lookup failed for: %s\n", dname );
154                 free( dname );
155                 return NULL;
156         }
157
158         a = ntohl( *((unsigned int *)hent->h_addr_list[0]) );
159         for( i = 3; i >= 0; i-- ) {
160                 octs[i] = a & 0xff;
161                 a = a >> 8;
162         }
163
164         if( tok ) {                                                     // if :port was given, smash it back on
165                 snprintf( buf, sizeof( buf ), "%d.%d.%d.%d:%s", octs[0], octs[1], octs[2], octs[3], tok );
166         } else {
167                 snprintf( buf, sizeof( buf ), "%d.%d.%d.%d", octs[0], octs[1], octs[2], octs[3] );
168         }
169
170         free( dname );
171         return strdup( buf );
172 }
173
174
175 /*
176         Looks for the environment variable RMR_RTG_SVC which we assume to be name[:port], and
177         does a dns lookup on the name. If the env does not have such a variable, we default to
178         "rtg" and a port of 5656.
179
180         Returns true (1) if lookup found something;
181
182         CAUTION:  this is ONLY used if the RTG is a pub and we are using pub/sub to get updates.
183                         There are issues with some underlying transport pub/sub implementations so this
184                         is likley NOT needed/used.
185 */
186 static int uta_lookup_rtg( uta_ctx_t* ctx ) {
187         char*   ev;                                     // pointer to the env value
188         char*   def_port = "5656";
189         char*   port = NULL;
190         char*   dstr = NULL;
191
192         if( ctx == NULL ) {
193                 return 0;
194         }
195
196
197         if( ctx->rtg_addr ) {
198                 free( ctx->rtg_addr );
199         }
200
201         if( (ev = getenv( "RMR_RTG_SVC" )) == NULL ) {
202                 ev = "rtg";
203                 port = def_port;
204         } else {
205                 dstr = strdup( ev );                    // copy so we can trash it
206                 if( (port = strchr( dstr, ':' )) == NULL ) {
207                         port = def_port;
208                 } else {
209                         *port = 0;
210                         port++;                                         // point at the first digit
211                 }
212                 ev = dstr;                                              // all references below assume ev
213         }
214
215         ctx->rtg_addr = uta_h2ip( ev );         // convert name to IP addr
216         ctx->rtg_port = atoi( port );
217         if( dstr ) {
218                 free( dstr );
219         }
220
221         return ctx->rtg_addr != NULL;
222 }
223
224
225 /*
226         Expects a buffer of 'sep' separated tokens and looks to see if
227         the given string is one of those tokens. Returns the token
228         index (0 - n-1) if the string is found; -1 otherwise. The max
229         parameter supplies the maximum number of tokens to search in
230         the buffer.
231
232         On failure (-1) errno will be set in cases where memory cannot
233         be alocated (is this even possible any more?). If errno is 0
234         and failure is returned, then the caller should assume that
235         the token isn't in the list, or the list had no elements.
236 */
237 static int uta_has_str( char const* buf, char const* str, char sep, int max ) {
238         char*   dbuf;                   // duplicated buf so we can trash
239         char** tokens;                  // pointer to tokens from the string
240         int             ntokens;                // number of tokens buf split into
241         int             i;
242         int             rc;                             // return code
243
244         if( max < 2 ) {
245                 return -1;
246         }
247
248         dbuf = strdup( buf );
249         if( dbuf == NULL  ) {
250                 errno = ENOMEM;
251                 return -1;
252         }
253
254         if( (tokens = (char **) malloc( sizeof( char * ) * max )) == NULL ) {
255                 errno = ENOMEM;
256                 free( dbuf );
257                 return -1;
258         }
259
260         ntokens = uta_tokenise( dbuf, tokens, max, sep );
261         errno = 0;
262         rc = -1;
263         for( i = 0; rc < 0 && i < ntokens; i++ ) {
264                 if( tokens[i] ) {
265                         if( strcmp( tokens[i], str ) == 0 ) {
266                                 rc = i;
267                         }
268                 }
269         }
270
271         free( dbuf );
272         free( tokens );
273         return rc;
274 }
275
276 /*
277         Generate a list of all IP address associated with the interfaces available.
278         For now we capture them all, but we may need to limit. The port is smashed
279         onto each IP we find so that we can do a direct compare against the addr
280         that could be in the route table.
281
282         If the environment variable which limits the binding of our listen port
283         to a single interface (ENV_BIND_IF) then ONLY that interface/address is added
284         to the list so that we don't pick up entries from the rtable that are for other
285         processes listening on different interfaces.
286
287         The ENV_BIN_IF environment variable may be either an IP address (v6 must be in 
288         square braces), or an interface name (e.g. eth0).
289 */
290 if_addrs_t*  mk_ip_list( char* port ) {
291         if_addrs_t* l;
292         struct  ifaddrs *ifs;           // pointer to head
293         struct  ifaddrs *ele;           // pointer into the list
294         char    octs[NI_MAXHOST+1];
295         char    wbuf[NI_MAXHOST+128];
296         char*   fmt;
297         char*   envp;                           // at the environment var if there
298         char*   target_if = NULL;       // target interface supplied by ENV_BIND_IF
299         char*   tok;
300
301
302         if( (l = (if_addrs_t *) malloc( sizeof( if_addrs_t ) )) == NULL ) {
303                 return NULL;
304         }
305         memset( l, 0, sizeof( if_addrs_t ) );
306         l->addrs = (char **) malloc( sizeof( char* ) * 128 );
307         if( l->addrs == NULL ) {
308                 free( l );
309                 return NULL;
310         }
311
312         if( (envp = getenv( ENV_BIND_IF )) != NULL ) {
313                 if( isdigit( *envp ) || *envp == '[' ) {                                        // ip address given and not device name
314                         snprintf( wbuf, sizeof( wbuf ), "%s:%s", envp, port );          // smash port onto the addr as is
315                         l->addrs[l->naddrs] = strdup( wbuf );
316                         l->naddrs++;
317                         if( DEBUG ) rmr_vlog( RMR_VL_INFO, "rmr: using only specific bind interface when searching specific RT entries: %s\n", wbuf );
318                         return l;
319                 }
320
321                 target_if = envp;               // device name given, suss it out below
322         }
323
324         getifaddrs( &ifs );
325         for( ele = ifs; ele; ele = ele->ifa_next ) {
326                 memset( octs, 0, sizeof( octs ) );
327                 if( ele && strcmp( ele->ifa_name, "lo" ) &&                                                                     // do NOT capture the loopback interface address
328                         (target_if == NULL || strcmp( ele->ifa_name, target_if ) == 0 ) ) {             // no target, or matches ENV_BIND_IF target
329
330                         if( ele->ifa_addr != NULL ) {                                           // possible for some interfaces to not have an address
331                                 if( ele->ifa_addr->sa_family == AF_INET ) {
332                                         getnameinfo( ele->ifa_addr, sizeof( struct sockaddr_in ),  octs, NI_MAXHOST, NULL, 0, NI_NUMERICHOST );
333                                         fmt = "%s:%s";
334                                 } else {
335                                         if( ele->ifa_addr->sa_family == AF_INET6 ) {
336                                                 getnameinfo( ele->ifa_addr, sizeof( struct sockaddr_in6 ),  octs, NI_MAXHOST, NULL, 0, NI_NUMERICHOST );
337                                                 fmt = "[%s]:%s";
338                                         }
339                                 }
340                         }
341
342                         if( *octs ) {
343                                 if( (tok = strchr( octs, '%' )) != NULL ) {                     // for unknown reasons some ip6 addrs have %if-name appended; truncate
344                                         *tok = 0;
345                                 }
346                                 if( l->naddrs < 128 ) {
347                                         if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "capture address: %s: %s\n", ele->ifa_name, octs );
348
349                                         snprintf( wbuf, sizeof( wbuf ), fmt, octs, port );              // smash port onto the addr
350                                         l->addrs[l->naddrs] = strdup( wbuf );
351                                         l->naddrs++;
352                                 }
353                         }
354                 }
355         }
356
357         if( ifs ) {
358                 freeifaddrs( ifs );
359         }
360
361         return l;
362 }
363
364 /*
365         Check the address:port passed in and return true if it matches
366         one of the addresses we saw when we built the list. Right now
367         this isn't a speed intensive part of our processing, so we just
368         do a straight search through the list. We don't expect this to
369         ever be a higly driven functions so not bothering to optimise.
370 */
371 int is_this_myip( if_addrs_t* l, char* addr ) {
372         int i;
373
374         if( l == NULL ) {
375                 return 0;
376         }
377
378         if( addr == NULL ) {
379                 return 0;
380         }
381
382         for( i = 0; i < l->naddrs; i++ ) {
383                 if( l->addrs[i] != NULL  &&  strcmp( addr, l->addrs[i] ) == 0 ) {
384                         return 1;
385                 }
386         }
387
388         return 0;
389 }
390
391 /*
392         Expects a buffer containing "sep" separated tokens, and a list of
393         IP addresses anchored by ip_list.  Searches the tokens to see if
394         any are an ip address:port which is in the ip list.  Returns true
395         (1) if a token is in the list, false otherwise.
396 */
397 static int has_myip( char const* buf, if_addrs_t* list, char sep, int max ) {
398         char*   dbuf;                   // duplicated buf so we can trash
399         char** tokens;                  // pointer to tokens from the string
400         int             ntokens;                // number of tokens buf split into
401         int             i;
402         int             rc = 0;                 // return code
403
404         if( max < 2 ) {
405                 return 0;
406         }
407
408         if( buf == NULL ) {
409                 return 0;
410         }
411
412         if( list == NULL ) {
413                 return 0;
414         }
415
416
417         dbuf = strdup( buf );                   // get a copy we can mess with
418         if( dbuf == NULL  ) {
419                 errno = ENOMEM;
420                 return 0;
421         }
422
423         if( (tokens = (char **) malloc( sizeof( char * ) * max )) == NULL ) {
424                 errno = ENOMEM;
425                 free( dbuf );
426                 return 0;
427         }
428
429         ntokens = uta_tokenise( dbuf, tokens, max, sep );
430         errno = 0;
431         rc = 0;
432         for( i = 0; ! rc  && i < ntokens; i++ ) {
433                 if( tokens[i] ) {
434                         if( is_this_myip( list, tokens[i] ) ) {
435                                 rc = 1;
436                                 break;
437                         }
438                 }
439         }
440
441         free( dbuf );
442         free( tokens );
443         return rc;
444 }
445
446 /*
447         Given a list manager block, return the default IP address.
448         For now, that is just the first address on the list which
449         easily could be non-deterministic and change with each restart
450         of the application if a specific interface is not provided via
451         the environment variable (ENV_BIND_IF) and if there is more than
452         one device available on the container/physical host.
453 */
454 static char* get_default_ip( if_addrs_t* iplist ) {
455
456         if( iplist != NULL  &&  iplist->naddrs > 0  &&  iplist->addrs != NULL ) {
457                 return strdup( iplist->addrs[0] );
458         }
459
460         return NULL;
461 }
462
463 #endif