2 Copyright (C) 2002-2005 Thomas Ries <tries@gmx.net>
4 This file is part of Siproxd.
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.
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.
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
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
33 #include <sys/ioctl.h>
36 # include <sys/sockio.h>
39 #include <sys/types.h>
42 #include <osipparser2/osip_parser.h>
47 static char const ident[]="$Id: utils.c,v 1.41 2005/01/08 10:05:13 hb9xar Exp $";
49 /* configuration storage */
50 extern struct siproxd_config configuration;
56 * resolve a hostname and return in_addr
57 * handles its own little DNS cache.
60 * STS_SUCCESS on success
61 * STS_FAILURE on failure
63 int get_ip_by_host(char *hostname, struct in_addr *addr) {
66 struct hostent *hostentry;
67 #if defined(HAVE_GETHOSTBYNAME_R)
68 struct hostent result_buffer;
69 char tmp[GETHOSTBYNAME_BUFLEN];
75 char hostname[HOSTNAME_SIZE+1];
76 } dns_cache[DNS_CACHE_SIZE];
77 static int cache_initialized=0;
79 if (hostname == NULL) {
80 ERROR("get_ip_by_host: NULL hostname requested");
85 ERROR("get_ip_by_host: NULL in_addr passed");
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));
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]));
107 * search requested entry in cache
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));
119 /* did not find it in cache, so I have to resolve it */
121 /* need to deal with reentrant versions of gethostbyname_r()
122 * as we may use threads... */
123 #if defined(HAVE_GETHOSTBYNAME_R)
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 */
131 if (hostentry == NULL) error = h_errno;
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 */
138 GETHOSTBYNAME_BUFLEN,
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 */
146 GETHOSTBYNAME_BUFLEN,
150 #error "gethostbyname_r() with 3, 5 or 6 arguments supported only"
152 #elif defined(HAVE_GETHOSTBYNAME)
153 hostentry=gethostbyname(hostname);
154 if (hostentry == NULL) error = h_errno;
156 #error "need gethostbyname() or gethostbyname_r()"
159 if (hostentry==NULL) {
161 * Some errors just tell us that there was no IP resolvable.
164 * The specified host is unknown.
166 * The specified host is unknown.
167 * NO_ADDRESS or NO_DATA
168 * The requested name is valid but does not have an IP
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));
178 DEBUGC(DBCLASS_DNS, "gethostbyname(%s) failed: h_errno=%i",
182 #ifdef HAVE_HSTRERROR
183 ERROR("gethostbyname(%s) failed: h_errno=%i [%s]",
184 hostname, h_errno, hstrerror(h_errno));
186 ERROR("gethostbyname(%s) failed: h_errno=%i",hostname, h_errno);
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));
197 * find an empty slot in the cache
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;
208 /* if no empty slot found, take oldest one */
209 if (i >= DNS_CACHE_SIZE) i=j;
212 * store the result in the cache
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));
226 * If running as root, put myself into a chroot jail and
227 * change UID/GID to user as requested in config file
229 void secure_enviroment (void) {
231 struct passwd *passwd=NULL;
233 DEBUGC(DBCLASS_CONFIG,"running w/uid=%i, euid=%i, gid=%i, egid=%i",
234 getuid(), geteuid(), getgid(), getegid());
236 if ((getuid()==0) || (geteuid()==0)) {
238 * preparation - after chrooting there will be NOTHING more around
240 if (configuration.user) passwd=getpwnam(configuration.user);
243 * change root directory into chroot jail
245 if (configuration.chrootjail) {
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
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));
266 * change user ID and group ID
269 DEBUGC(DBCLASS_CONFIG,"changing uid/gid to %s",
271 sts = setgid(passwd->pw_gid);
272 DEBUGC(DBCLASS_CONFIG,"changed gid to %i - %s",
273 passwd->pw_gid, (sts==0)?"Ok":"Failed");
275 sts = setegid(passwd->pw_gid);
276 DEBUGC(DBCLASS_CONFIG,"changed egid to %i - %s",
277 passwd->pw_gid, (sts==0)?"Ok":"Failed");
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");
286 sts = seteuid(passwd->pw_uid);
287 DEBUGC(DBCLASS_CONFIG,"changed euid to %i - %s",
288 passwd->pw_uid, (sts==0)?"Ok":"Failed");
296 * fetches own IP address by its interface name
298 * STS_SUCCESS on returning a valid IP and interface is UP
299 * STS_FAILURE if interface is DOWN or other problem
301 int get_ip_by_ifname(char *ifname, struct in_addr *retaddr) {
303 struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;
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;
316 if (ifname == NULL) {
317 WARN("get_ip_by_ifname: got NULL ifname passed - please check config"
318 "file ('if_inbound' and 'if_outbound')");
322 /* first time: initialize ifaddr cache */
323 if (cache_initialized == 0) {
324 DEBUGC(DBCLASS_DNS, "initializing ifaddr cache (%i entries)",
326 memset(ifaddr_cache, 0, sizeof(ifaddr_cache));
330 if (retaddr) memset(retaddr, 0, sizeof(struct in_addr));
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]));
343 * search requested entry in cache
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;
357 /* not found in cache, go and get it */
358 memset(&ifr, 0, sizeof(ifr));
360 if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
361 ERROR("Error in socket: %s\n",strerror(errno));
365 strcpy(ifr.ifr_name, ifname);
366 sin->sin_family = AF_INET;
368 /* get interface flags */
369 if(ioctl(sockfd, SIOCGIFFLAGS, &ifr) != 0) {
370 ERROR("Error in ioctl SIOCGIFFLAGS: %s [%s]\n",
371 strerror(errno), ifname);
375 ifflags=ifr.ifr_flags;
378 if(ioctl(sockfd, SIOCGIFADDR, &ifr) != 0) {
379 ERROR("Error in ioctl SIOCGIFADDR: %s (interface %s)\n",
380 strerror(errno), ifname);
385 if (ifflags & IFF_UP) isup=1;
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");
393 *find an empty slot in the cache
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;
404 /* if no empty slot found, take oldest one */
405 if (i >= IFADR_CACHE_SIZE) i=j;
408 * store the result in the cache
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;
417 if (retaddr) memcpy(retaddr, &sin->sin_addr, sizeof(sin->sin_addr));
420 return (isup)? STS_SUCCESS : STS_FAILURE;
426 * implements an inet_ntoa()
428 * Returns pointer to a static character string.
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));
438 #elif defined(HAVE_INET_NTOA)
439 return inet_ntoa(in);
441 #error "need inet_ntop() or inet_ntoa()"
448 * implements an inet_aton()
450 * converts the string in *cp and stores it into inp
451 * Returns != 0 on success
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);
459 #error "need inet_pton() or inet_aton()"
463 void get_local_ip(char *ip, struct in_addr *local_ip) {
464 /* Returns the local IP address used to reach ip. */
466 struct sockaddr_in local;
467 struct sockaddr_in remote;
470 sock = socket(AF_INET, SOCK_DGRAM, 0);
475 memset((char *) &remote, 0, sizeof(remote));
476 remote.sin_family = AF_INET;
477 remote.sin_addr.s_addr = inet_addr(ip);
479 if (connect(sock, (struct sockaddr *) &remote, sizeof(remote))) {
483 namelen = sizeof(local);
484 if (getsockname(sock, (struct sockaddr *) &local,
485 (socklen_t *) &namelen) < 0) {
486 perror("getsockname");
493 memcpy(local_ip, &local.sin_addr, sizeof(struct in_addr));