Revert "Revert "and added files""
[bcm963xx.git] / userapps / opensource / siproxd / src / utils.c
1 /*
2     Copyright (C) 2002-2005  Thomas Ries <tries@gmx.net>
3
4     This file is part of Siproxd.
5     
6     Siproxd is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10     
11     Siproxd is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15     
16     You should have received a copy of the GNU General Public License
17     along with Siproxd; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
19 */
20 #include "config.h"
21
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <errno.h>
25 #include <time.h>
26 #include <signal.h>
27 #include <string.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #include <netdb.h>
32 #include <net/if.h>
33 #include <sys/ioctl.h>
34
35 #ifdef _SOLARIS2
36 # include <sys/sockio.h>
37 #endif
38
39 #include <sys/types.h>
40 #include <pwd.h>
41
42 #include <osipparser2/osip_parser.h>
43
44 #include "siproxd.h"
45 #include "log.h"
46
47 static char const ident[]="$Id: utils.c,v 1.41 2005/01/08 10:05:13 hb9xar Exp $";
48
49 /* configuration storage */
50 extern struct siproxd_config configuration;
51
52 extern int h_errno;
53
54
55 /*
56  * resolve a hostname and return in_addr
57  * handles its own little DNS cache.
58  *
59  * RETURNS
60  *      STS_SUCCESS on success
61  *      STS_FAILURE on failure
62  */
63 int get_ip_by_host(char *hostname, struct in_addr *addr) {
64    int i, j;
65    time_t t;
66    struct hostent *hostentry;
67 #if defined(HAVE_GETHOSTBYNAME_R)
68    struct hostent result_buffer;
69    char tmp[GETHOSTBYNAME_BUFLEN];
70 #endif
71    int error;
72    static struct {
73       time_t timestamp;
74       struct in_addr addr;
75       char hostname[HOSTNAME_SIZE+1];
76    } dns_cache[DNS_CACHE_SIZE];
77    static int cache_initialized=0;
78
79    if (hostname == NULL) {
80       ERROR("get_ip_by_host: NULL hostname requested");
81       return STS_FAILURE;
82    }
83
84    if (addr == NULL) {
85       ERROR("get_ip_by_host: NULL in_addr passed");
86       return STS_FAILURE;
87    }
88
89    /* first time: initialize DNS cache */
90    if (cache_initialized == 0) {
91       DEBUGC(DBCLASS_DNS, "initializing DNS cache (%i entries)", DNS_CACHE_SIZE);
92       memset(dns_cache, 0, sizeof(dns_cache));
93       cache_initialized=1;
94    }
95
96    time(&t);
97    /* clean expired entries */
98    for (i=0; i<DNS_CACHE_SIZE; i++) {
99       if (dns_cache[i].hostname[0]=='\0') continue;
100       if ( (dns_cache[i].timestamp+DNS_MAX_AGE) < t ) {
101          DEBUGC(DBCLASS_DNS, "cleaning DNS cache (entry %i)", i);
102          memset (&dns_cache[i], 0, sizeof(dns_cache[0]));
103       }
104    }
105
106    /*
107     * search requested entry in cache
108     */
109    for (i=0; i<DNS_CACHE_SIZE; i++) {
110       if (dns_cache[i].hostname[0]=='\0') continue; /* empty */
111       if (strcmp(hostname, dns_cache[i].hostname) == 0) { /* match */
112          memcpy(addr, &dns_cache[i].addr, sizeof(struct in_addr));
113          DEBUGC(DBCLASS_DNS, "DNS lookup - from cache: %s -> %s",
114                 hostname, utils_inet_ntoa(*addr));
115          return STS_SUCCESS;
116       }
117    }
118    
119    /* did not find it in cache, so I have to resolve it */
120
121    /* need to deal with reentrant versions of gethostbyname_r()
122     * as we may use threads... */
123 #if defined(HAVE_GETHOSTBYNAME_R)
124
125    /* gethostbyname_r() with 3 arguments (e.g. osf/1) */
126    #if defined(HAVE_FUNC_GETHOSTBYNAME_R_3)
127    gethostbyname_r(hostname,            /* the FQDN */
128                    &result_buffer,      /* the result buffer */ 
129                    &hostentry
130                    );
131    if (hostentry == NULL) error = h_errno;
132
133    /* gethostbyname_r() with 5 arguments (e.g. solaris, linux libc5) */
134    #elif defined(HAVE_FUNC_GETHOSTBYNAME_R_5)
135    hostentry = gethostbyname_r(hostname,        /* the FQDN */
136                                &result_buffer,  /* the result buffer */
137                                tmp,
138                                GETHOSTBYNAME_BUFLEN,
139                                &error);
140
141    /* gethostbyname_r() with 6 arguments (e.g. linux glibc) */
142    #elif defined(HAVE_FUNC_GETHOSTBYNAME_R_6)
143    gethostbyname_r(hostname,        /* the FQDN */
144                    &result_buffer,  /* the result buffer */
145                    tmp,
146                    GETHOSTBYNAME_BUFLEN,
147                    &hostentry,
148                    &error);
149    #else
150       #error "gethostbyname_r() with 3, 5 or 6 arguments supported only"
151    #endif   
152 #elif defined(HAVE_GETHOSTBYNAME)
153    hostentry=gethostbyname(hostname);
154    if (hostentry == NULL) error = h_errno;
155 #else
156    #error "need gethostbyname() or gethostbyname_r()"
157 #endif
158
159    if (hostentry==NULL) {
160       /*
161        * Some errors just tell us that there was no IP resolvable.
162        * From the manpage:
163        *   HOST_NOT_FOUND
164        *      The specified host is unknown.
165        *   HOST_NOT_FOUND
166        *      The specified host is unknown.
167        *   NO_ADDRESS or NO_DATA
168        *      The requested name is valid but does not have an IP
169        *      address.
170        */
171       if ((error == HOST_NOT_FOUND) ||
172           (error == NO_ADDRESS) ||
173           (error == NO_DATA)) {
174 #ifdef HAVE_HSTRERROR
175          DEBUGC(DBCLASS_DNS, "gethostbyname(%s) failed: h_errno=%i [%s]",
176                 hostname, h_errno, hstrerror(error));
177 #else
178          DEBUGC(DBCLASS_DNS, "gethostbyname(%s) failed: h_errno=%i",
179                 hostname, error);
180 #endif
181       } else {
182 #ifdef HAVE_HSTRERROR
183          ERROR("gethostbyname(%s) failed: h_errno=%i [%s]",
184                hostname, h_errno, hstrerror(h_errno));
185 #else
186          ERROR("gethostbyname(%s) failed: h_errno=%i",hostname, h_errno);
187 #endif
188       }
189       return STS_FAILURE;
190    }
191
192    memcpy(addr, hostentry->h_addr, sizeof(struct in_addr));
193    DEBUGC(DBCLASS_DNS, "DNS lookup - resolved: %s -> %s",
194           hostname, utils_inet_ntoa(*addr));
195
196    /*
197     * find an empty slot in the cache
198     */
199    j=0;
200    for (i=0; i<DNS_CACHE_SIZE; i++) {
201       if (dns_cache[i].hostname[0]=='\0') break;
202       if (dns_cache[i].timestamp < t) {
203          /* remember oldest entry */
204          t=dns_cache[i].timestamp;
205          j=i;
206       }
207    }
208    /* if no empty slot found, take oldest one */
209    if (i >= DNS_CACHE_SIZE) i=j;
210
211    /*
212     * store the result in the cache
213     */
214    DEBUGC(DBCLASS_DNS, "DNS lookup - store into cache, entry %i)", i);
215    memset(&dns_cache[i], 0, sizeof(dns_cache[0]));
216    strncpy(dns_cache[i].hostname, hostname, HOSTNAME_SIZE);
217    time(&dns_cache[i].timestamp);
218    memcpy(&dns_cache[i].addr, addr, sizeof(struct in_addr));
219
220    return STS_SUCCESS;
221 }
222
223
224 /*
225  * Secure enviroment:
226  * If running as root, put myself into a chroot jail and
227  * change UID/GID to user as requested in config file
228  */
229 void secure_enviroment (void) {
230    int sts;
231    struct passwd *passwd=NULL;
232
233    DEBUGC(DBCLASS_CONFIG,"running w/uid=%i, euid=%i, gid=%i, egid=%i",
234           getuid(), geteuid(), getgid(), getegid());
235
236    if ((getuid()==0) || (geteuid()==0)) {
237       /*
238        * preparation - after chrooting there will be NOTHING more around
239        */
240       if (configuration.user) passwd=getpwnam(configuration.user);
241
242       /*
243        * change root directory into chroot jail
244        */
245       if (configuration.chrootjail) {
246          /* !!!
247           * Before chrooting I must at least once trigger the resolver
248           * as it loads some dynamic libraries. Once chrootet
249           * these libraries will *not* be found and gethostbyname()
250           * calls will simply fail (return NULL pointer and h_errno=0).
251           * Also (at least for FreeBSD) syslog() needs to be called
252           * before chroot()ing - this is done in main() by an INFO().
253           * Took me a while to figure THIS one out
254           */
255          struct in_addr dummy;
256          get_ip_by_host("localhost", &dummy);
257          DEBUGC(DBCLASS_CONFIG,"chrooting to %s",
258                 configuration.chrootjail);
259          sts = chroot(configuration.chrootjail);
260          if (sts != 0) DEBUGC(DBCLASS_CONFIG,"chroot(%s) failed: %s",
261                               configuration.chrootjail, strerror(errno));
262          chdir("/");
263       }
264
265       /*
266        * change user ID and group ID 
267        */
268       if (passwd) {
269          DEBUGC(DBCLASS_CONFIG,"changing uid/gid to %s",
270                 configuration.user);
271          sts = setgid(passwd->pw_gid);
272          DEBUGC(DBCLASS_CONFIG,"changed gid to %i - %s",
273                 passwd->pw_gid, (sts==0)?"Ok":"Failed");
274
275          sts = setegid(passwd->pw_gid);
276          DEBUGC(DBCLASS_CONFIG,"changed egid to %i - %s",
277                 passwd->pw_gid, (sts==0)?"Ok":"Failed");
278
279 /* don't set the real user id - as we need to elevate privs
280    when setting up an RTP masquerading tunnel */
281 /*&&& Actually this is no longer true (7-Jul-2004/xar) */
282 //         sts = setuid(passwd->pw_uid);
283 //         DEBUGC(DBCLASS_CONFIG,"changed uid to %i - %s",
284 //              passwd->pw_uid, (sts==0)?"Ok":"Failed");
285
286          sts = seteuid(passwd->pw_uid);
287          DEBUGC(DBCLASS_CONFIG,"changed euid to %i - %s",
288                 passwd->pw_uid, (sts==0)?"Ok":"Failed");
289       }
290    }
291 }
292
293
294 /*
295  * get_ip_by_ifname:
296  * fetches own IP address by its interface name
297  *
298  * STS_SUCCESS on returning a valid IP and interface is UP
299  * STS_FAILURE if interface is DOWN or other problem
300  */
301 int get_ip_by_ifname(char *ifname, struct in_addr *retaddr) {
302    struct ifreq ifr;
303    struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;
304    int sockfd;
305    int i, j;
306    int ifflags, isup;
307    time_t t;
308    static struct {
309       time_t timestamp;
310       struct in_addr ifaddr;    /* IP */
311       int isup;                 /* interface is UP */
312       char ifname[IFNAME_SIZE+1];
313    } ifaddr_cache[IFADR_CACHE_SIZE];
314    static int cache_initialized=0;
315
316    if (ifname == NULL) {
317       WARN("get_ip_by_ifname: got NULL ifname passed - please check config"
318            "file ('if_inbound' and 'if_outbound')");
319       return STS_FAILURE;
320    }
321
322    /* first time: initialize ifaddr cache */
323    if (cache_initialized == 0) {
324       DEBUGC(DBCLASS_DNS, "initializing ifaddr cache (%i entries)", 
325              IFADR_CACHE_SIZE);
326       memset(ifaddr_cache, 0, sizeof(ifaddr_cache));
327       cache_initialized=1;
328    }
329
330    if (retaddr) memset(retaddr, 0, sizeof(struct in_addr));
331
332    time(&t);
333    /* clean expired entries */
334    for (i=0; i<IFADR_CACHE_SIZE; i++) {
335       if (ifaddr_cache[i].ifname[0]=='\0') continue;
336       if ( (ifaddr_cache[i].timestamp+IFADR_MAX_AGE) < t ) {
337          DEBUGC(DBCLASS_DNS, "cleaning ifaddr cache (entry %i)", i);
338          memset (&ifaddr_cache[i], 0, sizeof(ifaddr_cache[0]));
339       }
340    }
341
342    /*
343     * search requested entry in cache
344     */
345    for (i=0; i<IFADR_CACHE_SIZE; i++) {
346       if (ifaddr_cache[i].ifname[0]=='\0') continue;
347       if (strcmp(ifname, ifaddr_cache[i].ifname) == 0) { /* match */
348          if (retaddr) memcpy(retaddr, &ifaddr_cache[i].ifaddr,
349                              sizeof(struct in_addr));
350          DEBUGC(DBCLASS_DNS, "ifaddr lookup - from cache: %s -> %s %s",
351                 ifname, utils_inet_ntoa(ifaddr_cache[i].ifaddr),
352                 (ifaddr_cache[i].isup)? "UP":"DOWN");
353          return (ifaddr_cache[i].isup)? STS_SUCCESS: STS_FAILURE;
354       } /* if */
355    } /* for i */
356
357    /* not found in cache, go and get it */
358    memset(&ifr, 0, sizeof(ifr));
359
360    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
361       ERROR("Error in socket: %s\n",strerror(errno));
362       return STS_FAILURE;
363    }
364
365    strcpy(ifr.ifr_name, ifname);
366    sin->sin_family = AF_INET;
367
368    /* get interface flags */
369    if(ioctl(sockfd, SIOCGIFFLAGS, &ifr) != 0) {
370       ERROR("Error in ioctl SIOCGIFFLAGS: %s [%s]\n",
371             strerror(errno), ifname);
372       close(sockfd);
373       return STS_FAILURE;
374    } 
375    ifflags=ifr.ifr_flags;
376
377    /* get address */
378    if(ioctl(sockfd, SIOCGIFADDR, &ifr) != 0) {
379       ERROR("Error in ioctl SIOCGIFADDR: %s (interface %s)\n",
380       strerror(errno), ifname);
381       close(sockfd);
382       return STS_FAILURE;
383    } 
384
385    if (ifflags & IFF_UP) isup=1;
386    else isup=0;
387
388    DEBUGC(DBCLASS_DNS, "get_ip_by_ifname: if %s has IP:%s (flags=%x) %s",
389           ifname, utils_inet_ntoa(sin->sin_addr), ifflags,
390           (isup)? "UP":"DOWN");
391
392    /*
393     *find an empty slot in the cache
394     */
395    j=0;
396    for (i=0; i<IFADR_CACHE_SIZE; i++) {
397       if (ifaddr_cache[i].ifname[0]=='\0') break;
398       if (ifaddr_cache[i].timestamp < t) {
399          /* remember oldest entry */
400          t=ifaddr_cache[i].timestamp;
401          j=i;
402       }
403    }
404    /* if no empty slot found, take oldest one */
405    if (i >= IFADR_CACHE_SIZE) i=j;
406
407    /*
408     * store the result in the cache
409     */
410    DEBUGC(DBCLASS_DNS, "ifname lookup - store into cache, entry %i)", i);
411    memset(&ifaddr_cache[i], 0, sizeof(ifaddr_cache[0]));
412    strncpy(ifaddr_cache[i].ifname, ifname, IFNAME_SIZE);
413    ifaddr_cache[i].timestamp=t;
414    memcpy(&ifaddr_cache[i].ifaddr, &sin->sin_addr, sizeof(sin->sin_addr));
415    ifaddr_cache[i].isup=isup;
416
417    if (retaddr) memcpy(retaddr, &sin->sin_addr, sizeof(sin->sin_addr));
418
419    close(sockfd);
420    return (isup)? STS_SUCCESS : STS_FAILURE;
421 }
422
423
424 /*
425  * utils_inet_ntoa:
426  * implements an inet_ntoa()
427  *
428  * Returns pointer to a static character string.
429  */
430 char *utils_inet_ntoa(struct in_addr in) {
431 #if defined(HAVE_INET_NTOP)
432    static char string[INET_ADDRSTRLEN];
433    if ((inet_ntop(AF_INET, &in, string, INET_ADDRSTRLEN)) == NULL) {
434       ERROR("inet_ntop() failed: %s\n",strerror(errno));
435       string[0]='\0';
436    }
437    return string;
438 #elif defined(HAVE_INET_NTOA)
439    return inet_ntoa(in);
440 #else
441 #error "need inet_ntop() or inet_ntoa()"
442 #endif
443 }
444
445
446 /*
447  * utils_inet_aton:
448  * implements an inet_aton()
449  *
450  * converts the string in *cp and stores it into inp
451  * Returns != 0 on success
452  */
453 int  utils_inet_aton(const char *cp, struct in_addr *inp) {
454 #if defined(HAVE_INET_PTON)
455    return inet_pton (AF_INET, cp, inp);
456 #elif defined(HAVE_INET_ATON)
457    return inet_aton(cp, inp);
458 #else
459 #error "need inet_pton() or inet_aton()"
460 #endif
461 }
462
463 void get_local_ip(char *ip, struct in_addr *local_ip) {
464    /* Returns the local IP address used to reach ip. */
465    int sock;
466    struct sockaddr_in local;
467    struct sockaddr_in remote;
468    int namelen;
469
470    sock = socket(AF_INET, SOCK_DGRAM, 0);
471    if (sock < 0) {
472       perror("socket");
473    }
474
475    memset((char *) &remote, 0, sizeof(remote));
476    remote.sin_family = AF_INET;
477    remote.sin_addr.s_addr = inet_addr(ip);
478
479    if (connect(sock, (struct sockaddr *) &remote, sizeof(remote))) {
480       perror("connect");
481    }
482
483    namelen = sizeof(local);
484    if (getsockname(sock, (struct sockaddr *) &local,
485                    (socklen_t *) &namelen) < 0) {
486       perror("getsockname");
487    }
488
489    if (close(sock)) {
490       perror("close");
491    }
492
493    memcpy(local_ip, &local.sin_addr, sizeof(struct in_addr));
494 }