/*****************************************************************************\ ** ** ** PBX4Linux ** ** ** **---------------------------------------------------------------------------** ** Copyright: Andreas Eversberg ** ** ** ** match processing of routing configuration ** ** ** \*****************************************************************************/ #include "main.h" struct route_ruleset *ruleset_first; /* first entry */ struct route_ruleset *ruleset_main; /* pointer to main ruleset */ struct cond_defs cond_defs[] = { { "extern", MATCH_EXTERN, COND_TYPE_NULL, "extern", "Matches if call is from external port (no extension)."}, { "intern", MATCH_INTERN,COND_TYPE_NULL, "intern", "Matches if call is from an extension."}, { "port", MATCH_PORT, COND_TYPE_INTEGER, "port=[-][,...]", "Matches if call is received from given port(s). NOT INTERFACE!"}, { "interface", MATCH_INTERFACE,COND_TYPE_STRING, "interface=[,...]", "Matches if call is received from given interface(s). NOT PORTS!"}, { "callerid", MATCH_CALLERID, COND_TYPE_STRING, "callerid=[-][,...]", "Matches if caller ID matches or begins with the given (range(s) of) prefixes(s)."}, { "callerid2", MATCH_CALLERID2,COND_TYPE_STRING, "callerid2=[-][,...]", "Matches the second caller ID (network provided)."}, { "extension", MATCH_EXTENSION,COND_TYPE_STRING, "extension=[-][,...]", "Matches if caller calls from given (range(s) of) extension(s)."}, { "dialing", MATCH_DIALING, COND_TYPE_STRING, "dialing=[-][,...]", "Matches if caller has dialed the given (range(s) of) digits at least."}, { "enblock", MATCH_ENBLOCK, COND_TYPE_NULL, "enblock", "Matches if caller dialed en block. (Dial the number before pick up.)"}, { "overlap", MATCH_OVERLAP, COND_TYPE_NULL, "overlap", "Matches if caller dialed digit by digit. (Dial the number after pick up.)"}, { "anonymous", MATCH_ANONYMOUS,COND_TYPE_NULL, "anonymous", "Matches if caller uses restricted caller ID or if not available."}, { "visible", MATCH_VISIBLE, COND_TYPE_NULL, "visible", "Matches if caller ID is presented and if available."}, { "unknown", MATCH_UNKNOWN, COND_TYPE_NULL, "unknown", "Matches if no ID is available from caller."}, { "available", MATCH_AVAILABLE,COND_TYPE_NULL, "available", "Matches if ID is available from caller."}, { "fake", MATCH_FAKE, COND_TYPE_NULL, "fake", "Matches if caller ID is not screened and may be faked by caller."}, { "real", MATCH_REAL, COND_TYPE_NULL, "real", "Matches if caller ID is screend and so it is the real caller's ID."}, { "redirected", MATCH_REDIRECTED,COND_TYPE_NULL, "redirected", "Matches if caller has been redirected."}, { "direct", MATCH_DIRECT ,COND_TYPE_NULL, "direct", "Matches if caller did not come from an redirection."}, { "redirid", MATCH_REDIRID ,COND_TYPE_STRING, "redirid=[-][,...]", "Matches if the caller has been redirected by the given (range(s) of) ID(s) or prefix(es))"}, { "time", MATCH_TIME, COND_TYPE_TIME, "time=[-][,...]", "Matches if the caller calls within the given (range(s) of) time(s). (e.g. 0700-1900)"}, { "mday", MATCH_MDAY, COND_TYPE_MDAY, "mday=[-][,...]", "Matches if the caller calls within the given (range(s) of) day(s) of the month. (1..31)"}, { "month", MATCH_MONTH, COND_TYPE_MONTH, "month=[-][,...]", "Matches if the caller calls within the given (range(s) of) month(s). (1=January..12=December)"}, { "year", MATCH_YEAR, COND_TYPE_YEAR, "year=[-][,...]", "Matches if the caller calls within the given (range(s) of) year(s). (1970..2106)"}, { "wday", MATCH_WDAY, COND_TYPE_WDAY, "wday=[-][,...]", "Matches if the caller calls within the given (range(s) of) weekday(s). (1=Monday..7=Sunday)"}, { "capability", MATCH_CAPABILITY, COND_TYPE_CAPABILITY, "capability=speech|audio|video|digital-restricted|digital-unrestricted|digital-unrestricted-tones[,...]", "Matches the given bearer capability(s)."}, { "infolayer1", MATCH_INFOLAYER1, COND_TYPE_INTEGER, "infolayer1=[,...]", "Matches the given information layer 1. (2=u-Law, 3=a-law, see info layer 1 in bearer capability.)"}, { "hlc", MATCH_HLC, COND_TYPE_HLC, "hlc=telephony|faxg2g3|faxg4|teletex1|teletex2|teletex3|videotex1|videotex2|telex|mhs|osi|maintenance|management|audiovisual[,...]", "Matches the high layer capability(s)."}, { "file", MATCH_FILE, COND_TYPE_STRING, "file=[,...]", "Mathes is the given file exists and if the first character is '1'."}, { "execute", MATCH_EXECUTE, COND_TYPE_STRING, "execute=[,...]","Matches if the return value of the given command is greater 0."}, { "default", MATCH_DEFAULT, COND_TYPE_NULL, "default","Matches if no further dialing could match."}, { "timeout", MATCH_TIMEOUT, COND_TYPE_INTEGER, "timeout=","Matches if the ruleset was entered AFTER given seconds."}, { "free", MATCH_FREE, COND_TYPE_IFATTR, "free=:","Matches if the given minimum of channels are free."}, { "notfree", MATCH_NOTFREE, COND_TYPE_IFATTR, "notfree=:","Matches if NOT the given minimum of channels are free."}, { "blocked", MATCH_DOWN, COND_TYPE_STRING, "blocked=[,...]","Matches if all of the given interfaces are blocked."}, { "idle", MATCH_UP, COND_TYPE_STRING, "idle=[,...]","Matches if any of the given interfaces is idle."}, { "busy", MATCH_BUSY, COND_TYPE_STRING, "busy=[,...]","Matches if any of the given extension is busy."}, { "notbusy", MATCH_IDLE, COND_TYPE_STRING, "notbusy=[,...]","Matches if any of the given extension is not busy."}, { "remote", MATCH_REMOTE, COND_TYPE_STRING, "remote=","Matches if remote application is running."}, { "notremote", MATCH_NOTREMOTE,COND_TYPE_STRING, "notremote=","Matches if remote application is not running."}, { "pots-flash", MATCH_POTS_FLASH,COND_TYPE_NULL, "pots-flash","When using POTS: Matches if call was invoked by flash/earth button."}, { "pots-cw", MATCH_POTS_CW, COND_TYPE_NULL, "pots-cw","When using POTS: Matches if a call is waiting."}, { "pots-calls", MATCH_POTS_CALLS,COND_TYPE_INTEGER, "pots-calls=","When using POTS: Matches if given number of calls are held."}, { "pots-last", MATCH_POTS_LAST,COND_TYPE_INTEGER, "pots-last=","When using POTS: Matches if given call number (1=oldest) was the last active call."}, { NULL, 0, 0, NULL} }; struct param_defs param_defs[] = { { PARAM_PROCEEDING, "proceeding", PARAM_TYPE_NULL, "proceeding", "Will set the call into 'proceeding' state to prevent dial timeout."}, { PARAM_ALERTING, "alerting", PARAM_TYPE_NULL, "alerting", "Will set the call into 'altering' state."}, { PARAM_CONNECT, "connect", PARAM_TYPE_NULL, "connect", "Will complete the call before processing the action. Audio path for external calls will be established."}, { PARAM_EXTENSION, "extension", PARAM_TYPE_STRING, "extension=", "Give extension name (digits) to relate this action to."}, { PARAM_EXTENSIONS, "extensions", PARAM_TYPE_STRING, "extensions=[,[,...]]", "One or more extensions may be given."}, { PARAM_PREFIX, "prefix", PARAM_TYPE_STRING, "prefix=", "Add prefix in front of the dialed number."}, { PARAM_CAPA, "capability", PARAM_TYPE_CAPABILITY, "capability=speech|audio|video|digital-restricted|digital-unrestricted|digital-unrestricted-tones", "Alter the service type of the call."}, { PARAM_BMODE, "bmode", PARAM_TYPE_BMODE, "bmode=transparent|hdlc", "Alter the bchannel mode of the call. Use hdlc for data calls."}, { PARAM_INFO1, "infolayer1", PARAM_TYPE_INTEGER, "infolayer1=", "Alter the layer 1 information of a call. Use 3 for ALAW or 2 for uLAW."}, { PARAM_HLC, "hlc", PARAM_TYPE_HLC, "hlc=telephony|faxg2g3|faxg4|teletex1|teletex2|teletex3|videotex1|videotex2|telex|mhs|osi|maintenance|management|audiovisual", "Alter the HLC identification."}, { PARAM_EXTHLC, "exthlc", PARAM_TYPE_HLC, "exthlc=", "Alter extended HLC value, see hlc. (Mainenance only, don't use it.)"}, { PARAM_PRESENT, "present", PARAM_TYPE_YESNO, "present=yes|no", "Allow or restrict caller ID regardless what the caller wants."}, { PARAM_DIVERSION, "diversion", PARAM_TYPE_DIVERSION, "diversion=cfu|cfnr|cfb|cfp", "Set diversion type."}, { PARAM_DEST, "dest", PARAM_TYPE_DESTIN, "dest=", "Destination number to divert to. Use 'vbox' to divert to vbox. (cfu,cfnr,cfb only)"}, { PARAM_SELECT, "select", PARAM_TYPE_NULL, "select", "Lets the caller select the history of calls using keys '1'/'3' or '*'/'#'."}, { PARAM_DELAY, "delay", PARAM_TYPE_INTEGER, "delay=", "Number of seconds to delay."}, { PARAM_LIMIT, "limit", PARAM_TYPE_INTEGER, "limit=", "Number of maximum retries."}, { PARAM_HOST, "host", PARAM_TYPE_STRING, "host=", "Name of remote VoIP host."}, { PARAM_PORT, "port", PARAM_TYPE_STRING, "port=", "Alternate port to use if 'host' is given."}, { PARAM_INTERFACES, "interfaces", PARAM_TYPE_STRING, "interfaces=[,[,...]]", "Give one or a list of Interfaces to select a free channel from."}, { PARAM_ADDRESS, "address", PARAM_TYPE_STRING, "address=", "Complete VoIP address. ( [user@]host[:port] )"}, { PARAM_SAMPLE, "sample", PARAM_TYPE_STRING, "sample=", "Filename of sample (current tone's dir) or full path to sample. ('.wav'/'.wave'/'.isdn' is added automatically."}, { PARAM_ANNOUNCEMENT, "announcement",PARAM_TYPE_STRING, "announcement=", "Filename of announcement (inside vbox recording dir) or full path to sample. ('.wav'/'.wave'/'.isdn' is added automatically."}, { PARAM_RULESET, "ruleset", PARAM_TYPE_STRING, "ruleset=", "Ruleset to go to."}, { PARAM_CAUSE, "cause", PARAM_TYPE_INTEGER, "cause=", "Cause value when disconnecting. (21=reject 1=unassigned 63=service not available)"}, { PARAM_LOCATION, "location", PARAM_TYPE_INTEGER, "location=", "Location of cause value when disconnecting. (0=user 1=private network sering local user)"}, { PARAM_DISPLAY, "display", PARAM_TYPE_STRING, "display=", "Message to display on the caller's telephone. (internal only)"}, { PARAM_PORTS, "ports", PARAM_TYPE_INTEGER, "ports=[,[,...]]", "ISDN port[s] to use."}, { PARAM_TPRESET, "tpreset", PARAM_TYPE_INTEGER, "tpreset=", "Preset of countdown timer."}, { PARAM_FILE, "file", PARAM_TYPE_STRING, "file=", "Full path to file name."}, { PARAM_CONTENT, "content", PARAM_TYPE_STRING, "content=", "Content to write into file."}, { PARAM_APPEND, "append", PARAM_TYPE_NULL, "append", "Will append to given file, rather than overwriting it."}, { PARAM_EXECUTE, "execute", PARAM_TYPE_STRING, "execute=", "Full path to script/command name. (Dialed digits are the argument 1.)"}, { PARAM_PARAM, "param", PARAM_TYPE_STRING, "param=", "Optionally this parameter can be inserted as argument 1, others are shifted."}, { PARAM_TYPE, "type", PARAM_TYPE_TYPE, "type=unknown|subscriber|national|international", "Type of number to dial, default is 'unknown'."}, { PARAM_COMPLETE, "complete", PARAM_TYPE_NULL, "complete", "Indicates complete number as given by prefix. Proceeding of long distance calls may be faster."}, { PARAM_CALLERID, "callerid", PARAM_TYPE_STRING, "callerid=", "Change caller ID to given string."}, { PARAM_CALLERIDTYPE, "calleridtype",PARAM_TYPE_CALLERIDTYPE, "calleridtype=[unknown|subscriber|national|international]", "Type of caller ID. For normal MSN use 'unknown'"}, { PARAM_CALLTO, "callto", PARAM_TYPE_STRING, "callto=", "Where to call back. By default the caller ID is used."}, { PARAM_ROOM, "room", PARAM_TYPE_INTEGER, "room=", "Conference room number, must be greater 0, as in real life."}, { PARAM_JINGLE, "jingle", PARAM_TYPE_NULL, "jingle", "Conference members will hear a jingle if a member joins."}, { PARAM_TIMEOUT, "timeout", PARAM_TYPE_INTEGER, "timeout=", "Timeout before continue with next action."}, { PARAM_NOPASSWORD, "nopassword", PARAM_TYPE_NULL, "nopassword", "Don't ask for password. Be sure to authenticate right via real caller ID."}, { PARAM_STRIP, "strip", PARAM_TYPE_NULL, "strip", "Remove digits that were required to match this rule."}, { PARAM_APPLICATION, "application",PARAM_TYPE_STRING, "application=", "Name of remote application to make call to."}, { PARAM_CONTEXT, "context", PARAM_TYPE_STRING, "context=", "Give context parameter to the remote application."}, { PARAM_EXTEN, "exten", PARAM_TYPE_STRING, "exten=", "Give exten parameter to the remote application. (overrides dialed number)"}, { PARAM_ON, "on", PARAM_TYPE_STRING, "on=[init|hangup]", "Defines if the action is executed on call init or on hangup."}, { PARAM_KEYPAD, "keypad", PARAM_TYPE_NULL, "keypad", "Use 'keypad facility' for dialing, instead of 'called number'."}, { PARAM_POTS_CALL, "pots-call", PARAM_TYPE_INTEGER, "pots-call=", "Select call number. The oldest call is number 1."}, { 0, NULL, 0, NULL, NULL} }; struct action_defs action_defs[] = { { ACTION_EXTERNAL, "extern", &EndpointAppPBX::action_init_call, &EndpointAppPBX::action_dialing_external, &EndpointAppPBX::action_hangup_call, PARAM_CONNECT | PARAM_PREFIX | PARAM_COMPLETE | PARAM_TYPE | PARAM_CAPA | PARAM_BMODE | PARAM_INFO1 | PARAM_HLC | PARAM_EXTHLC | PARAM_PRESENT | PARAM_INTERFACES | PARAM_CALLERID | PARAM_CALLERIDTYPE | PARAM_KEYPAD | PARAM_CONTEXT | PARAM_TIMEOUT, "Call is routed to extern number as dialed."}, { ACTION_INTERNAL, "intern", &EndpointAppPBX::action_init_call, &EndpointAppPBX::action_dialing_internal, &EndpointAppPBX::action_hangup_call, PARAM_CONNECT | PARAM_EXTENSION | PARAM_TYPE | PARAM_CAPA | PARAM_BMODE | PARAM_INFO1 | PARAM_HLC | PARAM_EXTHLC | PARAM_PRESENT | PARAM_TIMEOUT, "Call is routed to intern extension as given by the dialed number or specified by option."}, { ACTION_OUTDIAL, "outdial", &EndpointAppPBX::action_init_call, &EndpointAppPBX::action_dialing_external, &EndpointAppPBX::action_hangup_call, PARAM_CONNECT | PARAM_PREFIX | PARAM_COMPLETE | PARAM_TYPE | PARAM_CAPA | PARAM_BMODE | PARAM_INFO1 | PARAM_HLC | PARAM_EXTHLC | PARAM_PRESENT | PARAM_INTERFACES | PARAM_CALLERID | PARAM_CALLERIDTYPE | PARAM_KEYPAD | PARAM_TIMEOUT, "Same as 'extern'"}, { ACTION_VBOX_RECORD, "vbox-record",&EndpointAppPBX::action_init_call, &EndpointAppPBX::action_dialing_vbox_record, &EndpointAppPBX::action_hangup_call, PARAM_CONNECT | PARAM_EXTENSION | PARAM_ANNOUNCEMENT | PARAM_TIMEOUT, "Caller is routed to the voice box of given extension."}, { ACTION_PARTYLINE, "partyline",&EndpointAppPBX::action_init_partyline, NULL, &EndpointAppPBX::action_hangup_call, PARAM_ROOM | PARAM_JINGLE, "Caller is participating the conference with the given room number."}, { ACTION_LOGIN, "login", NULL, &EndpointAppPBX::action_dialing_login, NULL, PARAM_CONNECT | PARAM_EXTENSION | PARAM_NOPASSWORD, "Log into the given extension. Password required."}, { ACTION_CALLERID, "callerid", &EndpointAppPBX::action_init_change_callerid, &EndpointAppPBX::action_dialing_callerid, NULL, PARAM_CONNECT | PARAM_CALLERID | PARAM_CALLERIDTYPE | PARAM_PRESENT, "Caller changes the caller ID for all calls."}, { ACTION_CALLERIDNEXT, "calleridnext",&EndpointAppPBX::action_init_change_callerid, &EndpointAppPBX::action_dialing_calleridnext, NULL, PARAM_CONNECT | PARAM_CALLERID | PARAM_CALLERIDTYPE | PARAM_PRESENT, "Caller changes the caller ID for the next call."}, { ACTION_FORWARD, "forward", &EndpointAppPBX::action_init_change_forward, &EndpointAppPBX::action_dialing_forward, NULL, PARAM_CONNECT | PARAM_DIVERSION | PARAM_DEST | PARAM_DELAY, "Caller changes the diversion of given type to the given destination or voice box."}, { ACTION_REDIAL, "redial", &EndpointAppPBX::action_init_redial_reply, &EndpointAppPBX::action_dialing_redial, NULL, PARAM_CONNECT | PARAM_SELECT, "Caller redials. (last outgoing call(s))"}, { ACTION_REPLY, "reply", &EndpointAppPBX::action_init_redial_reply, &EndpointAppPBX::action_dialing_reply, NULL, PARAM_CONNECT | PARAM_SELECT, "Caller replies. (last incoming call(s))"}, { ACTION_POWERDIAL, "powerdial", NULL, &EndpointAppPBX::action_dialing_powerdial, NULL, PARAM_CONNECT | PARAM_DELAY | PARAM_LIMIT | PARAM_TIMEOUT, "Caller redials using powerdialing."}, { ACTION_CALLBACK, "callback", NULL, &EndpointAppPBX::action_dialing_callback, &EndpointAppPBX::action_hangup_callback, PARAM_PROCEEDING | PARAM_ALERTING | PARAM_CONNECT | PARAM_EXTENSION | PARAM_DELAY | PARAM_CALLTO | PARAM_PREFIX, "Caller will use the callback service. After disconnecting, the callback is triggered."}, { ACTION_ABBREV, "abbrev", NULL, &EndpointAppPBX::action_dialing_abbrev, NULL, PARAM_CONNECT, "Caller dials abbreviation."}, { ACTION_TEST, "test", NULL, &EndpointAppPBX::action_dialing_test, NULL, PARAM_CONNECT | PARAM_PREFIX | PARAM_TIMEOUT, "Caller dials test mode."}, { ACTION_PLAY, "play", &EndpointAppPBX::action_init_play, NULL, NULL, PARAM_PROCEEDING | PARAM_ALERTING | PARAM_CONNECT | PARAM_SAMPLE | PARAM_TIMEOUT, "Plays the given sample."}, { ACTION_VBOX_PLAY, "vbox-play", &EndpointAppPBX::action_init_vbox_play, &EndpointAppPBX::action_dialing_vbox_play, NULL, PARAM_EXTENSION, "Caller listens to her voice box or to given extension."}, { ACTION_CALCULATOR, "calculator", NULL, &EndpointAppPBX::action_dialing_calculator, NULL, PARAM_CONNECT, "Caller calls the calculator."}, { ACTION_TIMER, "timer", NULL, &EndpointAppPBX::action_dialing_timer, NULL, PARAM_CONNECT | PARAM_TPRESET | PARAM_TIMEOUT, NULL}, // "Caller calls the timer."}, { ACTION_GOTO, "goto", NULL, &EndpointAppPBX::action_dialing_goto, NULL, PARAM_PROCEEDING | PARAM_ALERTING | PARAM_CONNECT | PARAM_RULESET | PARAM_STRIP | PARAM_SAMPLE, "Jump to given ruleset and optionally play sample. Dialed digits are not flushed."}, { ACTION_MENU, "menu", NULL, &EndpointAppPBX::action_dialing_menu, NULL, PARAM_CONNECT | PARAM_RULESET | PARAM_SAMPLE, "Same as 'goto', but flushes all digits dialed so far."}, { ACTION_DISCONNECT, "disconnect", NULL, &EndpointAppPBX::action_dialing_disconnect, NULL, PARAM_CONNECT | PARAM_CAUSE | PARAM_LOCATION | PARAM_SAMPLE | PARAM_DISPLAY, "Caller gets disconnected optionally with given cause and given sample and given display text."}, { ACTION_RELEASE, "release", NULL, &EndpointAppPBX::action_dialing_release, NULL, PARAM_CONNECT | PARAM_CAUSE | PARAM_LOCATION | PARAM_DISPLAY, "Same as 'disconnect', but using RELEASE message on ISDN."}, { ACTION_DEFLECT, "deflect", NULL, &EndpointAppPBX::action_dialing_deflect, NULL, PARAM_DEST, NULL}, // "External call is deflected to the given destination within the telephone network."}, { ACTION_SETFORWARD, "setforward", NULL, &EndpointAppPBX::action_dialing_setforward, NULL, PARAM_CONNECT | PARAM_DIVERSION | PARAM_DEST | PARAM_PORT, NULL}, // "The call forward is set within the telephone network of the external line."}, { ACTION_EXECUTE, "execute", &EndpointAppPBX::action_init_execute, NULL, &EndpointAppPBX::action_hangup_execute, PARAM_CONNECT | PARAM_EXECUTE | PARAM_PARAM | PARAM_ON | PARAM_TIMEOUT, "Executes the given script file. The file must terminate quickly, because it will halt the PBX."}, { ACTION_FILE, "file", NULL, NULL, &EndpointAppPBX::action_hangup_file, PARAM_CONNECT | PARAM_FILE | PARAM_CONTENT | PARAM_APPEND, "Writes givent content to given file. If content is not given, the dialed digits are written."}, { ACTION_PICK, "pick", &EndpointAppPBX::action_init_pick, NULL, NULL, PARAM_EXTENSIONS, "Pick up a call that is ringing on any phone. Extensions may be given to limit the picking ability."}, { ACTION_PASSWORD, "password", NULL, &EndpointAppPBX::action_dialing_password, NULL, 0, NULL}, { ACTION_PASSWORD_WRITE, "password_wr",NULL, &EndpointAppPBX::action_dialing_password_wr, NULL, 0, NULL}, { ACTION_NOTHING, "nothing", NULL, NULL, NULL, PARAM_PROCEEDING | PARAM_ALERTING | PARAM_CONNECT | PARAM_TIMEOUT, "does nothing. Usefull to wait for calls to be released completely, by giving timeout value."}, { ACTION_EFI, "efi", &EndpointAppPBX::action_init_efi, NULL, NULL, PARAM_PROCEEDING | PARAM_ALERTING | PARAM_CONNECT, "Elektronische Fernsprecher Identifikation - announces caller ID."}, { ACTION_POTS_RETRIEVE, "pots-retrieve", &EndpointAppPBX::action_init_pots_retrieve, NULL, NULL, PARAM_POTS_CALL, "When using POTS: Select call on hold to retrieve."}, { ACTION_POTS_RELEASE, "pots-release", &EndpointAppPBX::action_init_pots_release, NULL, NULL, PARAM_POTS_CALL, "When using POTS: Select call on hold to release."}, { ACTION_POTS_REJECT, "pots-reject", &EndpointAppPBX::action_init_pots_reject, NULL, NULL, 0, "When using POTS: Reject incomming waiting call."}, { ACTION_POTS_ANSWER, "pots-answer", &EndpointAppPBX::action_init_pots_answer, NULL, NULL, 0, "When using POTS: Answer incomming waiting call."}, { ACTION_POTS_3PTY, "pots-3pty", &EndpointAppPBX::action_init_pots_3pty, NULL, NULL, 0, "When using POTS: Invoke 3PTY call of two calls on hold"}, { ACTION_POTS_TRANSFER, "pots-transfer", &EndpointAppPBX::action_init_pots_transfer, NULL, NULL, 0, "When using POTS: Interconnect two calls on hold"}, { -1, NULL, NULL, NULL, NULL, 0, NULL} }; /* display documentation of rules */ void doc_rules(const char *name) { int i, j; if (name) { i = 0; while(action_defs[i].name) { if (!strcasecmp(action_defs[i].name, name)) break; i++; } if (!action_defs[i].name) { fprintf(stderr, "Given action '%s' unknown.\n", name); return; } name = action_defs[i].name; } printf("Syntax overview:\n"); printf("----------------\n\n"); printf("[ruleset]\n"); printf(" ... : [parameter ...] [timeout=X : ...]\n"); printf("...\n"); printf("Please refer to the documentation for description on rule format.\n\n"); if (!name) { printf("Available conditions to match:\n"); printf("------------------------------\n\n"); i = 0; while(cond_defs[i].name) { printf("Usage: %s\n", cond_defs[i].doc); printf("%s\n\n", cond_defs[i].help); i++; } printf("Available actions with their parameters:\n"); printf("----------------------------------------\n\n"); } else { printf("Detailes parameter description of action:\n"); printf("-----------------------------------------\n\n"); } i = 0; while(action_defs[i].name) { if (name && !!strcmp(action_defs[i].name,name)) { /* not selected */ i++; continue; } if (!action_defs[i].help) { /* not internal actions */ i++; continue; } printf("Usage: %s", action_defs[i].name); j = 0; while(j < 64) { if ((1LL<next; while(ruleset->rule_first) { rule = ruleset->rule_first; ruleset->rule_first = rule->next; while(rule->cond_first) { cond = rule->cond_first; if (cond->string_value) { FREE(cond->string_value, 0); rmemuse--; } if (cond->string_value_to) { FREE(cond->string_value_to, 0); rmemuse--; } rule->cond_first = cond->next; FREE(cond, sizeof(struct route_cond)); rmemuse--; } while(rule->action_first) { action = rule->action_first; rule->action_first = action->next; while(action->param_first) { param = action->param_first; action->param_first = param->next; if (param->string_value) { FREE(param->string_value, 0); rmemuse--; } FREE(param, sizeof(struct route_param)); rmemuse--; } FREE(action, sizeof(struct route_action)); rmemuse--; } FREE(rule, sizeof(struct route_rule)); rmemuse--; } FREE(ruleset, sizeof(struct route_ruleset)); rmemuse--; } } void ruleset_debug(struct route_ruleset *ruleset_start) { struct route_ruleset *ruleset; struct route_rule *rule; struct route_cond *cond; struct route_action *action; struct route_param *param; int first; ruleset = ruleset_start; while(ruleset) { printf("Ruleset: '%s'\n", ruleset->name); rule = ruleset->rule_first; while(rule) { /* CONDITION */ first = 1; cond = rule->cond_first; while(cond) { if (first) printf(" Condition:"); else printf(" and "); first = 0; printf(" %s", cond_defs[cond->index].name); if (cond->value_type != VALUE_TYPE_NULL) printf(" = "); next_cond_value: switch(cond->value_type) { case VALUE_TYPE_NULL: break; case VALUE_TYPE_INTEGER: printf("%d", cond->integer_value); break; case VALUE_TYPE_INTEGER_RANGE: printf("%d-%d", cond->integer_value, cond->integer_value_to); break; case VALUE_TYPE_STRING: printf("'%s'", cond->string_value); break; case VALUE_TYPE_STRING_RANGE: printf("'%s'-'%s'", cond->string_value, cond->string_value_to); break; default: printf("Software error: VALUE_TYPE_* %d not known in function '%s' line=%d", cond->value_type, __FUNCTION__, __LINE__); } if (cond->value_extension && cond->next) { cond = cond->next; printf(" or "); goto next_cond_value; } cond = cond->next; printf("\n"); } /* ACTION */ action = rule->action_first; while(action) { printf(" Action: %s\n", action_defs[action->index].name); /* PARAM */ first = 1; param = action->param_first; while(param) { if (first) printf(" Param:"); else printf(" "); first = 0; printf(" %s", param_defs[param->index].name); if (param->value_type != VALUE_TYPE_NULL) printf(" = "); switch(param->value_type) { case VALUE_TYPE_NULL: break; case VALUE_TYPE_INTEGER: if (param_defs[param->index].type == PARAM_TYPE_CALLERIDTYPE) { switch(param->integer_value) { case INFO_NTYPE_UNKNOWN: printf("unknown"); break; case INFO_NTYPE_SUBSCRIBER: printf("subscriber"); break; case INFO_NTYPE_NATIONAL: printf("national"); break; case INFO_NTYPE_INTERNATIONAL: printf("international"); break; default: printf("unknown(%d)", param->integer_value); } break; } if (param_defs[param->index].type == PARAM_TYPE_CAPABILITY) { switch(param->integer_value) { case INFO_BC_SPEECH: printf("speech"); break; case INFO_BC_AUDIO: printf("audio"); break; case INFO_BC_VIDEO: printf("video"); break; case INFO_BC_DATARESTRICTED: printf("digital-restricted"); break; case INFO_BC_DATAUNRESTRICTED: printf("digital-unrestricted"); break; case INFO_BC_DATAUNRESTRICTED_TONES: printf("digital-unrestricted-tones"); break; default: printf("unknown(%d)", param->integer_value); } break; } if (param_defs[param->index].type == PARAM_TYPE_DIVERSION) { switch(param->integer_value) { case INFO_DIVERSION_CFU: printf("cfu"); break; case INFO_DIVERSION_CFNR: printf("cfnr"); break; case INFO_DIVERSION_CFB: printf("cfb"); break; case INFO_DIVERSION_CFP: printf("cfp"); break; default: printf("unknown(%d)", param->integer_value); } break; } if (param_defs[param->index].type == PARAM_TYPE_TYPE) { switch(param->integer_value) { case INFO_NTYPE_UNKNOWN: printf("unknown"); break; case INFO_NTYPE_SUBSCRIBER: printf("subscriber"); break; case INFO_NTYPE_NATIONAL: printf("national"); break; case INFO_NTYPE_INTERNATIONAL: printf("international"); break; default: printf("unknown(%d)", param->integer_value); } break; } if (param_defs[param->index].type == PARAM_TYPE_YESNO) { switch(param->integer_value) { case 1: printf("yes"); break; case 0: printf("no"); break; default: printf("unknown(%d)", param->integer_value); } break; } if (param_defs[param->index].type == PARAM_TYPE_NULL) { break; } printf("%d", param->integer_value); break; case VALUE_TYPE_STRING: printf("'%s'", param->string_value); break; default: printf("Software error: VALUE_TYPE_* %d not known in function '%s' line=%d", param->value_type, __FUNCTION__, __LINE__); } param = param->next; printf("\n"); } /* TIMEOUT */ if (action->timeout) printf(" Timeout: %d\n", action->timeout); action = action->next; } printf("\n"); rule = rule->next; } printf("\n"); ruleset = ruleset->next; } } /* * parse ruleset */ static char *read_string(char *p, char *key, int key_size, const char *special) { key[0] = 0; if (*p == '\"') { p++; /* quote */ while(*p) { if (*p == '\"') { p++; *key = '\0'; return(p); } if (*p == '\\') { p++; if (*p == '\0') { break; } } if (--key_size == 0) { UPRINT(key, "\001String too long."); return(p); } *key++ = *p++; } UPRINT(key, "\001Unexpected end of line inside quotes."); return(p); } /* no quote */ while(*p) { if (strchr(special, *p)) { *key = '\0'; return(p); } if (*p == '\\') { p++; if (*p == '\0') { UPRINT(key, "\001Unexpected end of line."); return(p); } } if (--key_size == 0) { UPRINT(key, "\001String too long."); return(p); } *key++ = *p++; } *key = '\0'; return(p); } char ruleset_error[256]; struct route_ruleset *ruleset_parse(void) { // char from[128]; // char to[128]; int i; unsigned long long j; // int a, // b; #define MAXNESTING 8 FILE *fp[MAXNESTING]; char filename[MAXNESTING][256]; int line[MAXNESTING]; int nesting = -1; char buffer[1024], key[1024], key_to[1024], pointer[1024+1], *p; int expecting = 1; /* 1 = expecting ruleset */ int index, value_type, integer, integer_to; /* condition index, .. */ struct route_ruleset *ruleset_start = NULL, *ruleset; struct route_ruleset **ruleset_pointer = &ruleset_start; struct route_rule *rule; struct route_rule **rule_pointer = NULL; struct route_cond *cond; struct route_cond **cond_pointer = NULL; struct route_action *action; struct route_action **action_pointer = NULL; struct route_param *param; struct route_param **param_pointer = NULL; char failure[256]; unsigned long long allowed_params; /* check the integrity of IDs for ACTION_* and PARAM_* */ i = 0; while(action_defs[i].name) { if (action_defs[i].id != i) { PERROR("Software Error action '%s' must have id of %d, but has %d.\n", action_defs[i].name, i, action_defs[i].id); goto openerror; } i++; } i = 0; j = 1; while(param_defs[i].name) { if (param_defs[i].id != j) { PERROR("Software Error param '%s' must have id of 0x%llx, but has 0x%llx.\n", param_defs[i].name, j, param_defs[i].id); goto openerror; } i++; j<<=1; } SPRINT(filename[0], "%s/routing.conf", CONFIG_DATA); if (!(fp[0]=fopen(filename[0],"r"))) { PERROR("Cannot open %s\n",filename[0]); goto openerror; } nesting++; fduse++; go_leaf: line[nesting]=0; go_root: while((GETLINE(buffer, fp[nesting]))) { line[nesting]++; p = buffer; /* remove tabs */ while(*p) { if (*p < 32) *p = 32; p++; } p = buffer; /* skip spaces, if any */ while(*p == 32) { if (*p == 0) break; p++; } /* skip comments */ if (*p == '#') { p++; /* don't skip "define" */ if (!!strncmp(p, "define", 6)) continue; p+=6; if (*p != 32) continue; /* skip spaces */ while(*p == 32) { if (*p == 0) break; p++; } p++; p = read_string(p, key, sizeof(key), " "); if (key[0] == 1) { /* error */ SPRINT(failure, "Parsing Filename failed: %s", key+1); goto parse_error; } if (nesting == MAXNESTING-1) { SPRINT(failure, "'include' is nesting too deep.\n"); goto parse_error; } if (key[0] == '/') SCPY(filename[nesting+1], key); else SPRINT(filename[nesting+1], "%s/%s", CONFIG_DATA, key); if (!(fp[nesting+1]=fopen(filename[nesting+1],"r"))) { PERROR("Cannot open %s\n", filename[nesting+1]); goto parse_error; } fduse++; nesting++; goto go_leaf; } if (*p == '/') if (p[1] == '/') continue; /* skip empty lines */ if (*p == 0) continue; /* expecting ruleset */ if (expecting) { new_ruleset: /* expecting [ */ if (*p != '[') { SPRINT(failure, "Expecting ruleset name starting with '['."); goto parse_error; } p++; /* reading ruleset name text */ i = 0; while(*p>' ' && *p<127 && *p!=']') { if (*p>='A' && *p<='Z') *p = *p-'A'+'a'; /* lower case */ key[i++] = *p++; if (i == sizeof(key)) i--; /* limit */ } key[i] = 0; if (key[0] == '\0') { SPRINT(failure, "Missing ruleset name after '['."); goto parse_error; } /* expecting ] and nothing more */ if (*p != ']') { SPRINT(failure, "Expecting ']' after ruleset name."); goto parse_error; } p++; if (*p != 0) { SPRINT(failure, "Unexpected character after ruleset name."); goto parse_error; } /* check for duplicate rulesets */ ruleset = ruleset_start; while(ruleset) { if (!strcmp(ruleset->name, key)) { SPRINT(failure, "Duplicate ruleset '%s', already defined in file '%s' line %d.", key, ruleset->file, ruleset->line); goto parse_error; } ruleset = ruleset->next; } /* create ruleset */ ruleset = (struct route_ruleset *)MALLOC(sizeof(struct route_ruleset)); rmemuse++; *ruleset_pointer = ruleset; ruleset_pointer = &(ruleset->next); SCPY(ruleset->name, key); SCPY(ruleset->file, filename[nesting]); ruleset->line = line[nesting]; rule_pointer = &(ruleset->rule_first); expecting = 0; continue; } /* for new ruleset [ */ if (*p == '[') { goto new_ruleset; } /* Alloc memory for rule */ rule = (struct route_rule *)MALLOC(sizeof(struct route_rule)); rmemuse++; *rule_pointer = rule; rule_pointer = &(rule->next); cond_pointer = &(rule->cond_first); action_pointer = &(rule->action_first); SCPY(rule->file, filename[nesting]); rule->line = line[nesting]; /* loop CONDITIONS */ while(*p!=':' && *p!='\0') { /* read item text */ i = 0; while((*p>='a' && *p<='z') || (*p>='A' && *p<='Z') || (*p>='0' && *p<='9') || *p == '-') { if (*p>='A' && *p<='Z') *p = *p-'A'+'a'; /* lower case */ key[i++] = *p++; if (i == sizeof(key)) i--; /* limit */ } key[i] = 0; if (key[0] == '\0') { SPRINT(failure, "Expecting condition item name or ':' for end of condition list."); goto parse_error; } if (*p!=' ' && *p!='=') { SPRINT(failure, "Illegal character '%c' after condition name '%s'. Expecting '=' for equation or ' ' to seperate condition items.", *p, key); goto parse_error; } /* check if condition exists */ index = 0; while(cond_defs[index].name) { if (!strcmp(cond_defs[index].name, key)) break; index++; } if (cond_defs[index].name == NULL) { SPRINT(failure, "Unknown condition item name '%s'.", key); goto parse_error; } /* items without values must not have any parameter */ if (cond_defs[index].type == COND_TYPE_NULL) { if (*p == '=') { SPRINT(failure, "Condition item '%s' must not have any value. Don't use '=' for this type of condition.", key); goto parse_error; } if (*p != ' ') { SPRINT(failure, "Condition item '%s' must not have any value. Expecting ' ' or tab after item name.", key); goto parse_error; } // p++; } else { if (*p == ' ') { SPRINT(failure, "Condition item '%s' must have at least one value, '=' expected, and not a space.", key); goto parse_error; } if (*p != '=') { SPRINT(failure, "Condition item '%s' must have at least one value, '=' expected.", key); goto parse_error; } p++; } /* check for duplicate condition */ cond = rule->cond_first; while(cond) { if (cond->index == index) { SPRINT(failure, "Duplicate condition '%s', use ',' to give multiple values.", key); goto parse_error; } cond = cond->next; } nextcondvalue: /* Alloc memory for item */ cond = (struct route_cond *)MALLOC(sizeof(struct route_cond)); rmemuse++; *cond_pointer = cond; cond_pointer = &(cond->next); cond->index = index; cond->match = cond_defs[index].match; switch(cond_defs[index].type) { case COND_TYPE_NULL: if (*p=='=') { SPRINT(failure, "Expecting no value."); goto parse_error; } value_type = VALUE_TYPE_NULL; break; /* parse all integer values/ranges */ case COND_TYPE_INTEGER: case COND_TYPE_TIME: case COND_TYPE_MDAY: case COND_TYPE_MONTH: case COND_TYPE_WDAY: case COND_TYPE_YEAR: integer = integer_to = 0; if (*p==',' || *p==' ' || *p=='\0') { SPRINT(failure, "Missing integer value."); goto parse_error; } while(*p>='0' && *p<='9') { integer = integer*10 + *p-'0'; p++; } value_type = VALUE_TYPE_INTEGER; if (*p == '-') { p++; if (*p==',' || *p==' ' || *p=='\0') { SPRINT(failure, "Missing integer value."); goto parse_error; } while(*p>='0' && *p<='9') { integer_to = integer_to*10 + *p-'0'; p++; } value_type = VALUE_TYPE_INTEGER_RANGE; } if (cond_defs[index].type == COND_TYPE_TIME) { // Simon: i store the time as decimal, later i compare it correctly: // hours * 100 + minutes if (integer == 2400) integer = 0; if (integer >= 2400) { timeoutofrange1: SPRINT(failure, "Given time '%d' not in range 0000..2359 (or 2400 for 0000)", integer); goto parse_error; } if (integer%100 >= 60) goto timeoutofrange1; if (value_type == VALUE_TYPE_INTEGER) goto integer_done; if (integer_to == 2400) integer_to = 0; if (integer_to >= 2400) { timeoutofrange2: SPRINT(failure, "Given time '%d' not in range 0000..2359 (or 2400 for 0000)", integer_to); goto parse_error; } if (integer_to%100 >= 60) goto timeoutofrange2; } if (cond_defs[index].type == COND_TYPE_MDAY) { if (integer<1 || integer>31) { SPRINT(failure, "Given day-of-month '%d' not in range 1..31", integer); goto parse_error; } if (value_type == VALUE_TYPE_INTEGER) goto integer_done; if (integer_to<1 || integer_to>31) { SPRINT(failure, "Given day-of-month '%d' not in range 1..31", integer_to); goto parse_error; } } if (cond_defs[index].type == COND_TYPE_WDAY) { if (integer<1 || integer>7) { SPRINT(failure, "Given day-of-week '%d' not in range 1..7", integer); goto parse_error; } if (value_type == VALUE_TYPE_INTEGER) goto integer_done; if (integer_to<1 || integer_to>7) { SPRINT(failure, "Given day-of-week '%d' not in range 1..7", integer_to); goto parse_error; } } if (cond_defs[index].type == COND_TYPE_MONTH) { if (integer<1 || integer>12) { SPRINT(failure, "Given month '%d' not in range 1..12", integer); goto parse_error; } if (value_type == VALUE_TYPE_INTEGER) goto integer_done; if (integer_to<1 || integer_to>12) { SPRINT(failure, "Given month '%d' not in range 1..12", integer_to); goto parse_error; } } if (cond_defs[index].type == COND_TYPE_YEAR) { if (integer<1970 || integer>2106) { SPRINT(failure, "Given year '%d' not in range 1970..2106", integer); goto parse_error; } if (value_type == VALUE_TYPE_INTEGER) goto integer_done; if (integer_to<1970 || integer_to>2106) { SPRINT(failure, "Given year '%d' not in range 1970..2106", integer_to); goto parse_error; } } integer_done: cond->integer_value = integer; cond->integer_value_to = integer_to; cond->value_type = value_type; break; /* parse all string values/ranges */ case COND_TYPE_STRING: key[0] = key_to[0] = '\0'; if (*p==',' || *p==' ' || *p=='\0') { SPRINT(failure, "Missing string value, use \"\" for empty string."); goto parse_error; } p = read_string(p, key, sizeof(key), "-, "); if (key[0] == 1) { /* error */ SPRINT(failure, "Parsing String failed: %s", key+1); goto parse_error; } value_type = VALUE_TYPE_STRING; if (*p == '-') { p++; if (*p==',' || *p==' ' || *p=='\0') { SPRINT(failure, "Missing string value, use \"\" for empty string."); goto parse_error; } p = read_string(p, key_to, sizeof(key_to), "-, "); if (key_to[0] == 1) { /* error */ SPRINT(failure, "Parsing string failed: %s", key_to+1); goto parse_error; } value_type = VALUE_TYPE_STRING_RANGE; if (strlen(key) != strlen(key_to)) { SPRINT(failure, "Given range of strings \"%s\"-\"%s\" have unequal length.", key, key_to); goto parse_error; } if (key[0] == '\0') { SPRINT(failure, "Given range has no length."); goto parse_error; } } alloc_string: cond->string_value = (char *)MALLOC(strlen(key)+1); rmemuse++; UCPY(cond->string_value, key); if (value_type == VALUE_TYPE_STRING_RANGE) { cond->string_value_to = (char *)MALLOC(strlen(key_to)+1); rmemuse++; UCPY(cond->string_value_to, key_to); cond->comp_string = strcmp(key, key_to); } cond->value_type = value_type; break; /* parse service value */ case COND_TYPE_CAPABILITY: if (!strncasecmp("speech", p, 6)) { cond->integer_value = INFO_BC_SPEECH; p += 6; } else if (!strncasecmp("audio", p, 5)) { cond->integer_value = INFO_BC_AUDIO; p += 5; } else if (!strncasecmp("video", p, 5)) { cond->integer_value = INFO_BC_VIDEO; p += 5; } else if (!strncasecmp("digital-restricted", p, 18)) { cond->integer_value = INFO_BC_DATARESTRICTED; p += 18; } else if (!strncasecmp("digital-unrestricted", p, 20)) { cond->integer_value = INFO_BC_DATAUNRESTRICTED; p += 20; } else if (!strncasecmp("digital-unrestricted-tones", p, 26)) { cond->integer_value = INFO_BC_DATAUNRESTRICTED_TONES; p += 26; } else { SPRINT(failure, "Given service type is invalid or misspelled."); goto parse_error; } cond->value_type = VALUE_TYPE_INTEGER; break; /* parse bmode value */ case COND_TYPE_BMODE: if (!strncasecmp("transparent", p, 11)) { cond->integer_value = INFO_BMODE_CIRCUIT; p += 11; } else if (!strncasecmp("hdlc", p, 4)) { cond->integer_value = INFO_BMODE_PACKET; p += 4; } else { SPRINT(failure, "Given bchannel mode is invalid or misspelled."); goto parse_error; } cond->value_type = VALUE_TYPE_INTEGER; break; /* parse service value */ case COND_TYPE_HLC: if (!strncasecmp("telephony", p, 9)) { cond->integer_value = INFO_HLC_TELEPHONY; p += 9; } else if (!strncasecmp("faxg2g3", p, 7)) { cond->integer_value = INFO_HLC_FAXG2G3; p += 7; } else if (!strncasecmp("faxg4", p, 5)) { cond->integer_value = INFO_HLC_FAXG4; p += 5; } else if (!strncasecmp("teletex1", p, 8)) { cond->integer_value = INFO_HLC_TELETEX1; p += 8; } else if (!strncasecmp("teletex2", p, 8)) { cond->integer_value = INFO_HLC_TELETEX2; p += 8; } else if (!strncasecmp("teletex3", p, 8)) { cond->integer_value = INFO_HLC_TELETEX3; p += 8; } else if (!strncasecmp("videotex1", p, 9)) { cond->integer_value = INFO_HLC_VIDEOTEX1; p += 9; } else if (!strncasecmp("videotex2", p, 9)) { cond->integer_value = INFO_HLC_VIDEOTEX2; p += 9; } else if (!strncasecmp("telex", p, 5)) { cond->integer_value = INFO_HLC_TELEX; p += 5; } else if (!strncasecmp("mhs", p, 3)) { cond->integer_value = INFO_HLC_MHS; p += 3; } else if (!strncasecmp("osi", p, 3)) { cond->integer_value = INFO_HLC_OSI; p += 3; } else if (!strncasecmp("maintenance", p, 11)) { cond->integer_value = INFO_HLC_MAINTENANCE; p += 11; } else if (!strncasecmp("management", p, 10)) { cond->integer_value = INFO_HLC_MANAGEMENT; p += 10; } else if (!strncasecmp("audiovisual", p, 11)) { cond->integer_value = INFO_HLC_AUDIOVISUAL; p += 11; } else { SPRINT(failure, "Given HLC type is invalid or misspelled."); goto parse_error; } cond->value_type = VALUE_TYPE_INTEGER; break; /* parse interface attribute : */ case COND_TYPE_IFATTR: key[0] = key_to[0] = '\0'; if (*p==':' || *p==',' || *p==' ' || *p=='\0') { SPRINT(failure, "Missing interface name."); goto parse_error; } p = read_string(p, key, sizeof(key), ":-, "); if (key[0] == 1) { /* error */ SPRINT(failure, "Parsing interface failed: %s", key+1); goto parse_error; } if (*p != ':') { SPRINT(failure, "Expeciting kolon to seperate value behind interface name."); goto parse_error; } SCCAT(key, *p++); while(*p>='0' && *p<='9') { SCCAT(key, *p++); } if (*p!=',' && *p!=' ' && *p!='\0') { SPRINT(failure, "Invalid characters behind value."); goto parse_error; } value_type = VALUE_TYPE_STRING; goto alloc_string; break; default: SPRINT(failure, "Software error: COND_TYPE_* %d not parsed in function '%s'", cond_defs[index].type, __FUNCTION__); goto parse_error; } /* if we have another value for that item, we attach it */ if (*p == ',') { p++; /* next item */ cond->value_extension = 1; goto nextcondvalue; } /* to seperate the items, a space is required */ if (*p != ' ') { SPRINT(failure, "Character '%c' not expected here. Use ',' to seperate multiple possible values.", *p); goto parse_error; } /* skip spaces */ while(*p == 32) { if (*p == 0) break; p++; } } /* we are done with CONDITIONS, so we expect the ACTION */ if (*p != ':') { SPRINT(failure, "Expecting ':' after condition item(s)."); goto parse_error; } p++; nextaction: /* skip spaces, if any */ while(*p == 32) { if (*p == 0) break; p++; } /* read action name */ i = 0; while((*p>='a' && *p<='z') || (*p>='A' && *p<='Z') || (*p>='0' && *p<='9') || *p == '-') { if (*p>='A' && *p<='Z') *p = *p-'A'+'a'; /* lower case */ key[i++] = *p++; if (i == sizeof(key)) i--; /* limit */ } key[i] = 0; if (key[0] == '\0') { SPRINT(failure, "Expecting action name."); goto parse_error; } /* check if item exists */ index = 0; while(action_defs[index].name) { if (!action_defs[index].help) { /* not internal actions */ index++; continue; } if (!strcmp(action_defs[index].name, key)) break; index++; } if (action_defs[index].name == NULL) { SPRINT(failure, "Unknown action name '%s'.", key); goto parse_error; } allowed_params = action_defs[index].params; /* alloc memory for action */ action = (struct route_action *)MALLOC(sizeof(struct route_action)); rmemuse++; *action_pointer = action; action_pointer = &(action->next); param_pointer = &(action->param_first); action->index = index; action->line = line[nesting]; /* skip spaces after param name */ while(*p == 32) { if (*p == 0) break; p++; } /* loop PARAMS */ while(*p != 0) { /* read param text */ i = 0; while((*p>='a' && *p<='z') || (*p>='A' && *p<='Z') || (*p>='0' && *p<='9') || *p == '-') { if (*p>='A' && *p<='Z') *p = *p-'A'+'a'; /* lower case */ key[i++] = *p++; if (i == sizeof(key)) i--; /* limit */ } if (*p == ':') { p++; goto nextaction; } key[i] = 0; if (key[0] == '\0') { SPRINT(failure, "Expecting parameter name."); goto parse_error; } /* check if item exists */ index = 0; while(param_defs[index].name) { if (!strcmp(param_defs[index].name, key)) break; index++; } if (param_defs[index].name == NULL) { SPRINT(failure, "Unknown param name '%s'.", key); goto parse_error; } /* check if item is allowed for the action */ if (!(param_defs[index].id & allowed_params)) { SPRINT(failure, "Param name '%s' exists, but not for this action.", key); goto parse_error; } /* params without values must not have any parameter */ if (param_defs[index].type == PARAM_TYPE_NULL) { if (*p!=' ' && *p!='\0') { SPRINT(failure, "Parameter '%s' must not have any value.", key); goto parse_error; } } else { if (*p == ' ') { SPRINT(failure, "Parameter '%s' must have at least one value, '=' expected and not a space.", key); goto parse_error; } if (*p != '=') { SPRINT(failure, "Parameter '%s' must have at least one value, '=' expected.", key); goto parse_error; } p++; } /* special timeout value */ if (!strcmp("timeout", key)) { if (action->timeout) { SPRINT(failure, "Duplicate timeout value."); goto parse_error; } if (*p==',' || *p==' ' || *p=='\0') { SPRINT(failure, "Missing integer value."); goto parse_error; } integer = 0; while(*p>='0' && *p<='9') { integer = integer*10 + *p-'0'; p++; } if (integer < 1) { SPRINT(failure, "Expecting timeout value greater 0."); goto parse_error; } if (*p!=' ' && *p!='\0') { SPRINT(failure, "Character '%c' not expected here. Use ' ' to seperate parameters.", *p); goto parse_error; } /* skip spaces */ while(*p == 32) { if (*p == 0) break; p++; } action->timeout = integer; /* check for next ACTION */ if (*p == ':') { p++; goto nextaction; } continue; } /* check for duplicate parameters */ param = action->param_first; while(param) { if (param->index == index) { SPRINT(failure, "Duplicate parameter '%s', use ',' to give multiple values.", key); goto parse_error; } param = param->next; } nextparamvalue: /* Alloc memory for param */ param = (struct route_param *)MALLOC(sizeof(struct route_param)); rmemuse++; *param_pointer = param; param_pointer = &(param->next); param->index = index; param->id = param_defs[index].id; switch(param_defs[index].type) { /* parse null value */ case PARAM_TYPE_NULL: param->value_type = VALUE_TYPE_NULL; break; /* parse integer value */ case PARAM_TYPE_INTEGER: integer = 0; if (*p==',' || *p==' ' || *p=='\0') { SPRINT(failure, "Missing integer value."); goto parse_error; } while(*p>='0' && *p<='9') { integer = integer*10 + *p-'0'; p++; } param->integer_value = integer; param->value_type = VALUE_TYPE_INTEGER; break; #if 0 /* parse ports value */ case PARAM_TYPE_PORTS: key[0] = '\0'; if (*p==',' || *p==' ' || *p=='\0') { SPRINT(failure, "Missing port number, omit parameter or give port number."); goto parse_error; } i = 0; nextport: integer = 0; while(*p>='0' && *p<='9') { if (i < (int)sizeof(key)-1) { key[i] = *p; key[i++] = '\0'; } integer = integer*10 + *p-'0'; p++; } if (integer > 255) { SPRINT(failure, "Port number too high."); goto parse_error; } if (*p==',') { if (i < (int)sizeof(key)-1) { key[i] = *p; key[i++] = '\0'; } p++; goto nextport; } goto mallocstring; #endif /* parse string value */ case PARAM_TYPE_STRING: case PARAM_TYPE_CALLERIDTYPE: case PARAM_TYPE_CAPABILITY: case PARAM_TYPE_BMODE: case PARAM_TYPE_HLC: case PARAM_TYPE_DIVERSION: case PARAM_TYPE_DESTIN: case PARAM_TYPE_TYPE: case PARAM_TYPE_YESNO: case PARAM_TYPE_ON: key[0] = '\0'; if (*p==',' || *p==' ' || *p=='\0') { SPRINT(failure, "Missing string value, use \"\" for empty string."); goto parse_error; } p = read_string(p, key, sizeof(key), " "); if (key[0] == 1) { /* error */ SPRINT(failure, "Parsing string failed: %s", key+1); goto parse_error; } if (param_defs[index].type == PARAM_TYPE_CALLERIDTYPE) { param->value_type = VALUE_TYPE_INTEGER; if (!strcasecmp(key, "unknown")) { param->integer_value = INFO_NTYPE_UNKNOWN; break; } if (!strcasecmp(key, "subscriber")) { param->integer_value = INFO_NTYPE_SUBSCRIBER; break; } if (!strcasecmp(key, "national")) { param->integer_value = INFO_NTYPE_NATIONAL; break; } if (!strcasecmp(key, "international")) { param->integer_value = INFO_NTYPE_INTERNATIONAL; break; } SPRINT(failure, "Caller ID type '%s' unknown.", key); goto parse_error; } if (param_defs[index].type == PARAM_TYPE_ON) { param->value_type = VALUE_TYPE_INTEGER; if (!strcasecmp(key, "init")) { param->integer_value = INFO_ON_INIT; break; } if (!strcasecmp(key, "hangup")) { param->integer_value = INFO_ON_HANGUP; break; } SPRINT(failure, "Execute on '%s' unknown.", key); goto parse_error; } if (param_defs[index].type == PARAM_TYPE_CAPABILITY) { param->value_type = VALUE_TYPE_INTEGER; if (!strcasecmp(key, "speech")) { param->integer_value = INFO_BC_SPEECH; break; } if (!strcasecmp(key, "audio")) { param->integer_value = INFO_BC_AUDIO; break; } if (!strcasecmp(key, "video")) { param->integer_value = INFO_BC_VIDEO; break; } if (!strcasecmp(key, "digital-restricted")) { param->integer_value = INFO_BC_DATARESTRICTED; break; } if (!strcasecmp(key, "digital-unrestricted")) { param->integer_value = INFO_BC_DATAUNRESTRICTED; break; } if (!strcasecmp(key, "digital-unrestricted-tones")) { param->integer_value = INFO_BC_DATAUNRESTRICTED_TONES; break; } SPRINT(failure, "Service type '%s' unknown.", key); goto parse_error; } if (param_defs[index].type == PARAM_TYPE_BMODE) { param->value_type = VALUE_TYPE_INTEGER; if (!strcasecmp(key, "transparent")) { param->integer_value = INFO_BMODE_CIRCUIT; break; } if (!strcasecmp(key, "hdlc")) { param->integer_value = INFO_BMODE_PACKET; break; } SPRINT(failure, "Bchannel mode '%s' unknown.", key); goto parse_error; } if (param_defs[index].type == PARAM_TYPE_HLC) { param->value_type = VALUE_TYPE_INTEGER; if (!strcasecmp(key, "telephony")) { param->integer_value = INFO_HLC_TELEPHONY; break; } if (!strcasecmp(key, "faxg2g3")) { param->integer_value = INFO_HLC_FAXG2G3; break; } if (!strcasecmp(key, "faxg4")) { param->integer_value = INFO_HLC_FAXG4; break; } if (!strcasecmp(key, "teletex1")) { param->integer_value = INFO_HLC_TELETEX1; break; } if (!strcasecmp(key, "teletex2")) { param->integer_value = INFO_HLC_TELETEX2; break; } if (!strcasecmp(key, "teletex3")) { param->integer_value = INFO_HLC_TELETEX3; break; } if (!strcasecmp(key, "videotex1")) { param->integer_value = INFO_HLC_VIDEOTEX1; break; } if (!strcasecmp(key, "videotex2")) { param->integer_value = INFO_HLC_VIDEOTEX2; break; } if (!strcasecmp(key, "telex")) { param->integer_value = INFO_HLC_TELEX; break; } if (!strcasecmp(key, "mhs")) { param->integer_value = INFO_HLC_MHS; break; } if (!strcasecmp(key, "osi")) { param->integer_value = INFO_HLC_OSI; break; } if (!strcasecmp(key, "maintenance")) { param->integer_value = INFO_HLC_MAINTENANCE; break; } if (!strcasecmp(key, "management")) { param->integer_value = INFO_HLC_MANAGEMENT; break; } if (!strcasecmp(key, "audiovisual")) { param->integer_value = INFO_HLC_AUDIOVISUAL; break; } SPRINT(failure, "HLC type '%s' unknown.", key); goto parse_error; } if (param_defs[index].type == PARAM_TYPE_DIVERSION) { param->value_type = VALUE_TYPE_INTEGER; if (!strcasecmp(key, "cfu")) { param->integer_value = INFO_DIVERSION_CFU; break; } if (!strcasecmp(key, "cfb")) { param->integer_value = INFO_DIVERSION_CFB; break; } if (!strcasecmp(key, "cfnr")) { param->integer_value = INFO_DIVERSION_CFNR; break; } if (!strcasecmp(key, "cfp")) { param->integer_value = INFO_DIVERSION_CFP; break; } SPRINT(failure, "Diversion type '%s' unknown.", key); goto parse_error; } if (param_defs[index].type == PARAM_TYPE_TYPE) { param->value_type = VALUE_TYPE_INTEGER; if (!strcasecmp(key, "unknown")) { param->integer_value = INFO_NTYPE_UNKNOWN; break; } if (!strcasecmp(key, "subscriber")) { param->integer_value = INFO_NTYPE_SUBSCRIBER; break; } if (!strcasecmp(key, "national")) { param->integer_value = INFO_NTYPE_NATIONAL; break; } if (!strcasecmp(key, "international")) { param->integer_value = INFO_NTYPE_INTERNATIONAL; break; } SPRINT(failure, "Number type '%s' unknown.", key); goto parse_error; } if (param_defs[index].type == PARAM_TYPE_YESNO) { param->value_type = VALUE_TYPE_INTEGER; if (!strcasecmp(key, "yes")) { param->integer_value = 1; break; } if (!strcasecmp(key, "no")) { param->integer_value = 0; break; } SPRINT(failure, "Value '%s' unknown. ('yes' or 'no')", key); goto parse_error; } param->string_value = (char *)MALLOC(strlen(key)+1); rmemuse++; UCPY(param->string_value, key); param->value_type = VALUE_TYPE_STRING; break; default: SPRINT(failure, "Software error: PARAM_TYPE_* %d not parsed in function '%s'", param_defs[index].type, __FUNCTION__); goto parse_error; } if (*p == ',') { p++; /* next item */ param->value_extension = 1; goto nextparamvalue; } /* end of line */ if (*p == '\0') break; /* to seperate the items, a space is required */ if (*p != ' ') { SPRINT(failure, "Character '%c' not expected here. Use ' ' to seperate parameters, or ',' for multiple values.", *p); goto parse_error; } /* skip spaces */ while(*p == 32) { if (*p == 0) break; p++; } /* check for next ACTION */ if (*p == ':') { p++; goto nextaction; } } } fclose(fp[nesting--]); fduse--; if (nesting >= 0) goto go_root; if (!ruleset_start) { SPRINT(failure, "No ruleset defined."); } return(ruleset_start); parse_error: printf("While parsing %s, an error occurred in line %d:\n", filename[nesting], line[nesting]); printf("-> %s\n", buffer); memset(pointer, ' ', sizeof(pointer)); pointer[p-buffer] = '^'; pointer[p-buffer+1] = '\0'; printf(" %s\n", pointer); printf("%s\n", failure); SPRINT(ruleset_error, "Error in file %s, line %d: %s", filename[nesting], line[nesting], failure); openerror: while(nesting >= 0) { fclose(fp[nesting--]); fduse--; } ruleset_free(ruleset_start); return(NULL); } /* * return ruleset by name */ struct route_ruleset *getrulesetbyname(const char *name) { struct route_ruleset *ruleset = ruleset_first; while(ruleset) { if (!strcasecmp(name, ruleset->name)) { break; } ruleset = ruleset->next; } PDEBUG(DEBUG_ROUTE, "ruleset %s %s.\n", name, ruleset?"found":"not found"); return(ruleset); } /* * parses the current ruleset and returns action */ struct route_action *EndpointAppPBX::route(struct route_ruleset *ruleset) { int match, couldmatch = 0, /* any rule could match */ istrue, couldbetrue, condition, dialing_required; struct route_rule *rule = ruleset->rule_first; struct route_cond *cond; struct route_action *action = NULL; unsigned long comp_len; int j; char isdn_port[10]; char *argv[11]; /* check also number of args below */ char callerid[64], callerid2[64], redirid[64]; int integer; char *string; FILE *tfp; long long timeout, now_ll = 0, match_timeout = 0; struct timeval current_time; #ifdef WITH_MISDN struct mISDNport *mISDNport; int avail; int jj; class Port *port; class Pfxs *ourfxs, *fxs; int fxs_count; int fxs_age; #endif struct admin_list *admin; time_t now; struct tm *now_tm; int pid, status; /* reset timeout action */ e_match_to_action = NULL; SCPY(callerid, numberrize_callerinfo(e_callerinfo.id, e_callerinfo.ntype, options.national, options.international)); SCPY(callerid2, numberrize_callerinfo(e_callerinfo.id2, e_callerinfo.ntype2, options.national, options.international)); SCPY(redirid, numberrize_callerinfo(e_redirinfo.id, e_redirinfo.ntype, options.national, options.international)); PDEBUG(DEBUG_ROUTE, "parsing ruleset '%s'\n", ruleset->name); while(rule) { PDEBUG(DEBUG_ROUTE, "checking rule in line %d\n", rule->line); match = 1; /* this rule matches */ dialing_required = 0; timeout = 0; /* timeout time */ cond = rule->cond_first; while(cond) { condition = 0; /* any condition element is true (1) or could be true (2) */ checkextension: istrue = 0; /* this condition-element is true */ couldbetrue = 0; /* this conditions-element could be true */ switch(cond->match) { case MATCH_EXTERN: if (!e_ext.number[0]) istrue = 1; break; case MATCH_INTERN: if (e_ext.number[0]) istrue = 1; break; case MATCH_PORT: #ifdef WITH_MISDN if (ea_endpoint->ep_portlist) if ((ea_endpoint->ep_portlist->port_type & PORT_CLASS_MASK) != PORT_CLASS_mISDN) break; integer = e_callerinfo.isdn_port; goto match_integer; #endif break; case MATCH_INTERFACE: if (!e_callerinfo.interface[0]) break; string = e_callerinfo.interface; goto match_string; case MATCH_CALLERID: string = callerid; goto match_string_prefix; case MATCH_CALLERID2: string = callerid2; goto match_string_prefix; case MATCH_EXTENSION: string = e_ext.number; goto match_string; case MATCH_DIALING: string = e_dialinginfo.id; goto match_string_prefix; case MATCH_ENBLOCK: if (!e_overlap) istrue = 1; break; case MATCH_OVERLAP: if (e_overlap) istrue = 1; break; case MATCH_ANONYMOUS: if (e_callerinfo.present != INFO_PRESENT_ALLOWED) istrue = 1; break; case MATCH_VISIBLE: if (e_callerinfo.present == INFO_PRESENT_ALLOWED) istrue = 1; break; case MATCH_UNKNOWN: if (e_callerinfo.present == INFO_PRESENT_NOTAVAIL) istrue = 1; break; case MATCH_AVAILABLE: if (e_callerinfo.present != INFO_PRESENT_NOTAVAIL) istrue = 1; break; case MATCH_FAKE: if (e_callerinfo.screen == INFO_SCREEN_USER) istrue = 1; break; case MATCH_REAL: if (e_callerinfo.screen != INFO_SCREEN_USER) istrue = 1; break; case MATCH_REDIRECTED: if (e_redirinfo.ntype != INFO_NTYPE_NOTPRESENT) istrue = 1; break; case MATCH_DIRECT: if (e_redirinfo.ntype == INFO_NTYPE_NOTPRESENT) istrue = 1; break; case MATCH_REDIRID: string = redirid; goto match_string_prefix; case MATCH_TIME: time(&now); now_tm = localtime(&now); integer = now_tm->tm_hour*100 + now_tm->tm_min; goto match_integer; case MATCH_MDAY: time(&now); now_tm = localtime(&now); integer = now_tm->tm_mday; goto match_integer; case MATCH_MONTH: time(&now); now_tm = localtime(&now); integer = now_tm->tm_mon+1; goto match_integer; case MATCH_YEAR: time(&now); now_tm = localtime(&now); integer = now_tm->tm_year + 1900; goto match_integer; case MATCH_WDAY: time(&now); now_tm = localtime(&now); integer = now_tm->tm_wday; integer = integer?integer:7; /* correct sunday */ goto match_integer; case MATCH_CAPABILITY: integer = e_capainfo.bearer_capa; goto match_integer; case MATCH_INFOLAYER1: integer = e_capainfo.bearer_info1; goto match_integer; case MATCH_HLC: integer = e_capainfo.hlc; goto match_integer; case MATCH_FILE: tfp = fopen(cond->string_value, "r"); if (!tfp) { break; } if (fgetc(tfp) == '1') istrue = 1; fclose(tfp); break; case MATCH_EXECUTE: j = 0; #if 0 argv[j++] = (char *)"/bin/sh"; argv[j++] = (char *)"-c"; argv[j++] = cond->string_value; #endif argv[j++] = cond->string_value; argv[j++] = e_extdialing; argv[j++] = (char *)numberrize_callerinfo(e_callerinfo.id, e_callerinfo.ntype, options.national, options.international); argv[j++] = e_callerinfo.extension; argv[j++] = e_callerinfo.name; SPRINT(isdn_port, "%d", e_callerinfo.isdn_port); argv[j++] = isdn_port; argv[j++] = e_callerinfo.imsi; argv[j++] = NULL; /* check also number of args above */ switch ((pid = fork())) { case 0: execve(cond->string_value, argv, environ); perror("execve"); exit(1); case -1: break; default: waitpid(pid, &status, 0); if (0 == WEXITSTATUS(status)) istrue = 1; } break; case MATCH_DEFAULT: if (!couldmatch) istrue = 1; break; case MATCH_TIMEOUT: if (!now_ll) { gettimeofday(¤t_time, NULL); now_ll = current_time.tv_sec * MICRO_SECONDS + current_time.tv_usec; } timeout = now_ll + (cond->integer_value * MICRO_SECONDS); istrue = 1; break; case MATCH_FREE: case MATCH_NOTFREE: #ifdef WITH_MISDN if (!(comp_len = (unsigned long)strchr(cond->string_value, ':'))) break; comp_len = comp_len-(unsigned long)cond->string_value; avail = 0; mISDNport = mISDNport_first; while(mISDNport) { if (mISDNport->ifport) if (strlen(mISDNport->ifport->interface->name) == comp_len) if (!strncasecmp(mISDNport->ifport->interface->name, cond->string_value, comp_len)) if (!mISDNport->l2hold || mISDNport->l2link>0) { j = 0; jj = mISDNport->b_num; avail += jj; while(j < jj) { if (mISDNport->b_state[j]) avail--; j++; } } mISDNport = mISDNport->next; } if (cond->match == MATCH_FREE) { if (avail >= atoi(cond->string_value + comp_len + 1)) istrue = 1; } else { if (avail < atoi(cond->string_value + comp_len + 1)) istrue = 1; } #endif break; case MATCH_DOWN: #ifdef WITH_MISDN mISDNport = mISDNport_first; while(mISDNport) { if (mISDNport->ifport) if (!strcasecmp(mISDNport->ifport->interface->name, cond->string_value)) if (!mISDNport->l2hold || mISDNport->l2link>0) break; mISDNport = mISDNport->next; } if (!mISDNport) /* all down */ istrue = 1; #endif break; case MATCH_UP: #ifdef WITH_MISDN mISDNport = mISDNport_first; while(mISDNport) { if (mISDNport->ifport) if (!strcasecmp(mISDNport->ifport->interface->name, cond->string_value)) if (!mISDNport->l2hold || mISDNport->l2link>0) break; mISDNport = mISDNport->next; } if (mISDNport) /* one link at least */ istrue = 1; #endif break; case MATCH_BUSY: case MATCH_IDLE: #ifdef WITH_MISDN mISDNport = mISDNport_first; while(mISDNport) { if (mISDNport->ifport) if (!strcasecmp(mISDNport->ifport->interface->name, cond->string_value)) if (mISDNport->use) /* break if in use */ break; mISDNport = mISDNport->next; } if (mISDNport && cond->match==MATCH_BUSY) istrue = 1; if (!mISDNport && cond->match==MATCH_IDLE) istrue = 1; #endif break; case MATCH_REMOTE: case MATCH_NOTREMOTE: admin = admin_first; while(admin) { if (admin->remote_name[0] && !strcmp(cond->string_value, admin->remote_name)) break; admin = admin->next; } if (admin && cond->match==MATCH_REMOTE) istrue = 1; if (!admin && cond->match==MATCH_NOTREMOTE) istrue = 1; break; #ifdef WITH_MISDN case MATCH_POTS_FLASH: if (e_dialinginfo.flash) istrue = 1; break; case MATCH_POTS_CW: port = find_port_id(ea_endpoint->ep_portlist->port_id); if (!port) break; if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) break; ourfxs = (class Pfxs *)port; port = port_first; while(port) { if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { fxs = (class Pfxs *)port; if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { if (fxs->p_state == PORT_STATE_OUT_ALERTING) { istrue = 1; break; } } } port = port->next; } break; case MATCH_POTS_CALLS: port = find_port_id(ea_endpoint->ep_portlist->port_id); if (!port) break; if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) break; ourfxs = (class Pfxs *)port; integer = 0; port = port_first; while(port) { if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { fxs = (class Pfxs *)port; if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { if (fxs->p_state == PORT_STATE_CONNECT) { integer++; } } } port = port->next; } goto match_integer; case MATCH_POTS_LAST: port = find_port_id(ea_endpoint->ep_portlist->port_id); if (!port) break; if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) break; ourfxs = (class Pfxs *)port; /* integer gets the call number that has been the least active call on hold */ fxs_age = -1; fxs_count = 0; integer = 0; port = port_first; while(port) { if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { fxs = (class Pfxs *)port; if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { if (fxs->p_state == PORT_STATE_CONNECT) { fxs_count++; if (fxs->p_m_fxs_age > fxs_age) { fxs_age = fxs->p_m_fxs_age; integer = fxs_count; } } } } port = port->next; } goto match_integer; #endif default: PERROR("Software error: MATCH_* %d not parsed in function '%s'", cond->match, __FUNCTION__); break; match_integer: if (cond->value_type == VALUE_TYPE_INTEGER) { if (integer != cond->integer_value) break; istrue = 1; break; } if (cond->value_type == VALUE_TYPE_INTEGER_RANGE) { /* check if negative range (2100 - 700 o'clock) */ if (cond->integer_value > cond->integer_value_to) { if (integer>=cond->integer_value && integer<=cond->integer_value_to) istrue = 1; break; } /* range is positive */ if (integer>=cond->integer_value && integer<=cond->integer_value_to) istrue = 1; break; } break; match_string: if (strlen(cond->string_value) != strlen(string)) break; /* fall through */ match_string_prefix: comp_len = strlen(cond->string_value); /* because we must reach value's length */ /* we must have greater or equal length to values */ if ((unsigned long)strlen(string) < comp_len) { /* special case for unfinished dialing */ if (cond->match == MATCH_DIALING && !e_dialinginfo.sending_complete) { couldbetrue = 1; /* could match */ comp_len = strlen(string); } else { break; } } /* on single string match */ if (cond->value_type == VALUE_TYPE_STRING) { if (!strncmp(string, cond->string_value, comp_len)) { istrue = 1; /* must be set for changing 'e_extdialing' */ if (cond->match == MATCH_DIALING) dialing_required = comp_len; break; } break; } /* on range match */ if (cond->value_type == VALUE_TYPE_STRING_RANGE) { /* check if negative range ("55"-"22") */ if (cond->comp_string > 0) { if (strncmp(string, cond->string_value, comp_len) >= 0) { istrue = 1; /* must be set for changing 'e_extdialing' */ if (cond->match == MATCH_DIALING) dialing_required = comp_len; break; } if (strncmp(string, cond->string_value_to, comp_len) <= 0) { /* must be set for changing 'e_extdialing' */ istrue = 1; if (cond->match == MATCH_DIALING) dialing_required = comp_len; break; } break; } /* range is positive */ if (strncmp(string, cond->string_value, comp_len) < 0) break; if (strncmp(string, cond->string_value_to, comp_len) > 0) break; istrue = 1; if (cond->match == MATCH_DIALING) dialing_required = comp_len; break; } break; } /* set current condition */ if (istrue && !couldbetrue) condition = 1; /* element matches, so condition matches */ if (istrue && couldbetrue && !condition) condition = 2; /* element could match and other elements don't match, so condition could match */ /* if not matching or could match */ if (condition != 1) { /* if we have more values to check */ if (cond->value_extension && cond->next) { cond = cond->next; goto checkextension; } match = condition; break; } /* skip exteded values, beacuse we already have one matching value */ while(cond->value_extension && cond->next) cond = cond->next; cond = cond->next; } /* if sending complete, we use future match now, since waiting does not make sense anymore */ if (timeout>now_ll && match==1 && !e_dialinginfo.sending_complete) { /* the matching rule with timeout in the future */ if (match_timeout == 0 || timeout < match_timeout) { /* first timeout or lower */ /* set timeout in the furture */ match_timeout = timeout; e_match_to_action = rule->action_first; e_match_to_extdialing = e_dialinginfo.id + dialing_required; } match = 0; /* matches in the future */ } if (match == 1) { /* matching, we return first action */ action = rule->action_first; match_timeout = 0; /* no timeout */ e_match_to_action = NULL; e_extdialing = e_dialinginfo.id + dialing_required; break; } if (match == 2) { /* rule could match if more is dialed */ couldmatch = 1; } rule = rule->next; } if (match_timeout == 0) unsched_timer(&e_match_timeout); /* no timeout */ else { schedule_timer(&e_match_timeout, (match_timeout-now_ll) / 1000000, (match_timeout-now_ll) % 1000000); } return(action); } /* * parses the current action's parameters and return them */ struct route_param *EndpointAppPBX::routeparam(struct route_action *action, unsigned long long id) { struct route_param *param = action->param_first; while(param) { if (param->id == id) break; param = param->next; } return(param); } /* * internal rules that are not defined by route.conf */ struct route_action action_password = { NULL, NULL, ACTION_PASSWORD, 0, 0, }; struct route_action action_password_write = { NULL, NULL, ACTION_PASSWORD_WRITE, 0, 0, }; struct route_action action_external = { NULL, NULL, ACTION_EXTERNAL, 0, 0, }; struct route_action action_internal = { NULL, NULL, ACTION_INTERNAL, 0, 0, }; struct route_action action_vbox = { NULL, NULL, ACTION_VBOX_RECORD, 0, 0, }; struct route_action action_partyline = { NULL, NULL, ACTION_PARTYLINE, 0, 0, }; struct route_action action_disconnect = { NULL, NULL, ACTION_DISCONNECT, 0, 0, };