modules_s/cpl-c/cpl_proxy.h

00001 /*
00002  * $Id$
00003  *
00004  * Copyright (C) 2001-2003 FhG Fokus
00005  *
00006  * This file is part of ser, a free SIP server.
00007  *
00008  * ser is free software; you can redistribute it and/or modify
00009  * it under the terms of the GNU General Public License as published by
00010  * the Free Software Foundation; either version 2 of the License, or
00011  * (at your option) any later version
00012  *
00013  * For a license to use the ser software under conditions
00014  * other than those described here, or to purchase support for this
00015  * software, please contact iptel.org by e-mail at the following addresses:
00016  *    info@iptel.org
00017  *
00018  * ser is distributed in the hope that it will be useful,
00019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00021  * GNU General Public License for more details.
00022  *
00023  * You should have received a copy of the GNU General Public License
00024  * along with this program; if not, write to the Free Software
00025  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00026  *
00027  * History:
00028  * -------
00029  * 2003-07-29: file created (bogdan)
00030  * 2004-06-14: flag CPL_IS_STATEFUL is set now immediately after the 
00031  *             transaction is created (bogdan)
00032  */
00033 
00034 #include "../../modules/tm/h_table.h"
00035 #include "../../parser/contact/parse_contact.h"
00036 
00037 
00038 #define duplicate_str( _orig_ , _new_ ) \
00039         do {\
00040                 (_new_) = (str*)shm_malloc(sizeof(str)+(_orig_)->len);\
00041                 if (!(_new_)) goto mem_error;\
00042                 (_new_)->len = (_orig_)->len;\
00043                 (_new_)->s = (char*)((_new_))+sizeof(str);\
00044                 memcpy((_new_)->s,(_orig_)->s,(_orig_)->len);\
00045         } while(0)
00046 
00047 #define search_and_duplicate_hdr( _intr_ , _field_ , _name_ , _sfoo_ ) \
00048         do {\
00049                 if (!(_intr_)->_field_) {\
00050                         if (!(_intr_)->msg->_field_) { \
00051                                 if (parse_headers((_intr_)->msg,_name_,0)==-1) {\
00052                                         LOG(L_ERR,"ERROR:run_proxy: bad %llx hdr\n",_name_);\
00053                                         goto runtime_error;\
00054                                 } else if ( !(_intr_)->msg->_field_) {\
00055                                         (_intr_)->_field_ = STR_NOT_FOUND;\
00056                                 } else {\
00057                                         (_sfoo_) = &((_intr_)->msg->_field_->body);\
00058                                         duplicate_str( (_sfoo_) , (_intr_)->_field_ );\
00059                                 }\
00060                         } else {\
00061                                 (_sfoo_) = &((_intr_)->msg->_field_->body);\
00062                                 duplicate_str( (_sfoo_) , (_intr_)->_field_ );\
00063                         }\
00064                 } else {\
00065                         (_sfoo_) = (_intr_)->_field_;\
00066                         duplicate_str( (_sfoo_) , (_intr_)->_field_ );\
00067                 }\
00068         }while(0)
00069 
00070 
00071 
00072 static inline int parse_q(str *q, unsigned int *prio)
00073 {
00074         if (q->s[0]=='0')
00075                 *prio=0;
00076         else if (q->s[0]=='1')
00077                 *prio=10;
00078         else
00079                 goto error;
00080         if (q->s[1]!='.')
00081                 goto error;
00082         if (q->s[2]<'0' || q->s[2]>'9')
00083                 goto error;
00084         *prio += q->s[2] - '0';
00085         if (*prio>10)
00086                 goto error;
00087 
00088         return 0;
00089 error:
00090         LOG(L_ERR,"ERROR:cpl-c:parse_q:bad q param <%.*s>\n",q->len,q->s);
00091         return -1;
00092 }
00093 
00094 
00095 
00096 static inline int add_contacts_to_loc_set(struct sip_msg* msg,
00097                                                                                                         struct location **loc_set)
00098 {
00099         struct sip_uri uri;
00100         struct contact *contacts;
00101         unsigned int prio;
00102 
00103         /* we need to have the contact header */
00104         if (msg->contact==0) {
00105                 /* find and parse the Contact header */
00106                 if ((parse_headers(msg, HDR_CONTACT_F, 0)==-1) || (msg->contact==0) ) {
00107                         LOG(L_ERR,"ERROR:cpl-c:add_contacts_to_loc_set: error parsing or "
00108                                 "no Contact hdr found!\n");
00109                         goto error;
00110                 }
00111         }
00112 
00113         /* extract from contact header the all the addresses */
00114         if (parse_contact( msg->contact )!=0) {
00115                 LOG(L_ERR,"ERROR:cpl-c:add_contacts_to_loc_set: unable to parse "
00116                         "Contact hdr!\n");
00117                 goto error;
00118         }
00119 
00120         /* in contact hdr, in parsed attr, we should have a list of contacts */
00121         if ( msg->contact->parsed ) {
00122                 contacts = ((struct contact_body*)msg->contact->parsed)->contacts;
00123                 for( ; contacts ; contacts=contacts->next) {
00124                         /* check if the contact is a valid sip uri */
00125                         if (parse_uri( contacts->uri.s, contacts->uri.len , &uri)!=0) {
00126                                 continue;
00127                         }
00128                         /* convert the q param to int value (if any) */
00129                         if (contacts->q) {
00130                                 if (parse_q( &(contacts->q->body), &prio )!=0)
00131                                         continue;
00132                         } else {
00133                                 prio = 10; /* set default to minimum */
00134                         }
00135                         /* add the uri to location set */
00136                         if (add_location( loc_set, &contacts->uri,prio, CPL_LOC_DUPL)!=0) {
00137                                 LOG(L_ERR,"ERROR:cpl-c:add_contacts_to_loc_set: unable to add "
00138                                 "<%.*s>\n",contacts->uri.len,contacts->uri.s);
00139                         }
00140                 }
00141         }
00142 
00143         return 0;
00144 error:
00145         return -1;
00146 }
00147 
00148 
00149 
00150 static void reply_callback( struct cell* t, int type, struct tmcb_params* ps)
00151 {
00152         struct cpl_interpreter *intr = (struct cpl_interpreter*)(*(ps->param));
00153         struct location        *loc  = 0;
00154         int rez;
00155 
00156         if (intr==0) {
00157                 LOG(L_WARN,"WARNING:cpl-c:reply_callback: param=0 for callback %d,"
00158                         " transaction=%p \n",type,t);
00159                 return;
00160         }
00161 
00162         if (type&TMCB_RESPONSE_OUT) {
00163                 /* the purpose of the final reply is to trash down the interpreter
00164                  * structure! it's the safest place to do that, since this callback
00165                  * it's called only once per transaction for final codes (>=200) ;-) */
00166                 if (ps->code>=200) {
00167                         DBG("DEBUG:cpl-c:final_reply: code=%d  -------------->\n"
00168                                 " --------------------------> final reply received\n",
00169                         ps->code);
00170                         /* CPL interpretation done, call established -> destroy */
00171                         free_cpl_interpreter( intr );
00172                         /* set to zero the param callback*/
00173                         *(ps->param) = 0;
00174                 }
00175                 return;
00176         } else if (!(type&TMCB_ON_FAILURE)) {
00177                 LOG(L_ERR,"BUG:cpl-c:reply_callback: unknown type %d\n",type);
00178                 goto exit;
00179         }
00180 
00181         DBG("DEBUG:cpl-c:negativ_reply: ------------------------------>\n"
00182                 " ---------------------------------> negativ reply received\n");
00183 
00184         intr->flags |= CPL_PROXY_DONE;
00185         intr->msg = ps->req;
00186 
00187         /* if it's a redirect-> do I have to added to the location set ? */
00188         if (intr->proxy.recurse && (ps->code)/100==3) {
00189                 DBG("DEBUG:cpl-c:negativ_reply: recurse level %d processing..\n",
00190                                 intr->proxy.recurse);
00191                 intr->proxy.recurse--;
00192                 /* get the locations from the Contact */
00193                 add_contacts_to_loc_set( ps->rpl, &(intr->loc_set));
00194                 switch (intr->proxy.ordering) {
00195                         case SEQUENTIAL_VAL:
00196                                 /* update the last_to_proxy to last location from set */
00197                                 if (intr->proxy.last_to_proxy==0) {
00198                                         /* the pointer went through entire old set -> set it to the
00199                                          * updated set, from the beginning  */
00200                                         if (intr->loc_set==0)
00201                                                 /* the updated set is also empty -> proxy ended */
00202                                                 break;
00203                                         intr->proxy.last_to_proxy = intr->loc_set;
00204                                 }
00205                                 while(intr->proxy.last_to_proxy->next)
00206                                         intr->proxy.last_to_proxy=intr->proxy.last_to_proxy->next;
00207                                 break;
00208                         case PARALLEL_VAL:
00209                                 /* push the whole new location set to be proxy */
00210                                 intr->proxy.last_to_proxy = intr->loc_set;
00211                                 break;
00212                         case FIRSTONLY_VAL:
00213                                 intr->proxy.last_to_proxy = 0;
00214                                 break;
00215                 }
00216         }
00217 
00218         /* the current proxying failed -> do I have another location to try ?
00219          * This applies only for SERIAL forking or if RECURSE is set */
00220         if (intr->proxy.last_to_proxy) {
00221                 /* continue proxying */
00222                 DBG("DEBUG:cpl-c:failed_reply: resuming proxying....\n");
00223                 switch (intr->proxy.ordering) {
00224                         case PARALLEL_VAL:
00225                                 /* I get here only if I got a 3xx and RECURSE in on ->
00226                                  * forward to all location from location set */
00227                                 intr->proxy.last_to_proxy = 0;
00228                                 cpl_proxy_to_loc_set(intr->msg,&(intr->loc_set),intr->flags );
00229                                 break;
00230                         case SEQUENTIAL_VAL:
00231                                 /* place a new branch to the next location from loc. set*/
00232                                 loc = remove_first_location( &(intr->loc_set) );
00233                                 /*print_location_set(intr->loc_set);*/
00234                                 /* update (if necessary) the last_to_proxy location  */
00235                                 if (intr->proxy.last_to_proxy==loc)
00236                                         intr->proxy.last_to_proxy = 0;
00237                                 cpl_proxy_to_loc_set(intr->msg,&loc,intr->flags );
00238                                 break;
00239                         default:
00240                                 LOG(L_CRIT,"BUG:cpl_c:failed_reply: unexpected ordering found "
00241                                         "when continuing proxying (%d)\n",intr->proxy.ordering);
00242                                 goto exit;
00243                 }
00244                 /* nothing more to be done */
00245                 return;
00246         } else {
00247                 /* done with proxying.... -> process the final response */
00248                 DBG("DEBUG:cpl-c:failed_reply:final_reply: got a final %d\n",ps->code);
00249                 intr->ip = 0;
00250                 if (ps->code==486 || ps->code==600) {
00251                         /* busy response */
00252                         intr->ip = intr->proxy.busy;
00253                 } else if (ps->code==408) {
00254                         /* request timeout -> no response */
00255                         intr->ip = intr->proxy.noanswer;
00256                 } else if (((ps->code)/100)==3) {
00257                         /* redirection */
00258                         /* add to the location list all the addresses from Contact */
00259                         add_contacts_to_loc_set( ps->rpl, &(intr->loc_set));
00260                         print_location_set( intr->loc_set );
00261                         intr->ip = intr->proxy.redirect;
00262                 } else {
00263                         /* generic failure */
00264                         intr->ip = intr->proxy.failure;
00265                 }
00266 
00267                 if (intr->ip==0)
00268                         intr->ip = (intr->proxy.default_)?
00269                                 intr->proxy.default_:DEFAULT_ACTION;
00270                 if (intr->ip!=DEFAULT_ACTION)
00271                         intr->ip = get_first_child( intr->ip );
00272 
00273                 if( intr->ip==DEFAULT_ACTION)
00274                         rez = run_default(intr);
00275                 else
00276                         rez = cpl_run_script(intr);
00277                 switch ( rez ) {
00278                         case SCRIPT_END:
00279                                 /* we don't need to free the interpreter here since it will 
00280                                  * be freed in the final_reply callback */
00281                         case SCRIPT_TO_BE_CONTINUED:
00282                                 return;
00283                         case SCRIPT_RUN_ERROR:
00284                         case SCRIPT_FORMAT_ERROR:
00285                                 goto exit;
00286                         default:
00287                                 LOG(L_CRIT,"BUG:cpl-c:failed_reply: improper result %d\n",
00288                                         rez);
00289                                 goto exit;
00290                 }
00291         }
00292 
00293 exit:
00294         /* in case of error the default response chosen by ser at the last
00295          * proxying will be forwarded to the UAC */
00296         free_cpl_interpreter( intr );
00297         /* set to zero the param callback*/
00298         *(ps->param) = 0;
00299         return;
00300 }
00301 
00302 
00303 
00304 static inline char *run_proxy( struct cpl_interpreter *intr )
00305 {
00306         unsigned short attr_name;
00307         unsigned short n;
00308         char *kid;
00309         char *p;
00310         int i;
00311         str *s;
00312         struct location *loc;
00313         int_str tmp;
00314 
00315         intr->proxy.ordering = PARALLEL_VAL;
00316         intr->proxy.recurse = (unsigned short)cpl_env.proxy_recurse;
00317 
00318         /* identify the attributes */
00319         for( i=NR_OF_ATTR(intr->ip),p=ATTR_PTR(intr->ip) ; i>0 ; i-- ) {
00320                 get_basic_attr( p, attr_name, n, intr, script_error);
00321                 switch (attr_name) {
00322                         case TIMEOUT_ATTR:
00323                                 if (cpl_env.timer_avp.n || cpl_env.timer_avp.s.s) {
00324                                         tmp.n=(int)n;
00325                                         if ( add_avp( AVP_TRACK_TO | cpl_env.timer_avp_type,
00326                                         cpl_env.timer_avp, tmp)<0) {
00327                                                 LOG(L_ERR,"ERROR:run_proxy: unable to set "
00328                                                         "timer AVP\n");
00329                                                 /* continue */
00330                                         }
00331                                 }
00332                                 break;
00333                         case RECURSE_ATTR:
00334                                 switch (n) {
00335                                         case NO_VAL:
00336                                                 intr->proxy.recurse = 0;
00337                                                 break;
00338                                         case YES_VAL:
00339                                                 /* already set as default */
00340                                                 break;
00341                                         default:
00342                                                 LOG(L_ERR,"ERROR:run_proxy: invalid value (%u) found"
00343                                                         " for attr. RECURSE in PROXY node!\n",n);
00344                                                 goto script_error;
00345                                 }
00346                                 break;
00347                         case ORDERING_ATTR:
00348                                 if (n!=PARALLEL_VAL && n!=SEQUENTIAL_VAL && n!=FIRSTONLY_VAL){
00349                                         LOG(L_ERR,"ERROR:run_proxy: invalid value (%u) found"
00350                                                 " for attr. ORDERING in PROXY node!\n",n);
00351                                         goto script_error;
00352                                 }
00353                                 intr->proxy.ordering = n;
00354                                 break;
00355                         default:
00356                                 LOG(L_ERR,"ERROR:run_proxy: unknown attribute (%d) in"
00357                                         "PROXY node\n",attr_name);
00358                                 goto script_error;
00359                 }
00360         }
00361 
00362         intr->proxy.busy = intr->proxy.noanswer = 0;
00363         intr->proxy.redirect = intr->proxy.failure = intr->proxy.default_ = 0;
00364 
00365         /* this is quite an "expensive" node to run, so let's make some checking
00366          * before getting deeply into it */
00367         for( i=0 ; i<NR_OF_KIDS(intr->ip) ; i++ ) {
00368                 kid = intr->ip + KID_OFFSET(intr->ip,i);
00369                 check_overflow_by_ptr( kid+SIMPLE_NODE_SIZE(kid), intr, script_error);
00370                 switch ( NODE_TYPE(kid) ) {
00371                         case BUSY_NODE :
00372                                 intr->proxy.busy = kid;
00373                                 break;
00374                         case NOANSWER_NODE:
00375                                 intr->proxy.noanswer = kid;
00376                                 break;
00377                         case REDIRECTION_NODE:
00378                                 intr->proxy.redirect = kid;
00379                                 break;
00380                         case FAILURE_NODE:
00381                                 intr->proxy.failure = kid;
00382                                 break;
00383                         case DEFAULT_NODE:
00384                                 intr->proxy.default_ = kid;
00385                                 break;
00386                         default:
00387                                 LOG(L_ERR,"ERROR:run_proxy: unknown output node type"
00388                                         " (%d) for PROXY node\n",NODE_TYPE(kid));
00389                                 goto script_error;
00390                 }
00391         }
00392 
00393         /* if the location set if empty, I will go directly on failure/default */
00394         if (intr->loc_set==0) {
00395                 DBG("DEBUG:run_proxy: location set found empty -> going on "
00396                         "failure/default branch\n");
00397                         if (intr->proxy.failure)
00398                                 return get_first_child(intr->proxy.failure);
00399                         else if (intr->proxy.default_)
00400                                 return get_first_child(intr->proxy.default_);
00401                         else return DEFAULT_ACTION;
00402         }
00403 
00404         /* if it's the first execution of a proxy node, force parsing of the needed
00405          * headers and duplicate them in shared memory */
00406         if (!(intr->flags&CPL_PROXY_DONE)) {
00407                 /* user name is already in shared memory */
00408                 /* requested URI - mandatory in SIP msg (cannot be STR_NOT_FOUND) */
00409                 s = GET_RURI( intr->msg );
00410                 duplicate_str( s , intr->ruri );
00411                 intr->flags |= CPL_RURI_DUPLICATED;
00412                 /* TO header - mandatory in SIP msg (cannot be STR_NOT_FOUND) */
00413                 if (!intr->to) {
00414                         if (!intr->msg->to &&
00415                         (parse_headers(intr->msg,HDR_TO_F,0)==-1 || !intr->msg->to)) {
00416                                 LOG(L_ERR,"ERROR:run_proxy: bad msg or missing TO header\n");
00417                                 goto runtime_error;
00418                         }
00419                         s = &(get_to(intr->msg)->uri);
00420                 } else {
00421                         s = intr->to;
00422                 }
00423                 duplicate_str( s , intr->to );
00424                 intr->flags |= CPL_TO_DUPLICATED;
00425                 /* FROM header - mandatory in SIP msg (cannot be STR_NOT_FOUND) */
00426                 if (!intr->from) {
00427                         if (parse_from_header( intr->msg )==-1)
00428                                 goto runtime_error;
00429                         s = &(get_from(intr->msg)->uri);
00430                 } else {
00431                         s = intr->from;
00432                 }
00433                 duplicate_str( s , intr->from );
00434                 intr->flags |= CPL_FROM_DUPLICATED;
00435                 /* SUBJECT header - optional in SIP msg (can be STR_NOT_FOUND) */
00436                 if (intr->subject!=STR_NOT_FOUND) {
00437                         search_and_duplicate_hdr(intr,subject,HDR_SUBJECT_F,s);
00438                         if (intr->subject!=STR_NOT_FOUND)
00439                                 intr->flags |= CPL_SUBJECT_DUPLICATED;
00440                 }
00441                 /* ORGANIZATION header - optional in SIP msg (can be STR_NOT_FOUND) */
00442                 if ( intr->organization!=STR_NOT_FOUND) {
00443                         search_and_duplicate_hdr(intr,organization,HDR_ORGANIZATION_F,s);
00444                         if ( intr->organization!=STR_NOT_FOUND)
00445                                 intr->flags |= CPL_ORGANIZATION_DUPLICATED;
00446                 }
00447                 /* USER_AGENT header - optional in SIP msg (can be STR_NOT_FOUND) */
00448                 if (intr->user_agent!=STR_NOT_FOUND) {
00449                         search_and_duplicate_hdr(intr,user_agent,HDR_USERAGENT_F,s);
00450                         if (intr->user_agent!=STR_NOT_FOUND)
00451                                 intr->flags |= CPL_USERAGENT_DUPLICATED;
00452                 }
00453                 /* ACCEPT_LANGUAGE header - optional in SIP msg
00454                  * (can be STR_NOT_FOUND) */
00455                 if (intr->accept_language!=STR_NOT_FOUND) {
00456                         search_and_duplicate_hdr(intr,accept_language,
00457                                 HDR_ACCEPTLANGUAGE_F,s);
00458                         if (intr->accept_language!=STR_NOT_FOUND)
00459                                 intr->flags |= CPL_ACCEPTLANG_DUPLICATED;
00460                 }
00461                 /* PRIORITY header - optional in SIP msg (can be STR_NOT_FOUND) */
00462                 if (intr->priority!=STR_NOT_FOUND) {
00463                         search_and_duplicate_hdr(intr,priority,HDR_PRIORITY_F,s);
00464                         if (intr->priority!=STR_NOT_FOUND)
00465                                 intr->flags |= CPL_PRIORITY_DUPLICATED;
00466                 }
00467 
00468                 /* now is the first time doing proxy, so I can still be stateless;
00469                  * as proxy is done all the time stateful, I have to switch from
00470                  * stateless to stateful if necessary.  */
00471                 if ( !(intr->flags&CPL_IS_STATEFUL) ) {
00472                         i = cpl_fct.tmb.t_newtran( intr->msg );
00473                         if (i<0) {
00474                                 LOG(L_ERR,"ERROR:cpl-c:run_proxy: failed to build new "
00475                                         "transaction!\n");
00476                                 goto runtime_error;
00477                         } else if (i==0) {
00478                                 LOG(L_ERR,"ERROR:cpl-c:run_proxy: processed INVITE is a "
00479                                         "retransmission!\n");
00480                                 /* instead of generating an error is better just to break the
00481                                  * script by returning EO_SCRIPT */
00482                                 return EO_SCRIPT;
00483                         }
00484                         intr->flags |= CPL_IS_STATEFUL;
00485                 }
00486 
00487                 /* as I am interested in getting the responses back - I need to install
00488                  * some callback functions for replies  */
00489                 if (cpl_fct.tmb.register_tmcb(intr->msg,0,
00490                 TMCB_ON_FAILURE|TMCB_RESPONSE_OUT,reply_callback,(void*)intr, 0) <= 0){
00491                         LOG(L_ERR, "ERROR:cpl_c:run_proxy: failed to register "
00492                                 "TMCB_RESPONSE_OUT callback\n");
00493                         goto runtime_error;
00494                 }
00495         }
00496 
00497         switch (intr->proxy.ordering) {
00498                 case FIRSTONLY_VAL:
00499                         /* forward the request only to the first address from loc. set */
00500                         /* location set cannot be empty -> was checked before */
00501                         loc = remove_first_location( &(intr->loc_set) );
00502                         intr->proxy.last_to_proxy = 0;
00503                         /* set the new ip before proxy -> otherwise race cond with rpls */
00504                         intr->ip = CPL_TO_CONTINUE;
00505                         if (cpl_proxy_to_loc_set(intr->msg,&loc,intr->flags )==-1)
00506                                 goto runtime_error;
00507                         break;
00508                 case PARALLEL_VAL:
00509                         /* forward to all location from location set */
00510                         intr->proxy.last_to_proxy = 0;
00511                         /* set the new ip before proxy -> otherwise race cond with rpls */
00512                         intr->ip = CPL_TO_CONTINUE;
00513                         if (cpl_proxy_to_loc_set(intr->msg,&(intr->loc_set),intr->flags)
00514                         ==-1)
00515                                 goto runtime_error;
00516                         break;
00517                 case SEQUENTIAL_VAL:
00518                         /* forward the request one at the time to all addresses from
00519                          * loc. set; location set cannot be empty -> was checked before */
00520                         /* use the first location from set */
00521                         loc = remove_first_location( &(intr->loc_set) );
00522                         /* set as the last_to_proxy the last location from set */
00523                         intr->proxy.last_to_proxy = intr->loc_set;
00524                         while (intr->proxy.last_to_proxy&&intr->proxy.last_to_proxy->next)
00525                                 intr->proxy.last_to_proxy = intr->proxy.last_to_proxy->next;
00526                         /* set the new ip before proxy -> otherwise race cond with rpls */
00527                         intr->ip = CPL_TO_CONTINUE;
00528                         if (cpl_proxy_to_loc_set(intr->msg,&loc,intr->flags)==-1)
00529                                 goto runtime_error;
00530                         break;
00531         }
00532 
00533         return CPL_TO_CONTINUE;
00534 script_error:
00535         return CPL_SCRIPT_ERROR;
00536 mem_error:
00537         LOG(L_ERR,"ERROR:run_proxy: no more free shm memory\n");
00538 runtime_error:
00539         return CPL_RUNTIME_ERROR;
00540 }
00541 
00542 
00543