/* $Id: resolve.c 1782 2007-03-09 13:04:51Z bogdan_iancu $ * * Copyright (C) 2001-2003 FhG Fokus * Copyright (C) 2005-2007 Voice Sistem S.R.L. * * This file is part of openser, a free SIP server. * * openser 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 * * openser 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) * 2007-01-25 support for DNS failover added (bogdan) */ #include #include #include #include #include #include "mem/mem.h" #include "mem/shm_mem.h" #include "resolve.h" #include "dprint.h" #include "ut.h" #include "ip_addr.h" #include "globals.h" #include "blacklists.h" /* stuff related to DNS failover */ #define DNS_NODE_SRV 1 #define DNS_NODE_A 2 struct dns_val { unsigned int ival; char *sval; }; /* mallocs for local stuff */ #define local_malloc pkg_malloc #define local_free pkg_free int dns_try_ipv6=1; /* default on */ /* declared in globals.h */ int dns_retr_time=-1; int dns_retr_no=-1; int dns_servers_no=-1; int dns_search_list=-1; int disable_dns_blacklist=0; static struct bl_head *failover_bl=0; #define DNS_REVOLVER_BL_ID 17 #define DNS_REVOLVER_BL_NAME "dns" #define DNS_BL_EXPIRE 4*60 /* 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 */ int resolv_init() { res_init(); #ifdef HAVE_RESOLV_RES if (dns_retr_time>0) _res.retrans=dns_retr_time; if (dns_retr_no>0) _res.retry=dns_retr_no; if (dns_servers_no>=0) _res.nscount=dns_servers_no; if (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; } int resolv_blacklist_init() { str name = str_init(DNS_REVOLVER_BL_NAME); if (!disable_dns_blacklist) { failover_bl = create_bl_head( DNS_REVOLVER_BL_ID, BL_DO_EXPIRE|BL_BY_DEFAULT, 0, 0, &name); if (failover_bl==NULL) { LOG(L_ERR,"ERROR:resolv_blacklist_init: failed to create " "blacklist\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(p=end)?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 * 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* rdata) { struct srv_rdata* srv; int len; srv=0; if ((rdata+6)>=end) goto error; srv=(struct srv_rdata*)local_malloc(sizeof(struct srv_rdata)); if (srv==0){ LOG(L_ERR, "ERROR: dns_srv_parser: out of memory\n"); goto error; } memcpy((void*)&srv->priority, rdata, 2); memcpy((void*)&srv->weight, rdata+2, 2); memcpy((void*)&srv->port, rdata+4, 2); rdata+=6; srv->priority=ntohs(srv->priority); srv->weight=ntohs(srv->weight); srv->port=ntohs(srv->port); if ((len=dn_expand(msg, end, rdata, srv->name, MAX_DNS_NAME-1))==-1) goto error; /* add terminating 0 ? (warning: len=compressed name len) */ 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 * 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* rdata) { struct naptr_rdata* naptr; naptr = 0; if ((rdata + 7) >= end) goto error; naptr=(struct naptr_rdata*)local_malloc(sizeof(struct naptr_rdata)); if (naptr == 0){ LOG(L_ERR, "ERROR: dns_naptr_parser: out of memory\n"); goto error; } memcpy((void*)&naptr->order, rdata, 2); naptr->order=ntohs(naptr->order); memcpy((void*)&naptr->pref, rdata + 2, 2); naptr->pref=ntohs(naptr->pref); naptr->flags_len = (int)rdata[4]; if ((rdata + 7 + naptr->flags_len) >= end) goto error; memcpy((void*)&naptr->flags, rdata + 5, naptr->flags_len); naptr->services_len = (int)rdata[5 + naptr->flags_len]; if ((rdata + 7 + naptr->flags_len + naptr->services_len) >= end) goto error; memcpy((void*)&naptr->services, rdata + 6 + naptr->flags_len, naptr->services_len); naptr->regexp_len = (int)rdata[6 + naptr->flags_len + naptr->services_len]; if ((rdata + 7 + naptr->flags_len + naptr->services_len + naptr->regexp_len) >= end) goto error; memcpy((void*)&naptr->regexp, rdata + 7 + naptr->flags_len + naptr->services_len, naptr->regexp_len); rdata = rdata + 7 + naptr->flags_len + naptr->services_len + naptr->regexp_len; naptr->repl_len=dn_expand(msg, end, rdata, naptr->repl, MAX_DNS_NAME-1); if ( naptr->repl_len==(unsigned int)-1 ) goto error; /* add terminating 0 ? (warning: len=compressed name len) */ 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; cname=0; cname=(struct cname_rdata*)local_malloc(sizeof(struct cname_rdata)); if(cname==0){ LOG(L_ERR, "ERROR: dns_cname_parser: out of memory\n"); goto error; } if ((len=dn_expand(msg, end, rdata, cname->name, MAX_DNS_NAME-1))==-1) goto error; 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* end) { struct a_rdata* a; if (rdata+4>=end) 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* end) { struct aaaa_rdata* aaaa; if (rdata+16>=end) 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; } /* RFC1035: * * is a single length octet followed by that number of characters. * TXT-DATA One or more s. * * We only take the first string here. */ /* parses a TXT record into a txt_rdata structure */ struct txt_rdata* dns_txt_parser( unsigned char* msg, unsigned char* end, unsigned char* rdata) { struct txt_rdata* txt; unsigned int len; txt=0; txt=(struct txt_rdata*)local_malloc(sizeof(struct txt_rdata)); if(txt==0){ LOG(L_ERR, "ERROR: dns_txt_parser: out of memory\n"); goto error; } len = *rdata; if (rdata + 1 + len >= end) goto error; /* something fishy in the record */ if (len >= sizeof(txt->txt)) goto error; /* not enough space? */ memcpy(txt->txt, rdata+1, len); txt->txt[len] = 0; /* 0-terminate string */ return txt; error: if (txt) local_free(txt); return 0; } /* EBL Record * * 0 1 2 3 4 5 6 7 * +--+--+--+--+--+--+--+--+ * | POSITION | * +--+--+--+--+--+--+--+--+ * / SEPARATOR / * +--+--+--+--+--+--+--+--+ * / APEX / * +--+--+--+--+--+--+--+--+ */ /* parses a EBL record into a ebl_rdata structure */ struct ebl_rdata* dns_ebl_parser( unsigned char* msg, unsigned char* end, unsigned char* rdata) { struct ebl_rdata* ebl; int len; ebl=0; ebl=(struct ebl_rdata*)local_malloc(sizeof(struct ebl_rdata)); if(ebl==0){ LOG(L_ERR, "ERROR: dns_ebl_parser: out of memory\n"); goto error; } len = *rdata; if (rdata + 1 + len >= end) goto error; /* something fishy in the record */ ebl->position = *rdata; if ( ebl->position > 15 ) goto error; /* doesn't make sense: E.164 numbers can't be longer */ rdata++; ebl->separator_len = (int) *rdata; rdata++; if ((rdata + 1 + ebl->separator_len) >= end) goto error; memcpy((void*)&ebl->separator, rdata, ebl->separator_len); rdata += ebl->separator_len; ebl->apex_len=dn_expand(msg, end, rdata, ebl->apex, MAX_DNS_NAME-1); if ( ebl->apex_len==(unsigned int)-1 ) goto error; ebl->apex[ebl->apex_len] = 0; /* 0-terminate string */ return ebl; error: if (ebl) local_free(ebl); return 0; } /* frees completely a struct rdata list */ void free_rdata_list(struct rdata* head) { struct rdata* l; struct rdata* next_l; for( l=head; l ; l=next_l) { next_l = l->next; /* free the parsed rdata*/ if (l->rdata) local_free(l->rdata); local_free(l); } } /* 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 size; int qno, answers_no; int r; int ans_len; static union dns_query buff; unsigned char* p; unsigned char* t; unsigned char* end; static unsigned char answer[ANS_SIZE]; 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; size=res_search(name, C_IN, type, buff.buff, sizeof(buff)); if (size<0) { DBG("get_record: lookup(%s, %d) failed\n", name, type); goto not_found; } else if ((unsigned int)size > sizeof(buff)) size=sizeof(buff); head=rd=0; last=crt=&head; p=buff.buff+DNS_HDR_SIZE; end=buff.buff+size; if (p>=end) goto error_boundary; qno=ntohs((unsigned short)buff.hdr.qdcount); for (r=0; r=end) { LOG(L_ERR, "ERROR: get_record: p>=end\n"); goto error; } }; answers_no=ntohs((unsigned short)buff.hdr.ancount); ans_len=ANS_SIZE; t=answer; for (r=0; (r=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; /* check for type */ /* if (rtype!=type){ LOG(L_ERR, "WARNING: get_record: wrong type in answer (%d!=%d)\n", rtype, type); p+=rdlength; continue; } */ /* expand the "type" record (rdata)*/ rd=(struct rdata*) local_malloc(sizeof(struct rdata)); if (rd==0){ LOG(L_ERR, "ERROR: get_record: out of memory\n"); goto error; } rd->type=rtype; rd->class=class; rd->ttl=ttl; rd->next=0; switch(rtype){ case T_SRV: srv_rd= dns_srv_parser(buff.buff, end, p); rd->rdata=(void*)srv_rd; if (srv_rd==0) goto error_parse; /* insert sorted into the list */ for (crt=&head; *crt; crt= &((*crt)->next)){ 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 elem*/ skip: /* insert here */ rd->next=*crt; *crt=rd; break; case T_A: rd->rdata=(void*) dns_a_parser(p,end); if (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,end); if (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(rd->rdata==0) goto error_parse; *last=rd; last=&(rd->next); break; case T_NAPTR: rd->rdata=(void*) dns_naptr_parser(buff.buff, end, p); if(rd->rdata==0) goto error_parse; *last=rd; last=&(rd->next); break; case T_TXT: rd->rdata=(void*) dns_txt_parser(buff.buff, end, p); if(rd->rdata==0) goto error_parse; *last=rd; last=&(rd->next); break; case T_EBL: rd->rdata=(void*) dns_ebl_parser(buff.buff, end, p); if(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; } 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 \n"); 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: return 0; } static inline int get_naptr_proto(struct naptr_rdata *n) { #ifdef USE_TLS if (n->services[3]=='s' || n->services[3]=='S' ) return PROTO_TLS; #endif switch (n->services[n->services_len-1]) { case 'U': case 'u': return PROTO_UDP; break; #ifdef USE_TCP case 'T': case 't': return PROTO_TCP; break; #endif } LOG(L_CRIT,"BUG:get_naptr_proto: failed to detect proto\n"); return PROTO_NONE; } static inline int srv2dns_node(struct rdata *head, struct dns_node **dn) { unsigned int mem; unsigned int l; struct rdata *r; struct dns_node *n; char *p; /* calculate how much mem is required */ mem = sizeof(struct dns_node); for( r=head,l=0 ; r ; r=r->next,l++ ) mem +=sizeof(struct dns_val) + get_naptr(r)->repl_len + 1; n = (struct dns_node*)shm_malloc(mem); if (n==NULL) { LOG(L_ERR,"ERROR:srv2dns_node: no more shm mem (%d)\n", mem); return -1; } n->type = DNS_NODE_SRV; n->size = mem; n->idx = 0; n->no = l; n->kids = *dn; *dn = n; n->vals = (struct dns_val*)(n+1); p = (char*)(n->vals+l); for( r=head,l=0 ; r ; r=r->next,l++ ) { n->vals[l].ival = get_naptr_proto( get_naptr(r) ); n->vals[l].sval = p; memcpy( p, get_naptr(r)->repl, get_naptr(r)->repl_len ); p += get_naptr(r)->repl_len; *(p++) = 0; } return 0; } static inline int a2dns_node(struct rdata *head, struct dns_node **dn) { unsigned int mem; unsigned int l; struct rdata *r; struct dns_node *n; char *p; /* calculate how much mem is required */ mem = sizeof(struct dns_node); for( r=head,l=0 ; r ; r=r->next,l++ ) { get_srv(r)->name_len = strlen(get_srv(r)->name); mem +=sizeof(struct dns_val) + get_srv(r)->name_len + 1; } n = (struct dns_node*)shm_malloc(mem); if (n==NULL) { LOG(L_ERR,"ERROR:a2dns_node: no more shm mem (%d)\n", mem); return -1; } n->type = DNS_NODE_A; n->size = mem; n->idx = 0; n->no = l; n->kids = 0; *dn = n; n->vals = (struct dns_val*)(n+1); p = (char*)(n->vals+l); for( r=head,l=0 ; r ; r=r->next,l++ ) { n->vals[l].ival = get_srv(r)->port; n->vals[l].sval = p; memcpy( p, get_srv(r)->name, get_srv(r)->name_len ); p += get_srv(r)->name_len; *(p++) = 0; } return 0; } static inline struct hostent* do_srv_lookup(char *name, unsigned short* port, struct dns_node **dn) { struct hostent *he; struct srv_rdata *srv; struct rdata *head; struct rdata *rd; /* perform SRV lookup */ head = get_record( name, T_SRV); for( rd=head; rd ; rd=rd->next ) { if (rd->type!=T_SRV) continue; /*should never happen*/ srv = (struct srv_rdata*) rd->rdata; if (srv==0) { LOG(L_CRIT, "BUG:do_srv_lookup: null rdata\n"); free_rdata_list(head); return 0; } he = resolvehost(srv->name, 1); if ( he!=0 ) { DBG("DEBUG:do_srv_lookup: SRV(%s) = %s:%d\n", name, srv->name, srv->port); *port=srv->port; if (dn && rd->next && a2dns_node( rd->next, dn)==-1) *dn = 0; free_rdata_list(head); return he; } } if (head) free_rdata_list(head); return 0; } #define naptr_prio(_naptr) \ ((unsigned int)((((_naptr)->order) << 16) + ((_naptr)->pref))) static inline void filter_and_sort_naptr( struct rdata** head_p, struct rdata** filtered_p, int is_sips) { struct naptr_rdata *naptr; struct rdata *head; struct rdata *last; struct rdata *out; struct rdata *l, *ln, *it, *itp; unsigned int prio; char p; head = 0; last = 0; out = 0; for( l=*head_p ; l ; l=ln ) { ln = l->next; if (l->type != T_NAPTR) goto skip0; /*should never happen*/ naptr = (struct naptr_rdata*)l->rdata; if (naptr == 0) { LOG(L_CRIT, "BUG:filter_and_sort_naptr: null rdata\n"); goto skip0; } /* first filter out by flag and service */ if (naptr->flags_len!=1||(naptr->flags[0]!='s'&&naptr->flags[0]!='S')) goto skip; if (naptr->repl_len==0 || naptr->regexp_len!=0 ) goto skip; if ( (is_sips || naptr->services_len!=7 || strncasecmp(naptr->services,"sip+d2",6) ) && ( #ifdef USE_TLS tls_disable || #endif naptr->services_len!=8 || strncasecmp(naptr->services,"sips+d2",7))) goto skip; p = naptr->services[naptr->services_len-1]; /* by default we do not support SCTP */ if ( p!='U' && p!='u' #ifdef USE_TCP && (tcp_disable || (p!='T' && p!='t')) #endif ) goto skip; /* is it valid? (SIPS+D2U is not!) */ if ( naptr->services_len==8 && (p=='U' || p=='u')) goto skip; DBG("DEBUG:filter_and_sort_naptr: found valid %.*s -> %s\n", (int)naptr->services_len,naptr->services, naptr->repl); /* this is a supported service -> add it according to order to the * new head list */ prio = naptr_prio(get_naptr(l)); if (head==0) { head = last = l; l->next = 0; } else if ( naptr_prio(get_naptr(head)) >= prio ) { l->next = head; head = l; } else if ( prio >= naptr_prio(get_naptr(last)) ) { l->next = 0; last->next = l; last = l; } else { for( itp=head,it=head->next ; it && it->next ; itp=it,it=it->next ){ if ( prio <= naptr_prio(get_naptr(it))) break; } l->next = itp->next; itp->next = l; } continue; skip: DBG("DEBUG:filter_and_sort_naptr: skipping %.*s -> %s\n", (int)naptr->services_len, naptr->services, naptr->repl); skip0: l->next = out; out = l; } *head_p = head; *filtered_p = out; } #if 0 struct hostent* sip_resolvehost(str* name, unsigned short* port, int *proto, int is_sips) { static char tmp[MAX_DNS_NAME]; struct ip_addr *ip; struct rdata *head; struct rdata *rd; struct hostent* he; if ( (is_sips) #ifdef USE_TLS && (tls_disable) #endif ) { LOG(L_ERR, "ERROR:sip_resolvehost2: cannot resolve SIPS as no TLS " "support is configured\n"); return 0; } /* 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 */ if (proto && *proto==PROTO_NONE) *proto = (is_sips)?PROTO_TLS:PROTO_UDP; if (port && *port==0) *port = (is_sips||((*proto)==PROTO_TLS))?SIPS_PORT:SIP_PORT; return ip_addr2he(name,ip); } /* do we have a port? */ if ( !port || (*port)!=0 ) { /* have port -> no NAPTR, no SRV lookup, just A record lookup */ DBG("DEBUG:sip_resolvehost2: has port -> do A record lookup!\n"); /* set default PROTO if not set */ if (proto && *proto==PROTO_NONE) *proto = (is_sips)?PROTO_TLS:PROTO_UDP; goto do_a; } /* no port... what about proto? */ if ( !proto || (*proto)!=PROTO_NONE ) { /* have proto, but no port -> do SRV lookup */ DBG("DEBUG:sip_resolvehost2: no port, has proto -> do SRV lookup!\n"); if (is_sips && (*proto)!=PROTO_TLS) { LOG(L_ERR, "ERROR:sip_resolvehost2: forced proto %d not matching " "sips uri\n", *proto); return 0; } goto do_srv; } DBG("DEBUG:sip_resolvehost2: no port, no proto -> do NAPTR lookup!\n"); /* no proto, no port -> do NAPTR lookup */ if (name->len >= MAX_DNS_NAME) { LOG(L_ERR, "ERROR:sip_resolvehost2: domain name too long\n"); return 0; } memcpy(tmp, name->s, name->len); tmp[name->len] = '\0'; /* do NAPTR lookup */ head = get_record( tmp, T_NAPTR); if (head) { /* filter and sort the records */ filter_and_sort_naptr( &head, &rd, is_sips); /* free what is useless */ free_rdata_list( rd ); /* process the NAPTR records */ for( rd=head ; rd ; rd=rd->next ) { he = do_srv_lookup( get_naptr(rd)->repl, port ); if ( he ) { *proto = get_naptr_proto( get_naptr(rd) ); DBG("DEBUG:sip_resolvehost2: found!\n"); free_rdata_list(head); return he; } } if (head) free_rdata_list(head); } DBG("DEBUG:sip_resolvehost2: no valid NAPTR record found for %.*s," " trying direct SRV lookup...\n", name->len, name->s); *proto = (is_sips)?PROTO_TLS:PROTO_UDP; do_srv: if ((name->len+SRV_MAX_PREFIX_LEN+1)>MAX_DNS_NAME) { LOG(L_WARN, "WARNING:sip_resolvehost2: domain name too long (%d)," " unable to perform SRV lookup\n", name->len); /* set defaults */ *port = (is_sips)?SIPS_PORT:SIP_PORT; goto do_a; } switch (*proto) { 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; #ifdef USE_TCP case PROTO_TCP: if (tcp_disable) goto err_proto; 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; #endif #ifdef USE_TLS case PROTO_TLS: if (tls_disable) goto err_proto; 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; #endif default: goto err_proto; } he = do_srv_lookup( tmp, port ); if (he) return he; DBG("DEBUG:sip_resolvehost2: no valid SRV record found for %s," " trying A record lookup...\n", tmp); /* set default port */ *port = (is_sips||((*proto)==PROTO_TLS))?SIPS_PORT:SIP_PORT; do_a: /* do A record lookup */ if (name->len >= MAX_DNS_NAME) { LOG(L_ERR, "ERROR:sip_resolvehost2: domain name too long\n"); return 0; } memcpy(tmp, name->s, name->len); tmp[name->len] = '\0'; he = resolvehost(tmp,1); return he; err_proto: LOG(L_ERR, "ERROR:sip_resolvehost: unsupported proto %d\n", *proto); return 0; } #endif struct hostent* sip_resolvehost( str* name, unsigned short* port, unsigned short *proto, int is_sips, struct dns_node **dn) { static char tmp[MAX_DNS_NAME]; struct ip_addr *ip; struct rdata *head; struct rdata *rd; struct hostent* he; if ( (is_sips) #ifdef USE_TLS && (tls_disable) #endif ) { LOG(L_ERR, "ERROR:sip_resolvehost2: cannot resolve SIPS as no TLS " "support is configured\n"); return 0; } if (dn) *dn = 0; /* 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 */ if (proto && *proto==PROTO_NONE) *proto = (is_sips)?PROTO_TLS:PROTO_UDP; if (port && *port==0) *port = (is_sips||((*proto)==PROTO_TLS))?SIPS_PORT:SIP_PORT; return ip_addr2he(name,ip); } /* do we have a port? */ if ( !port || (*port)!=0 ) { /* have port -> no NAPTR, no SRV lookup, just A record lookup */ DBG("DEBUG:sip_resolvehost2: has port -> do A record lookup!\n"); /* set default PROTO if not set */ if (proto && *proto==PROTO_NONE) *proto = (is_sips)?PROTO_TLS:PROTO_UDP; goto do_a; } /* no port... what about proto? */ if ( !proto || (*proto)!=PROTO_NONE ) { /* have proto, but no port -> do SRV lookup */ DBG("DEBUG:sip_resolvehost2: no port, has proto -> do SRV lookup!\n"); if (is_sips && (*proto)!=PROTO_TLS) { LOG(L_ERR, "ERROR:sip_resolvehost2: forced proto %d not matching " "sips uri\n", *proto); return 0; } goto do_srv; } DBG("DEBUG:sip_resolvehost2: no port, no proto -> do NAPTR lookup!\n"); /* no proto, no port -> do NAPTR lookup */ if (name->len >= MAX_DNS_NAME) { LOG(L_ERR, "ERROR:sip_resolvehost2: domain name too long\n"); return 0; } memcpy(tmp, name->s, name->len); tmp[name->len] = '\0'; /* do NAPTR lookup */ head = get_record( tmp, T_NAPTR); if (head) { /* filter and sort the records */ filter_and_sort_naptr( &head, &rd, is_sips); /* free what is useless */ free_rdata_list( rd ); /* process the NAPTR records */ for( rd=head ; rd ; rd=rd->next ) { *proto = get_naptr_proto( get_naptr(rd) ); he = do_srv_lookup( get_naptr(rd)->repl, port, dn); if ( he ) { DBG("DEBUG:sip_resolvehost2: found!\n"); if (dn) { /* save the state of the resolver for failure cases */ if (*dn==NULL) rd = rd->next; if (rd && srv2dns_node( rd, dn)!=0) { shm_free(*dn); *dn = 0; } } free_rdata_list(head); return he; } } if (head) free_rdata_list(head); } DBG("DEBUG:sip_resolvehost2: no valid NAPTR record found for %.*s," " trying direct SRV lookup...\n", name->len, name->s); *proto = (is_sips)?PROTO_TLS:PROTO_UDP; do_srv: if ((name->len+SRV_MAX_PREFIX_LEN+1)>MAX_DNS_NAME) { LOG(L_WARN, "WARNING:sip_resolvehost2: domain name too long (%d)," " unable to perform SRV lookup\n", name->len); /* set defaults */ *port = (is_sips)?SIPS_PORT:SIP_PORT; goto do_a; } switch (*proto) { 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; #ifdef USE_TCP case PROTO_TCP: if (tcp_disable) goto err_proto; 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; #endif #ifdef USE_TLS case PROTO_TLS: if (tls_disable) goto err_proto; 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; #endif default: goto err_proto; } he = do_srv_lookup( tmp, port, dn); if (he) return he; DBG("DEBUG:sip_resolvehost2: no valid SRV record found for %s," " trying A record lookup...\n", tmp); /* set default port */ *port = (is_sips||((*proto)==PROTO_TLS))?SIPS_PORT:SIP_PORT; do_a: /* do A record lookup */ if (name->len >= MAX_DNS_NAME) { LOG(L_ERR, "ERROR:sip_resolvehost2: domain name too long\n"); return 0; } memcpy(tmp, name->s, name->len); tmp[name->len] = '\0'; he = resolvehost(tmp,1); return he; err_proto: LOG(L_ERR, "ERROR:sip_resolvehost: unsupported proto %d\n", *proto); return 0; } static inline struct hostent* get_next_he(struct dns_node **node, unsigned short *proto, unsigned short *port) { struct hostent *he; struct dns_node *n; struct dns_node *last_srv; struct dns_node *dn; if (node==NULL || *node==NULL) return 0; n = *node; last_srv = NULL; he = 0; do { switch (n->type) { case DNS_NODE_SRV: last_srv = n; if (n->kids==NULL) { /* need to resolve this SRV and get all the AAA records */ do { dn = 0; he = do_srv_lookup( n->vals[n->idx].sval, port, &dn); if (he) { *proto = n->vals[n->idx].ival; break; } n->idx++; } while(n->idx<=n->no); if (he==NULL || (he && n->idx+1==n->no) ) { /* colapse the SRV node */ shm_free(n); *node = dn; return he; } n->kids = dn; return he; } /* go for the AAA records */ n = n->kids; break; case DNS_NODE_A: /* do resolve until success */ do { he = resolvehost(n->vals[n->idx].sval,1); if (he) { *port = n->vals[n->idx].ival; break; } n->idx++; }while(n->idxno); /* found something? */ if (he==NULL || (he && n->idx+1==n->no)) { shm_free(n); /* any SRV level? */ if (last_srv==NULL) { /* nothing left */ *node = 0; return he; } last_srv->kids = 0; /* increase the index on the SRV level */ if (++last_srv->idxno) return he; /* colapse the SRV node also */ shm_free(last_srv); *node = 0; } return he; break; default: LOG(L_CRIT,"BUG:dns_get_next_ip: unknown %d node type\n", n->type); return 0; } } while(1); } void free_dns_res( struct proxy_l *p ) { if (p==NULL || p->dn==NULL) return; if (p->dn->kids) shm_free(p->dn->kids); shm_free(p->dn); p->dn = 0; } int get_next_su(struct proxy_l *p, union sockaddr_union* su, int add_to_bl) { struct hostent *he; struct bl_rule *list; struct net ip_net; int n; if (failover_bl && add_to_bl) { memset( &ip_net, 0xff , sizeof(struct net)); hostent2ip_addr( &ip_net.ip, &p->host, p->addr_idx); ip_net.mask.af = ip_net.ip.af; ip_net.mask.len = ip_net.ip.len; list = 0; n = add_rule_to_list( &list, &list, &ip_net, 0, p->port, p->proto, 0); if (n!=0) { LOG(L_ERR,"ERROR:get_next_su: failed to build bl rule\n"); } else { add_list_to_head( failover_bl, list, list, 0, DNS_BL_EXPIRE); } } /* any more available IPs in he ? */ if ( p->host.h_addr_list[++p->addr_idx] ) { /* yes -> return the IP*/ hostent2su( su, &p->host, p->addr_idx, (p->port)?p->port:SIP_PORT); return 0; } /* get a new he from DNS */ he = get_next_he( &p->dn, &p->proto, &p->port); if (he==NULL) return -1; /* replace the current he */ if (p->flags&PROXY_SHM_FLAG) { free_shm_hostent( &p->host ); n = hostent_shm_cpy(&(p->host), he); } else { free_hostent( &p->host ); n = hostent_cpy(&(p->host), he); } if (n!=0) { free_dns_res( p ); return -1; } hostent2su( su, &p->host, 0, (p->port)?p->port:SIP_PORT); p->addr_idx = 0; return 0; } static inline struct dns_node *dns_node_copy(struct dns_node *s) { struct dns_node *d; unsigned int i; d = (struct dns_node*)shm_malloc(s->size); if (d==NULL) { LOG(L_ERR,"ERROR:dns_node_copy: no more shm mem\n"); return 0; } memcpy( d, s, s->size); d->vals = (struct dns_val*)((char*)d + ((char*)s->vals-(char*)s)); for( i=0 ; ino ; i++ ) d->vals[i].sval = (char*)d + ((char*)s->vals[i].sval-(char*)s); return d; } struct dns_node *dns_res_copy(struct dns_node *s) { struct dns_node *d; d = dns_node_copy(s); if (d==NULL) return 0; if (s->kids) { d->kids = dns_node_copy(s->kids); if (d->kids==NULL) { shm_free(d); return 0; } } return d; }