/* * $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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * History: * ------- * 2003-07-29: file created (bogdan) * 2004-06-14: flag CPL_IS_STATEFUL is set now immediately after the * transaction is created (bogdan) */ #include "../../modules/tm/h_table.h" #include "../../parser/contact/parse_contact.h" #define duplicate_str( _orig_ , _new_ ) \ do {\ (_new_) = (str*)shm_malloc(sizeof(str)+(_orig_)->len);\ if (!(_new_)) goto mem_error;\ (_new_)->len = (_orig_)->len;\ (_new_)->s = (char*)((_new_))+sizeof(str);\ memcpy((_new_)->s,(_orig_)->s,(_orig_)->len);\ } while(0) #define search_and_duplicate_hdr( _intr_ , _field_ , _name_ , _sfoo_ ) \ do {\ if (!(_intr_)->_field_) {\ if (!(_intr_)->msg->_field_) { \ if (parse_headers((_intr_)->msg,_name_,0)==-1) {\ LOG(L_ERR,"ERROR:run_proxy: bad %llx hdr\n",_name_);\ goto runtime_error;\ } else if ( !(_intr_)->msg->_field_) {\ (_intr_)->_field_ = STR_NOT_FOUND;\ } else {\ (_sfoo_) = &((_intr_)->msg->_field_->body);\ duplicate_str( (_sfoo_) , (_intr_)->_field_ );\ }\ } else {\ (_sfoo_) = &((_intr_)->msg->_field_->body);\ duplicate_str( (_sfoo_) , (_intr_)->_field_ );\ }\ } else {\ (_sfoo_) = (_intr_)->_field_;\ duplicate_str( (_sfoo_) , (_intr_)->_field_ );\ }\ }while(0) static inline int parse_q(str *q, unsigned int *prio) { if (q->s[0]=='0') *prio=0; else if (q->s[0]=='1') *prio=10; else goto error; if (q->s[1]!='.') goto error; if (q->s[2]<'0' || q->s[2]>'9') goto error; *prio += q->s[2] - '0'; if (*prio>10) goto error; return 0; error: LOG(L_ERR,"ERROR:cpl-c:parse_q:bad q param <%.*s>\n",q->len,q->s); return -1; } static inline int add_contacts_to_loc_set(struct sip_msg* msg, struct location **loc_set) { struct sip_uri uri; struct contact *contacts; unsigned int prio; /* we need to have the contact header */ if (msg->contact==0) { /* find and parse the Contact header */ if ((parse_headers(msg, HDR_CONTACT_F, 0)==-1) || (msg->contact==0) ) { LOG(L_ERR,"ERROR:cpl-c:add_contacts_to_loc_set: error parsing or " "no Contact hdr found!\n"); goto error; } } /* extract from contact header the all the addresses */ if (parse_contact( msg->contact )!=0) { LOG(L_ERR,"ERROR:cpl-c:add_contacts_to_loc_set: unable to parse " "Contact hdr!\n"); goto error; } /* in contact hdr, in parsed attr, we should have a list of contacts */ if ( msg->contact->parsed ) { contacts = ((struct contact_body*)msg->contact->parsed)->contacts; for( ; contacts ; contacts=contacts->next) { /* check if the contact is a valid sip uri */ if (parse_uri( contacts->uri.s, contacts->uri.len , &uri)!=0) { continue; } /* convert the q param to int value (if any) */ if (contacts->q) { if (parse_q( &(contacts->q->body), &prio )!=0) continue; } else { prio = 10; /* set default to minimum */ } /* add the uri to location set */ if (add_location( loc_set, &contacts->uri,prio, CPL_LOC_DUPL)!=0) { LOG(L_ERR,"ERROR:cpl-c:add_contacts_to_loc_set: unable to add " "<%.*s>\n",contacts->uri.len,contacts->uri.s); } } } return 0; error: return -1; } static void reply_callback( struct cell* t, int type, struct tmcb_params* ps) { struct cpl_interpreter *intr = (struct cpl_interpreter*)(*(ps->param)); struct location *loc = 0; int rez; if (intr==0) { LOG(L_WARN,"WARNING:cpl-c:reply_callback: param=0 for callback %d," " transaction=%p \n",type,t); return; } if (type&TMCB_RESPONSE_OUT) { /* the purpose of the final reply is to trash down the interpreter * structure! it's the safest place to do that, since this callback * it's called only once per transaction for final codes (>=200) ;-) */ if (ps->code>=200) { DBG("DEBUG:cpl-c:final_reply: code=%d -------------->\n" " --------------------------> final reply received\n", ps->code); /* CPL interpretation done, call established -> destroy */ free_cpl_interpreter( intr ); /* set to zero the param callback*/ *(ps->param) = 0; } return; } else if (!(type&TMCB_ON_FAILURE)) { LOG(L_ERR,"BUG:cpl-c:reply_callback: unknown type %d\n",type); goto exit; } DBG("DEBUG:cpl-c:negativ_reply: ------------------------------>\n" " ---------------------------------> negativ reply received\n"); intr->flags |= CPL_PROXY_DONE; intr->msg = ps->req; /* if it's a redirect-> do I have to added to the location set ? */ if (intr->proxy.recurse && (ps->code)/100==3) { DBG("DEBUG:cpl-c:negativ_reply: recurse level %d processing..\n", intr->proxy.recurse); intr->proxy.recurse--; /* get the locations from the Contact */ add_contacts_to_loc_set( ps->rpl, &(intr->loc_set)); switch (intr->proxy.ordering) { case SEQUENTIAL_VAL: /* update the last_to_proxy to last location from set */ if (intr->proxy.last_to_proxy==0) { /* the pointer went through entire old set -> set it to the * updated set, from the beginning */ if (intr->loc_set==0) /* the updated set is also empty -> proxy ended */ break; intr->proxy.last_to_proxy = intr->loc_set; } while(intr->proxy.last_to_proxy->next) intr->proxy.last_to_proxy=intr->proxy.last_to_proxy->next; break; case PARALLEL_VAL: /* push the whole new location set to be proxy */ intr->proxy.last_to_proxy = intr->loc_set; break; case FIRSTONLY_VAL: intr->proxy.last_to_proxy = 0; break; } } /* the current proxying failed -> do I have another location to try ? * This applies only for SERIAL forking or if RECURSE is set */ if (intr->proxy.last_to_proxy) { /* continue proxying */ DBG("DEBUG:cpl-c:failed_reply: resuming proxying....\n"); switch (intr->proxy.ordering) { case PARALLEL_VAL: /* I get here only if I got a 3xx and RECURSE in on -> * forward to all location from location set */ intr->proxy.last_to_proxy = 0; cpl_proxy_to_loc_set(intr->msg,&(intr->loc_set),intr->flags ); break; case SEQUENTIAL_VAL: /* place a new branch to the next location from loc. set*/ loc = remove_first_location( &(intr->loc_set) ); /*print_location_set(intr->loc_set);*/ /* update (if necessary) the last_to_proxy location */ if (intr->proxy.last_to_proxy==loc) intr->proxy.last_to_proxy = 0; cpl_proxy_to_loc_set(intr->msg,&loc,intr->flags ); break; default: LOG(L_CRIT,"BUG:cpl_c:failed_reply: unexpected ordering found " "when continuing proxying (%d)\n",intr->proxy.ordering); goto exit; } /* nothing more to be done */ return; } else { /* done with proxying.... -> process the final response */ DBG("DEBUG:cpl-c:failed_reply:final_reply: got a final %d\n",ps->code); intr->ip = 0; if (ps->code==486 || ps->code==600) { /* busy response */ intr->ip = intr->proxy.busy; } else if (ps->code==408) { /* request timeout -> no response */ intr->ip = intr->proxy.noanswer; } else if (((ps->code)/100)==3) { /* redirection */ /* add to the location list all the addresses from Contact */ add_contacts_to_loc_set( ps->rpl, &(intr->loc_set)); print_location_set( intr->loc_set ); intr->ip = intr->proxy.redirect; } else { /* generic failure */ intr->ip = intr->proxy.failure; } if (intr->ip==0) intr->ip = (intr->proxy.default_)? intr->proxy.default_:DEFAULT_ACTION; if (intr->ip!=DEFAULT_ACTION) intr->ip = get_first_child( intr->ip ); if( intr->ip==DEFAULT_ACTION) rez = run_default(intr); else rez = cpl_run_script(intr); switch ( rez ) { case SCRIPT_END: /* we don't need to free the interpreter here since it will * be freed in the final_reply callback */ case SCRIPT_TO_BE_CONTINUED: return; case SCRIPT_RUN_ERROR: case SCRIPT_FORMAT_ERROR: goto exit; default: LOG(L_CRIT,"BUG:cpl-c:failed_reply: improper result %d\n", rez); goto exit; } } exit: /* in case of error the default response chosen by ser at the last * proxying will be forwarded to the UAC */ free_cpl_interpreter( intr ); /* set to zero the param callback*/ *(ps->param) = 0; return; } static inline char *run_proxy( struct cpl_interpreter *intr ) { unsigned short attr_name; unsigned short n; char *kid; char *p; int i; str *s; struct location *loc; int_str tmp; intr->proxy.ordering = PARALLEL_VAL; intr->proxy.recurse = (unsigned short)cpl_env.proxy_recurse; /* identify the attributes */ for( i=NR_OF_ATTR(intr->ip),p=ATTR_PTR(intr->ip) ; i>0 ; i-- ) { get_basic_attr( p, attr_name, n, intr, script_error); switch (attr_name) { case TIMEOUT_ATTR: if (cpl_env.timer_avp.n || cpl_env.timer_avp.s.s) { tmp.n=(int)n; if ( add_avp( AVP_TRACK_TO | cpl_env.timer_avp_type, cpl_env.timer_avp, tmp)<0) { LOG(L_ERR,"ERROR:run_proxy: unable to set " "timer AVP\n"); /* continue */ } } break; case RECURSE_ATTR: switch (n) { case NO_VAL: intr->proxy.recurse = 0; break; case YES_VAL: /* already set as default */ break; default: LOG(L_ERR,"ERROR:run_proxy: invalid value (%u) found" " for attr. RECURSE in PROXY node!\n",n); goto script_error; } break; case ORDERING_ATTR: if (n!=PARALLEL_VAL && n!=SEQUENTIAL_VAL && n!=FIRSTONLY_VAL){ LOG(L_ERR,"ERROR:run_proxy: invalid value (%u) found" " for attr. ORDERING in PROXY node!\n",n); goto script_error; } intr->proxy.ordering = n; break; default: LOG(L_ERR,"ERROR:run_proxy: unknown attribute (%d) in" "PROXY node\n",attr_name); goto script_error; } } intr->proxy.busy = intr->proxy.noanswer = 0; intr->proxy.redirect = intr->proxy.failure = intr->proxy.default_ = 0; /* this is quite an "expensive" node to run, so let's make some checking * before getting deeply into it */ for( i=0 ; iip) ; i++ ) { kid = intr->ip + KID_OFFSET(intr->ip,i); check_overflow_by_ptr( kid+SIMPLE_NODE_SIZE(kid), intr, script_error); switch ( NODE_TYPE(kid) ) { case BUSY_NODE : intr->proxy.busy = kid; break; case NOANSWER_NODE: intr->proxy.noanswer = kid; break; case REDIRECTION_NODE: intr->proxy.redirect = kid; break; case FAILURE_NODE: intr->proxy.failure = kid; break; case DEFAULT_NODE: intr->proxy.default_ = kid; break; default: LOG(L_ERR,"ERROR:run_proxy: unknown output node type" " (%d) for PROXY node\n",NODE_TYPE(kid)); goto script_error; } } /* if the location set if empty, I will go directly on failure/default */ if (intr->loc_set==0) { DBG("DEBUG:run_proxy: location set found empty -> going on " "failure/default branch\n"); if (intr->proxy.failure) return get_first_child(intr->proxy.failure); else if (intr->proxy.default_) return get_first_child(intr->proxy.default_); else return DEFAULT_ACTION; } /* if it's the first execution of a proxy node, force parsing of the needed * headers and duplicate them in shared memory */ if (!(intr->flags&CPL_PROXY_DONE)) { /* user name is already in shared memory */ /* requested URI - mandatory in SIP msg (cannot be STR_NOT_FOUND) */ s = GET_RURI( intr->msg ); duplicate_str( s , intr->ruri ); intr->flags |= CPL_RURI_DUPLICATED; /* TO header - mandatory in SIP msg (cannot be STR_NOT_FOUND) */ if (!intr->to) { if (!intr->msg->to && (parse_headers(intr->msg,HDR_TO_F,0)==-1 || !intr->msg->to)) { LOG(L_ERR,"ERROR:run_proxy: bad msg or missing TO header\n"); goto runtime_error; } s = &(get_to(intr->msg)->uri); } else { s = intr->to; } duplicate_str( s , intr->to ); intr->flags |= CPL_TO_DUPLICATED; /* FROM header - mandatory in SIP msg (cannot be STR_NOT_FOUND) */ if (!intr->from) { if (parse_from_header( intr->msg )==-1) goto runtime_error; s = &(get_from(intr->msg)->uri); } else { s = intr->from; } duplicate_str( s , intr->from ); intr->flags |= CPL_FROM_DUPLICATED; /* SUBJECT header - optional in SIP msg (can be STR_NOT_FOUND) */ if (intr->subject!=STR_NOT_FOUND) { search_and_duplicate_hdr(intr,subject,HDR_SUBJECT_F,s); if (intr->subject!=STR_NOT_FOUND) intr->flags |= CPL_SUBJECT_DUPLICATED; } /* ORGANIZATION header - optional in SIP msg (can be STR_NOT_FOUND) */ if ( intr->organization!=STR_NOT_FOUND) { search_and_duplicate_hdr(intr,organization,HDR_ORGANIZATION_F,s); if ( intr->organization!=STR_NOT_FOUND) intr->flags |= CPL_ORGANIZATION_DUPLICATED; } /* USER_AGENT header - optional in SIP msg (can be STR_NOT_FOUND) */ if (intr->user_agent!=STR_NOT_FOUND) { search_and_duplicate_hdr(intr,user_agent,HDR_USERAGENT_F,s); if (intr->user_agent!=STR_NOT_FOUND) intr->flags |= CPL_USERAGENT_DUPLICATED; } /* ACCEPT_LANGUAGE header - optional in SIP msg * (can be STR_NOT_FOUND) */ if (intr->accept_language!=STR_NOT_FOUND) { search_and_duplicate_hdr(intr,accept_language, HDR_ACCEPTLANGUAGE_F,s); if (intr->accept_language!=STR_NOT_FOUND) intr->flags |= CPL_ACCEPTLANG_DUPLICATED; } /* PRIORITY header - optional in SIP msg (can be STR_NOT_FOUND) */ if (intr->priority!=STR_NOT_FOUND) { search_and_duplicate_hdr(intr,priority,HDR_PRIORITY_F,s); if (intr->priority!=STR_NOT_FOUND) intr->flags |= CPL_PRIORITY_DUPLICATED; } /* now is the first time doing proxy, so I can still be stateless; * as proxy is done all the time stateful, I have to switch from * stateless to stateful if necessary. */ if ( !(intr->flags&CPL_IS_STATEFUL) ) { i = cpl_fct.tmb.t_newtran( intr->msg ); if (i<0) { LOG(L_ERR,"ERROR:cpl-c:run_proxy: failed to build new " "transaction!\n"); goto runtime_error; } else if (i==0) { LOG(L_ERR,"ERROR:cpl-c:run_proxy: processed INVITE is a " "retransmission!\n"); /* instead of generating an error is better just to break the * script by returning EO_SCRIPT */ return EO_SCRIPT; } intr->flags |= CPL_IS_STATEFUL; } /* as I am interested in getting the responses back - I need to install * some callback functions for replies */ if (cpl_fct.tmb.register_tmcb(intr->msg,0, TMCB_ON_FAILURE|TMCB_RESPONSE_OUT,reply_callback,(void*)intr, 0) <= 0){ LOG(L_ERR, "ERROR:cpl_c:run_proxy: failed to register " "TMCB_RESPONSE_OUT callback\n"); goto runtime_error; } } switch (intr->proxy.ordering) { case FIRSTONLY_VAL: /* forward the request only to the first address from loc. set */ /* location set cannot be empty -> was checked before */ loc = remove_first_location( &(intr->loc_set) ); intr->proxy.last_to_proxy = 0; /* set the new ip before proxy -> otherwise race cond with rpls */ intr->ip = CPL_TO_CONTINUE; if (cpl_proxy_to_loc_set(intr->msg,&loc,intr->flags )==-1) goto runtime_error; break; case PARALLEL_VAL: /* forward to all location from location set */ intr->proxy.last_to_proxy = 0; /* set the new ip before proxy -> otherwise race cond with rpls */ intr->ip = CPL_TO_CONTINUE; if (cpl_proxy_to_loc_set(intr->msg,&(intr->loc_set),intr->flags) ==-1) goto runtime_error; break; case SEQUENTIAL_VAL: /* forward the request one at the time to all addresses from * loc. set; location set cannot be empty -> was checked before */ /* use the first location from set */ loc = remove_first_location( &(intr->loc_set) ); /* set as the last_to_proxy the last location from set */ intr->proxy.last_to_proxy = intr->loc_set; while (intr->proxy.last_to_proxy&&intr->proxy.last_to_proxy->next) intr->proxy.last_to_proxy = intr->proxy.last_to_proxy->next; /* set the new ip before proxy -> otherwise race cond with rpls */ intr->ip = CPL_TO_CONTINUE; if (cpl_proxy_to_loc_set(intr->msg,&loc,intr->flags)==-1) goto runtime_error; break; } return CPL_TO_CONTINUE; script_error: return CPL_SCRIPT_ERROR; mem_error: LOG(L_ERR,"ERROR:run_proxy: no more free shm memory\n"); runtime_error: return CPL_RUNTIME_ERROR; }