/* status.c - Allow control of a running server using a socket
Copyright (C) 2000, 2001 Thomas Moestl
Copyright (C) 2002, 2003, 2004, 2005, 2007, 2008, 2009, 2011 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
.
*/
#include
#include
#include
#ifdef HAVE_ALLOCA_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* for offsetof */
#include "ipvers.h"
#include "status.h"
#include "thread.h"
#include "cache.h"
#include "error.h"
#include "servers.h"
#include "dns_answer.h"
#include "helpers.h"
#include "conf-parser.h"
#if !defined(HAVE_ALLOCA) && !defined(alloca)
#define alloca malloc
#endif
char *sock_path=NULL;
int stat_sock;
/* Print an error to the socket */
static int print_serr(int rs, const char *msg)
{
uint16_t cmd;
DEBUG_MSG("Sending error message to control socket: '%s'\n",msg);
cmd=htons(1);
if(write(rs,&cmd,sizeof(cmd))!=sizeof(cmd) ||
write_all(rs,msg,strlen(msg))<0)
{
DEBUG_MSG("Error writing to control socket: %s\n",strerror(errno));
return 0;
}
return 1;
}
/* Print a success code to the socket */
static int print_succ(int rs)
{
uint16_t cmd;
cmd=htons(0);
if(write(rs,&cmd,sizeof(cmd))!=sizeof(cmd)) {
DEBUG_MSG("Error writing to control socket: %s\n"
"Failed to send success code.\n",strerror(errno));
return 0;
}
return 1;
}
/* Read a cmd short */
static int read_short(int fh, uint16_t *res)
{
uint16_t cmd;
if (read(fh,&cmd,sizeof(cmd))!=sizeof(cmd)) {
/* print_serr(fh,"Bad arg."); */
return 0;
}
*res= ntohs(cmd);
return 1;
}
/* Read a cmd long */
static int read_long(int fh, uint32_t *res)
{
uint32_t cmd;
if (read(fh,&cmd,sizeof(cmd))!=sizeof(cmd)) {
/* print_serr(fh,"Bad arg."); */
return 0;
}
*res= ntohl(cmd);
return 1;
}
/* Read a string preceded by a char count.
A buffer of the right size is allocated to hold the result.
A return value of 1 means success,
-1 means the result is undefined (*res is set to NULL),
0 means read or allocation error.
*/
static int read_allocstring(int fh, char **res, unsigned *len)
{
uint16_t count;
char *buf;
unsigned int nread;
if(!read_short(fh,&count)) return 0;
if(count==(uint16_t)(~0)) {*res=NULL; return -1;}
if(!(buf=malloc(count+1))) return 0;
nread=0;
while(nread=buflen) return 0;
nread=0;
while(nread=buflen) return 0;
buf[count]='.'; buf[count+1]=0;
}
#endif
return 1;
}
static void *status_thread (void *p)
{
THREAD_SIGINIT;
/* (void)p; */ /* To inhibit "unused variable" warning */
if (!global.strict_suid) {
if (!run_as(global.run_as)) {
pdnsd_exit();
}
}
if (listen(stat_sock,5)==-1) {
log_warn("Error: could not listen on socket: %s.\nStatus readback will be impossible",strerror(errno));
goto exit_thread;
}
for(;;) {
struct sockaddr_un ra;
socklen_t res=sizeof(ra);
int rs;
if ((rs=accept(stat_sock,(struct sockaddr *)&ra,&res))!=-1) {
uint16_t cmd;
DEBUG_MSG("Status socket query pending.\n");
if (read_short(rs,&cmd)) {
/* Check magic number in command */
if((cmd & 0xff00) == CTL_CMDVERNR) {
const char *errmsg;
cmd &= 0xff;
switch(cmd) {
case CTL_STATS: {
struct utsname nm;
DEBUG_MSG("Received STATUS query.\n");
if(!print_succ(rs))
break;
uname(&nm);
if(fsprintf(rs,"pdnsd-%s running on %s.\n",VERSION,nm.nodename)<0 ||
report_cache_stat(rs)<0 ||
report_thread_stat(rs)<0 ||
report_conf_stat(rs)<0)
{
DEBUG_MSG("Error writing to control socket: %s\n"
"Failed to send status report.\n",strerror(errno));
}
}
break;
case CTL_SERVER: {
char *label,*dnsaddr;
int indx;
uint16_t cmd2;
DEBUG_MSG("Received SERVER command.\n");
if (read_allocstring(rs,&label,NULL)<=0) {
print_serr(rs,"Error reading server label.");
break;
}
if (!read_short(rs,&cmd2)) {
print_serr(rs,"Missing up|down|retest.");
goto free_label_break;
}
if(!read_allocstring(rs, &dnsaddr,NULL)) {
print_serr(rs,"Error reading DNS addresses.");
goto free_label_break;
}
/* Note by Paul Rombouts:
We are about to access server configuration data.
Now that the configuration can be changed during run time,
we should be using locks before accessing server config data, even if it
is read-only access.
However, as long as this is the only thread that calls reload_config_file()
it should be OK to read the server config without locks, but it is
something to keep in mind.
*/
{
char *endptr;
indx=strtol(label,&endptr,0);
if(!*endptr) {
if (indx<0 || indx>=DA_NEL(servers)) {
print_serr(rs,"Server index out of range.");
goto free_dnsaddr_label_break;
}
}
else {
if (!strcmp(label, "all"))
indx=-2; /* all servers */
else
indx=-1; /* compare names */
}
}
if(cmd2==CTL_S_UP || cmd2==CTL_S_DOWN || cmd2==CTL_S_RETEST) {
if(!dnsaddr) {
if (indx==-1) {
int i;
for (i=0;idomain=malloc(sz))) {
print_serr(rs,"Out of memory.");
goto free_sla_names_break;
}
memcpy(sl->domain,rhn,sz);
sl->exact=0;
sl->rule=tp;
p = q+1;
}
}
if(empty_cache(sla))
print_succ(rs);
else
print_serr(rs,"Could not lock the cache.");
free_sla_names_break:
free_slist_array(sla);
free_names_break:
free(names);
}
break;
case CTL_DUMP: {
int rv,exact=0;
unsigned char *nm=NULL;
char buf[DNSNAMEBUFSIZE];
unsigned char rhn[DNSNAMEBUFSIZE];
DEBUG_MSG("Received DUMP command.\n");
if (!(rv=read_domain(rs,buf,sizeof(buf)))) {
print_serr(rs,"Bad domain name.");
break;
}
if(rv>0) {
int sz;
exact=1; nm= ucharp buf; sz=sizeof(buf);
if(buf[0]=='.' && buf[1]) {
exact=0; ++nm; --sz;
}
if ((errmsg=parsestr2rhn(nm,sz,rhn))!=NULL)
goto bad_domain_name;
nm=rhn;
}
if(!print_succ(rs))
break;
if((rv=dump_cache(rs,nm,exact))<0 ||
(!rv && fsprintf(rs,"Could not find %s%s in the cache.\n",
exact?"":nm?"any entries matching ":"any entries",
nm?buf:"")<0))
{
DEBUG_MSG("Error writing to control socket: %s\n",strerror(errno));
}
}
break;
incomplete_command:
print_serr(rs,"Malformed or incomplete command.");
break;
bad_arg:
print_serr(rs,"Bad arg.");
break;
bad_domain_name:
print_serr(rs,errmsg);
break;
bad_ttl:
print_serr(rs, "Bad TTL.");
break;
bad_flags:
print_serr(rs, "Bad cache flags.");
break;
out_of_memory:
print_serr(rs,"Out of memory.");
break;
default:
print_serr(rs,"Unknown command.");
}
}
else {
DEBUG_MSG("Incorrect magic number in status-socket command code: %02x\n",cmd>>8);
print_serr(rs,"Command code contains incompatible version number.");
}
}
else {
DEBUG_MSG("short status-socket query\n");
print_serr(rs,"Command code missing or too short.");
}
close(rs);
usleep_r(100000); /* sleep some time. I do not want the query frequency to be too high. */
}
else if (errno!=EINTR) {
log_warn("Failed to accept connection on status socket: %s. "
"Status readback will be impossible",strerror(errno));
break;
}
}
exit_thread:
stat_pipe=0;
close(stat_sock);
statsock_thrid=main_thrid;
return NULL;
}
/*
* Initialize the status socket
*/
void init_stat_sock()
{
struct sockaddr_un *sa;
/* Should I include the terminating null byte in the calculation of the length parameter
for the socket address? The glibc info page "Details of Local Namespace" tells me I should not,
yet it is immediately followed by an example that contradicts that.
The SUN_LEN macro seems to be defined as
(offsetof(struct sockaddr_un, sun_path) + strlen(sa->sun_path)),
so I conclude it is not necessary to count the null byte, but it probably makes no
difference if you do.
*/
unsigned int sa_len = (offsetof(struct sockaddr_un, sun_path) + strlitlen("/pdnsd.status") + strlen(global.cache_dir));
sa=(struct sockaddr_un *)alloca(sa_len+1);
stpcpy(stpcpy(sa->sun_path,global.cache_dir),"/pdnsd.status");
if (unlink(sa->sun_path)!=0 && errno!=ENOENT) { /* Delete the socket */
log_warn("Failed to unlink %s: %s.\nStatus readback will be disabled",sa->sun_path, strerror(errno));
stat_pipe=0;
return;
}
if ((stat_sock=socket(PF_UNIX,SOCK_STREAM,0))==-1) {
log_warn("Failed to open socket: %s. Status readback will be impossible",strerror(errno));
stat_pipe=0;
return;
}
sa->sun_family=AF_UNIX;
#ifdef BSD44_SOCKA
sa->sun_len=SUN_LEN(sa);
#endif
/* Early initialization, so that umask can be used race-free. */
{
mode_t old_mask = umask((S_IRWXU|S_IRWXG|S_IRWXO)&(~global.ctl_perms));
if (bind(stat_sock,(struct sockaddr *)sa,sa_len)==-1) {
log_warn("Error: could not bind socket: %s.\nStatus readback will be impossible",strerror(errno));
close(stat_sock);
stat_pipe=0;
}
umask(old_mask);
}
if(stat_pipe) sock_path= strdup(sa->sun_path);
}
/*
* Start the status socket thread (see above)
*/
int start_stat_sock()
{
pthread_t st;
int rv=pthread_create(&st,&attr_detached,status_thread,NULL);
if (rv)
log_warn("Failed to start status thread. The status socket will be unuseable");
else {
statsock_thrid=st;
log_info(2,"Status thread started.");
}
return rv;
}