/* * $Id: permissions.c,v 1.17 2004/11/09 17:50:26 andrei Exp $ * * PERMISSIONS module * * Copyright (C) 2003 Miklós Tirpák (mtirpak@sztaki.hu) * Copyright (C) 2003 iptel.org * Copyright (C) 2003 Juha Heinanen (jh@tutpro.com) * * 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 * */ #include #include "permissions.h" #include "parse_config.h" #include "trusted.h" #include "../../mem/mem.h" #include "../../parser/parse_from.h" #include "../../parser/parse_uri.h" #include "../../parser/contact/parse_contact.h" #include "../../str.h" #include "../../dset.h" #include "../../globals.h" MODULE_VERSION static rule_file_t allow[MAX_RULE_FILES]; /* Parsed allow files */ static rule_file_t deny[MAX_RULE_FILES]; /* Parsed deny files */ static int rules_num; /* Number of parsed allow/deny files */ /* Module parameter variables */ static char* default_allow_file = DEFAULT_ALLOW_FILE; static char* default_deny_file = DEFAULT_DENY_FILE; static char* allow_suffix = ".allow"; static char* deny_suffix = ".deny"; /* for allow_trusted function */ char* db_url = 0; /* Don't connect to the database by default */ int db_mode = DISABLE_CACHE; /* Database usage mode: 0=no cache, 1=cache */ char* trusted_table = "trusted"; /* Name of trusted table */ char* source_col = "src_ip"; /* Name of source address column */ char* proto_col = "proto"; /* Name of protocol column */ char* from_col = "from_pattern"; /* Name of from pattern column */ /* * By default we check all branches */ static int check_all_branches = 1; /* * Convert the name of the files into table index */ static int load_fixup(void** param, int param_no); /* * Convert the name of the file into table index, this * function takes just one name, appends .allow and .deny * to and and the rest is same as in load_fixup */ static int single_fixup(void** param, int param_no); static int allow_routing_0(struct sip_msg* msg, char* str1, char* str2); static int allow_routing_1(struct sip_msg* msg, char* basename, char* str2); static int allow_routing_2(struct sip_msg* msg, char* allow_file, char* deny_file); static int allow_register_1(struct sip_msg* msg, char* basename, char* s); static int allow_register_2(struct sip_msg* msg, char* allow_file, char* deny_file); static int mod_init(void); static void mod_exit(void); static int child_init(int rank); /* Exported functions */ static cmd_export_t cmds[] = { {"allow_routing", allow_routing_0, 0, 0, REQUEST_ROUTE | FAILURE_ROUTE}, {"allow_routing", allow_routing_1, 1, single_fixup, REQUEST_ROUTE | FAILURE_ROUTE}, {"allow_routing", allow_routing_2, 2, load_fixup, REQUEST_ROUTE | FAILURE_ROUTE}, {"allow_register", allow_register_1, 1, single_fixup, REQUEST_ROUTE | FAILURE_ROUTE}, {"allow_register", allow_register_2, 2, load_fixup, REQUEST_ROUTE | FAILURE_ROUTE}, {"allow_trusted", allow_trusted, 0, 0, REQUEST_ROUTE | FAILURE_ROUTE}, {0, 0, 0, 0, 0} }; /* Exported parameters */ static param_export_t params[] = { {"default_allow_file", STR_PARAM, &default_allow_file}, {"default_deny_file", STR_PARAM, &default_deny_file }, {"check_all_branches", INT_PARAM, &check_all_branches}, {"allow_suffix", STR_PARAM, &allow_suffix }, {"deny_suffix", STR_PARAM, &deny_suffix }, {"db_url", STR_PARAM, &db_url }, {"db_mode", INT_PARAM, &db_mode }, {"trusted_table", STR_PARAM, &trusted_table }, {"source_col", STR_PARAM, &source_col }, {"proto_col", STR_PARAM, &proto_col }, {"from_col", STR_PARAM, &from_col }, {0, 0, 0} }; /* Module interface */ struct module_exports exports = { "permissions", cmds, /* Exported functions */ params, /* Exported parameters */ mod_init, /* module initialization function */ 0, /* response function */ mod_exit, /* destroy function */ 0, /* oncancel function */ child_init /* child initialization function */ }; /* * Extract path (the beginning of the string * up to the last / character * Returns length of the path */ static int get_path(char* pathname) { char* c; if (!pathname) return 0; c = strrchr(pathname, '/'); if (!c) return 0; return c - pathname + 1; } /* * Prepend path if necessary */ static char* get_pathname(char* name) { char* buffer; int path_len, name_len; if (!name) return 0; name_len = strlen(name); if (strchr(name, '/')) { buffer = (char*)pkg_malloc(name_len + 1); if (!buffer) goto err; strcpy(buffer, name); return buffer; } else { path_len = get_path(cfg_file); buffer = (char*)pkg_malloc(path_len + name_len + 1); if (!buffer) goto err; memcpy(buffer, cfg_file, path_len); memcpy(buffer + path_len, name, name_len); buffer[path_len + name_len] = '\0'; return buffer; } err: LOG(L_ERR, "get_pathname(): No memory left\n"); return 0; } /* * If the file pathname has been parsed already then the * function returns its index in the tables, otherwise it * returns -1 to indicate that the file needs to be read * and parsed yet */ static int find_index(rule_file_t* array, char* pathname) { int i; for(i = 0; i < rules_num; i++) { if (!strcmp(pathname, array[i].filename)) return i; } return -1; } /* * Return URI without all the bells and whistles, that means only * sip:username@domain, resulting buffer is statically allocated and * zero terminated */ static char* get_plain_uri(const str* uri) { static char buffer[EXPRESSION_LENGTH + 1]; struct sip_uri puri; int len; if (!uri) return 0; if (parse_uri(uri->s, uri->len, &puri) < 0) { LOG(L_ERR, "get_plain_uri(): Error while parsing URI\n"); return 0; } if (puri.user.len) { len = puri.user.len + puri.host.len + 5; } else { len = puri.host.len + 4; } if (len > EXPRESSION_LENGTH) { LOG(L_ERR, "allow_register(): (module permissions) Request-URI is too long: %d chars\n", len); return 0; } strcpy(buffer, "sip:"); if (puri.user.len) { memcpy(buffer + 4, puri.user.s, puri.user.len); buffer[puri.user.len + 4] = '@'; memcpy(buffer + puri.user.len + 5, puri.host.s, puri.host.len); } else { memcpy(buffer + 4, puri.host.s, puri.host.len); } buffer[len] = '\0'; return buffer; } /* * determines the permission of the call * return values: * -1: deny * 1: allow */ static int check_routing(struct sip_msg* msg, int idx) { struct hdr_field *from; int len, q; static char from_str[EXPRESSION_LENGTH+1]; static char ruri_str[EXPRESSION_LENGTH+1]; char* uri_str; str branch; /* turn off control, allow any routing */ if ((!allow[idx].rules) && (!deny[idx].rules)) { DBG("check_routing(): No rules => allow any routing\n"); return 1; } /* looking for FROM HF */ if ((!msg->from) && (parse_headers(msg, HDR_FROM, 0) == -1)) { LOG(L_ERR, "check_routing(): Error while parsing message\n"); return -1; } if (!msg->from) { LOG(L_ERR, "check_routing(): FROM header field not found\n"); return -1; } /* we must call parse_from_header explicitly */ if ((!(msg->from)->parsed) && (parse_from_header(msg) < 0)) { LOG(L_ERR, "check_routing(): Error while parsing From body\n"); return -1; } from = msg->from; len = ((struct to_body*)from->parsed)->uri.len; if (len > EXPRESSION_LENGTH) { LOG(L_ERR, "check_routing(): From header field is too long: %d chars\n", len); return -1; } strncpy(from_str, ((struct to_body*)from->parsed)->uri.s, len); from_str[len] = '\0'; /* looking for request URI */ if (parse_sip_msg_uri(msg) < 0) { LOG(L_ERR, "check_routing(): uri parsing failed\n"); return -1; } len = msg->parsed_uri.user.len + msg->parsed_uri.host.len + 5; if (len > EXPRESSION_LENGTH) { LOG(L_ERR, "check_routing(): Request URI is too long: %d chars\n", len); return -1; } strcpy(ruri_str, "sip:"); memcpy(ruri_str + 4, msg->parsed_uri.user.s, msg->parsed_uri.user.len); ruri_str[msg->parsed_uri.user.len + 4] = '@'; memcpy(ruri_str + msg->parsed_uri.user.len + 5, msg->parsed_uri.host.s, msg->parsed_uri.host.len); ruri_str[len] = '\0'; DBG("check_routing(): looking for From: %s Request-URI: %s\n", from_str, ruri_str); /* rule exists in allow file */ if (search_rule(allow[idx].rules, from_str, ruri_str)) { if (check_all_branches) goto check_branches; DBG("check_routing(): allow rule found => routing is allowed\n"); return 1; } /* rule exists in deny file */ if (search_rule(deny[idx].rules, from_str, ruri_str)) { DBG("check_routing(): deny rule found => routing is denied\n"); return -1; } if (!check_all_branches) { DBG("check_routing(): Neither allow nor deny rule found => routing is allowed\n"); return 1; } check_branches: init_branch_iterator(); while((branch.s = next_branch(&branch.len, &q, 0, 0))) { uri_str = get_plain_uri(&branch); if (!uri_str) { LOG(L_ERR, "check_uri(): Error while extracting plain URI\n"); return -1; } DBG("check_routing: Looking for From: %s Branch: %s\n", from_str, uri_str); if (search_rule(allow[idx].rules, from_str, uri_str)) { continue; } if (search_rule(deny[idx].rules, from_str, uri_str)) { LOG(LOG_INFO, "check_routing(): Deny rule found for one of branches => routing is denied\n"); return -1; } } LOG(LOG_INFO, "check_routing(): Check of branches passed => routing is allowed\n"); return 1; } /* * Convert the name of the files into table index */ static int load_fixup(void** param, int param_no) { char* pathname; int idx; rule_file_t* table; if (param_no == 1) { table = allow; } else { table = deny; } pathname = get_pathname(*param); idx = find_index(table, pathname); if (idx == -1) { /* Not opened yet, open the file and parse it */ table[rules_num].filename = pathname; table[rules_num].rules = parse_config_file(pathname); if (table[rules_num].rules) { LOG(L_INFO, "load_fixup(): File (%s) parsed\n", pathname); } else { LOG(L_WARN, "load_fixup(): File (%s) not found => empty rule set\n", pathname); } *param = (void*)(long)rules_num; if (param_no == 2) rules_num++; } else { /* File already parsed, re-use it */ LOG(L_INFO, "load_fixup(): File (%s) already loaded, re-using\n", pathname); pkg_free(pathname); *param = (void*)(long)idx; } return 0; } /* * Convert the name of the file into table index */ static int single_fixup(void** param, int param_no) { char* buffer; void* tmp; int param_len, ret, suffix_len; if (param_no != 1) return 0; param_len = strlen((char*)*param); if (strlen(allow_suffix) > strlen(deny_suffix)) { suffix_len = strlen(allow_suffix); } else { suffix_len = strlen(deny_suffix); } buffer = pkg_malloc(param_len + suffix_len + 1); if (!buffer) { LOG(L_ERR, "single_fixup(): No memory left\n"); return -1; } strcpy(buffer, (char*)*param); strcat(buffer, allow_suffix); tmp = buffer; ret = load_fixup(&tmp, 1); strcpy(buffer + param_len, deny_suffix); tmp = buffer; ret |= load_fixup(&tmp, 2); *param = tmp; pkg_free(buffer); return ret; } /* * module initialization function */ static int mod_init(void) { LOG(L_INFO, "permissions - initializing\n"); allow[0].filename = get_pathname(DEFAULT_ALLOW_FILE); allow[0].rules = parse_config_file(allow[0].filename); if (allow[0].rules) { LOG(L_INFO, "Default allow file (%s) parsed\n", allow[0].filename); } else { LOG(L_WARN, "Default allow file (%s) not found => empty rule set\n", allow[0].filename); } deny[0].filename = get_pathname(DEFAULT_DENY_FILE); deny[0].rules = parse_config_file(deny[0].filename); if (deny[0].rules) { LOG(L_INFO, "Default deny file (%s) parsed\n", deny[0].filename); } else { LOG(L_WARN, "Default deny file (%s) not found => empty rule set\n", deny[0].filename); } if (init_trusted() != 0) { LOG(L_ERR, "Error while initializing allow_trusted function\n"); } rules_num = 1; return 0; } static int child_init(int rank) { return init_child_trusted(rank); } /* * destroy function */ static void mod_exit(void) { int i; for(i = 0; i < rules_num; i++) { free_rule(allow[i].rules); pkg_free(allow[i].filename); free_rule(deny[i].rules); pkg_free(deny[i].filename); } clean_trusted(); } /* * Uses default rule files from the module parameters */ int allow_routing_0(struct sip_msg* msg, char* str1, char* str2) { return check_routing(msg, 0); } int allow_routing_1(struct sip_msg* msg, char* basename, char* s) { return check_routing(msg, (int)(long)basename); } /* * Accepts allow and deny files as parameters */ int allow_routing_2(struct sip_msg* msg, char* allow_file, char* deny_file) { /* Index converted by load_lookup */ return check_routing(msg, (int)(long)allow_file); } /* * Test of REGISTER messages. Creates To-Contact pairs and compares them * against rules in allow and deny files passed as parameters. The function * iterates over all Contacts and creates a pair with To for each contact * found. That allows to restrict what IPs may be used in registrations, for * example */ static int check_register(struct sip_msg* msg, int idx) { int len; static char to_str[EXPRESSION_LENGTH + 1]; char* contact_str; contact_t* c; /* turn off control, allow any routing */ if ((!allow[idx].rules) && (!deny[idx].rules)) { DBG("check_register(): No rules => allow any registration\n"); return 1; } /* * Note: We do not parse the whole header field here although the message can * contain multiple Contact header fields. We try contacts one by one and if one * of them causes reject then we don't look at others, this could improve performance * a little bit in some situations */ if (parse_headers(msg, HDR_TO | HDR_CONTACT, 0) == -1) { LOG(L_ERR, "check_register(): Error while parsing headers\n"); return -1; } if (!msg->to) { LOG(L_ERR, "check_register(): To or Contact not found\n"); return -1; } if (!msg->contact) { /* REGISTER messages that contain no Contact header field * are allowed. Such messages do not modify the contents of * the user location database anyway and thus are not harmful */ DBG("check_register(): No Contact found, allowing\n"); return 1; } /* Check if the REGISTER message contains start Contact and if * so then allow it */ if (parse_contact(msg->contact) < 0) { LOG(L_ERR, "check_register(): Error while parsing Contact HF\n"); return -1; } if (((contact_body_t*)msg->contact->parsed)->star) { DBG("check_register(): * Contact found, allowing\n"); return 1; } len = ((struct to_body*)msg->to->parsed)->uri.len; if (len > EXPRESSION_LENGTH) { LOG(L_ERR, "check_register(): To header field is too long: %d chars\n", len); return -1; } strncpy(to_str, ((struct to_body*)msg->to->parsed)->uri.s, len); to_str[len] = '\0'; if (contact_iterator(&c, msg, 0) < 0) { return -1; } while(c) { contact_str = get_plain_uri(&c->uri); if (!contact_str) { LOG(L_ERR, "check_register(): Can't extract plain Contact URI\n"); return -1; } DBG("check_register(): Looking for To: %s Contact: %s\n", to_str, contact_str); /* rule exists in allow file */ if (search_rule(allow[idx].rules, to_str, contact_str)) { if (check_all_branches) goto skip_deny; } /* rule exists in deny file */ if (search_rule(deny[idx].rules, to_str, contact_str)) { DBG("check_register(): Deny rule found => Register denied\n"); return -1; } skip_deny: if (contact_iterator(&c, msg, c) < 0) { return -1; } } DBG("check_register(): No contact denied => Allowed\n"); return 1; } int allow_register_1(struct sip_msg* msg, char* basename, char* s) { return check_register(msg, (int)(long)basename); } int allow_register_2(struct sip_msg* msg, char* allow_file, char* deny_file) { return check_register(msg, (int)(long)allow_file); }