siproxd/src/utils.c
2002-11-03 15:21:01 +00:00

335 lines
8.8 KiB
C

/*
Copyright (C) 2002 Thomas Ries <tries@gmx.net>
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 <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/types.h>
#include <pwd.h>
#include <osip/smsg.h>
#include <osip/port.h>
#include "siproxd.h"
#include "log.h"
static char const ident[]="$Id: " __FILE__ ": " PACKAGE "-" VERSION "-"\
BUILDSTR " $";
/* configuration storage */
extern struct siproxd_config configuration;
extern int h_errno;
/*
* create a reply template from an given SIP request
*
* RETURNS a pointer to sip_t
*/
sip_t *msg_make_template_reply (sip_t * request, int code) {
sip_t *response;
char *tmp;
int pos;
msg_init (&response);
msg_setversion (response, sgetcopy ("SIP/2.0"));
tmp = malloc(STATUSCODE_SIZE);
snprintf (tmp, STATUSCODE_SIZE, "%i", code);
msg_setstatuscode (response, tmp);
msg_setreasonphrase (response, msg_getreason (code));
to_clone (request->to, &response->to);
from_clone (request->from, &response->from);
/* via headers */
pos = 0;
while (!list_eol (request->vias, pos)) {
via_t *via;
via = (via_t *) list_get (request->vias, pos);
via_2char (via, &tmp);
msg_setvia (response, tmp);
free (tmp);
pos++;
}
call_id_clone(request->call_id,&response->call_id);
cseq_clone(request->cseq,&response->cseq);
return response;
}
/*
* check for a via loop.
* It checks for the presense of a via entry that holds one of
* my IP addresses and is *not* the topmost via.
*
* RETURNS
* STS_TRUE if loop detected
* STS_FALSE if no loop
*/
int check_vialoop (sip_t *my_msg) {
int sts;
int pos;
int found_own_via;
found_own_via=0;
pos = 1; /* for detecting a loop, don't check the first entry
as this is my VIA! */
while (!list_eol (my_msg->vias, pos)) {
via_t *via;
via = (via_t *) list_get (my_msg->vias, pos);
sts = is_via_local (via);
if (sts == 1) found_own_via=1;
pos++;
}
return (found_own_via)? STS_TRUE : STS_FALSE;
}
/*
* check if a given via_t is local. I.e. its address is owned
* by my inbound or outbound interface
*
* RETURNS
* STS_TRUE if the given VIA is one of my interfaces
* STS_FALSE otherwise
*/
int is_via_local (via_t *via) {
int sts;
struct in_addr addr_via, addr_myself;
char *my_hostnames[]=
{ configuration.inboundhost, configuration.outboundhost, NULL };
int port;
int i;
char *ptr;
DEBUGC(DBCLASS_BABBLE,"via name %s",via->host);
if (inet_aton(via->host,&addr_via) == 0) {
/* need name resolution */
get_ip_by_host(via->host, &addr_via);
}
sts=0;
for (i=0; ; i++) {
ptr=my_hostnames[i];
if (ptr==NULL) break;
DEBUGC(DBCLASS_BABBLE,"local name %s",ptr);
/* check the extracted VIA against my own host addresses */
sts = get_ip_by_host(ptr, &addr_myself);
if (via->port) port=atoi(via->port);
else port=SIP_PORT;
if ( (memcmp(&addr_myself, &addr_via, sizeof(addr_myself))==0) &&
(port == configuration.sip_listen_port) ) {
sts=1;
break;
}
}
return (sts)? STS_TRUE : STS_FALSE;
}
/*
* 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;
time_t t;
struct hostent *hostentry;
static struct {
time_t timestamp;
struct in_addr addr;
char hostname[HOSTNAME_SIZE];
} dns_cache[DNS_CACHE_SIZE];
static int cache_initialized=0;
if (hostname == NULL) 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(&t);
/* clean expired entries */
for (i=0; i<DNS_CACHE_SIZE; i++) {
if (dns_cache[i].hostname[0]=='\0') continue;
if ( (dns_cache[i].timestamp+DNS_MAX_AGE) < t ) {
DEBUGC(DBCLASS_DNS, "cleaning DNS cache (entry %i)", i);
memset (&dns_cache[i], 0, sizeof(dns_cache[0]));
}
}
/*
* search requested entry in cache
*/
for (i=0; i<DNS_CACHE_SIZE; i++) {
if (dns_cache[i].hostname[0]=='\0') continue; /* empty */
if (strcmp(hostname, dns_cache[i].hostname) == 0) { /* match */
memcpy(addr, &dns_cache[i].addr, sizeof(struct in_addr));
DEBUGC(DBCLASS_DNS, "DNS lookup - from cache: %s -> %s",
hostname, inet_ntoa(*addr));
return STS_SUCCESS;
}
}
/* did not find it in cache, so I have to resolve it */
hostentry=gethostbyname(hostname);
if (hostentry==NULL) {
ERROR("gethostbyname(%s) failed: %s",hostname,hstrerror(h_errno));
return STS_FAILURE;
}
memcpy(addr, hostentry->h_addr, sizeof(struct in_addr));
DEBUGC(DBCLASS_DNS, "DNS lookup - resolved: %s -> %s",
hostname, inet_ntoa(*addr));
/*
* remember the result in the cache
*/
/* find an empty slot */
j=0;
for (i=0; i<DNS_CACHE_SIZE; i++) {
if (dns_cache[i].hostname[0]=='\0') break;
if (dns_cache[i].timestamp < t) {
/* remember oldest entry */
t=dns_cache[i].timestamp;
j=i;
}
}
/* if no empty slot found, take oldest one */
if (i >= DNS_CACHE_SIZE) i=j;
/* store in cache */
DEBUGC(DBCLASS_DNS, "DNS lookup - store into cache, entry %i)", i);
memset(&dns_cache[i], 0, sizeof(dns_cache[0]));
strncpy(dns_cache[i].hostname, hostname, HOSTNAME_SIZE);
time(&dns_cache[i].timestamp);
memcpy(&dns_cache[i].addr, addr, sizeof(struct in_addr));
return STS_SUCCESS;
}
/*
* compares two URLs
* (by now, only hostname and username are compared)
*
* RETURNS
* STS_SUCCESS if equal
* STS_FAILURE if non equal or error
*/
int compare_url(url_t *url1, url_t *url2) {
int sts;
if ((url1 == NULL) || (url2 == NULL)) return STS_FAILURE;
/* comparison of hosts should be based on IP addresses, no? */
DEBUGC(DBCLASS_BABBLE, "comparing urls: %s@%s -> %s@%s",
url1->username, url1->host, url2->username, url2->host);
if ((strcmp(url1->username, url2->username)==0) &&
(strcmp(url1->host, url2->host)==0)) {
sts = STS_SUCCESS;
} else {
sts = STS_FAILURE;
}
return sts;
}
/*
* 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",
getuid(), geteuid(), getgid(), 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) {
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));
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",
passwd->pw_gid, (sts==0)?"Ok":"Failed");
sts = setegid(passwd->pw_uid);
DEBUGC(DBCLASS_CONFIG,"changed egid to %i - %s",
passwd->pw_gid, (sts==0)?"Ok":"Failed");
sts = setuid(passwd->pw_uid);
DEBUGC(DBCLASS_CONFIG,"changed uid to %i - %s",
passwd->pw_uid, (sts==0)?"Ok":"Failed");
sts = seteuid(passwd->pw_uid);
DEBUGC(DBCLASS_CONFIG,"changed euid to %i - %s",
passwd->pw_uid, (sts==0)?"Ok":"Failed");
}
}
}