siproxd/src/siproxd.c

541 lines
16 KiB
C

/* -*- Mode: C; c-basic-offset: 3 -*-
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 <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#include <osipparser2/osip_parser.h>
#include "siproxd.h"
#include "log.h"
static char const ident[]="$Id$";
/* configuration storage */
struct siproxd_config configuration;
/* Global File instance on pw file */
FILE *siproxd_passwordfile;
/* -h help option text */
static const char str_helpmsg[] =
PACKAGE "-" VERSION "-" BUILDSTR " (c) 2002-2005 Thomas Ries\n"
"\nUsage: siproxd [options]\n\n"
"options:\n"
#ifdef HAVE_GETOPT_LONG
" -h, --help help\n"
" -d, --debug <pattern> set debug-pattern\n"
" -c, --config <cfgfile> use the specified config file\n"
" -p, --pid-file <pidfile> create pid file <pidfile>\n"
#else
" -h help\n"
" -d <pattern> set debug-pattern\n"
" -c <cfgfile> use the specified config file\n"
" -p <pidfile> create pid file <pidfile>\n"
#endif
"";
/*
* module local data
*/
static int dmalloc_dump=0;
static int exit_program=0;
/*
* local prototypes
*/
static void sighandler(int sig);
int main (int argc, char *argv[])
{
int sts;
int i;
int access;
char buff [BUFFER_SIZE];
sip_ticket_t ticket;
extern char *optarg; /* Defined in libc getopt and unistd.h */
int ch1;
char configfile[64]="siproxd"; /* basename of configfile */
int config_search=1; /* search the config file */
int cmdline_debuglevel=0;
char *pidfilename=NULL;
struct sigaction act;
log_set_stderr(1);
/*
* setup signal handlers
*/
act.sa_handler=sighandler;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_RESTART;
if (sigaction(SIGTERM, &act, NULL)) {
ERROR("Failed to install SIGTERM handler");
}
if (sigaction(SIGINT, &act, NULL)) {
ERROR("Failed to install SIGINT handler");
}
if (sigaction(SIGUSR2, &act, NULL)) {
ERROR("Failed to install SIGUSR2 handler");
}
/*
* prepare default configuration
*/
make_default_config();
log_set_pattern(configuration.debuglevel);
/*
* open a the pwfile instance, so we still have access after
* we possibly have chroot()ed to somewhere.
*/
if (configuration.proxy_auth_pwfile) {
siproxd_passwordfile = fopen(configuration.proxy_auth_pwfile, "r");
} else {
siproxd_passwordfile = NULL;
}
/*
* parse command line
*/
{
#ifdef HAVE_GETOPT_LONG
int option_index = 0;
static struct option long_options[] = {
{"help", no_argument, NULL, 'h'},
{"config", required_argument, NULL, 'c'},
{"debug", required_argument, NULL, 'd'},
{"pid-file", required_argument, NULL,'p'},
{0,0,0,0}
};
while ((ch1 = getopt_long(argc, argv, "hc:d:p:",
long_options, &option_index)) != -1) {
#else /* ! HAVE_GETOPT_LONG */
while ((ch1 = getopt(argc, argv, "hc:d:p:")) != -1) {
#endif
switch (ch1) {
case 'h': /* help */
DEBUGC(DBCLASS_CONFIG,"option: help");
fprintf(stderr,str_helpmsg);
exit(0);
break;
case 'c': /* load config file */
DEBUGC(DBCLASS_CONFIG,"option: config file=%s",optarg);
i=sizeof(configfile)-1;
strncpy(configfile,optarg,i-1);
configfile[i]='\0';
config_search=0;
break;
case 'd': /* set debug level */
DEBUGC(DBCLASS_CONFIG,"option: set debug level: %s",optarg);
cmdline_debuglevel=atoi(optarg);
log_set_pattern(cmdline_debuglevel);
break;
case 'p':
pidfilename = optarg;
break;
default:
DEBUGC(DBCLASS_CONFIG,"no command line options");
break;
}
}
}
/*
* Init stuff
*/
INFO(PACKAGE"-"VERSION"-"BUILDSTR" starting up");
DEBUGC(DBCLASS_ALL,UNAME);
/* read the config file */
if (read_config(configfile, config_search) == STS_FAILURE) exit(1);
/* if a debug level > 0 has been given on the commandline use its
value and not what is in the config file */
if (cmdline_debuglevel != 0) {
configuration.debuglevel=cmdline_debuglevel;
}
/* set debug level as desired */
log_set_pattern(configuration.debuglevel);
log_set_listen_port(configuration.debugport);
/* change user and group IDs */
secure_enviroment();
/* daemonize if requested to */
if (configuration.daemonize) {
DEBUGC(DBCLASS_CONFIG,"daemonizing");
if (fork()!=0) exit(0);
setsid();
if (fork()!=0) exit(0);
log_set_stderr(0);
INFO("daemonized, pid=%i", getpid());
}
/* write PID file of main thread */
if (pidfilename == NULL) pidfilename = configuration.pid_file;
if (pidfilename) {
FILE *pidfile;
DEBUGC(DBCLASS_CONFIG,"creating PID file [%s]", pidfilename);
sts=unlink(configuration.pid_file);
if ((sts==0) ||(errno == ENOENT)) {
if ((pidfile=fopen(pidfilename, "w"))) {
fprintf(pidfile,"%i\n",(int)getpid());
fclose(pidfile);
} else {
WARN("couldn't create new PID file: %s", strerror(errno));
}
} else {
WARN("couldn't delete old PID file: %s", strerror(errno));
}
}
/* initialize the RTP proxy */
sts=rtpproxy_init();
if (sts != STS_SUCCESS) {
ERROR("unable to initialize RTP proxy - aborting");
exit(1);
}
/* init the oSIP parser */
parser_init();
/* listen for incoming messages */
sts=sipsock_listen();
if (sts == STS_FAILURE) {
/* failure to allocate SIP socket... */
ERROR("unable to bind to SIP listening socket - aborting");
exit(1);
}
/* initialize the registration facility */
register_init();
/*
* silence the log - if so required...
*/
log_set_silence(configuration.silence_log);
INFO(PACKAGE"-"VERSION"-"BUILDSTR" started");
DEBUGC(DBCLASS_ALL,UNAME);
/*
* Main loop
*/
while (!exit_program) {
DEBUGC(DBCLASS_BABBLE,"going into sipsock_wait\n");
while (sipsock_wait()<=0) {
/* got no input, here by timeout. do aging */
register_agemap();
/* TCP log: check for a connection */
log_tcp_connect();
/* dump memory stats if requested to do so */
if (dmalloc_dump) {
dmalloc_dump=0;
#ifdef DMALLOC
INFO("SIGUSR2 - DMALLOC statistics is dumped");
dmalloc_log_stats();
dmalloc_log_unfreed();
#else
INFO("SIGUSR2 - DMALLOC support is not compiled in");
#endif
}
if (exit_program) goto exit_prg;
}
/* got input, process */
DEBUGC(DBCLASS_BABBLE,"back from sipsock_wait");
i=sipsock_read(&buff, sizeof(buff)-1, &ticket.from, &ticket.protocol);
buff[i]='\0';
/* evaluate the access lists (IP based filter)*/
access=accesslist_check(ticket.from);
if (access == 0) {
DEBUGC(DBCLASS_ACCESS,"access for this packet was denied");
continue; /* there are no resources to free */
}
/* integrity checks */
sts=security_check_raw(buff, i);
if (sts != STS_SUCCESS) {
DEBUGC(DBCLASS_SIP,"security check (raw) failed");
continue; /* there are no resources to free */
}
/* init sip_msg */
sts=osip_message_init(&ticket.sipmsg);
ticket.sipmsg->message=NULL;
if (sts != 0) {
ERROR("osip_message_init() failed... this is not good");
continue; /* skip, there are no resources to free */
}
/*
* RFC 3261, Section 16.3 step 1
* Proxy Behavior - Request Validation - Reasonable Syntax
* (parse the received message)
*/
sts=osip_message_parse(ticket.sipmsg, buff);
if (sts != 0) {
ERROR("osip_message_parse() failed... this is not good");
DUMP_BUFFER(-1, buff, i);
goto end_loop; /* skip and free resources */
}
/* integrity checks - parsed buffer*/
sts=security_check_sip(&ticket);
if (sts != STS_SUCCESS) {
ERROR("security_check_sip() failed... this is not good");
DUMP_BUFFER(-1, buff, i);
goto end_loop; /* skip and free resources */
}
/*
* RFC 3261, Section 16.3 step 2
* Proxy Behavior - Request Validation - URI scheme
* (check request URI and refuse with 416 if not understood)
*/
/* NOT IMPLEMENTED */
/*
* RFC 3261, Section 16.3 step 3
* Proxy Behavior - Request Validation - Max-Forwards check
* (check Max-Forwards header and refuse with 483 if too many hops)
*/
{
osip_header_t *max_forwards;
int forwards_count = DEFAULT_MAXFWD;
osip_message_get_max_forwards(ticket.sipmsg, 0, &max_forwards);
if (max_forwards && max_forwards->hvalue) {
forwards_count = atoi(max_forwards->hvalue);
}
DEBUGC(DBCLASS_PROXY,"checking Max-Forwards (=%i)",forwards_count);
if (forwards_count <= 0) {
DEBUGC(DBCLASS_SIP, "Forward count reached 0 -> 483 response");
sip_gen_response(&ticket, 483 /*Too many hops*/);
goto end_loop; /* skip and free resources */
}
}
/*
* RFC 3261, Section 16.3 step 4
* Proxy Behavior - Request Validation - Loop Detection check
* (check for loop and return 482 if a loop is detected)
*/
if (check_vialoop(&ticket) == STS_TRUE) {
/* make sure we don't end up in endless loop when detecting
* an loop in an "loop detected" message - brrr */
if (MSG_IS_RESPONSE(ticket.sipmsg) &&
MSG_TEST_CODE(ticket.sipmsg, 482)) {
DEBUGC(DBCLASS_SIP,"loop in loop-response detected, ignoring");
} else {
DEBUGC(DBCLASS_SIP,"via loop detected, ignoring request");
sip_gen_response(&ticket, 482 /*Loop detected*/);
}
goto end_loop; /* skip and free resources */
}
/*
* RFC 3261, Section 16.3 step 5
* Proxy Behavior - Request Validation - Proxy-Require check
* (check Proxy-Require header and return 420 if unsupported option)
*/
/* NOT IMPLEMENTED */
/*
* RFC 3261, Section 16.5
* Proxy Behavior - Determining Request Targets
*/
/* NOT IMPLEMENTED */
DEBUGC(DBCLASS_SIP,"received SIP type %s:%s",
(MSG_IS_REQUEST(ticket.sipmsg))? "REQ" : "RES",
(MSG_IS_REQUEST(ticket.sipmsg) ?
((ticket.sipmsg->sip_method)?
ticket.sipmsg->sip_method : "NULL") :
((ticket.sipmsg->reason_phrase) ?
ticket.sipmsg->reason_phrase : "NULL")));
/*
* if an REQ REGISTER, check if it is directed to myself,
* or am I just the outbound proxy but no registrar.
* - If I'm the registrar, register & generate answer
* - If I'm just the outbound proxy, register, rewrite & forward
*/
if (MSG_IS_REGISTER(ticket.sipmsg) &&
MSG_IS_REQUEST(ticket.sipmsg)) {
if (access & ACCESSCTL_REG) {
osip_uri_t *url;
struct in_addr addr1, addr2, addr3;
int dest_port;
url = osip_message_get_uri(ticket.sipmsg);
dest_port= (url->port)?atoi(url->port):SIP_PORT;
if ( (get_ip_by_host(url->host, &addr1) == STS_SUCCESS) &&
(get_ip_by_ifname(configuration.inbound_if,&addr2) ==
STS_SUCCESS) &&
(get_ip_by_ifname(configuration.outbound_if,&addr3) ==
STS_SUCCESS)) {
if ((configuration.sip_listen_port == dest_port) &&
((memcmp(&addr1, &addr2, sizeof(addr1)) == 0) ||
(memcmp(&addr1, &addr3, sizeof(addr1)) == 0))) {
/* I'm the registrar, send response myself */
sts = register_client(&ticket, 0);
sts = register_response(&ticket, sts);
} else {
/* I'm just the outbound proxy */
DEBUGC(DBCLASS_SIP,"proxying REGISTER request to:%s",
url->host);
sts = register_client(&ticket, 1);
sts = proxy_request(&ticket);
}
} else {
if (MSG_IS_REQUEST(ticket.sipmsg)) {
sip_gen_response(&ticket, 408 /*request timeout*/);
}
}
} else {
WARN("non-authorized registration attempt from %s",
utils_inet_ntoa(ticket.from.sin_addr));
}
/*
* check if outbound interface is UP.
* If not, send back error to UA and
* skip any proxying attempt
*/
} else if (get_ip_by_ifname(configuration.outbound_if,NULL) !=
STS_SUCCESS) {
DEBUGC(DBCLASS_SIP, "got a %s to proxy, but outbound interface "
"is down", (MSG_IS_REQUEST(ticket.sipmsg))? "REQ" : "RES");
if (MSG_IS_REQUEST(ticket.sipmsg))
sip_gen_response(&ticket, 408 /*request timeout*/);
/*
* MSG is a request, add current via entry,
* do a lookup in the URLMAP table and
* send to the final destination
*/
} else if (MSG_IS_REQUEST(ticket.sipmsg)) {
if (access & ACCESSCTL_SIP) {
sts = proxy_request(&ticket);
} else {
INFO("non-authorized request received from %s",
utils_inet_ntoa(ticket.from.sin_addr));
}
/*
* MSG is a response, remove current via and
* send to the next VIA in chain
*/
} else if (MSG_IS_RESPONSE(ticket.sipmsg)) {
if (access & ACCESSCTL_SIP) {
sts = proxy_response(&ticket);
} else {
INFO("non-authorized response received from %s",
utils_inet_ntoa(ticket.from.sin_addr));
}
/*
* unsupported message
*/
} else {
ERROR("received unsupported SIP type %s %s",
(MSG_IS_REQUEST(ticket.sipmsg))? "REQ" : "RES",
ticket.sipmsg->sip_method);
}
/*
* free the SIP message buffers
*/
end_loop:
osip_message_free(ticket.sipmsg);
} /* while TRUE */
exit_prg:
/* dump current known SIP registrations */
register_shut();
INFO("properly terminating siproxd");
/* remove PID file */
if (pidfilename) {
DEBUGC(DBCLASS_CONFIG,"deleting PID file [%s]", pidfilename);
sts=unlink(pidfilename);
if (sts != 0) {
WARN("couldn't delete old PID file: %s", strerror(errno));
}
}
/* END */
return 0;
} /* main */
/*
* Signal handler
*
* this one is called asynchronously whevener a registered
* signal is applied. Just set a flag and don't do any funny
* things here.
*/
static void sighandler(int sig) {
if (sig==SIGTERM) exit_program=1;
if (sig==SIGINT) exit_program=1;
if (sig==SIGUSR2) dmalloc_dump=1;
return;
}