tor-android/tor-android-binary/src/main/jni/pdnsd/src/main.c

711 lines
19 KiB
C

/* main.c - Command line parsing, intialisation and server start
Copyright (C) 2000, 2001 Thomas Moestl
Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2010 Paul A. Rombouts
This file is part of the pdnsd package.
pdnsd 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 3 of the License, or
(at your option) any later version.
pdnsd 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 pdnsd; see the file COPYING. If not, see
<http://www.gnu.org/licenses/>.
*/
/* in order to use O_NOFOLLOW on Linux: */
/* #define _GNU_SOURCE */
#include <config.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <syslog.h>
#include <pwd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "consts.h"
#include "cache.h"
#include "status.h"
#include "servers.h"
#include "dns_answer.h"
#include "dns_query.h"
#include "error.h"
#include "helpers.h"
#include "icmp.h"
#include "hash.h"
#if DEBUG>0
short int debug_p=0;
#endif
short int stat_pipe=0;
/* int sigr=0; */
#if defined(ENABLE_IPV4) && defined(ENABLE_IPV6)
short int run_ipv4=DEFAULT_IPV4;
short int cmdlineipv=0;
#endif
cmdlineflags_t cmdline={0};
pthread_t main_thrid,servstat_thrid,statsock_thrid,tcps_thrid,udps_thrid;
uid_t init_uid;
#if DEBUG>0
FILE *dbg_file=NULL;
#endif
volatile int tcp_socket=-1;
volatile int udp_socket=-1;
sigset_t sigs_msk;
char *conf_file=CONFDIR"/pdnsd.conf";
/* version and licensing information */
static const char info_message[] =
"pdnsd - dns proxy daemon, version " VERSION "\n\n"
"Copyright (C) 2000, 2001 Thomas Moestl\n"
"Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2010 Paul A. Rombouts\n\n"
"pdnsd is free software; you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
"the Free Software Foundation; either version 3 of the License, or\n"
"(at your option) any later version.\n\n"
"pdnsd is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
"GNU General Public License for more details.\n\n"
"You should have received a copy of the GNU General Public License\n"
"along with pdsnd; see the file COPYING. If not, see\n"
"<http://www.gnu.org/licenses/>.\n";
/* the help page */
static const char help_message[] =
"\n\nUsage: pdnsd [-h] [-V] [-s] [-d] [-g] [-t] [-p file] [-vn] [-mxx] [-c file]"
#ifdef ENABLE_IPV4
" [-4]"
#endif
#ifdef ENABLE_IPV6
" [-6] [-i prefix]"
#endif
#if defined(ENABLE_IPV4) && defined(ENABLE_IPV6)
" [-a]"
#endif
"\n\n"
"Options:\n"
"-h\t\t--or--\n"
"--help\t\tprint this help page and exit.\n"
"-V\t\t--or--\n"
"--version\tprint version and license information and exit.\n"
"--pdnsd-user\tprint the user pdnsd will run as and exit.\n"
"-s\t\t--or--\n"
"--status\tEnable status control socket in the cache directory.\n"
"-d\t\t--or--\n"
"--daemon\tStart pdnsd in daemon mode (as background process.)\n"
"-g\t\t--or--\n"
"--debug\t\tPrint some debug messages on the console or to the\n"
"\t\tfile pdnsd.debug in your cache directory (in daemon mode).\n"
"-t\t\t--or--\n"
"--tcp\t\tEnables the TCP server thread. pdnsd will then serve\n"
"\t\tTCP and UDP queries.\n"
"-p\t\tWrites the pid the server runs as to a specified filename.\n"
"\t\tWorks only in daemon mode.\n"
"-vn\t\tsets the verbosity of pdnsd. n is a numeric argument from 0\n"
"\t\t(normal operation) to 9 (many messages for debugging).\n"
"\t\tUse like -v2\n"
"-mxx\t\tsets the query method pdnsd uses. Possible values for xx are:\n"
"\t\tuo (UDP only), to (TCP only), tu (TCP or, if the server\n"
"\t\tdoes not support this, UDP) and ut (UDP and, if the reply was\n"
"\t\ttruncated, TCP). Use like -muo. Preset: "
#if M_PRESET==UDP_ONLY
"-muo"
#elif M_PRESET==TCP_ONLY
"-mto"
#elif M_PRESET==TCP_UDP
"-mtu"
#else
"-mut"
#endif
"\n"
"-c\t\t--or--\n"
"--config-file\tspecifies the file the configuration is read from.\n"
"\t\tDefault is " CONFDIR "/pdnsd.conf\n"
#ifdef ENABLE_IPV4
"-4\t\tswitches to IPv4 mode.\n"
"\t\t"
# if DEFAULT_IPV4
"On"
# else
"Off"
# endif
" by default.\n"
#endif
#ifdef ENABLE_IPV6
"-6\t\tswitches to IPv6 mode.\n"
"\t\t"
# if DEFAULT_IPV4
"Off"
# else
"On"
# endif
" by default.\n"
"-i\t\t--or--\n"
"--ipv4_6_prefix\tspecifies the prefix pdnsd uses to map IPv4 to IPv6\n"
"\t\taddresses. Must be a valid IPv6 address.\n"
"\t\tDefault is " DEFAULT_IPV4_6_PREFIX "\n"
#endif
#if defined(ENABLE_IPV4) && defined(ENABLE_IPV6)
"-a\t\tWith this option, pdnsd will try to detect automatically if\n"
"\t\tthe system supports IPv6, and revert to IPv4 otherwise.\n"
#endif
"\n\n\"no\" can be prepended to the --status, --daemon, --debug and --tcp\n"
"options (e.g. --notcp) to reverse their effect.\n";
/* These are some init steps we have to call before we get daemon on linux, but need
* to call after daemonizing on other OSes.
* Theay are also the last steps before we drop privileges. */
int final_init()
{
#ifndef NO_TCP_SERVER
if (!global.notcp)
tcp_socket=init_tcp_socket();
#endif
udp_socket=init_udp_socket();
if (tcp_socket==-1 && udp_socket==-1) {
log_error("tcp and udp initialization failed. Exiting.");
return 0;
}
if (global.strict_suid) {
if (!run_as(global.run_as)) {
return 0;
}
}
return 1;
}
#if defined(ENABLE_IPV4) && defined(ENABLE_IPV6)
/* Check if IPv6 is available.
* With thanks to Juliusz Chroboczek.
*/
static int check_ipv6()
{
int fd;
fd = socket(PF_INET6, SOCK_STREAM, 0);
if(fd < 0) {
if(errno == EPROTONOSUPPORT || errno == EAFNOSUPPORT || errno == EINVAL)
return 0;
return -1;
}
close(fd);
return 1;
}
#endif
/*
* Argument parsing, init, server startup
*/
int main(int argc,char *argv[])
{
int i,sig,pfd=-1; /* Initialized to inhibit compiler warning */
main_thrid=pthread_self();
servstat_thrid=main_thrid;
statsock_thrid=main_thrid;
tcps_thrid=main_thrid;
udps_thrid=main_thrid;
init_uid=getuid();
#ifdef ENABLE_IPV6
{
int err;
if((err=inet_pton(AF_INET6,DEFAULT_IPV4_6_PREFIX,&global.ipv4_6_prefix))<=0) {
fprintf(stderr,"Error: inet_pton() wont accept default prefix %s in %s, line %d\n",
DEFAULT_IPV4_6_PREFIX,__FILE__,__LINE__);
if(err)
perror("inet_pton");
exit(1);
}
}
#endif
/* Parse the command line.
Remember which options were specified here, because the command-line options
shall override the ones given in the config file */
for (i=1;i<argc;i++) {
char *arg=argv[i];
if (strcmp(arg,"-h")==0 || strcmp(arg,"--help")==0) {
fputs(info_message,stdout);
fputs(help_message,stdout);
exit(1);
} else if (strcmp(arg,"-V")==0 || strcmp(arg,"--version")==0) {
fputs(info_message,stdout);
exit(1);
} else if (strcmp(arg,"-c")==0 || strcmp(arg,"--config-file")==0) {
if (++i<argc) {
conf_file=argv[i];
} else {
fprintf(stderr,"Error: file name expected after %s option.\n",arg);
exit(1);
}
} else if (strcmp(arg,"-4")==0) {
#ifdef ENABLE_IPV4
# ifdef ENABLE_IPV6
run_ipv4=1; cmdlineipv=1;
# endif
#else
fprintf(stderr,"Error: -4: pdnsd was compiled without IPv4 support.\n");
exit(1);
#endif
} else if (strcmp(arg,"-6")==0) {
#ifdef ENABLE_IPV6
# ifdef ENABLE_IPV4
run_ipv4=0; cmdlineipv=1;
# endif
#else
fprintf(stderr,"Error: -6: pdnsd was compiled without IPv6 support.\n");
exit(1);
#endif
} else if (strcmp(arg,"-a")==0) {
#if defined(ENABLE_IPV4) && defined(ENABLE_IPV6)
int rv=check_ipv6();
if(rv<0) {
fprintf(stderr,"Error: -a: can't check availability of IPv6: %s\n"
"Try using -4 or -6 option instead.\n",strerror(errno));
exit(1);
}
if((run_ipv4= !rv))
fprintf(stderr,"Switching to IPv4 mode.\n");
cmdlineipv=1;
#else
fprintf(stderr,"Warning: -a option does nothing unless pdnsd is compiled with both IPv4 AND IPv6 support.\n");
#endif
} else if(strcmp(arg,"-i")==0 || strcmp(arg,"--ipv4_6_prefix")==0) {
if (++i<argc) {
#ifdef ENABLE_IPV6
if(inet_pton(AF_INET6,argv[i],&global.ipv4_6_prefix)<=0) {
fprintf(stderr,"Error: %s: argument not a valid IPv6 address.\n",arg);
exit(1);
}
cmdline.prefix=1;
#else
fprintf(stderr,"pdnsd was compiled without IPv6 support. %s will be ignored.\n",arg);
#endif
} else {
fprintf(stderr,"Error: IPv6 address expected after %s option.\n",arg);
exit(1);
}
} else if (strcmp(arg,"-s")==0 || strcmp(arg,"--status")==0) {
global.stat_pipe=1; cmdline.stat_pipe=1;
} else if (strcmp(arg,"--nostatus")==0) {
global.stat_pipe=0; cmdline.stat_pipe=1;
} else if (strcmp(arg,"-d")==0 || strcmp(arg,"--daemon")==0) {
global.daemon=1; cmdline.daemon=1;
} else if (strcmp(arg,"--nodaemon")==0) {
global.daemon=0; cmdline.daemon=1;
} else if (strcmp(arg,"-t")==0 || strcmp(arg,"--tcp")==0) {
global.notcp=0; cmdline.notcp=1;
#ifdef NO_TCP_SERVER
fprintf(stderr,"pdnsd was compiled without tcp server support. -t has no effect.\n");
#endif
} else if (strcmp(arg,"--notcp")==0) {
global.notcp=1; cmdline.notcp=1;
} else if (strcmp(arg,"-p")==0) {
if (++i<argc) {
global.pidfile=argv[i]; cmdline.pidfile=1;
} else {
fprintf(stderr,"Error: file name expected after -p option.\n");
exit(1);
}
} else if (strncmp(arg,"-v",2)==0) {
if (strlen(arg)!=3 || !isdigit(arg[2])) {
fprintf(stderr,"Error: one digit expected after -v option (like -v2).\n");
exit(1);
}
global.verbosity=arg[2]-'0'; cmdline.verbosity=1;
} else if (strncmp(arg,"-m",2)==0) {
if (strlen(arg)!=4) {
fprintf(stderr,"Error: uo, to or tu expected after the -m option (like -muo).\n");
exit(1);
}
if (strcmp(&arg[2],"uo")==0) {
#ifdef NO_UDP_QUERIES
fprintf(stderr,"Error: pdnsd was compiled without UDP support.\n");
exit(1);
#else
global.query_method=UDP_ONLY;
#endif
} else if (strcmp(&arg[2],"to")==0) {
#ifdef NO_TCP_QUERIES
fprintf(stderr,"Error: pdnsd was compiled without TCP support.\n");
exit(1);
#else
global.query_method=TCP_ONLY;
#endif
} else if (strcmp(&arg[2],"tu")==0) {
#if defined(NO_UDP_QUERIES) || defined(NO_TCP_QUERIES)
fprintf(stderr,"Error: pdnsd was not compiled with UDP and TCP support.\n");
exit(1);
#else
global.query_method=TCP_UDP;
#endif
} else if (strcmp(&arg[2],"ut")==0) {
#if defined(NO_UDP_QUERIES) || defined(NO_TCP_QUERIES)
fprintf(stderr,"Error: pdnsd was not compiled with UDP and TCP support.\n");
exit(1);
#else
global.query_method=UDP_TCP;
#endif
} else {
fprintf(stderr,"Error: uo, to, tu or ut expected after the -m option (like -muo).\n");
exit(1);
}
cmdline.query_method=1;
} else if (strcmp(arg,"-g")==0 || strcmp(arg,"--debug")==0) {
global.debug=1; cmdline.debug=1;
#if !DEBUG
fprintf(stderr,"pdnsd was compiled without debugging support. -g has no effect.\n");
#endif
} else if (strcmp(arg,"--nodebug")==0) {
global.debug=0; cmdline.debug=1;
} else if (strcmp(arg,"--pdnsd-user")==0) {
cmdline.pdnsduser=1;
} else {
char *equ=strchr(arg,'=');
if(equ) {
int plen=equ-arg;
char *valstr=equ+1;
# define arg_isparam(strlit) (!strncmp(arg,strlit,strlitlen(strlit)) && plen==strlitlen(strlit))
if(arg_isparam("--config-file")) {
conf_file=valstr;
}
else if(arg_isparam("--ipv4_6_prefix")) {
#ifdef ENABLE_IPV6
if(inet_pton(AF_INET6,valstr,&global.ipv4_6_prefix)<=0) {
fprintf(stderr,"Error: --ipv4_6_prefix: argument not a valid IPv6 address.\n");
exit(1);
}
cmdline.prefix=1;
#else
fprintf(stderr,"pdnsd was compiled without IPv6 support. --ipv4_6_prefix will be ignored.\n");
#endif
}
else {
fprintf(stderr,"Error: unknown option: %.*s\n",plen,arg);
exit(1);
}
} else {
fprintf(stderr,"Error: unknown option: %s\n",arg);
exit(1);
}
}
}
init_cache();
{
char *errmsg;
if(!read_config_file(conf_file,&global,&servers,0,&errmsg)) {
fputs(errmsg?:"Out of memory.",stderr);
fputc('\n',stderr);
exit(3);
}
}
if(cmdline.pdnsduser) {
if (global.run_as[0]) {
printf("%s\n",global.run_as);
} else {
uid_t uid=getuid();
struct passwd *pws=getpwuid(uid);
if (pws)
printf("%s\n",pws->pw_name);
else
printf("%i\n",uid);
}
exit(0);
}
if(!global.cache_dir) global.cache_dir = CACHEDIR;
if(!global.scheme_file) global.scheme_file = "/var/lib/pcmcia/scheme";
stat_pipe=global.stat_pipe;
if (!(global.run_as[0] && global.strict_suid)) {
for (i=0; i<DA_NEL(servers); i++) {
servparm_t *sp=&DA_INDEX(servers,i);
if (sp->uptest==C_EXEC && sp->uptest_usr[0]=='\0') {
uid_t uid=getuid();
struct passwd *pws=getpwuid(uid);
/* No explicit uptest user given. If we run_as and strict_suid, we assume that
* this is safe. If not - warn. */
fprintf(stderr,"Warning: uptest command \"%s\" will implicitly be executed as user ", sp->uptest_cmd);
if (pws)
fprintf(stderr,"%s\n",pws->pw_name);
else
fprintf(stderr,"%i\n",uid);
}
}
}
if (global.daemon && global.pidfile) {
if (unlink(global.pidfile)!=0 && errno!=ENOENT) {
log_error("Error: could not unlink pid file %s: %s",global.pidfile, strerror(errno));
exit(1);
}
if ((pfd=open(global.pidfile,O_WRONLY|O_CREAT|O_EXCL
#ifdef O_NOFOLLOW
|O_NOFOLLOW
#else
/*
* No O_NOFOLLOW. Nevertheless, this not a hole, since the
* directory for pidfiles should not be world writeable.
* OS's that do not support O_NOFOLLOW are currently not
* supported, this is just-in-case code.
*/
#endif
, 0600))==-1)
{
log_error("Error: could not open pid file %s: %s",global.pidfile, strerror(errno));
exit(1);
}
}
for (i=0;i<DA_NEL(servers);i++) {
if (DA_INDEX(servers,i).uptest==C_PING) {
init_ping_socket();
break;
}
}
if (!init_rng())
exit(1);
#if (TARGET==TARGET_LINUX)
if (!final_init())
exit(1);
#endif
{
struct sigaction action;
action.sa_handler = SIG_IGN;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
if(sigaction(SIGPIPE, &action, NULL) != 0)
log_error("Could not call sigaction to ignore SIGPIPE: %s",strerror(errno));
}
umask(0077); /* for security reasons */
if (global.daemon) {
pid_t pid;
int fd;
/* become a daemon */
pid=fork();
if (pid==-1) {
log_error("Could not become a daemon: fork #1 failed: %s",strerror(errno));
exit(1);
}
if (pid!=0) {
/* This is the parent.
The child is going to do another fork() and will exit quickly.
Perhaps we should wait for the child and return
its exit status? */
exit(0); /* exit parent */
}
/* dissociate from controlling terminal */
if (setsid()==-1) {
log_error("Could not become a daemon: setsid failed: %s",strerror(errno));
_exit(1);
}
pid=fork();
if (pid==-1) {
log_error("Could not become a daemon: fork #2 failed: %s",strerror(errno));
_exit(1);
}
if (pid!=0) {
int exitval=0;
if (global.pidfile) {
if(fsprintf(pfd,"%i\n",(int)pid)<0) {
log_error("Error: could not write to pid file %s: %s",
global.pidfile, strerror(errno));
exitval=1;
}
if(close(pfd)<0) {
log_error("Error: could not close pid file %s: %s",
global.pidfile, strerror(errno));
exitval=1;
}
}
_exit(exitval); /* exit parent, so we are no session group leader */
}
if (global.pidfile) close(pfd);
if(chdir("/"))
log_warn("Cannot chdir to root directory: %s",strerror(errno));
if ((fd=open("/dev/null",O_RDONLY))==-1) {
log_error("Could not become a daemon: open for /dev/null failed: %s",strerror(errno));
_exit(1);
}
dup2(fd,0);
close(fd);
if ((fd=open("/dev/null",O_WRONLY))==-1) {
log_error("Could not become a daemon: open for /dev/null failed: %s",strerror(errno));
_exit(1);
}
dup2(fd,1);
dup2(fd,2);
close(fd);
#if DEBUG>0
if (global.debug) {
char dbgpath[strlen(global.cache_dir)+sizeof("/pdnsd.debug")];
stpcpy(stpcpy(dbgpath,global.cache_dir),"/pdnsd.debug");
if (!(dbg_file=fopen(dbgpath,"w")))
log_warn("Warning: could not open debug file %s: %s",dbgpath, strerror(errno));
}
#endif
} else {
#if DEBUG>0
dbg_file=stdout;
#endif
}
#if DEBUG>0
debug_p= (global.debug && dbg_file);
#endif
log_info(0,"pdnsd-%s starting.\n",VERSION);
DEBUG_MSG("Debug messages activated\n");
#if (TARGET!=TARGET_LINUX)
if (!final_init())
_exit(1);
#endif
DEBUG_MSG(SEL_IPVER("Using IPv4.\n", "Using IPv6.\n"));
/* initialize attribute for creating detached threads */
pthread_attr_init(&attr_detached);
pthread_attr_setdetachstate(&attr_detached,PTHREAD_CREATE_DETACHED);
read_disk_cache();
/* This must be done before any other thread is started to avoid races. */
if (stat_pipe)
init_stat_sock();
/* Before this point, logging and cache accesses are not locked because we are single-threaded. */
init_log_lock();
init_cache_lock();
sigemptyset(&sigs_msk);
sigaddset(&sigs_msk,SIGHUP);
sigaddset(&sigs_msk,SIGINT);
#ifndef THREADLIB_NPTL
sigaddset(&sigs_msk,SIGILL);
#endif
sigaddset(&sigs_msk,SIGABRT);
sigaddset(&sigs_msk,SIGFPE);
#ifndef THREADLIB_NPTL
sigaddset(&sigs_msk,SIGSEGV);
#endif
sigaddset(&sigs_msk,SIGTERM);
/* if (!daemon_p) {
sigaddset(&sigs_msk,SIGQUIT);
} */
#if (TARGET==TARGET_LINUX)
pthread_sigmask(SIG_BLOCK,&sigs_msk,NULL);
#endif
#if DEBUG>0
{
int err;
/* Generate a key for storing our thread id's */
if ((err=pthread_key_create(&thrid_key, NULL)) != 0) {
log_error("pthread_key_create failed: %s",strerror(err));
_exit(1);
}
}
#endif
{
#if DEBUG>0
int thrdsucc=1;
# define thrdfail (thrdsucc=0)
#else
# define thrdfail
#endif
if(start_servstat_thread()) thrdfail;
#if (TARGET==TARGET_LINUX)
if (!global.strict_suid) {
if (!run_as(global.run_as)) {
_exit(1);
}
}
#endif
if (stat_pipe)
if(start_stat_sock()) thrdfail;
start_dns_servers();
#if DEBUG>0
if(thrdsucc) {
DEBUG_MSG("All threads started successfully.\n");
}
#endif
#undef thrdfail
}
#if (TARGET==TARGET_LINUX) && !defined(THREADLIB_NPTL)
pthread_sigmask(SIG_BLOCK,&sigs_msk,NULL);
waiting=1;
#endif
{
int err;
while ((err=sigwait(&sigs_msk,&sig))) {
if (err!=EINTR) {
log_error("sigwait failed: %s",strerror(err));
sig=0;
break;
}
}
}
if(sig) DEBUG_MSG("Signal %i caught.\n",sig);
write_disk_cache();
destroy_cache();
if(sig) log_warn("Caught signal %i. Exiting.",sig);
if (sig==SIGSEGV || sig==SIGILL || sig==SIGBUS)
crash_msg("This is a fatal signal probably triggered by a bug.");
if (ping_isocket!=-1)
close(ping_isocket);
#ifdef ENABLE_IPV6
if (ping6_isocket!=-1)
close(ping6_isocket);
#endif
/* Close and delete the status socket */
if(stat_pipe) close(stat_sock);
if (sock_path && unlink(sock_path))
log_warn("Failed to unlink %s: %s",sock_path, strerror(errno));
free_rng();
#if DEBUG>0
if (debug_p && global.daemon)
if(fclose(dbg_file)<0) {
log_warn("Could not close debug file: %s", strerror(errno));
}
#endif
_exit(0);
}