/* Copyright (C) 2002-2008 Thomas Ries This file is part of Siproxd. Siproxd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Siproxd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Siproxd; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _SOLARIS2 # include #endif #include #include #include #include "siproxd.h" #include "log.h" static char const ident[]="$Id$"; /* configuration storage */ extern struct siproxd_config configuration; extern int h_errno; /* * resolve a hostname and return in_addr * handles its own little DNS cache. * * RETURNS * STS_SUCCESS on success * STS_FAILURE on failure */ int get_ip_by_host(char *hostname, struct in_addr *addr) { int i, j, k, idx; time_t t1, t2; struct hostent *hostentry; #if defined(HAVE_GETHOSTBYNAME_R) struct hostent result_buffer; char tmp[GETHOSTBYNAME_BUFLEN]; #endif int error; static struct { time_t expires_timestamp; /* time of expiration */ struct in_addr addr; /* IP address or 0.0.0.0 if a bad entry */ char error_count; /* counts failed resolution attempts */ char bad_entry; /* != 0 if resolving failed */ char hostname[HOSTNAME_SIZE+1]; } dns_cache[DNS_CACHE_SIZE]; static int cache_initialized=0; if (hostname == NULL) { ERROR("get_ip_by_host: NULL hostname requested"); return STS_FAILURE; } if (addr == NULL) { ERROR("get_ip_by_host: NULL in_addr passed"); return STS_FAILURE; } /* first time: initialize DNS cache */ if (cache_initialized == 0) { DEBUGC(DBCLASS_DNS, "initializing DNS cache (%i entries)", DNS_CACHE_SIZE); memset(dns_cache, 0, sizeof(dns_cache)); cache_initialized=1; } time(&t1); /* clean expired entries */ for (i=0; i 0) { DEBUGC(DBCLASS_DNS, "DNS lookup - previous resolution failed: %s" ", attempt %i", hostname, dns_cache[i].error_count); idx=i; break; } DEBUGC(DBCLASS_DNS, "DNS lookup - from cache: %s -> %s", hostname, utils_inet_ntoa(*addr)); return STS_SUCCESS; } } /* I did not find it in cache, so I have to resolve it */ error = 0; /* need to deal with reentrant versions of gethostbyname_r() * as we may use threads... */ #if defined(HAVE_GETHOSTBYNAME_R) /* gethostbyname_r() with 3 arguments (e.g. osf/1) */ #if defined(HAVE_FUNC_GETHOSTBYNAME_R_3) gethostbyname_r(hostname, /* the FQDN */ &result_buffer, /* the result buffer */ &hostentry ); if (hostentry == NULL) error = h_errno; /* gethostbyname_r() with 5 arguments (e.g. solaris, linux libc5) */ #elif defined(HAVE_FUNC_GETHOSTBYNAME_R_5) hostentry = gethostbyname_r(hostname, /* the FQDN */ &result_buffer, /* the result buffer */ tmp, GETHOSTBYNAME_BUFLEN, &error); /* gethostbyname_r() with 6 arguments (e.g. linux glibc) */ #elif defined(HAVE_FUNC_GETHOSTBYNAME_R_6) gethostbyname_r(hostname, /* the FQDN */ &result_buffer, /* the result buffer */ tmp, GETHOSTBYNAME_BUFLEN, &hostentry, &error); #else #error "gethostbyname_r() with 3, 5 or 6 arguments supported only" #endif #elif defined(HAVE_GETHOSTBYNAME) hostentry=gethostbyname(hostname); if (hostentry == NULL) error = h_errno; #else #error "need gethostbyname() or gethostbyname_r()" #endif /* Here I have 'hostentry' and 'error' */ if (hostentry==NULL) { /* * Some errors just tell us that there was no IP resolvable. * From the manpage: * HOST_NOT_FOUND * The specified host is unknown. * NO_ADDRESS or NO_DATA * The requested name is valid but does not have an IP * address. */ if ((error == HOST_NOT_FOUND) || (error == NO_ADDRESS) || (error == NO_DATA)) { #ifdef HAVE_HSTRERROR DEBUGC(DBCLASS_DNS, "gethostbyname(%s) failed: h_errno=%i [%s]", hostname, h_errno, hstrerror(error)); #else DEBUGC(DBCLASS_DNS, "gethostbyname(%s) failed: h_errno=%i", hostname, error); #endif } else { #ifdef HAVE_HSTRERROR ERROR("gethostbyname(%s) failed: h_errno=%i [%s]", hostname, h_errno, hstrerror(h_errno)); #else ERROR("gethostbyname(%s) failed: h_errno=%i",hostname, h_errno); #endif } } if (hostentry) { memcpy(addr, hostentry->h_addr, sizeof(struct in_addr)); DEBUGC(DBCLASS_DNS, "DNS lookup - resolved: %s -> %s", hostname, utils_inet_ntoa(*addr)); } /* if we already have the entry, skip finding a new empty one */ if (idx == 0) { /* * find an empty slot in the cache */ j=0; k=0; t1=INT_MAX; t2=INT_MAX; for (i=0; i= DNS_CACHE_SIZE) { if (k > 0) i=k; else i=j; } idx=i; memset(&dns_cache[idx], 0, sizeof(dns_cache[0])); } /* * store the result in the cache */ DEBUGC(DBCLASS_DNS, "DNS lookup - store into cache, entry %i)", idx); strncpy(dns_cache[idx].hostname, hostname, HOSTNAME_SIZE); dns_cache[idx].expires_timestamp = time(NULL) + DNS_GOOD_AGE; if (hostentry) { memcpy(&dns_cache[idx].addr, addr, sizeof(struct in_addr)); dns_cache[idx].error_count = 0; dns_cache[idx].bad_entry = 0; } else { dns_cache[idx].error_count++; DEBUGC(DBCLASS_DNS, "DNS lookup - errcnt=%i", dns_cache[idx].error_count); if (dns_cache[idx].error_count >= DNS_ATTEMPTS) { DEBUGC(DBCLASS_DNS, "DNS lookup - blacklisting entry"); dns_cache[idx].expires_timestamp = time(NULL) + DNS_BAD_AGE; dns_cache[idx].bad_entry = 1; } return STS_FAILURE; } return STS_SUCCESS; } /* * Secure enviroment: * If running as root, put myself into a chroot jail and * change UID/GID to user as requested in config file */ void secure_enviroment (void) { int sts; struct passwd *passwd=NULL; DEBUGC(DBCLASS_CONFIG,"running w/uid=%i, euid=%i, gid=%i, egid=%i", (int)getuid(), (int)geteuid(), (int)getgid(), (int)getegid()); if ((getuid()==0) || (geteuid()==0)) { /* * preparation - after chrooting there will be NOTHING more around */ if (configuration.user) passwd=getpwnam(configuration.user); /* * change root directory into chroot jail */ if (configuration.chrootjail) { /* !!! * Before chrooting I must at least once trigger the resolver * as it loads some dynamic libraries. Once chrootet * these libraries will *not* be found and gethostbyname() * calls will simply fail (return NULL pointer and h_errno=0). * Also (at least for FreeBSD) syslog() needs to be called * before chroot()ing - this is done in main() by an INFO(). * Took me a while to figure THIS one out */ struct in_addr dummy; get_ip_by_host("localhost", &dummy); DEBUGC(DBCLASS_CONFIG,"chrooting to %s", configuration.chrootjail); sts = chroot(configuration.chrootjail); if (sts != 0) DEBUGC(DBCLASS_CONFIG,"chroot(%s) failed: %s", configuration.chrootjail, strerror(errno)); sts=chdir("/"); } /* * change user ID and group ID */ if (passwd) { DEBUGC(DBCLASS_CONFIG,"changing uid/gid to %s", configuration.user); sts = setgid(passwd->pw_gid); DEBUGC(DBCLASS_CONFIG,"changed gid to %i - %s", (int)passwd->pw_gid, (sts==0)?"Ok":"Failed"); sts = setegid(passwd->pw_gid); DEBUGC(DBCLASS_CONFIG,"changed egid to %i - %s", (int)passwd->pw_gid, (sts==0)?"Ok":"Failed"); sts = seteuid(passwd->pw_uid); DEBUGC(DBCLASS_CONFIG,"changed euid to %i - %s", (int)passwd->pw_uid, (sts==0)?"Ok":"Failed"); } } } /* * get_interface_ip: * fetches own IP address by interface INBOUND/OUTBOUND * takes into account a possible outbound_host setting. * * STS_SUCCESS on returning a valid IP and interface is UP * STS_FAILURE if interface is DOWN or other problem */ int get_interface_ip(int interface, struct in_addr *retaddr) { int sts=STS_FAILURE; if ((interface == IF_OUTBOUND) && (configuration.outbound_host) && (strcmp(configuration.outbound_host, "")!=0)) { DEBUGC(DBCLASS_DNS, "fetching outbound IP by HOSTNAME"); if (retaddr) { sts = get_ip_by_host(configuration.outbound_host, retaddr); } else { sts = STS_SUCCESS; } } else { sts = get_interface_real_ip(interface, retaddr); } return sts; } /* * get_interface_real_ip: * fetches the real IP address of my interface INBOUND/OUTBOUND * * STS_SUCCESS on returning a valid IP and interface is UP * STS_FAILURE if interface is DOWN or other problem */ int get_interface_real_ip(int interface, struct in_addr *retaddr) { int sts=STS_FAILURE; char *tmp=NULL; if (interface == IF_INBOUND) { tmp = configuration.inbound_if; } else if (interface == IF_OUTBOUND) { tmp = configuration.outbound_if; } if (tmp && (strcmp(tmp, "")!=0)) { DEBUGC(DBCLASS_DNS, "fetching interface IP by INTERFACE [%i]", interface); sts = get_ip_by_ifname(tmp, retaddr); if (sts != STS_SUCCESS) { ERROR("can't find interface %s - configuration error?", tmp); } } else { ERROR("Don't know what interface to look for - configuration error?"); } return sts; } /* * get_ip_by_ifname: * fetches own IP address by its interface name * * STS_SUCCESS on returning a valid IP and interface is UP * STS_FAILURE if interface is DOWN or other problem */ int get_ip_by_ifname(char *ifname, struct in_addr *retaddr) { struct ifreq ifr; struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr; int sockfd; int i, j; int ifflags, isup; time_t t; static struct { time_t timestamp; struct in_addr ifaddr; /* IP */ int isup; /* interface is UP */ char ifname[IFNAME_SIZE+1]; } ifaddr_cache[IFADR_CACHE_SIZE]; static int cache_initialized=0; if (ifname == NULL) { WARN("get_ip_by_ifname: got NULL ifname passed - please check config" "file ('if_inbound' and 'if_outbound')"); return STS_FAILURE; } /* first time: initialize ifaddr cache */ if (cache_initialized == 0) { DEBUGC(DBCLASS_DNS, "initializing ifaddr cache (%i entries)", IFADR_CACHE_SIZE); memset(ifaddr_cache, 0, sizeof(ifaddr_cache)); cache_initialized=1; } if (retaddr) memset(retaddr, 0, sizeof(struct in_addr)); time(&t); /* clean expired entries */ for (i=0; i %s %s", ifname, utils_inet_ntoa(ifaddr_cache[i].ifaddr), (ifaddr_cache[i].isup)? "UP":"DOWN"); return (ifaddr_cache[i].isup)? STS_SUCCESS: STS_FAILURE; } /* if */ } /* for i */ /* not found in cache, go and get it */ memset(&ifr, 0, sizeof(ifr)); if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { ERROR("Error in socket: %s\n",strerror(errno)); return STS_FAILURE; } strcpy(ifr.ifr_name, ifname); sin->sin_family = AF_INET; /* get interface flags */ if(ioctl(sockfd, SIOCGIFFLAGS, &ifr) != 0) { ERROR("Error in ioctl SIOCGIFFLAGS: %s [%s]\n", strerror(errno), ifname); close(sockfd); return STS_FAILURE; } ifflags=ifr.ifr_flags; /* get address */ if(ioctl(sockfd, SIOCGIFADDR, &ifr) != 0) { ERROR("Error in ioctl SIOCGIFADDR: %s (interface %s)\n", strerror(errno), ifname); close(sockfd); return STS_FAILURE; } if (ifflags & IFF_UP) isup=1; else isup=0; DEBUGC(DBCLASS_DNS, "get_ip_by_ifname: if %s has IP:%s (flags=%x) %s", ifname, utils_inet_ntoa(sin->sin_addr), ifflags, (isup)? "UP":"DOWN"); /* *find an empty slot in the cache */ j=0; for (i=0; i= IFADR_CACHE_SIZE) i=j; /* * store the result in the cache */ DEBUGC(DBCLASS_DNS, "ifname lookup - store into cache, entry %i)", i); memset(&ifaddr_cache[i], 0, sizeof(ifaddr_cache[0])); strncpy(ifaddr_cache[i].ifname, ifname, IFNAME_SIZE); ifaddr_cache[i].timestamp=t; memcpy(&ifaddr_cache[i].ifaddr, &sin->sin_addr, sizeof(sin->sin_addr)); ifaddr_cache[i].isup=isup; if (retaddr) memcpy(retaddr, &sin->sin_addr, sizeof(sin->sin_addr)); close(sockfd); return (isup)? STS_SUCCESS : STS_FAILURE; } /* * utils_inet_ntoa: * implements an inet_ntoa() * * Returns pointer to a STATIC character string. * NOte: BE AWARE OF THE STATIC NATURE of the string! Never pass it as * calling argument to a function and use it immediately or strcpy() * it into a buffer. * !! Any subsequent call to this function will DESTROY the previous * !! value - and may result in very strange effects like magically * !! changing variable value (that has been passed to a function) * Been there, seen that, so TAKE CARE! */ char *utils_inet_ntoa(struct in_addr in) { #if defined(HAVE_INET_NTOP) static char string[INET_ADDRSTRLEN]; if ((inet_ntop(AF_INET, &in, string, INET_ADDRSTRLEN)) == NULL) { ERROR("inet_ntop() failed: %s\n",strerror(errno)); string[0]='\0'; } return string; #elif defined(HAVE_INET_NTOA) return inet_ntoa(in); #else #error "need inet_ntop() or inet_ntoa()" #endif } /* * utils_inet_aton: * implements an inet_aton() * * converts the string in *cp and stores it into inp * Returns != 0 on success */ int utils_inet_aton(const char *cp, struct in_addr *inp) { #if defined(HAVE_INET_PTON) return inet_pton (AF_INET, cp, inp); #elif defined(HAVE_INET_ATON) return inet_aton(cp, inp); #else #error "need inet_pton() or inet_aton()" #endif } /* * Create the PID file */ int createpidfile(char *pidfilename) { FILE *f = NULL; int sts; DEBUGC(DBCLASS_CONFIG,"creating PID file [%s]", pidfilename); sts=unlink(pidfilename); if ((sts==0) || (errno == ENOENT)) { if ((f=fopen(pidfilename, "w"))) { fprintf(f,"%i\n",(int)getpid()); fclose(f); } else { WARN("couldn't create new PID file: %s", strerror(errno)); return STS_FAILURE; } } else { WARN("couldn't delete old PID file: %s", strerror(errno)); return STS_FAILURE; } return STS_SUCCESS; } /* * compare_client_id: * Compares two client_id_t structures. If both have the Contact item * defined (not NULL), then compare it and return. * If one (or both) do NOT have the contact item defined, then * fall back on comparing the from_ip (IP address). * * returns: * STS_SUCCESS on match * STS_FAILURE on no match */ int compare_client_id(client_id_t cid1, client_id_t cid2) { /* Prio 1: Contact - if present in both structures */ if ((cid1.idstring[0] != '\0') && (cid2.idstring[0] != '\0')) { if (strncmp(cid1.idstring, cid2.idstring, CLIENT_ID_SIZE) == 0) { DEBUGC(DBCLASS_BABBLE, "compare_client_id: contact match [%s]", cid1.idstring); return STS_SUCCESS; } DEBUGC(DBCLASS_BABBLE, "compare_client_id: contact NO match [%s<->%s]", cid1.idstring, cid2.idstring); return STS_FAILURE; } /* Prio 2: IP (always present) - fallback, if no ID string present. */ if (memcmp(&cid1.from_ip, &cid2.from_ip, sizeof(struct in_addr)) == 0) { DEBUGC(DBCLASS_BABBLE, "compare_client_id: IP match [%s]", utils_inet_ntoa(cid1.from_ip)); return STS_SUCCESS; } DEBUGC(DBCLASS_BABBLE, "compare_client_id: no match"); return STS_FAILURE; }