/* helpers.c - Various helper functions Copyright (C) 2000, 2001 Thomas Moestl Copyright (C) 2002, 2003, 2005, 2006, 2008, 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 #include #include #include #include #include #include #include #include #include #include #include "ipvers.h" #include "thread.h" #include "error.h" #include "helpers.h" #include "cache.h" #include "conff.h" /* * This is to exit pdnsd from any thread. */ void pdnsd_exit() { pthread_kill(main_thrid,SIGTERM); pthread_exit(NULL); } /* * Try to grab a mutex. If we can't, fail. This will loop until we get the * mutex or fail. This is only used in debugging code or at exit, otherwise * we might run into lock contention problems. */ int softlock_mutex(pthread_mutex_t *mutex) { unsigned int tr=0; while(pthread_mutex_trylock(mutex)) { if (++tr>=SOFTLOCK_MAXTRIES) return 0; usleep_r(10000); } return 1; } /* * setuid() and setgid() for a specified user. */ int run_as(const char *user) { if (user[0]) { #ifdef HAVE_GETPWNAM_R struct passwd pwdbuf, *pwd; size_t buflen; int err; for(buflen=128;; buflen*=2) { char buf[buflen]; /* variable length array */ /* Note that we use getpwnam_r() instead of getpwnam(), which returns its result in a statically allocated buffer and cannot be considered thread safe. Doesn't use NSS! */ err=getpwnam_r(user, &pwdbuf, buf, buflen, &pwd); if(err==0 && pwd) { /* setgid first, because we may not be allowed to do it anymore after setuid */ if (setgid(pwd->pw_gid)!=0) { log_error("Could not change group id to that of run_as user '%s': %s", user,strerror(errno)); return 0; } /* initgroups uses NSS, so we can disable it, i.e. we might need DNS for LDAP lookups, which times out */ if (global.use_nss && (initgroups(user, pwd->pw_gid)!=0)) { log_error("Could not initialize the group access list of run_as user '%s': %s", user,strerror(errno)); return 0; } if (setuid(pwd->pw_uid)!=0) { log_error("Could not change user id to that of run_as user '%s': %s", user,strerror(errno)); return 0; } break; } else if(err!=ERANGE) { if(err) log_error("run_as user '%s' could not be found: %s",user,strerror(err)); else log_error("run_as user '%s' could not be found.",user); return 0; } else if(buflen>=16*1024) { /* If getpwnam_r() seems defective, call it quits rather than keep on allocating ever larger buffers until we crash. */ log_error("getpwnam_r() requires more than %u bytes of buffer space.",(unsigned)buflen); return 0; } /* Else try again with larger buffer. */ } #else /* No getpwnam_r() :-( We'll use getpwnam() and hope for the best. */ struct passwd *pwd; if (!(pwd=getpwnam(user))) { log_error("run_as user %s could not be found.",user); return 0; } /* setgid first, because we may not allowed to do it anymore after setuid */ if (setgid(pwd->pw_gid)!=0) { log_error("Could not change group id to that of run_as user '%s': %s", user,strerror(errno)); return 0; } /* initgroups uses NSS, so we can disable it, i.e. we might need DNS for LDAP lookups, which times out */ if (global.use_nss && (initgroups(user, pwd->pw_gid)!=0)) { log_error("Could not initialize the group access list of run_as user '%s': %s", user,strerror(errno)); return 0; } if (setuid(pwd->pw_uid)!=0) { log_error("Could not change user id to that of run_as user '%s': %s", user,strerror(errno)); return 0; } #endif } return 1; } /* * returns whether c is allowed in IN domain names */ #if 0 int isdchar (unsigned char c) { if ((c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9') || c=='-' #ifdef UNDERSCORE || c=='_' #endif ) return 1; return 0; } #endif /* * Convert a string given in dotted notation to the transport format (length byte prepended * domain name parts, ended by a null length sequence) * The memory areas referenced by str and rhn may not overlap. * The buffer rhn points to is assumed to be at least DNSNAMEBUFSIZE bytes in size. * * Returns 1 if successful, otherwise 0. */ int str2rhn(const unsigned char *str, unsigned char *rhn) { unsigned int i,j; if(*str=='.' && !*(str+1)) { /* Special case: root domain */ rhn[0]=0; return 1; } for(i=0;;) { unsigned int jlim= i+63; if(jlim>DNSNAMEBUFSIZE-2) jlim=DNSNAMEBUFSIZE-2; /* DNSNAMEBUFSIZE-2 because the termination 0 has to follow */ for(j=i; str[j] && str[j]!='.'; ++j) { if(j>=jlim) return 0; rhn[j+1]=str[j]; } if(!str[j]) break; if(j<=i) return 0; rhn[i]=(unsigned char)(j-i); i = j+1; } rhn[i]=0; if (j>i || i==0) return 0; return 1; } /* parsestr2rhn is essentially the same as str2rhn, except that it doesn't look beyond the first len chars in the input string. It also tolerates strings not ending in a dot and returns a message in case of an error. */ const char *parsestr2rhn(const unsigned char *str, unsigned int len, unsigned char *rhn) { unsigned int i,j; if(len>0 && *str=='.' && (len==1 || !*(str+1))) { /* Special case: root domain */ rhn[0]=0; return NULL; } i=0; do { unsigned int jlim= i+63; if(jlim>DNSNAMEBUFSIZE-2) jlim=DNSNAMEBUFSIZE-2; for(j=i; j=jlim) return "Domain name element too long"; rhn[j+1]=str[j]; } if(j<=i) { if(j=2) str[j++]='.'; } else { do { for (;lb;--lb) { unsigned char c; if(j+2>=size) goto overflow; c=rhn[i++]; if(isgraph(c)) { if(c=='.' || c=='\\' || c=='"') { str[j++]='\\'; if(j+2>=size) goto overflow; } str[j++]=c; } else { unsigned int rem=size-1-j; int n=snprintf(charp &str[j],rem,"\\%03o",c); if(n<0 || n>=rem) { str[j++]='.'; goto overflow; } j+=n; } } str[j++]='.'; lb=rhn[i++]; } while(lb); } str[j]=0; return str; overflow: j=size; str[--j]=0; if(j>0) { str[--j]='.'; if(j>0) { str[--j]='.'; if(j>0) str[--j]='.'; } } return str; } /* Return the length of a domain name in transport format. The definition has in fact been moved to helpers.h as an inline function. Note added by Paul Rombouts: Compared to the definition used by Thomas Moestl (strlen(rhn)+1), the following definition of rhnlen may yield a different result in certain error situations (when a domain name segment contains null byte). */ #if 0 unsigned int rhnlen(const unsigned char *rhn) { unsigned int i=0,lb; while((lb=rhn[i++])) i+=lb; return i; } #endif /* * Non-validating rhn copy (use with checked or generated data only). * Returns number of characters copied. The buffer dst points to is assumed to be DNSNAMEBUFSIZE (or * at any rate large enough) bytes in size. * The answer assembly code uses this; it is guaranteed to not clobber anything * after the name. */ unsigned int rhncpy(unsigned char *dst, const unsigned char *src) { unsigned int len = rhnlen(src); PDNSD_ASSERT(len<=DNSNAMEBUFSIZE,"rhncpy: src too long!"); memcpy(dst,src,len>DNSNAMEBUFSIZE?DNSNAMEBUFSIZE:len); return len; } /* Check whether a name is a normal wire-encoded domain name, i.e. is not compressed, doesn't use extended labels and is not too long. */ int isnormalencdomname(const unsigned char *rhn, unsigned maxlen) { unsigned int i,lb; if(maxlen>DNSNAMEBUFSIZE) maxlen=DNSNAMEBUFSIZE; for(i=0;;) { if(i>=maxlen) return 0; lb=rhn[i++]; if(lb==0) break; if(lb>0x3f) return 0; i += lb; } return 1; } int str2pdnsd_a(const char *addr, pdnsd_a *a) { #ifdef ENABLE_IPV4 if (run_ipv4) { return inet_aton(addr,&a->ipv4); } #endif #ifdef ENABLE_IPV6 ELSE_IPV6 { /* Try to map an IPv4 address to IPv6 */ struct in_addr a4; if(inet_aton(addr,&a4)) { a->ipv6=global.ipv4_6_prefix; ((uint32_t *)(&a->ipv6))[3]=a4.s_addr; return 1; } return inet_pton(AF_INET6,addr,&a->ipv6)>0; } #endif /* return 0; */ } /* definition moved to helpers.h */ #if 0 int is_inaddr_any(pdnsd_a *a) { return SEL_IPVER( a->ipv4.s_addr==INADDR_ANY, IN6_IS_ADDR_UNSPECIFIED(&a->ipv6) ); } #endif /* * This is used for user output only, so it does not matter when an error occurs. */ const char *pdnsd_a2str(pdnsd_a *a, char *buf, int maxlen) { const char *res= SEL_IPVER( inet_ntop(AF_INET,&a->ipv4,buf,maxlen), inet_ntop(AF_INET6,&a->ipv6,buf,maxlen) ); if (!res) { log_error("inet_ntop: %s", strerror(errno)); return "?.?.?.?"; } return res; } /* Appropriately set our random device */ #ifdef R_DEFAULT # if (TARGET == TARGET_BSD) && !defined(__NetBSD__) # define R_ARC4RANDOM 1 # else # define R_RANDOM 1 # endif #endif #ifdef RANDOM_DEVICE FILE *rand_file; #endif #ifdef R_RANDOM void init_crandom() { struct timeval tv; struct timezone tz; gettimeofday(&tv,&tz); srandom(tv.tv_sec^tv.tv_usec); /* not as guessable as time() */ } #endif /* initialize the PRNG */ int init_rng() { #ifdef RANDOM_DEVICE if (!(rand_file=fopen(RANDOM_DEVICE,"r"))) { log_error("Could not open %s.",RANDOM_DEVICE); return 0; } #endif #ifdef R_RANDOM init_crandom(); #endif return 1; } /* The following function is now actually defined as a macro in helpers.h */ #if 0 void free_rng() { #ifdef RANDOM_DEVICE if (rand_file) fclose(rand_file); #endif } #endif /* generate a (more or less) random number 16 bits long. */ unsigned short get_rand16() { #ifdef RANDOM_DEVICE unsigned short rv; if (rand_file) { if (fread(&rv,sizeof(rv),1, rand_file)!=1) { log_error("Error while reading from random device: %s", strerror(errno)); pdnsd_exit(); } return rv&0xffff; } else return random()&0xffff; #endif #ifdef R_RANDOM return random()&0xffff; #endif #ifdef R_ARC4RANDOM return arc4random()&0xffff; #endif } /* fsprintf does formatted output to a file descriptor. The functionality is similar to fprintf, but note that fd is of type int instead of FILE*. This function has been rewritten by Paul Rombouts */ int fsprintf(int fd, const char *format, ...) { int n; va_list va; { char buf[256]; va_start(va,format); n=vsnprintf(buf,sizeof(buf),format,va); va_end(va); if(n<(int)sizeof(buf)) { if(n>0) n=write_all(fd,buf,n); return n; } } /* retry with a right sized buffer, needs glibc 2.1 or higher to work */ { unsigned bufsize=n+1; char buf[bufsize]; va_start(va,format); n=vsnprintf(buf,bufsize,format,va); va_end(va); if(n>0) n=write_all(fd,buf,n); } return n; } /* Convert data into a hexadecimal representation (for debugging purposes).. The result is stored in the character array buf. If buf is not large enough to hold the result, the truncation is indicated by trailing dots. */ void hexdump(const void *data, int dlen, char *buf, int buflen) { const unsigned char *p=data; int i,j=0; for(i=0;i=rem) goto truncated; j += n; } goto done; truncated: if(j>=6) { j -= 3; if(j+4>=buflen) j -= 3; buf[j++]=' '; buf[j++]='.'; buf[j++]='.'; buf[j++]='.'; } else { int ndots=buflen-1; if(ndots>3) ndots=3; j=0; while(j=size) return -1; c=in[i]; if(!isprint(c)) { int rem=size-j; int n=snprintf(&str[j],rem,"\\%03o",c); if(n<0 || n>=rem) { return -1; } j+=n; } else { if(c=='\\' || c=='"') { str[j++]='\\'; if(j+1>=size) return -1; } str[j++]=c; } } str[j]=0; return j; } /* * This is not like strcmp, but will return 1 on match or 0 if the * strings are different. */ /* definition moved to helpers.h as an inline function. */ #if 0 int stricomp(char *a, char *b) { int i; if (strlen(a) != strlen(b)) return 0; for (i=0;i= dstsz-1 && o!='\0') return 0; return 1; } #endif #ifndef HAVE_GETLINE /* Note by Paul Rombouts: I know that getline is a GNU extension and is not really portable, but the alternative standard functions have some real problems. The following substitute does not have exactly the same semantics as the GNU getline, but it should be good enough, as long as the stream doesn't contain any null chars. This version is actually based on fgets_realloc() that I found in the WWWOFFLE source. */ #define BUFSIZE 256 int getline(char **lineptr, size_t *n, FILE *stream) { char *line=*lineptr; size_t sz=*n,i; if(!line || sz=sz) { /* retry with a right sized buffer, needs glibc 2.1 or higher to work */ sz=n+1; { char *tmp=realloc(line,sz); if(tmp) { line=tmp; n=vsnprintf(line,sz,format,vasave); } else n= -1; } } va_end(vasave); if(n>=0) *lineptr=line; else free(line); return n; } #endif #ifndef HAVE_ASPRINTF int asprintf (char **lineptr, const char *format, ...) { int n; va_list va; va_start(va,format); n=vasprintf(lineptr,format,va); va_end(va); return n; } #endif #ifndef HAVE_INET_NTOP const char *inet_ntop(int af, const void *src, char *dst, size_t size) { const char *rc = NULL; if (src != NULL && dst != NULL && size > 0) { switch (af) { case AF_INET: { const unsigned char *p=src; int n = snprintf(dst, size, "%u.%u.%u.%u", p[0],p[1],p[2],p[3]); if (n >= 0 && n < size) rc = dst; } break; #ifdef AF_INET6 case AF_INET6: { const unsigned char *p=src; unsigned int i,offs=0; for (i=0;i<16;i+=2) { int n=snprintf(dst+offs, size-offs,i==0?"%x":":%x", ((unsigned)p[i]<<8)|p[i+1]); if(n<0) return NULL; offs+=n; if(offs>=size) return NULL; } rc = dst; } break; #endif } } return rc; } #endif