/* $Id$*/ /* * * Copyright (C) 2001-2003 FhG Fokus * * This file is part of ser, a free SIP server. * * ser 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 * * For a license to use the ser software under conditions * other than those described here, or to purchase support for this * software, please contact iptel.org by e-mail at the following addresses: * info@iptel.org * * ser 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * History: * ------- * 2003-02-13 added proto to sip_resolvehost, for SRV lookups (andrei) * 2003-07-03 default port value set according to proto (andrei) * 2005-07-11 added resolv_init (timeouts a.s.o) (andrei) * 2006-04-13 added sip_hostport2su() (andrei) * 2006-07-13 rdata structures put on diet (andrei) * 2006-07-17 rdata contains now also the record name (andrei) * 2006-08-18 get_record can append also the additional records to the * returned list (andrei) * 2007-06-15 naptr support (andrei) * 2007-10-10 short name resolution using search list supported (mma) * set dns_use_search_list=1 (default on) * new option dns_search_full_match (default on) controls * whether rest of the name is matched against search list * or blindly accepted (better performance but exploitable) * 2008-01-31 resolver options use the configuration framework, and the * resolver is reinitialized when the options change (Miklos) * 2008-08-12 sctp preference support for NAPTR queries (andrei) * 2009-03-30 TXT record support (andrei) * 2009-03-31 EBL record support (andrei) * 2009-04-01 PTR record support (andrei) */ /*! * \file * \brief SIP-router core :: * \ingroup core * Module: \ref core */ #include #include #include #include #include #include "resolve.h" #include "compiler_opt.h" #include "dprint.h" #include "mem/mem.h" #include "ip_addr.h" #include "error.h" #include "globals.h" /* tcp_disable, tls_disable a.s.o */ #include "cfg_core.h" #include "socket_info.h" #ifdef USE_DNS_CACHE #include "dns_cache.h" #endif /* counters framework */ struct dns_counters_h dns_cnts_h; counter_def_t dns_cnt_defs[] = { {&dns_cnts_h.failed_dns_req, "failed_dns_request", 0, 0, 0, "incremented each time a DNS request has failed."}, {0, 0, 0, 0, 0, 0 } }; /* mallocs for local stuff */ #define local_malloc pkg_malloc #define local_free pkg_free #ifdef USE_NAPTR static int naptr_proto_pref[PROTO_LAST+1]; #endif #ifdef USE_NAPTR void init_naptr_proto_prefs() { if ((PROTO_UDP > PROTO_LAST) || (PROTO_TCP > PROTO_LAST) || (PROTO_TLS > PROTO_LAST) || (PROTO_SCTP > PROTO_LAST)){ BUG("init_naptr_proto_prefs: array too small \n"); return; } naptr_proto_pref[PROTO_UDP]=cfg_get(core, core_cfg, dns_udp_pref); naptr_proto_pref[PROTO_TCP]=cfg_get(core, core_cfg, dns_tcp_pref); naptr_proto_pref[PROTO_TLS]=cfg_get(core, core_cfg, dns_tls_pref); naptr_proto_pref[PROTO_SCTP]=cfg_get(core, core_cfg, dns_sctp_pref); } #endif /* USE_NAPTR */ #ifdef DNS_WATCHDOG_SUPPORT static on_resolv_reinit on_resolv_reinit_cb = NULL; /* register the callback function */ int register_resolv_reinit_cb(on_resolv_reinit cb) { if (on_resolv_reinit_cb) { LOG(L_ERR, "ERROR: register_resolv_reinit_cb(): " "callback function has been already registered\n"); return -1; } on_resolv_reinit_cb = cb; return 0; } #endif /* counter init function must be called before fork */ static int stat_init() { if (counter_register_array("dns", dns_cnt_defs) < 0) goto error; return 0; error: return -1; } /* init. the resolver * params: retr_time - time before retransmitting (must be >0) * retr_no - retransmissions number * servers_no - how many dns servers will be used * (from the one listed in /etc/resolv.conf) * search - if 0 the search list in /etc/resolv.conf will * be ignored (HINT: even if you don't have a * search list in resolv.conf, it's still better * to set search to 0, because an empty seachlist * means in fact search "" => it takes more time) * If any of the parameters <0, the default (system specific) value * will be used. See also resolv.conf(5). * returns: 0 on success, -1 on error */ static int _resolv_init() { res_init(); #ifdef HAVE_RESOLV_RES if (cfg_get(core, core_cfg, dns_retr_time)>0) _res.retrans=cfg_get(core, core_cfg, dns_retr_time); if (cfg_get(core, core_cfg, dns_retr_no)>0) _res.retry=cfg_get(core, core_cfg, dns_retr_no); if ((cfg_get(core, core_cfg, dns_servers_no)>=0) && (cfg_get(core, core_cfg, dns_servers_no)<_res.nscount)) _res.nscount=cfg_get(core, core_cfg, dns_servers_no); if (cfg_get(core, core_cfg, dns_search_list)==0) _res.options&=~(RES_DEFNAMES|RES_DNSRCH); #else #warning "no resolv timeout support" LOG(L_WARN, "WARNING: _resolv_init: no resolv options support - resolv" " options will be ignored\n"); #endif return 0; } /* wrapper function to initialize the resolver at startup */ int resolv_init() { int res = -1; _resolv_init(); #ifdef USE_NAPTR init_naptr_proto_prefs(); #endif /* init counter API only at startup * This function must be called before DNS cache init method (if available) */ res = stat_init(); return res; } /* wrapper function to reinitialize the resolver * This function must be called by each child process whenever * a resolver option changes */ void resolv_reinit(str *gname, str *name) { _resolv_init(); #ifdef DNS_WATCHDOG_SUPPORT if (on_resolv_reinit_cb) on_resolv_reinit_cb(name); #endif LOG(L_DBG, "DEBUG: resolv_reinit(): " "DNS resolver has been reinitialized\n"); } /* fixup function for dns_reinit variable * (resets the variable to 0) */ int dns_reinit_fixup(void *handle, str *gname, str *name, void **val) { *val = (void *)(long)0; return 0; } /* wrapper function to recalculate the naptr protocol preferences */ void reinit_naptr_proto_prefs(str *gname, str *name) { #ifdef USE_NAPTR init_naptr_proto_prefs(); #endif } /* fixup function for dns_try_ipv6 * verifies that SER really listens on an ipv6 interface */ int dns_try_ipv6_fixup(void *handle, str *gname, str *name, void **val) { if ((int)(long)(*val) && !(socket_types & SOCKET_T_IPV6)) { LOG(L_ERR, "ERROR: dns_try_ipv6_fixup(): " "SER does not listen on any ipv6 interface, " "there is no point in resolving ipv6 addresses\n"); return -1; } return 0; } /* skips over a domain name in a dns message * (it can be a sequence of labels ending in \0, a pointer or * a sequence of labels ending in a pointer -- see rfc1035 * returns pointer after the domain name or null on error*/ unsigned char* dns_skipname(unsigned char* p, unsigned char* end) { while(pend)?0:p; } /* parses the srv record into a srv_rdata structure * msg - pointer to the dns message * end - pointer to the end of the message * eor - pointer to the end of the record/rdata * rdata - pointer to the rdata part of the srv answer * returns 0 on error, or a dyn. alloc'ed srv_rdata structure */ /* SRV rdata format: * 111111 * 0123456789012345 * +----------------+ * | priority | * |----------------| * | weight | * |----------------| * | port number | * |----------------| * | | * ~ name ~ * | | * +----------------+ */ struct srv_rdata* dns_srv_parser( unsigned char* msg, unsigned char* end, unsigned char* eor, unsigned char* rdata) { struct srv_rdata* srv; unsigned short priority; unsigned short weight; unsigned short port; int len; char name[MAX_DNS_NAME]; srv=0; if ((rdata+6+1)>eor) goto error; memcpy((void*)&priority, rdata, 2); memcpy((void*)&weight, rdata+2, 2); memcpy((void*)&port, rdata+4, 2); rdata+=6; if (dn_expand(msg, end, rdata, name, MAX_DNS_NAME-1)<0) goto error; len=strlen(name); if (len>255) goto error; /* alloc enought space for the struct + null terminated name */ srv=local_malloc(sizeof(struct srv_rdata)-1+len+1); if (srv==0){ LOG(L_ERR, "ERROR: dns_srv_parser: out of memory\n"); goto error; } srv->priority=ntohs(priority); srv->weight=ntohs(weight); srv->port=ntohs(port); srv->name_len=len; memcpy(srv->name, name, srv->name_len); srv->name[srv->name_len]=0; return srv; error: if (srv) local_free(srv); return 0; } /* parses the naptr record into a naptr_rdata structure * msg - pointer to the dns message * end - pointer to the end of the message * eor - pointer to the end of the record/rdata * rdata - pointer to the rdata part of the naptr answer * returns 0 on error, or a dyn. alloc'ed naptr_rdata structure */ /* NAPTR rdata format: * 111111 * 0123456789012345 * +----------------+ * | order | * |----------------| * | preference | * |----------------| * ~ flags ~ * | (string) | * |----------------| * ~ services ~ * | (string) | * |----------------| * ~ regexp ~ * | (string) | * |----------------| * ~ replacement ~ | (name) | * +----------------+ */ struct naptr_rdata* dns_naptr_parser( unsigned char* msg, unsigned char* end, unsigned char* eor, unsigned char* rdata) { struct naptr_rdata* naptr; unsigned char* flags; unsigned char* services; unsigned char* regexp; unsigned short order; unsigned short pref; unsigned char flags_len; unsigned char services_len; unsigned char regexp_len; int len; char repl[MAX_DNS_NAME]; naptr = 0; if ((rdata + 7 + 1)>eor) goto error; memcpy((void*)&order, rdata, 2); memcpy((void*)&pref, rdata + 2, 2); flags_len = rdata[4]; if ((rdata + 7 + 1 + flags_len) > eor) goto error; flags=rdata+5; services_len = rdata[5 + flags_len]; if ((rdata + 7 + 1 + flags_len + services_len) > eor) goto error; services=rdata + 6 + flags_len; regexp_len = rdata[6 + flags_len + services_len]; if ((rdata + 7 +1 + flags_len + services_len + regexp_len) > eor) goto error; regexp=rdata + 7 + flags_len + services_len; rdata = rdata + 7 + flags_len + services_len + regexp_len; if (dn_expand(msg, end, rdata, repl, MAX_DNS_NAME-1) == -1) goto error; len=strlen(repl); if (len>255) goto error; naptr=local_malloc(sizeof(struct naptr_rdata)+flags_len+services_len+ regexp_len+len+1-1); if (naptr == 0){ LOG(L_ERR, "ERROR: dns_naptr_parser: out of memory\n"); goto error; } naptr->order=ntohs(order); naptr->pref=ntohs(pref); naptr->flags=&naptr->str_table[0]; naptr->flags_len=flags_len; memcpy(naptr->flags, flags, naptr->flags_len); naptr->services=&naptr->str_table[flags_len]; naptr->services_len=services_len; memcpy(naptr->services, services, naptr->services_len); naptr->regexp=&naptr->str_table[flags_len+services_len]; naptr->regexp_len=regexp_len; memcpy(naptr->regexp, regexp, naptr->regexp_len); naptr->repl=&naptr->str_table[flags_len+services_len+regexp_len]; naptr->repl_len=len; memcpy(naptr->repl, repl, len); naptr->repl[len]=0; /* null term. */ return naptr; error: if (naptr) local_free(naptr); return 0; } /* parses a CNAME record into a cname_rdata structure */ struct cname_rdata* dns_cname_parser( unsigned char* msg, unsigned char* end, unsigned char* rdata) { struct cname_rdata* cname; int len; char name[MAX_DNS_NAME]; cname=0; if (dn_expand(msg, end, rdata, name, MAX_DNS_NAME-1)==-1) goto error; len=strlen(name); if (len>255) goto error; /* alloc sizeof struct + space for the null terminated name */ cname=local_malloc(sizeof(struct cname_rdata)-1+len+1); if(cname==0){ LOG(L_ERR, "ERROR: dns_cname_parser: out of memory\n"); goto error; } cname->name_len=len; memcpy(cname->name, name, cname->name_len); cname->name[cname->name_len]=0; return cname; error: if (cname) local_free(cname); return 0; } /* parses an A record rdata into an a_rdata structure * returns 0 on error or a dyn. alloc'ed a_rdata struct */ struct a_rdata* dns_a_parser(unsigned char* rdata, unsigned char* eor) { struct a_rdata* a; if (rdata+4>eor) goto error; a=(struct a_rdata*)local_malloc(sizeof(struct a_rdata)); if (a==0){ LOG(L_ERR, "ERROR: dns_a_parser: out of memory\n"); goto error; } memcpy(a->ip, rdata, 4); return a; error: return 0; } /* parses an AAAA (ipv6) record rdata into an aaaa_rdata structure * returns 0 on error or a dyn. alloc'ed aaaa_rdata struct */ struct aaaa_rdata* dns_aaaa_parser(unsigned char* rdata, unsigned char* eor) { struct aaaa_rdata* aaaa; if (rdata+16>eor) goto error; aaaa=(struct aaaa_rdata*)local_malloc(sizeof(struct aaaa_rdata)); if (aaaa==0){ LOG(L_ERR, "ERROR: dns_aaaa_parser: out of memory\n"); goto error; } memcpy(aaaa->ip6, rdata, 16); return aaaa; error: return 0; } /** parses a TXT record into a txt_rdata structure. * @param msg - pointer to the dns message * @param end - pointer to the end of the record (rdata end) * @param rdata - pointer to the rdata part of the txt answer * returns 0 on error, or a dyn. alloc'ed txt_rdata structure */ /* TXT rdata format: * * one or several character strings: * 01234567 * +--------------------+ * | len | string / ... * |------------------+ */ static struct txt_rdata* dns_txt_parser(unsigned char* msg, unsigned char* end, unsigned char* rdata) { struct txt_rdata* txt; int len, n, i; int str_size; unsigned char* p; unsigned char* st; txt=0; if (unlikely((rdata+1)>end)) goto error; n=0; str_size=0; /* count the number of strings */ p=rdata; do{ len=*p; p+=len+1; str_size+=len+1; /* 1 for the term. 0 */ if (unlikely(p>end)) goto error; n++; }while(pcstr_no=n; txt->tslen=str_size; /* fill the structure */ p=rdata; for (i=0; itxt[i].cstr_len=len; txt->txt[i].cstr=(char*)st; st+=len+1; p+=len+1; } return txt; error: if (txt) local_free(txt); return 0; } /** parses an EBL record into a txt_rdata structure. * @param msg - pointer to the dns message * @param end - pointer to the end of the dns message * @param eor - pointer to the end of the record (rdata end) * @param rdata - pointer to the rdata part of the txt answer * returns 0 on error, or a dyn. alloc'ed txt_rdata structure */ /* EBL rdata format: * (see http://tools.ietf.org/html/draft-ietf-enum-branch-location-record-03) * one or several character strings: * 01234567 * +--------+ * | postion| * +-----------+ * / separator / * +-----------+ * / apex / * +----------+ * * where separator is a character string ( 8 bit len, followed by len chars) * and apex is a domain-name. */ static struct ebl_rdata* dns_ebl_parser(unsigned char* msg, unsigned char* end, unsigned char* eor, unsigned char* rdata) { struct ebl_rdata* ebl; int sep_len; int apex_len; char apex[MAX_DNS_NAME]; ebl=0; /* check if len is at least 4 chars (minimum possible): pos (1 byte) + sep. (min 1 byte) + apex (min. 2 bytes) and also check if rdata+1 (pos) + 1 (sep. len) + sep_len + 1 is ok*/ if (unlikely(((rdata+4)>eor)||((rdata+1+1+rdata[1]+2)>eor))) goto error; sep_len=rdata[1]; if (unlikely(dn_expand(msg, end, rdata+1+1+sep_len, apex, MAX_DNS_NAME-1)==-1)) goto error; apex_len=strlen(apex); /* alloc sizeof struct + space for the 2 null-terminated strings */ ebl=local_malloc(sizeof(struct ebl_rdata)-1+sep_len+1+apex_len+1); if (ebl==0){ LOG(L_ERR, "ERROR: dns_ebl_parser: out of memory\n"); goto error; } ebl->position=rdata[0]; ebl->separator=&ebl->str_table[0]; ebl->apex=ebl->separator+sep_len+1; ebl->separator_len=sep_len; ebl->apex_len=apex_len; memcpy(ebl->separator, rdata+2, sep_len); ebl->separator[sep_len]=0; memcpy(ebl->apex, apex, apex_len); ebl->apex[apex_len]=0; return ebl; error: if (ebl) local_free(ebl); return 0; } /* parses a PTR record into a ptr_rdata structure */ struct ptr_rdata* dns_ptr_parser( unsigned char* msg, unsigned char* end, unsigned char* rdata) { struct ptr_rdata* pname; int len; char name[MAX_DNS_NAME]; pname=0; if (dn_expand(msg, end, rdata, name, MAX_DNS_NAME-1)==-1) goto error; len=strlen(name); if (len>255) goto error; /* alloc sizeof struct + space for the null terminated name */ pname=local_malloc(sizeof(struct ptr_rdata)-1+len+1); if(pname==0){ LOG(L_ERR, "ERROR: dns_ptr_parser: out of memory\n"); goto error; } pname->ptrdname_len=len; memcpy(pname->ptrdname, name, pname->ptrdname_len); pname->ptrdname[pname->ptrdname_len]=0; return pname; error: if (pname) local_free(pname); return 0; } /* frees completely a struct rdata list */ void free_rdata_list(struct rdata* head) { struct rdata* l; struct rdata* next_l; l=head; while (l != 0) { next_l = l->next; /* free the parsed rdata*/ if (l->rdata) local_free(l->rdata); local_free(l); l = next_l; } } #ifdef HAVE_RESOLV_RES /* checks whether supplied name exists in the resolver search list * returns 1 if found * 0 if not found */ int match_search_list(const struct __res_state* res, char* name) { int i; for (i=0; (idnsrch[i]); i++) { if (strcasecmp(name, res->dnsrch[i])==0) return 1; } return 0; } #endif /* gets the DNS records for name:type * returns a dyn. alloc'ed struct rdata linked list with the parsed responses * or 0 on error * see rfc1035 for the query/response format */ struct rdata* get_record(char* name, int type, int flags) { int size; int skip; int qno, answers_no; int r; static union dns_query buff; unsigned char* p; unsigned char* end; unsigned char* rd_end; static char rec_name[MAX_DNS_NAME]; /* placeholder for the record name */ int rec_name_len; unsigned short rtype, class, rdlength; unsigned int ttl; struct rdata* head; struct rdata** crt; struct rdata** last; struct rdata* rd; struct srv_rdata* srv_rd; struct srv_rdata* crt_srv; int search_list_used; int name_len; struct rdata* fullname_rd; if (cfg_get(core, core_cfg, dns_search_list)==0) { search_list_used=0; name_len=0; } else { search_list_used=1; name_len=strlen(name); } fullname_rd=0; size=res_search(name, C_IN, type, buff.buff, sizeof(buff)); if (unlikely(size<0)) { DBG("get_record: lookup(%s, %d) failed\n", name, type); goto not_found; } else if (unlikely(size > sizeof(buff))) size=sizeof(buff); head=rd=0; last=crt=&head; p=buff.buff+DNS_HDR_SIZE; end=buff.buff+size; if (unlikely(p>=end)) goto error_boundary; qno=ntohs((unsigned short)buff.hdr.qdcount); for (r=0; rend)) { LOG(L_ERR, "ERROR: get_record: p>=end\n"); goto error; } }; answers_no=ntohs((unsigned short)buff.hdr.ancount); again: for (r=0; (r255)){ LOG(L_ERR, "ERROR: get_record: dn_expand(rec_name): name too" " long (%d)\n", rec_name_len); goto error; } /* check if enough space is left for type, class, ttl & size */ if (unlikely((p+2+2+4+2)>end)) goto error_boundary; /* get type */ memcpy((void*) &rtype, (void*)p, 2); rtype=ntohs(rtype); p+=2; /* get class */ memcpy((void*) &class, (void*)p, 2); class=ntohs(class); p+=2; /* get ttl*/ memcpy((void*) &ttl, (void*)p, 4); ttl=ntohl(ttl); p+=4; /* get size */ memcpy((void*)&rdlength, (void*)p, 2); rdlength=ntohs(rdlength); p+=2; rd_end=p+rdlength; if (unlikely((rd_end)>end)) goto error_boundary; if ((flags & RES_ONLY_TYPE) && (rtype!=type)){ /* skip */ p=rd_end; continue; } /* expand the "type" record (rdata)*/ rd=(struct rdata*) local_malloc(sizeof(struct rdata)+rec_name_len+ 1-1); if (rd==0){ LOG(L_ERR, "ERROR: get_record: out of memory\n"); goto error; } rd->type=rtype; rd->pclass=class; rd->ttl=ttl; rd->next=0; memcpy(rd->name, rec_name, rec_name_len); rd->name[rec_name_len]=0; rd->name_len=rec_name_len; /* check if full name matches */ if ((search_list_used==1)&&(fullname_rd==0)&& (rec_name_len>=name_len)&& (strncasecmp(rec_name, name, name_len)==0)) { /* now we have record whose name is the same (up-to the * name_len with the searched one): * if the length is the same - we found full match, no fake * cname needed, just clear the flag * if the length of the name differs - it has matched using * search list remember the rd, so we can create fake CNAME * record when all answers are used and no better match found */ if (rec_name_len==name_len) search_list_used=0; /* this is safe.... here was rec_name_len > name_len */ else if (rec_name[name_len]=='.') { #ifdef HAVE_RESOLV_RES if ((cfg_get(core, core_cfg, dns_search_fmatch)==0) || (match_search_list(&_res, rec_name+name_len+1)!=0)) #endif fullname_rd=rd; } } switch(rtype){ case T_SRV: srv_rd= dns_srv_parser(buff.buff, end, rd_end, p); rd->rdata=(void*)srv_rd; if (unlikely(srv_rd==0)) goto error_parse; /* insert sorted into the list */ for (crt=&head; *crt; crt= &((*crt)->next)){ if ((*crt)->type!=T_SRV) continue; crt_srv=(struct srv_rdata*)(*crt)->rdata; if ((srv_rd->priority < crt_srv->priority) || ( (srv_rd->priority == crt_srv->priority) && (srv_rd->weight > crt_srv->weight) ) ){ /* insert here */ goto skip; } } last=&(rd->next); /*end of for => this will be the last element*/ skip: /* insert here */ rd->next=*crt; *crt=rd; break; case T_A: rd->rdata=(void*) dns_a_parser(p, rd_end); if (unlikely(rd->rdata==0)) goto error_parse; *last=rd; /* last points to the last "next" or the list head*/ last=&(rd->next); break; case T_AAAA: rd->rdata=(void*) dns_aaaa_parser(p, rd_end); if (unlikely(rd->rdata==0)) goto error_parse; *last=rd; last=&(rd->next); break; case T_CNAME: rd->rdata=(void*) dns_cname_parser(buff.buff, end, p); if(unlikely(rd->rdata==0)) goto error_parse; *last=rd; last=&(rd->next); break; case T_NAPTR: rd->rdata=(void*)dns_naptr_parser(buff.buff, end, rd_end, p); if(unlikely(rd->rdata==0)) goto error_parse; *last=rd; last=&(rd->next); break; case T_TXT: rd->rdata= dns_txt_parser(buff.buff, rd_end, p); if (rd->rdata==0) goto error_parse; *last=rd; last=&(rd->next); break; case T_EBL: rd->rdata= dns_ebl_parser(buff.buff, end, rd_end, p); if (rd->rdata==0) goto error_parse; *last=rd; last=&(rd->next); break; case T_PTR: rd->rdata=(void*) dns_ptr_parser(buff.buff, end, p); if(unlikely(rd->rdata==0)) goto error_parse; *last=rd; last=&(rd->next); break; default: LOG(L_ERR, "WARNING: get_record: unknown type %d\n", rtype); rd->rdata=0; *last=rd; last=&(rd->next); } p+=rdlength; } if (flags & RES_AR){ flags&=~RES_AR; answers_no=ntohs((unsigned short)buff.hdr.nscount); #ifdef RESOLVE_DBG DBG("get_record: skipping %d NS (p=%p, end=%p)\n", answers_no, p, end); #endif for (r=0; (rend)) goto error_boundary; memcpy((void*)&rdlength, (void*)p+2+2+4, 2); p+=2+2+4+2+ntohs(rdlength); } answers_no=ntohs((unsigned short)buff.hdr.arcount); #ifdef RESOLVE_DBG DBG("get_record: parsing %d ARs (p=%p, end=%p)\n", answers_no, p, end); #endif goto again; /* add also the additional records */ } /* if the name was expanded using DNS search list * create fake CNAME record to convert the short name * (queried) to long name (answered) */ if ((search_list_used==1)&&(fullname_rd!=0)) { rd=(struct rdata*) local_malloc(sizeof(struct rdata)+name_len+1-1); if (unlikely(rd==0)){ LOG(L_ERR, "ERROR: get_record: out of memory\n"); goto error; } rd->type=T_CNAME; rd->pclass=fullname_rd->pclass; rd->ttl=fullname_rd->ttl; rd->next=head; memcpy(rd->name, name, name_len); rd->name[name_len]=0; rd->name_len=name_len; /* alloc sizeof struct + space for the null terminated name */ rd->rdata=(void*)local_malloc(sizeof(struct cname_rdata)-1+ head->name_len+1); if(unlikely(rd->rdata==0)){ LOG(L_ERR, "ERROR: get_record: out of memory\n"); goto error_rd; } ((struct cname_rdata*)(rd->rdata))->name_len=fullname_rd->name_len; memcpy(((struct cname_rdata*)(rd->rdata))->name, fullname_rd->name, fullname_rd->name_len); ((struct cname_rdata*)(rd->rdata))->name[head->name_len]=0; head=rd; } return head; error_boundary: LOG(L_ERR, "ERROR: get_record: end of query buff reached\n"); if (head) free_rdata_list(head); return 0; error_parse: LOG(L_ERR, "ERROR: get_record: rdata parse error (%s, %d), %p-%p" " rtype=%d, class=%d, ttl=%d, rdlength=%d \n", name, type, p, end, rtype, class, ttl, rdlength); error_rd: if (rd) local_free(rd); /* rd->rdata=0 & rd is not linked yet into the list */ error: LOG(L_ERR, "ERROR: get_record \n"); if (head) free_rdata_list(head); not_found: /* increment error counter */ counter_inc(dns_cnts_h.failed_dns_req); return 0; } #ifdef USE_NAPTR /* service matching constants, lowercase */ #define SIP_SCH 0x2b706973 #define SIPS_SCH 0x73706973 #define SIP_D2U 0x00753264 #define SIP_D2T 0x00743264 #define SIP_D2S 0x00733264 #define SIPS_D2T 0x7432642b /* get protocol from a naptr rdata and check for validity * returns > 0 (PROTO_UDP, PROTO_TCP, PROTO_SCTP or PROTO_TLS) * <=0 on error */ char naptr_get_sip_proto(struct naptr_rdata* n) { unsigned int s; char proto; proto=-1; if ((n->flags_len!=1) || ((*n->flags | 0x20 )!='s')) return -1; if (n->regexp_len!=0) return -1; /* SIP+D2U, SIP+D2T, SIP+D2S, SIPS+D2T */ if (n->services_len==7){ /* SIP+D2X */ s=n->services[0]+(n->services[1]<<8)+(n->services[2]<<16)+ (n->services[3]<<24); s|=0x20202020; if (s==SIP_SCH){ s=n->services[4]+(n->services[5]<<8)+(n->services[6]<<16); s|=0x00202020; switch(s){ case SIP_D2U: proto=PROTO_UDP; break; case SIP_D2T: proto=PROTO_TCP; break; case SIP_D2S: proto=PROTO_SCTP; break; default: return -1; } }else{ return -1; } }else if (n->services_len==8){ /*SIPS+D2T */ s=n->services[0]+(n->services[1]<<8)+(n->services[2]<<16)+ (n->services[3]<<24); s|=0x20202020; if (s==SIPS_SCH){ s=n->services[4]+(n->services[5]<<8)+(n->services[6]<<16)+ (n->services[7]<<24); s|=0x20202020; if (s==SIPS_D2T){ proto=PROTO_TLS; } }else{ return -1; } }else{ return -1; } return proto; } inline static int proto_pref_score(char proto) { if ((proto>=PROTO_UDP) && (proto<= PROTO_LAST)) return naptr_proto_pref[(int)proto]; return 0; } /* returns true if we support the protocol */ int naptr_proto_supported(char proto) { if (proto_pref_score(proto)<0) return 0; switch(proto){ case PROTO_UDP: return 1; #ifdef USE_TCP case PROTO_TCP: return !tcp_disable; #ifdef USE_TLS case PROTO_TLS: return !tls_disable; #endif /* USE_TLS */ #endif /* USE_TCP */ #ifdef USE_SCTP case PROTO_SCTP: return !sctp_disable; #endif } return 0; } /* returns true if new_proto is preferred over old_proto */ int naptr_proto_preferred(char new_proto, char old_proto) { return proto_pref_score(new_proto)>proto_pref_score(old_proto); } /* choose between 2 naptr records, should take into account local * preferences too * returns 1 if the new record was selected, 0 otherwise */ int naptr_choose (struct naptr_rdata** crt, char* crt_proto, struct naptr_rdata* n , char n_proto) { #ifdef NAPTR_DBG DBG("naptr_choose(o: %d w: %d p:%d , o: %d w:%d p:%d)\n", *crt?(int)(*crt)->order:-1, *crt?(int)(*crt)->pref:-1, (int)*crt_proto, (int)n->order, (int)n->pref, (int)n_proto); #endif if ((*crt==0) || ((*crt_proto!=n_proto) && ( naptr_proto_preferred(n_proto, *crt_proto))) ) goto change; if (!naptr_proto_preferred(*crt_proto, n_proto) && ((n->order<(*crt)->order) || ((n->order== (*crt)->order) && (n->pref < (*crt)->pref)))){ goto change; } #ifdef NAPTR_DBG DBG("naptr_choose: no change\n"); #endif return 0; change: #ifdef NAPTR_DBG DBG("naptr_choose: changed\n"); #endif *crt_proto=n_proto; *crt=n; return 1; } #endif /* USE_NAPTR */ /* internal sip srv resolver: resolves a host name trying: * - SRV lookup if the address is not an ip *port==0. The result of the SRV * query will be used for an A/AAAA lookup. * - normal A/AAAA lookup (either fallback from the above or if *port!=0 * and *proto!=0 or port==0 && proto==0) * when performing SRV lookup (*port==0) it will use *proto to look for * tcp or udp hosts, otherwise proto is unused; if proto==0 => no SRV lookup * If zt is set, name will be assumed to be 0 terminated and some copy * operations will be avoided. * If is_srv is set it will assume name has the srv prefixes for sip already * appended and it's already 0-term'ed; if not it will append them internally. * If ars !=0, it will first try to look through them and only if the SRV * record is not found it will try doing a DNS query (ars will not be * freed, the caller should take care of them) * returns: hostent struct & *port filled with the port from the SRV record; * 0 on error */ struct hostent* srv_sip_resolvehost(str* name, int zt, unsigned short* port, char* proto, int is_srv, struct rdata* ars) { struct hostent* he; struct ip_addr* ip; static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups and null. term strings */ struct rdata* l; struct srv_rdata* srv; struct rdata* srv_head; char* srv_target; char srv_proto; /* init */ srv_head=0; srv_target=0; if (name->len >= MAX_DNS_NAME) { LOG(L_ERR, "sip_resolvehost: domain name too long\n"); he=0; goto end; } #ifdef RESOLVE_DBG DBG("srv_sip_resolvehost: %.*s:%d proto=%d\n", name->len, name->s, port?(int)*port:-1, proto?(int)*proto:-1); #endif if (is_srv){ /* skip directly to srv resolving */ srv_proto=(proto)?*proto:0; *port=(srv_proto==PROTO_TLS)?SIPS_PORT:SIP_PORT; if (zt){ srv_target=name->s; /* name.s must be 0 terminated in this case */ }else{ memcpy(tmp, name->s, name->len); tmp[name->len] = '\0'; srv_target=tmp; } goto do_srv; /* skip to the actual srv query */ } if (proto){ /* makes sure we have a protocol set*/ if (*proto==0) *proto=srv_proto=PROTO_UDP; /* default */ else srv_proto=*proto; }else{ srv_proto=PROTO_UDP; } /* try SRV if no port specified (draft-ietf-sip-srv-06) */ if ((port)&&(*port==0)){ *port=(srv_proto==PROTO_TLS)?SIPS_PORT:SIP_PORT; /* just in case we don't find another */ /* check if it's an ip address */ if (((ip=str2ip(name))!=0) #ifdef USE_IPV6 || ((ip=str2ip6(name))!=0) #endif ){ /* we are lucky, this is an ip address */ he=ip_addr2he(name, ip); goto end; } if ((name->len+SRV_MAX_PREFIX_LEN+1)>MAX_DNS_NAME){ LOG(L_WARN, "WARNING: sip_resolvehost: domain name too long (%d)," " unable to perform SRV lookup\n", name->len); }else{ switch(srv_proto){ case PROTO_NONE: /* no proto specified, use udp */ if (proto) *proto=PROTO_UDP; /* no break */ case PROTO_UDP: memcpy(tmp, SRV_UDP_PREFIX, SRV_UDP_PREFIX_LEN); memcpy(tmp+SRV_UDP_PREFIX_LEN, name->s, name->len); tmp[SRV_UDP_PREFIX_LEN + name->len] = '\0'; break; case PROTO_TCP: memcpy(tmp, SRV_TCP_PREFIX, SRV_TCP_PREFIX_LEN); memcpy(tmp+SRV_TCP_PREFIX_LEN, name->s, name->len); tmp[SRV_TCP_PREFIX_LEN + name->len] = '\0'; break; case PROTO_TLS: memcpy(tmp, SRV_TLS_PREFIX, SRV_TLS_PREFIX_LEN); memcpy(tmp+SRV_TLS_PREFIX_LEN, name->s, name->len); tmp[SRV_TLS_PREFIX_LEN + name->len] = '\0'; break; case PROTO_SCTP: memcpy(tmp, SRV_SCTP_PREFIX, SRV_SCTP_PREFIX_LEN); memcpy(tmp+SRV_SCTP_PREFIX_LEN, name->s, name->len); tmp[SRV_SCTP_PREFIX_LEN + name->len] = '\0'; break; default: LOG(L_CRIT, "BUG: sip_resolvehost: unknown proto %d\n", srv_proto); he=0; goto end; } srv_target=tmp; do_srv: /* try to find the SRV records inside previous ARs first*/ for (l=ars; l; l=l->next){ if (l->type!=T_SRV) continue; srv=(struct srv_rdata*) l->rdata; if (srv==0){ LOG(L_CRIT, "sip_resolvehost: BUG: null rdata\n"); /* cleanup on exit only */ break; } he=resolvehost(srv->name); if (he!=0){ /* we found it*/ #ifdef RESOLVE_DBG DBG("sip_resolvehost: found SRV(%s) = %s:%d in AR\n", srv_target, srv->name, srv->port); #endif *port=srv->port; /* cleanup on exit */ goto end; } } srv_head=get_record(srv_target, T_SRV, RES_ONLY_TYPE); for(l=srv_head; l; l=l->next){ if (l->type!=T_SRV) continue; /*should never happen*/ srv=(struct srv_rdata*) l->rdata; if (srv==0){ LOG(L_CRIT, "sip_resolvehost: BUG: null rdata\n"); /* cleanup on exit only */ break; } he=resolvehost(srv->name); if (he!=0){ /* we found it*/ #ifdef RESOLVE_DBG DBG("sip_resolvehost: SRV(%s) = %s:%d\n", srv_target, srv->name, srv->port); #endif *port=srv->port; /* cleanup on exit */ goto end; } } if (is_srv){ /* if the name was already into SRV format it doesn't make * any sense to fall back to A/AAAA */ he=0; goto end; } /* cleanup on exit */ #ifdef RESOLVE_DBG DBG("sip_resolvehost: no SRV record found for %.*s," " trying 'normal' lookup...\n", name->len, name->s); #endif } } /*skip_srv:*/ if (likely(!zt)){ memcpy(tmp, name->s, name->len); tmp[name->len] = '\0'; he=resolvehost(tmp); }else{ he=resolvehost(name->s); } end: #ifdef RESOLVE_DBG DBG("srv_sip_resolvehost: returning %p (%.*s:%d proto=%d)\n", he, name->len, name->s, port?(int)*port:-1, proto?(int)*proto:-1); #endif if (srv_head) free_rdata_list(srv_head); return he; } #ifdef USE_NAPTR /* iterates over a naptr rr list, returning each time a "good" naptr record * is found.( srv type, no regex and a supported protocol) * params: * naptr_head - naptr rr list head * tried - bitmap used to keep track of the already tried records * (no more then sizeof(tried)*8 valid records are * ever walked * srv_name - if succesfull, it will be set to the selected record * srv name (naptr repl.) * proto - if succesfull it will be set to the selected record * protocol * returns 0 if no more records found or a pointer to the selected record * and sets protocol and srv_name * WARNING: when calling first time make sure you run first * naptr_iterate_init(&tried) */ struct rdata* naptr_sip_iterate(struct rdata* naptr_head, naptr_bmp_t* tried, str* srv_name, char* proto) { int i, idx; struct rdata* l; struct rdata* l_saved; struct naptr_rdata* naptr; struct naptr_rdata* naptr_saved; char saved_proto; char naptr_proto; idx=0; naptr_proto=PROTO_NONE; naptr_saved=0; l_saved=0; saved_proto=0; i=0; for(l=naptr_head; l && (inext){ if (l->type!=T_NAPTR) continue; naptr=(struct naptr_rdata*) l->rdata; if (naptr==0){ LOG(L_CRIT, "naptr_iterate: BUG: null rdata\n"); goto end; } /* check if valid and get proto */ if ((naptr_proto=naptr_get_sip_proto(naptr))<=0) continue; if (*tried& (1<repl_len, naptr->repl, (int)naptr_proto); #endif if ((naptr_proto_supported(naptr_proto))){ if (naptr_choose(&naptr_saved, &saved_proto, naptr, naptr_proto)) idx=i; l_saved=l; } i++; } if (naptr_saved){ /* found something */ #ifdef NAPTR_DBG DBG("naptr_iterate: choosed NAPTR rr %.*s, proto %d" " tried: 0x%x\n", naptr_saved->repl_len, naptr_saved->repl, (int)saved_proto, *tried); #endif *tried|=1<s=naptr_saved->repl; srv_name->len=naptr_saved->repl_len; return l_saved; } end: return 0; } /* internal sip naptr resolver function: resolves a host name trying: * - NAPTR lookup if the address is not an ip and *proto==0 and *port==0. * The result of the NAPTR query will be used for a SRV lookup * - SRV lookup if the address is not an ip *port==0. The result of the SRV * query will be used for an A/AAAA lookup. * - normal A/AAAA lookup (either fallback from the above or if *port!=0 * and *proto!=0 or port==0 && proto==0) * when performing SRV lookup (*port==0) it will use proto to look for * tcp or udp hosts, otherwise proto is unused; if proto==0 => no SRV lookup * returns: hostent struct & *port filled with the port from the SRV record; * 0 on error */ struct hostent* naptr_sip_resolvehost(str* name, unsigned short* port, char* proto) { struct hostent* he; struct ip_addr* ip; static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups and null. term strings */ struct rdata* l; struct rdata* naptr_head; char n_proto; str srv_name; naptr_bmp_t tried_bmp; /* tried bitmap */ naptr_head=0; he=0; if (name->len >= MAX_DNS_NAME) { LOG(L_ERR, "naptr_sip_resolvehost: domain name too long\n"); goto end; } /* try NAPTR if no port or protocol is specified and NAPTR lookup is * enabled */ if (port && proto && (*proto==0) && (*port==0)){ *proto=PROTO_UDP; /* just in case we don't find another */ if ( ((ip=str2ip(name))!=0) #ifdef USE_IPV6 || ((ip=str2ip6(name))!=0) #endif ){ /* we are lucky, this is an ip address */ he=ip_addr2he(name,ip); *port=SIP_PORT; goto end; } memcpy(tmp, name->s, name->len); tmp[name->len] = '\0'; naptr_head=get_record(tmp, T_NAPTR, RES_AR); naptr_iterate_init(&tried_bmp); while((l=naptr_sip_iterate(naptr_head, &tried_bmp, &srv_name, &n_proto))!=0){ if ((he=srv_sip_resolvehost(&srv_name, 1, port, proto, 1, l))!=0){ *proto=n_proto; return he; } } /*clean up on exit*/ #ifdef RESOLVE_DBG DBG("naptr_sip_resolvehost: no NAPTR record found for %.*s," " trying SRV lookup...\n", name->len, name->s); #endif } /* fallback to normal srv lookup */ he=srv_sip_resolvehost(name, 0, port, proto, 0, 0); end: if (naptr_head) free_rdata_list(naptr_head); return he; } #endif /* USE_NAPTR */ /* resolves a host name trying: * - NAPTR lookup if enabled, the address is not an ip and *proto==0 and * *port==0. The result of the NAPTR query will be used for a SRV lookup * - SRV lookup if the address is not an ip *port==0. The result of the SRV * query will be used for an A/AAAA lookup. * - normal A/AAAA lookup (either fallback from the above or if *port!=0 * and *proto!=0 or port==0 && proto==0) * when performing SRV lookup (*port==0) it will use *proto to look for * tcp or udp hosts, otherwise proto is unused; if proto==0 => no SRV lookup * * returns: hostent struct & *port filled with the port from the SRV record; * 0 on error */ struct hostent* _sip_resolvehost(str* name, unsigned short* port, char* proto) { struct hostent* res = NULL; #ifdef USE_NAPTR if (cfg_get(core, core_cfg, dns_try_naptr)) res = naptr_sip_resolvehost(name, port, proto); #endif res = srv_sip_resolvehost(name, 0, port, proto, 0, 0); if( unlikely(!res) ){ /* failed DNS request */ counter_inc(dns_cnts_h.failed_dns_req); } return res; } /* resolve host, port, proto using sip rules (e.g. use SRV if port=0 a.s.o) * and write the result in the sockaddr_union to * returns -1 on error (resolve failed), 0 on success */ int sip_hostport2su(union sockaddr_union* su, str* name, unsigned short port, char* proto) { struct hostent* he; he=sip_resolvehost(name, &port, proto); if (he==0){ ser_error=E_BAD_ADDRESS; LOG(L_ERR, "ERROR: sip_hostport2su: could not resolve hostname:" " \"%.*s\"\n", name->len, name->s); goto error; } /* port filled by sip_resolvehost if empty*/ if (hostent2su(su, he, 0, port)<0){ ser_error=E_BAD_ADDRESS; goto error; } return 0; error: return -1; }