/* * Refresh the server status by pinging or testing the interface in the given interval. * Note that you may get inaccuracies in the dimension of the ping timeout or the runtime * of your uptest command if you have uptest=ping or uptest=exec for at least one server. * This happens when all the uptests for the first n servers take more time than the inteval * of n+1 (or 0 when n+1>servnum). I do not think that these delays are critical, so I did * not to anything about that (because that may also be costly). */ void *servstat_thread(void *p) { struct sigaction action; int keep_testing; /* (void)p; */ /* To inhibit "unused variable" warning */ THREAD_SIGINIT; pthread_mutex_lock(&servers_lock); /* servstat_thrid=pthread_self(); */ signal_interrupt=0; action.sa_handler = sigint_handler; sigemptyset(&action.sa_mask); action.sa_flags = 0; if(sigaction(statusintsig, &action, NULL) == 0) { sigset_t smask; sigemptyset(&smask); sigaddset(&smask, statusintsig); pthread_sigmask(SIG_UNBLOCK,&smask,NULL); } else { log_warn("Cannot install signal handler for server status thread: %s\n",strerror(errno)); } for(;;) { do { int i,n; keep_testing=0; retest_flag=0; schm[0] = '\0'; n=DA_NEL(servers); for (i=0;i<n;++i) { servparm_t *sp=&DA_INDEX(servers,i); int j,m; if(sp->rootserver==2) { /* First get addresses of root servers. */ addr2_array adrs; int l, one_up=0; if(!scheme_ok(sp)) { time_t now=time(NULL); m=DA_NEL(sp->atup_a); for(j=0;j<m;++j) DA_INDEX(sp->atup_a,j).i_ts=now; } else if(sp->uptest==C_PING || sp->uptest==C_QUERY) { /* Skip ping or query tests until after discovery. */ if(sp->interval>0) one_up= DA_NEL(sp->atup_a); else { time_t now=time(NULL); m=DA_NEL(sp->atup_a); for(j=0;j<m;++j) { atup_t *at=&DA_INDEX(sp->atup_a,j); if(at->is_up || at->i_ts==0) one_up=1; at->i_ts=now; } } } else { retest(i,-1); m=DA_NEL(sp->atup_a); for(j=0;j<m;++j) { if(DA_INDEX(sp->atup_a,j).is_up) { one_up=1; break; } } } if(!one_up) { if (needs_intermittent_testing(sp)) keep_testing=1; continue; } DEBUG_MSG("Attempting to discover root servers for server section #%d.\n",i); adrs=resolv_rootserver_addrs(sp->atup_a,sp->port,sp->timeout); l= DA_NEL(adrs); if(l>0) { struct timeval now; struct timespec timeout; atup_array ata; DEBUG_MSG("Filling server section #%d with %d root server addresses.\n",i,l); gettimeofday(&now,NULL); timeout.tv_sec = now.tv_sec + 60; /* time out after 60 seconds */ timeout.tv_nsec = now.tv_usec * 1000; while (server_data_users>0) { if(pthread_cond_timedwait(&server_data_cond, &servers_lock, &timeout) == ETIMEDOUT) { DEBUG_MSG("Timed out while waiting for exclusive access to server data" " to set root server addresses of server section #%d\n",i); da_free(adrs); keep_testing=1; continue; } } ata = DA_CREATE(atup_array, l); if(!ata) { log_warn("Out of memory in servstat_thread() while discovering root servers."); da_free(adrs); keep_testing=1; continue; } for(j=0; j<l; ++j) { atup_t *at = &DA_INDEX(ata,j); at->a = DA_INDEX(adrs,j); at->is_up=sp->preset; at->i_ts= sp->interval<0 ? time(NULL): 0; } da_free(sp->atup_a); sp->atup_a=ata; da_free(adrs); /* Successfully set IP addresses for this server section. */ sp->rootserver=1; } else { DEBUG_MSG("Failed to discover root servers in servstat_thread() (server section #%d).\n",i); if(adrs) da_free(adrs); if(DA_NEL(sp->atup_a)) keep_testing=1; continue; } } if (needs_testing(sp)) keep_testing=1; m=DA_NEL(sp->atup_a); for(j=0;j<m;++j) if(DA_INDEX(sp->atup_a,j).i_ts) goto individual_tests; /* Test collectively */ if(!signal_interrupt) retest(i,-1); continue; individual_tests: for(j=0; !signal_interrupt && j<m; ++j) { time_t ts=DA_INDEX(sp->atup_a,j).i_ts, now; if (ts==0 /* Always test servers with timestamp 0 */ || (needs_intermittent_testing(sp) && ((now=time(NULL))-ts>sp->interval || ts>now /* kluge for clock skew */))) { retest(i,j); } } } } while(!signal_interrupt && retest_flag); signal_interrupt=0; /* Break the loop and exit the thread if it is no longer needed. */ if(!keep_testing) break; { struct timeval now; struct timespec timeout; time_t minwait; int i,n,retval; gettimeofday(&now,NULL); minwait=3600; /* Check at least once every hour. */ n=DA_NEL(servers); for (i=0;i<n;++i) { servparm_t *sp=&DA_INDEX(servers,i); int j,m=DA_NEL(sp->atup_a); for(j=0;j<m;++j) { time_t ts= DA_INDEX(sp->atup_a,j).i_ts; if(ts==0) { /* Test servers with timestamp 0 without delay */ if(minwait > 0) minwait=0; } else if(needs_intermittent_testing(sp)) { time_t wait= ts + sp->interval - now.tv_sec; if(wait < minwait) minwait=wait; } } } timeout.tv_sec = now.tv_sec; if(minwait>0) timeout.tv_sec += minwait; timeout.tv_nsec = now.tv_usec * 1000 + 500000000; /* wait at least half a second. */ if(timeout.tv_nsec>=1000000000) { timeout.tv_nsec -= 1000000000; ++timeout.tv_sec; } /* While we wait for a server_test_cond condition or a timeout the servers_lock mutex is unlocked, so other threads can access server data */ retval=pthread_cond_timedwait(&server_test_cond, &servers_lock, &timeout); DEBUG_MSG("Server status thread woke up (%s signal).\n", retval==0?"test condition":retval==ETIMEDOUT?"timer":retval==EINTR?"interrupt":"error"); } } /* server status thread no longer needed. */ servstat_thrid=main_thrid; pthread_mutex_unlock(&servers_lock); DEBUG_MSG("Server status thread exiting.\n"); return NULL; }
/* * Execute an individual uptest. Call with locks applied */ static int uptest (servparm_t *serv, int j) { int ret=0, count_running_ping=0; pdnsd_a *s_addr= PDNSD_A2_TO_A(&DA_INDEX(serv->atup_a,j).a); DEBUG_PDNSDA_MSG("performing uptest (type=%s) for %s\n",const_name(serv->uptest),PDNSDA2STR(s_addr)); /* Unlock the mutex because some of the tests may take a while. */ ++server_data_users; if((serv->uptest==C_PING || serv->uptest==C_QUERY) && pthread_equal(pthread_self(),servstat_thrid)) { /* Inform other threads that a ping is in progress. */ count_running_ping=1; ++server_status_ping; } pthread_mutex_unlock(&servers_lock); switch (serv->uptest) { case C_NONE: /* Don't change */ ret=DA_INDEX(serv->atup_a,j).is_up; break; case C_PING: ret=ping(is_inaddr_any(&serv->ping_a) ? s_addr : &serv->ping_a, serv->ping_timeout,PINGREPEAT)!=-1; break; case C_IF: case C_DEV: case C_DIALD: ret=if_up(serv->interface); #if (TARGET==TARGET_LINUX) if (ret!=0) { if(serv->uptest==C_DEV) ret=dev_up(serv->interface,serv->device); else if (serv->uptest==C_DIALD) ret=dev_up("diald",serv->device); } #endif break; case C_EXEC: { pid_t pid; if ((pid=fork())==-1) { DEBUG_MSG("Could not fork to perform exec uptest: %s\n",strerror(errno)); break; } else if (pid==0) { /* child */ /* * If we ran as setuid or setgid, do not inherit this to the * command. This is just a last guard. Running pdnsd as setuid() * or setgid() is a no-no. */ if (setgid(getgid()) == -1 || setuid(getuid()) == -1) { log_error("Could not reset uid or gid: %s",strerror(errno)); _exit(1); } /* Try to setuid() to a different user as specified. Good when you don't want the test command to run as root */ if (!run_as(serv->uptest_usr)) { _exit(1); } { struct rlimit rl; int i; /* * Mark all open fd's FD_CLOEXEC for paranoia reasons. */ if (getrlimit(RLIMIT_NOFILE, &rl) == -1) { log_error("getrlimit() failed: %s",strerror(errno)); _exit(1); } for (i = 0; i < rl.rlim_max; i++) { if (fcntl(i, F_SETFD, FD_CLOEXEC) == -1 && errno != EBADF) { log_error("fcntl(F_SETFD) failed: %s",strerror(errno)); _exit(1); } } } execl("/bin/sh", "uptest_sh","-c",serv->uptest_cmd,(char *)NULL); _exit(1); /* failed execl */ } else { /* parent */ int status; pid_t wpid = waitpid(pid,&status,0); if (wpid==pid) { if(WIFEXITED(status)) { int exitstatus=WEXITSTATUS(status); DEBUG_MSG("uptest command \"%s\" exited with status %d\n", serv->uptest_cmd, exitstatus); ret=(exitstatus==0); } #if DEBUG>0 else if(WIFSIGNALED(status)) { DEBUG_MSG("uptest command \"%s\" was terminated by signal %d\n", serv->uptest_cmd, WTERMSIG(status)); } else { DEBUG_MSG("status of uptest command \"%s\" is of unkown type (0x%x)\n", serv->uptest_cmd, status); } #endif } #if DEBUG>0 else if (wpid==-1) { DEBUG_MSG("Error while waiting for uptest command \"%s\" to terminate: " "waitpid for pid %d failed: %s\n", serv->uptest_cmd, pid, strerror(errno)); } else { DEBUG_MSG("Error while waiting for uptest command \"%s\" to terminate: " "waitpid returned %d, expected pid %d\n", serv->uptest_cmd, wpid, pid); } #endif } } break; case C_QUERY: ret=query_uptest(s_addr, serv->port, serv->timeout>=global.timeout?serv->timeout:global.timeout, PINGREPEAT); } /* end of switch */ pthread_mutex_lock(&servers_lock); if(count_running_ping) --server_status_ping; PDNSD_ASSERT(server_data_users>0, "server_data_users non-positive before attempt to decrement it"); if (--server_data_users==0) pthread_cond_broadcast(&server_data_cond); DEBUG_PDNSDA_MSG("result of uptest for %s: %s\n", PDNSDA2STR(s_addr), ret?"OK":"failed"); return ret; }
/* * Re-Read the configuration file. * Return 1 on success, 0 on failure. * In case of failure, the old configuration will be unchanged (although the cache may not) and * **errstr will refer to a newly allocated string containing an error message. */ int reload_config_file(const char *nm, char **errstr) { globparm_t global_new; servparm_array servers_new; global_new=global; global_new.cache_dir=NULL; global_new.pidfile=NULL; global_new.scheme_file=NULL; global_new.deleg_only_zones=NULL; global_new.onquery=0; servers_new=NULL; if(read_config_file(nm,&global_new,&servers_new,0,errstr)) { if(global_new.cache_dir && strcmp(global_new.cache_dir,global.cache_dir)) { *errstr=strdup("Cannot reload config file: the specified cache_dir directory has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } if(global_new.pidfile && (!global.pidfile || strcmp(global_new.pidfile,global.pidfile))) { *errstr=strdup("Cannot reload config file: the specified pid_file has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } if(global_new.scheme_file && strcmp(global_new.scheme_file,global.scheme_file)) { *errstr=strdup("Cannot reload config file: the specified scheme_file has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } if(global_new.port!=global.port) { *errstr=strdup("Cannot reload config file: the specified server_port has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } if(!ADDR_EQUIV(&global_new.a,&global.a)) { *errstr=strdup("Cannot reload config file: the specified interface address (server_ip) has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } #ifdef ENABLE_IPV6 if(!IN6_ARE_ADDR_EQUAL(&global_new.ipv4_6_prefix,&global.ipv4_6_prefix)) { *errstr=strdup("Cannot reload config file: the specified ipv4_6_prefix has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } #endif if(strcmp(global_new.run_as,global.run_as)) { *errstr=strdup("Cannot reload config file: the specified run_as id has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } if(global_new.daemon!=global.daemon) { *errstr=strdup("Cannot reload config file: the daemon option has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } if(global_new.debug!=global.debug) { *errstr=strdup("Cannot reload config file: the debug option has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } if(global_new.stat_pipe!=global.stat_pipe) { *errstr=strdup("Cannot reload config file: the status_ctl option has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } if(global_new.notcp!=global.notcp) { *errstr=strdup("Cannot reload config file: the tcp_server option has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } if(global_new.strict_suid!=global.strict_suid) { *errstr=strdup("Cannot reload config file: the strict_setuid option has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } if(global_new.ctl_perms!=global.ctl_perms) { *errstr=strdup("Cannot reload config file: the specified ctl_perms has changed.\n" "Try restarting pdnsd instead."); goto cleanup_return; } if(ping_isocket==-1 #ifdef ENABLE_IPV6 && ping6_isocket==-1 #endif ) { int i,n=DA_NEL(servers_new); for (i=0;i<n;++i) { if (DA_INDEX(servers_new,i).uptest==C_PING) { if(asprintf(errstr,"Cannot reload config file: the ping socket is not initialized" " and the new config contains uptest=ping in server section %i.\n" "Try restarting pdnsd instead.",i)<0) *errstr=NULL; goto cleanup_return; } } } /* we need exclusive access to the server data to make the changes */ /* Wait at most 60 seconds to obtain a lock. */ if(!exclusive_lock_server_data(60)) { *errstr=strdup("Cannot reload config file: Timed out while waiting for access to config data."); goto cleanup_return; } free(global_new.cache_dir); global_new.cache_dir=global.cache_dir; free(global_new.pidfile); global_new.pidfile=global.pidfile; free(global_new.scheme_file); global_new.scheme_file=global.scheme_file; free_zones(global.deleg_only_zones); global=global_new; free_server_data(servers); servers=servers_new; /* schedule a retest to check which servers are up, and free the lock. */ exclusive_unlock_server_data(1); return 1; } cleanup_return: free(global_new.cache_dir); free(global_new.pidfile); free(global_new.scheme_file); free_zones(global_new.deleg_only_zones); free_server_data(servers_new); return 0; }