action.c
4ac74c03
 /*
  * $Id$
7dd0b342
  *
  * 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
049f64c2
  *
  * History:
  * ---------
3e8c3475
  *  2003-02-28  scratchpad compatibility abandoned (jiri)
  *  2003-01-29  removed scratchpad (jiri)
  *  2003-03-19  fixed set* len calculation bug & simplified a little the code
b621d616
  *              (should be a little faster now) (andrei)
3e8c3475
  *              replaced all mallocs/frees w/ pkg_malloc/pkg_free (andrei)
  *  2003-04-01  Added support for loose routing in forward (janakj)
  *  2003-04-12  FORCE_RPORT_T added (andrei)
1d9f91d6
  *  2003-04-22  strip_tail added (jiri)
4ac74c03
  */
 
 
049f64c2
 #include "comp_defs.h"
7dd0b342
 
4ac74c03
 #include "action.h"
 #include "config.h"
 #include "error.h"
 #include "dprint.h"
 #include "proxy.h"
3e429f5c
 #include "forward.h"
 #include "udp_server.h"
 #include "route.h"
3881f12c
 #include "parser/msg_parser.h"
855c2e68
 #include "parser/parse_uri.h"
104316b6
 #include "ut.h"
3bf76e49
 #include "sr_module.h"
dda9dab1
 #include "mem/mem.h"
1400b772
 #include "globals.h"
caf80ae6
 #include "dset.h"
0c5da34b
 #ifdef USE_TCP
 #include "tcp_server.h"
 #endif
4ac74c03
 
a46cdb8c
 #include <sys/types.h>
 #include <sys/socket.h>
4ac74c03
 #include <netdb.h>
 #include <stdlib.h>
63fa628f
 #include <netinet/in.h>
 #include <arpa/inet.h>
e22bbdb8
 #include <string.h>
63fa628f
 
03150098
 #ifdef DEBUG_DMALLOC
 #include <dmalloc.h>
 #endif
 
4ac74c03
 
7268726e
 /* ret= 0! if action -> end of list(e.g DROP), 
       > 0 to continue processing next actions
    and <0 on error */
4ac74c03
 int do_action(struct action* a, struct sip_msg* msg)
 {
 	int ret;
f20a56a2
 	int v;
4e2fdd79
 	union sockaddr_union* to;
36ef0329
 	struct socket_info* send_sock;
4ac74c03
 	struct proxy_l* p;
7268726e
 	char* tmp;
 	char *new_uri, *end, *crt;
 	int len;
45072d7a
 	int user;
9de57c42
 	struct sip_uri uri, next_hop;
855c2e68
 	struct sip_uri* u;
5ada8f8a
 	unsigned short port;
f2f969dd
 	int proto;
4ac74c03
 
1400b772
 	/* reset the value of error to E_UNSPEC so avoid unknowledgable
 	   functions to return with errror (status<0) and not setting it
 	   leaving there previous error; cache the previous value though
 	   for functions which want to process it */
 	prev_ser_error=ser_error;
 	ser_error=E_UNSPEC;
 
3e429f5c
 	ret=E_BUG;
f2f969dd
 	switch ((unsigned char)a->type){
4ac74c03
 		case DROP_T:
 				ret=0;
 			break;
 		case FORWARD_T:
f2f969dd
 #ifdef USE_TCP
 		case FORWARD_TCP_T:
 #endif
 		case FORWARD_UDP_T:
 
 			if (a->type==FORWARD_UDP_T) proto=PROTO_UDP;
 #ifdef USE_TCP
 			else if (a->type==FORWARD_TCP_T) proto= PROTO_TCP;
 #endif
 			else proto=msg->rcv.proto;
5ada8f8a
 			if (a->p1_type==URIHOST_ST){
 				/*parse uri*/
9de57c42
 
 				if (msg->dst_uri.len) {
 					ret = parse_uri(msg->dst_uri.s, msg->dst_uri.len, &next_hop);
 					u = &next_hop;
 				} else {
 					ret = parse_sip_msg_uri(msg);
 					u = &msg->parsed_uri;
 				}
 
1400b772
 				if (ret<0) {
855c2e68
 					LOG(L_ERR, "ERROR: do_action: forward: bad_uri "
 								" dropping packet\n");
5ada8f8a
 					break;
 				}
9de57c42
 				
5ada8f8a
 				switch (a->p2_type){
 					case URIPORT_ST:
1baa06b5
 									port=u->port_no;
5ada8f8a
 									break;
 					case NUMBER_ST:
 									port=a->p2.number;
 									break;
 					default:
 							LOG(L_CRIT, "BUG: do_action bad forward 2nd"
 										" param type (%d)\n", a->p2_type);
7f858173
 							ret=E_UNSPEC;
 							goto error_fwd_uri;
5ada8f8a
 				}
f2e456c3
 				switch(u->proto){
 					case PROTO_NONE:
 						proto=PROTO_UDP;
 						break;
 					case PROTO_UDP:
 #ifdef USE_TCP
 					case PROTO_TCP:
 #endif
 						proto=u->proto;
 						break;
 					default:
 						LOG(L_ERR,"ERROR: do action: forward: bad uri protocol"
 								" %d\n", u->proto);
 						ret=E_BAD_PROTO;
 						goto error_fwd_uri;
 				}
5ada8f8a
 				/* create a temporary proxy*/
d531a5d5
 				p=mk_proxy(&u->host, port, proto);
7f858173
 				if (p==0){
 					LOG(L_ERR, "ERROR:  bad host name in uri,"
 							" dropping packet\n");
 					ret=E_BAD_ADDRESS;
 					goto error_fwd_uri;
 				}
f2f969dd
 				ret=forward_request(msg, p, proto);
855c2e68
 				/*free_uri(&uri); -- no longer needed, in sip_msg*/
5ada8f8a
 				free_proxy(p); /* frees only p content, not p itself */
e3dccdc9
 				pkg_free(p);
5ada8f8a
 				if (ret>=0) ret=1;
 			}else if ((a->p1_type==PROXY_ST) && (a->p2_type==NUMBER_ST)){
f2f969dd
 				ret=forward_request(msg,(struct proxy_l*)a->p1.data, proto);
5ada8f8a
 				if (ret>=0) ret=1;
 			}else{
3e429f5c
 				LOG(L_CRIT, "BUG: do_action: bad forward() types %d, %d\n",
 						a->p1_type, a->p2_type);
4ac74c03
 				ret=E_BUG;
 			}
 			break;
 		case SEND_T:
0c5da34b
 		case SEND_TCP_T:
3e429f5c
 			if ((a->p1_type!= PROXY_ST)|(a->p2_type!=NUMBER_ST)){
 				LOG(L_CRIT, "BUG: do_action: bad send() types %d, %d\n",
 						a->p1_type, a->p2_type);
4ac74c03
 				ret=E_BUG;
 				break;
 			}
e3dccdc9
 			to=(union sockaddr_union*)
 					pkg_malloc(sizeof(union sockaddr_union));
4e2fdd79
 			if (to==0){
 				LOG(L_ERR, "ERROR: do_action: "
 							"memory allocation failure\n");
 				ret=E_OUT_OF_MEM;
 				break;
 			}
4ac74c03
 			
 			p=(struct proxy_l*)a->p1.data;
 			
 			if (p->ok==0){
 				if (p->host.h_addr_list[p->addr_idx+1])
 					p->addr_idx++;
5ada8f8a
 				else 
 					p->addr_idx=0;
4ac74c03
 				p->ok=1;
 			}
4e2fdd79
 			ret=hostent2su(	to, &p->host, p->addr_idx,
6eacb2bc
 						(p->port)?p->port:SIP_PORT );
4e2fdd79
 			if (ret==0){
 				p->tx++;
 				p->tx_bytes+=msg->len;
0c5da34b
 				if (a->type==SEND_T){
 					/*udp*/
f2f969dd
 					send_sock=get_send_socket(to, PROTO_UDP);
0c5da34b
 					if (send_sock!=0){
049f64c2
 						ret=udp_send(send_sock, msg->buf, msg->len, to);
0c5da34b
 					}else{
 						ret=-1;
 					}
 				}
 #ifdef USE_TCP
 					else{
 					/*tcp*/
049f64c2
 					ret=tcp_send(msg->buf, msg->len, to, 0);
36ef0329
 				}
0c5da34b
 #endif
4e2fdd79
 			}
e3dccdc9
 			pkg_free(to);
4ac74c03
 			if (ret<0){
 				p->errors++;
 				p->ok=0;
 			}else ret=1;
 			
 			break;
 		case LOG_T:
3e429f5c
 			if ((a->p1_type!=NUMBER_ST)|(a->p2_type!=STRING_ST)){
 				LOG(L_CRIT, "BUG: do_action: bad log() types %d, %d\n",
 						a->p1_type, a->p2_type);
 				ret=E_BUG;
 				break;
 			}
 			LOG(a->p1.number, a->p2.string);
4ac74c03
 			ret=1;
 			break;
3881f12c
 
caf80ae6
 		/* jku -- introduce a new branch */
 		case APPEND_BRANCH_T:
 			if ((a->p1_type!=STRING_ST)) {
 				LOG(L_CRIT, "BUG: do_action: bad append_branch_t %d\n",
 					a->p1_type );
 				ret=E_BUG;
 				break;
 			}
 			ret=append_branch( msg, a->p1.string, 
 				a->p1.string ? strlen(a->p1.string):0 );
 			break;
 
1f377e97
 		/* jku begin: is_length_greater_than */
 		case LEN_GT_T:
 			if (a->p1_type!=NUMBER_ST) {
 				LOG(L_CRIT, "BUG: do_action: bad len_gt type %d\n",
 					a->p1_type );
 				ret=E_BUG;
 				break;
 			}
 			/* DBG("XXX: message length %d, max %d\n", 
 				msg->len, a->p1.number ); */
 			ret = msg->len >= a->p1.number ? 1 : -1;
 			break;
 		/* jku end: is_length_greater_than */
 			
3881f12c
 		/* jku - begin : flag processing */
 
 		case SETFLAG_T:
 			if (a->p1_type!=NUMBER_ST) {
 				LOG(L_CRIT, "BUG: do_action: bad setflag() type %d\n",
 					a->p1_type );
 				ret=E_BUG;
 				break;
 			}
 			if (!flag_in_range( a->p1.number )) {
 				ret=E_CFG;
 				break;
 			}
 			setflag( msg, a->p1.number );
 			ret=1;
 			break;
 
 		case RESETFLAG_T:
 			if (a->p1_type!=NUMBER_ST) {
 				LOG(L_CRIT, "BUG: do_action: bad resetflag() type %d\n",
 					a->p1_type );
 				ret=E_BUG;
 				break;
 			}
 			if (!flag_in_range( a->p1.number )) {
 				ret=E_CFG;
 				break;
 			}
 			resetflag( msg, a->p1.number );
 			ret=1;
 			break;
 			
 		case ISFLAGSET_T:
 			if (a->p1_type!=NUMBER_ST) {
 				LOG(L_CRIT, "BUG: do_action: bad isflagset() type %d\n",
 					a->p1_type );
 				ret=E_BUG;
 				break;
 			}
 			if (!flag_in_range( a->p1.number )) {
 				ret=E_CFG;
 				break;
 			}
 			ret=isflagset( msg, a->p1.number );
 			break;
 		/* jku - end : flag processing */
 
4ac74c03
 		case ERROR_T:
3e429f5c
 			if ((a->p1_type!=STRING_ST)|(a->p2_type!=STRING_ST)){
 				LOG(L_CRIT, "BUG: do_action: bad error() types %d, %d\n",
 						a->p1_type, a->p2_type);
 				ret=E_BUG;
 				break;
 			}
4ac74c03
 			LOG(L_NOTICE, "WARNING: do_action: error(\"%s\", \"%s\") "
 					"not implemented yet\n", a->p1.string, a->p2.string);
 			ret=1;
 			break;
 		case ROUTE_T:
3e429f5c
 			if (a->p1_type!=NUMBER_ST){
 				LOG(L_CRIT, "BUG: do_action: bad route() type %d\n",
 						a->p1_type);
 				ret=E_BUG;
 				break;
 			}
 			if ((a->p1.number>RT_NO)||(a->p1.number<0)){
 				LOG(L_ERR, "ERROR: invalid routing table number in"
a81b2e7f
 							"route(%lu)\n", a->p1.number);
3e429f5c
 				ret=E_CFG;
 				break;
 			}
f20a56a2
 			ret=((ret=run_actions(rlist[a->p1.number], msg))<0)?ret:1;
4ac74c03
 			break;
 		case EXEC_T:
3e429f5c
 			if (a->p1_type!=STRING_ST){
 				LOG(L_CRIT, "BUG: do_action: bad exec() type %d\n",
 						a->p1_type);
 				ret=E_BUG;
 				break;
 			}
 			LOG(L_NOTICE, "WARNING: exec(\"%s\") not fully implemented,"
4ac74c03
 						" using dumb version...\n", a->p1.string);
 			ret=system(a->p1.string);
 			if (ret!=0){
 				LOG(L_NOTICE, "WARNING: exec() returned %d\n", ret);
 			}
 			ret=1;
 			break;
caf80ae6
 		case REVERT_URI_T:
 			if (msg->new_uri.s) {
 				pkg_free(msg->new_uri.s);
 				msg->new_uri.len=0;
 				msg->new_uri.s=0;
a6982b85
 				msg->parsed_uri_ok=0; /* invalidate current parsed uri*/
caf80ae6
 			};
 			ret=1;
 			break;
7268726e
 		case SET_HOST_T:
 		case SET_HOSTPORT_T:
 		case SET_USER_T:
 		case SET_USERPASS_T:
 		case SET_PORT_T:
 		case SET_URI_T:
1f377e97
 		case PREFIX_T:
 		case STRIP_T:
1d9f91d6
 		case STRIP_TAIL_T:
45072d7a
 				user=0;
1d9f91d6
 				if (a->type==STRIP_T || a->type==STRIP_TAIL_T) {
1f377e97
 					if (a->p1_type!=NUMBER_ST) {
 						LOG(L_CRIT, "BUG: do_action: bad set*() type %d\n",
 							a->p1_type);
 						break;
 					}
 				} else if (a->p1_type!=STRING_ST){
7268726e
 					LOG(L_CRIT, "BUG: do_action: bad set*() type %d\n",
 							a->p1_type);
 					ret=E_BUG;
 					break;
 				}
 				if (a->type==SET_URI_T){
f8d46776
 					if (msg->new_uri.s) {
b78959d0
 							pkg_free(msg->new_uri.s);
f8d46776
 							msg->new_uri.len=0;
903766ce
 					}
a6982b85
 					msg->parsed_uri_ok=0;
7268726e
 					len=strlen(a->p1.string);
b78959d0
 					msg->new_uri.s=pkg_malloc(len+1);
f8d46776
 					if (msg->new_uri.s==0){
45072d7a
 						LOG(L_ERR, "ERROR: do_action: memory allocation"
 								" failure\n");
7268726e
 						ret=E_OUT_OF_MEM;
 						break;
 					}
f8d46776
 					memcpy(msg->new_uri.s, a->p1.string, len);
 					msg->new_uri.s[len]=0;
 					msg->new_uri.len=len;
 					
7268726e
 					ret=1;
 					break;
 				}
f8d46776
 				if (msg->new_uri.s) {
 					tmp=msg->new_uri.s;
 					len=msg->new_uri.len;
 				}else{
 					tmp=msg->first_line.u.request.uri.s;
 					len=msg->first_line.u.request.uri.len;
 				}
 				if (parse_uri(tmp, len, &uri)<0){
45072d7a
 					LOG(L_ERR, "ERROR: do_action: bad uri <%s>, dropping"
 								" packet\n", tmp);
7268726e
 					ret=E_UNSPEC;
 					break;
 				}
 				
b78959d0
 				new_uri=pkg_malloc(MAX_URI_SIZE);
7268726e
 				if (new_uri==0){
45072d7a
 					LOG(L_ERR, "ERROR: do_action: memory allocation "
 								" failure\n");
7268726e
 					ret=E_OUT_OF_MEM;
 					break;
 				}
 				end=new_uri+MAX_URI_SIZE;
 				crt=new_uri;
 				/* begin copying */
 				len=strlen("sip:"); if(crt+len>end) goto error_uri;
 				memcpy(crt,"sip:",len);crt+=len;
1f377e97
 
7268726e
 				/* user */
1f377e97
 
 				/* prefix (-jiri) */
 				if (a->type==PREFIX_T) {
45072d7a
 					tmp=a->p1.string;
7268726e
 					len=strlen(tmp); if(crt+len>end) goto error_uri;
 					memcpy(crt,tmp,len);crt+=len;
b621d616
 					/* whatever we had before, with prefix we have username 
 					   now */
1f377e97
 					user=1;
 				}
 
 				if ((a->type==SET_USER_T)||(a->type==SET_USERPASS_T)) {
 					tmp=a->p1.string;
 					len=strlen(tmp);
 				} else if (a->type==STRIP_T) {
 					if (a->p1.number>uri.user.len) {
b621d616
 						LOG(L_WARN, "Error: too long strip asked; "
a81b2e7f
 									" deleting username: %lu of <%.*s>\n",
b621d616
 									a->p1.number, uri.user.len, uri.user.s );
1f377e97
 						len=0;
 					} else if (a->p1.number==uri.user.len) {
 						len=0;
 					} else {
 						tmp=uri.user.s + a->p1.number;
 						len=uri.user.len - a->p1.number;
 					}
1d9f91d6
 				} else if (a->type==STRIP_TAIL_T) {
 					if (a->p1.number>uri.user.len) {
 						LOG(L_WARN, "WARNING: too long strip_tail asked; "
a81b2e7f
 									" deleting username: %lu of <%.*s>\n",
1d9f91d6
 									a->p1.number, uri.user.len, uri.user.s );
 						len=0;
 					} else if (a->p1.number==uri.user.len) {
 						len=0;
 					} else {
 						tmp=uri.user.s;
 						len=uri.user.len - a->p1.number;
 					}
1f377e97
 				} else {
 					tmp=uri.user.s;
 					len=uri.user.len;
 				}
 
 				if (len){
 					if(crt+len>end) goto error_uri;
 					memcpy(crt,tmp,len);crt+=len;
45072d7a
 					user=1; /* we have an user field so mark it */
7268726e
 				}
1f377e97
 
7268726e
 				if (a->type==SET_USERPASS_T) tmp=0;
f8d46776
 				else tmp=uri.passwd.s;
7268726e
 				/* passwd */
 				if (tmp){
b621d616
 					len=uri.passwd.len; if(crt+len+1>end) goto error_uri;
 					*crt=':'; crt++;
7268726e
 					memcpy(crt,tmp,len);crt+=len;
 				}
 				/* host */
45072d7a
 				if (user || tmp){ /* add @ */
b621d616
 					if(crt+1>end) goto error_uri;
 					*crt='@'; crt++;
45072d7a
 				}
a6982b85
 				if ((a->type==SET_HOST_T) ||(a->type==SET_HOSTPORT_T)) {
45072d7a
 					tmp=a->p1.string;
a6982b85
 					if (tmp) len = strlen(tmp);
b621d616
 					else len=0;
a6982b85
 				} else {
f8d46776
 					tmp=uri.host.s;
a6982b85
 					len = uri.host.len;
 				}
7268726e
 				if (tmp){
a6982b85
 					if(crt+len>end) goto error_uri;
7268726e
 					memcpy(crt,tmp,len);crt+=len;
 				}
 				/* port */
 				if (a->type==SET_HOSTPORT_T) tmp=0;
a6982b85
 				else if (a->type==SET_PORT_T) {
 					tmp=a->p1.string;
 					if (tmp) len = strlen(tmp);
b621d616
 					else len = 0;
a6982b85
 				} else {
 					tmp=uri.port.s;
 					len = uri.port.len;
 				}
7268726e
 				if (tmp){
b621d616
 					if(crt+len+1>end) goto error_uri;
 					*crt=':'; crt++;
7268726e
 					memcpy(crt,tmp,len);crt+=len;
 				}
 				/* params */
f8d46776
 				tmp=uri.params.s;
7268726e
 				if (tmp){
b621d616
 					len=uri.params.len; if(crt+len+1>end) goto error_uri;
 					*crt=';'; crt++;
7268726e
 					memcpy(crt,tmp,len);crt+=len;
 				}
 				/* headers */
f8d46776
 				tmp=uri.headers.s;
7268726e
 				if (tmp){
b621d616
 					len=uri.headers.len; if(crt+len+1>end) goto error_uri;
 					*crt='?'; crt++;
7268726e
 					memcpy(crt,tmp,len);crt+=len;
 				}
 				*crt=0; /* null terminate the thing */
 				/* copy it to the msg */
b78959d0
 				if (msg->new_uri.s) pkg_free(msg->new_uri.s);
f8d46776
 				msg->new_uri.s=new_uri;
 				msg->new_uri.len=crt-new_uri;
a6982b85
 				msg->parsed_uri_ok=0;
7268726e
 				ret=1;
 				break;
f20a56a2
 		case IF_T:
 				/* if null expr => ignore if? */
 				if ((a->p1_type==EXPR_ST)&&a->p1.data){
 					v=eval_expr((struct expr*)a->p1.data, msg);
 					if (v<0){
a1041efe
 						if (v==EXPR_DROP){ /* hack to quit on DROP*/
 							ret=0;
 							break;
 						}else{
 							LOG(L_WARN,"WARNING: do_action:"
 										"error in expression\n");
 						}
f20a56a2
 					}
a1041efe
 					
9e973a63
 					ret=1;  /*default is continue */
9598488f
 					if (v>0) {
f20a56a2
 						if ((a->p2_type==ACTIONS_ST)&&a->p2.data){
 							ret=run_actions((struct action*)a->p2.data, msg);
 						}
 					}else if ((a->p3_type==ACTIONS_ST)&&a->p3.data){
 							ret=run_actions((struct action*)a->p3.data, msg);
 					}
 				}
 			break;
3bf76e49
 		case MODULE_T:
21f03122
 			if ( ((a->p1_type==CMDF_ST)&&a->p1.data)/*&&
 					((a->p2_type==STRING_ST)&&a->p2.data)*/ ){
34fd2612
 				ret=((cmd_function)(a->p1.data))(msg, (char*)a->p2.data,
 													  (char*)a->p3.data);
3bf76e49
 			}else{
 				LOG(L_CRIT,"BUG: do_action: bad module call\n");
 			}
 			break;
3e8c3475
 		case FORCE_RPORT_T:
 			msg->msg_flags|=FL_FORCE_RPORT;
 			ret=1; /* continue processing */
 			break;
4ac74c03
 		default:
 			LOG(L_CRIT, "BUG: do_action: unknown type %d\n", a->type);
 	}
e22bbdb8
 /*skip:*/
4ac74c03
 	return ret;
7268726e
 	
 error_uri:
 	LOG(L_ERR, "ERROR: do_action: set*: uri too long\n");
764df2b2
 	if (new_uri) pkg_free(new_uri);
7268726e
 	return E_UNSPEC;
7f858173
 error_fwd_uri:
855c2e68
 	/*free_uri(&uri); -- not needed anymore, using msg->parsed_uri*/
7f858173
 	return ret;
4ac74c03
 }
 
 
 
f20a56a2
 /* returns: 0, or 1 on success, <0 on error */
 /* (0 if drop or break encountered, 1 if not ) */
4ac74c03
 int run_actions(struct action* a, struct sip_msg* msg)
 {
 	struct action* t;
e22bbdb8
 	int ret=E_UNSPEC;
3e429f5c
 	static int rec_lev=0;
4bd1673d
 	struct sr_module *mod;
3e429f5c
 
 	rec_lev++;
 	if (rec_lev>ROUTE_MAX_REC_LEV){
 		LOG(L_ERR, "WARNING: too many recursive routing table lookups (%d)"
 					" giving up!\n", rec_lev);
 		ret=E_UNSPEC;
 		goto error;
 	}
 		
4ac74c03
 	if (a==0){
ea6721d7
 		LOG(L_ERR, "WARNING: run_actions: null action list (rec_level=%d)\n", 
 			rec_lev);
4ac74c03
 		ret=0;
 	}
 
 	for (t=a; t!=0; t=t->next){
 		ret=do_action(t, msg);
 		if(ret==0) break;
5ada8f8a
 		/* ignore errors */
 		/*else if (ret<0){ ret=-1; goto error; }*/
4ac74c03
 	}
3e429f5c
 	
 	rec_lev--;
4bd1673d
 	/* process module onbreak handlers if present */
 	if (rec_lev==0 && ret==0) 
 		for (mod=modules;mod;mod=mod->next) 
 			if (mod->exports && mod->exports->onbreak_f) {
 				mod->exports->onbreak_f( msg );
 				DBG("DEBUG: %s onbreak handler called\n", mod->exports->name);
 			}
f20a56a2
 	return ret;
3e429f5c
 	
4ac74c03
 
 error:
3e429f5c
 	rec_lev--;
4ac74c03
 	return ret;
 }