From: jonl <jonl@sippbx.hig.no> Date: Thu, 12 Jan 2012 14:51:58 +0000 (+0100) Subject: Hermes is a framework for managing SIP-accounts, aliases and automatic phone-provisio... X-Git-Url: https://git.defcon.no/?a=commitdiff_plain;h=665e8f51f50d2f09069e870a71826d3b8295ae34;p=hermes Hermes is a framework for managing SIP-accounts, aliases and automatic phone-provisioning, based on Kamailio. This is the initial import. --- 665e8f51f50d2f09069e870a71826d3b8295ae34 diff --git a/alias.php b/alias.php new file mode 100644 index 0000000..398c12a --- /dev/null +++ b/alias.php @@ -0,0 +1,133 @@ +<?php +require_once('config.php'); +require_once('lib/user_functions.php'); +require_once('lib/number_functions.php'); +require_once('lib/common_functions.php'); +require_once('lib/db_functions.php'); +require_once('lib/alias_functions.php'); + +$config = get_config(); + +$config['sql_link'] = @mysql_connect( + $config['sql_server'], + $config['sql_username'], + $config['sql_password'] +); +if ( !$config['sql_link'] ) +{ + print json_encode( array( 'response' => 'failed', 'cause' => 'error', 'detail' => 'Database connection failed.')); + exit; +} + +//************************************************************************************* + switch ( $_SERVER['PATH_INFO'] ) + { + case "/list": + break; + case "/add": + if ( array_key_exists( 'destination', $_GET) + //&& array_key_exists( 'domain', $_GET ) + && array_key_exists( 'alias_username', $_GET ) + && array_key_exists( 'alias_domain', $_GET ) ) + { + $alias_username = $_GET['alias_username']; + $alias_domain = $_GET['alias_domain']; + + if ( !verify_sipadress($_GET['destination']) ) + { + // TODO: Provide a better response.. + print json_encode ( array( 'response' => 'invalid', 'cause' => 'destination' ) ); + break; + } + if ( !verify_sipadress( $alias_username . "@" . $alias_domain) ) + { + // TODO: Provide a better response.. + print json_encode ( array( 'response' => 'invalid', 'cause' => 'alias' ) ); + break; + } + + list ( $dest_username, $dest_domain ) = split_sipaddress( $_GET['destination']); + + if ( (!$dest_username)||(!$dest_domain)) + { + // TODO: Provide a better response.. + print json_encode ( array( 'response' => 'invalid', 'cause' => 'alias' ) ); + break; + } + if ( !is_kamailio_domain( $alias_domain ) ) + { + print json_encode ( array( 'response' => 'invalid', 'cause' => 'nxdomain' ) ); + break; + } + if ( is_kamailio_domain( $dest_domain) && ( !is_kamailio_subscriber($dest_username, $dest_domain) ) ) + { + print json_encode( array ( 'response' => 'failed', 'cause' => 'nonexistant', 'detail' => 'Requesting a local alias, but there is no such user')); + break; + } + if ( verify_e164( $alias_username ) && is_kamailio_subscriber($dest_username, $dest_domain) ) + { + $t = get_e164_alias( $dest_username, $dest_domain ); + if ( $t ) + { + print json_encode ( array( + 'response' => 'failed', + 'cause' => 'exists', + 'detail' => 'User already has E164 number alias', + 'alias' => $t )); + break; + } + } + if ( alias_exists ( $alias_username, $alias_domain ) ) + { + print json_encode( array ( 'response' => 'failed', 'cause' => 'exists', 'detail' => 'The requested alias is already present.')); + break; + } + if ( add_alias( $alias_username, $alias_domain, $dest_username, $dest_domain ) ) + { + print json_encode( array ('response' => 'ok', + 'alias' => $alias_username . "@" . $alias_domain, + 'destination' => $dest_username . "@" . $dest_domain )); + break; + } + print json_encode ( array ( 'response' => 'error' )); + break; + } + else + { + print json_encode ( array( 'response' => 'invalid') ); + } + break; + case "/remove": + if ( array_key_exists( 'alias_username', $_GET ) + && array_key_exists( 'alias_domain', $_GET ) ) + { + $alias_username = $_GET['alias_username']; + $alias_domain = $_GET['alias_domain']; + + if ( !verify_sipadress( $alias_username . "@" . $alias_domain) ) + { + // TODO: Provide a better response.. + print json_encode ( array( 'response' => 'invalid', 'cause' => 'address', 'detail' => 'Not a valid SIP address' ) ); + break; + } + if ( ! alias_exists ( $alias_username, $alias_domain ) ) + { + print json_encode( array ( 'response' => 'failed', 'cause' => 'nonexistant', 'detail' => 'The requested alias does not exist.')); + break; + } + if ( remove_alias( $alias_username, $alias_domain ) ) + { + print json_encode( array ('response' => 'ok', + 'alias' => $alias_username . "@" . $alias_domain)); + break; + } + print json_encode ( array ( 'response' => 'error' )); + break; + } + print json_encode ( array( 'response' => 'invalid') ); + break; + default: + print json_encode ( array( 'response' => 'invalid') ); + } +mysql_close( $config['sql_link'] ); +?> diff --git a/api-nodes.txt b/api-nodes.txt new file mode 100644 index 0000000..376beb1 --- /dev/null +++ b/api-nodes.txt @@ -0,0 +1,168 @@ +All API nodes currently use GET requests for all parameters. +All API nodes return JSON data, and all results will contain +a 'response' element. The 'response' may be set to: + * 'ok' on success. Further contents depend on the node requested. + * 'invalid' on invalid requests. A 'cause' element should give a + keyword denoting what was invalid about the request... + * 'failed' on simple failure. A 'cause' element is supplied, with a + failure-keyword (e.g. 'nonexistant', 'inuse', 'dbfail'). + A final 'detail' is commonly included, with a verbose desctiption. + * 'error' on critical failure. Regard this as a soft form of "500 server error"... + +List of API nodes: +--------------------- + +user/get?username=foo&domain=bar + Return User information, not including hardphone association. + Possible extension: allow user/get?address=foo@bar + +user/list +user/list?search=foo@bar + Return a list of users in user@domain format. + If the search parameter is included, a globbing search is performed, + and only matches are listed. + +user/add_local?username=foo&domain=bar&displayname=baz&email=qux@zef.tld + Adds a user account to both Kamailio and provisioning, + if the username@domain is nonexistant, and the domain is local (handled by Kamailio). + The password for the user is auto-generated. + Returns a full user object, the same form as user/get + +user/add_remote?username=foo&domain=bar&password=S3cr3t&displayname=baz®istrar=sip.example.com + optionals: &r_port=5060&proxy=sipproxy.example.com&p_port=5060&authid=realfoo&dialplan=(.*)&linetext=lalala + Adds a provisioning user for a remote SIP account. This allows locally provisioned hardphones + to be associated with non-local SIP accounts. + Returns a full user object, the same form as user/get + +user/remove?username=foo&domain=bar + Removes user account from Kamailio, if present, and removes user from provisioning. + Will fail if user has associated hardphones, remove phones before removing user. + Returns 'ok' on success. + +user/gen_pw + Test-node, generates a random password on the same form as that used + by user/add_local. May be used with the to-be-implemented change_pw node. + +user/change_pw?username=foo&domain=bar&password=baz + Not implemented! + +user/update?username=foo&domain=bar + optionals: &displayname=baz&dialplan=(.*)&linetext=lalala + Not implemented! Will only be implemented for Local users. + Remote users will have to be removed, and re-added. + +phone/get?mac=f00ba2ba5c00 +phone/get?user=foo@bar.baz + Returns a list of associated elements. If MAC address is given as parameter, + a list of associated user@domain addresses is retruned. If user@domain is + given as parameter, a list of associated mac addresses is returned. + +phone/list?search=f00ba2 + Returns the list of registered MAC addresses. If the (optional) search + parameter is included, the list is the result of a globbing search, + a subset matching on the MAC addresses. + +phone/add?mac=f00ba2ba5c00&username=foo&domain=bar.bz +phone/add?mac=f00ba2ba5c00&user=foo@bar.bz + Adds a phone-to-user association to Provisioning. The user may + be provided as 'username' and 'domain' in separate parameters, + or as 'user' in sip-address form. The MAC address must be valid, + in plain-hex or colon-separated form, and the user must + match a valid user registration. + Returns the mac+user combinations on success. + +phone/remove?mac=f00ba2ba5c00&username=foo&domain=bar.bz +phone/remove?mac=f00ba2ba5c00&user=foo@bar.bz + Deletes a phone-to-user association. Required parameters + and formats are identical to the phone/add node. + Returns 'ok' on success. + + +numbers/list +numbers/list?search=1234 +numbers/list?random=true + optional: limit=n + Gives a list of entries in the phone number pool. + With no parameters, the entire (ordered) list of available numbers is returned. + With 'search', the result of a pre-post globbing search is returned + With 'random', a list of entries is returned unordered (random order) + For all nodes, 'limit' may be a numeric limit of desired results. + +numbers/add_range?start=%2B471234&end=%2B47123456 + The start and end must have identical lead-in, and start must be less than end. + Both numbers must be given in E164 format, remember to urlencode the plus (+ -> %2B) + The numeric component of both will be extracted, and all numbers in the range + will be iteratively added to the pool, including the ending number. + Returns 'ok' on success. + +numbers/add?number=%2B4761123456 + Adds the given number to the pool, after verifying that the number is in + valid E164 format, and that the number does not already exist in the pool. + Returns 'response' 'ok' and "Added <number>" in 'detail' on success. + +numbers/remove?number=%2B4761123456 + Removes/pulls a number from the pool. + The number must be a valid e164 number, and must be present in the pool. + Returns 'response' as 'ok' with 'number' set to the number that was pulled from the pool. + +numbers/inpool?number=%2B4761123456 + A debug-node, may disappear at any time ^_^ + Tests if the given E164 number is in the pool. + Returns 'response' = 'ok' with 'number' = '<number>' If the number is in the pool + Returns response = 'failed' with 'cause' = 'nonexistant' if not. + + +alias/list + Currently not implemented. + +alias/user?destination=foo@bar.bz +alias/user?destination=foo@bar.bz&e164=true + Currently not implemented + +alias/add?alias_username=foo&alias_domain=bar.bz&destination=bar@qux.zx + Add an alias specified by alias_username and alias_domain that + points to the destination SIP-adress. + + On success, 'ok' is returned, with 'alias' and 'destination' set + to the resulting alias and destination adresses. + + Returns 'invalid' with 'cause' = 'destination' or 'alias' if the + resulting alias-adress or destination are invalid SIP-adresses. + Returns 'failed' with 'cause' = 'nxdomain' if the given alias domain + is not a Kamailio domain. + Returns 'failed' with 'cause' = 'exists' if the alias is an E164 number + and the user already has an E164 alias registered. + Returns 'failed' with 'cause' = 'nonexistant' when an alias for a local + domain is requested, but the local subscriber does not exist. + Returns 'failed' with 'cause' = 'exists' for aliases that already exists, + (and aliases that overlaps SIP-accounts - not implemented). + + +alias/remove?alias_username=foo&alias_domain=bar.bz + Removes the alias given by alias_username and alias_domain. + + Returns 'ok' with the removed alias adress as 'alias' on success + Returns 'failed' with 'cause' = 'nonexistant' it the alias does not exists. + Returns 'invalid' with 'cause' = 'address' if the given alias is not a valid SIP adress. + +TODO list: +--------------------- +Planned, but not implemented nodes/functions: + user/change_pw?username=foo&domain=bar&password=baz + user/update?username=foo&domain=bar + alias/list + alias/user?destination=foo@bar.bz + +Parameter updates: + Update nodes where username=foo&domain=bar.bz is used, to also support user=foo@bar.bz + user/get + user/add_local + user/add_remote + user/remove + user/change_pw + user/update + phone/get (reverse, needs username & domain param) + alias/add + alias/remove + +authentication-mechanism :) diff --git a/config.php.sample b/config.php.sample new file mode 100644 index 0000000..5b0a82b --- /dev/null +++ b/config.php.sample @@ -0,0 +1,24 @@ +<?php + +require_once('lib/auth.php'); +function get_config() +{ + return array( + 'sql_server' => 'localhost', + 'sql_username' => 'dbusername', + 'sql_password' => 'dbuserpass', + 'kamailio_db' => 'kamailio', + 'kamailio_domain_table' => 'domain', + 'kamailio_subscriber_table' => 'subscriber', + 'kamailio_alias_table' => 'dbaliases', + 'provision_db' => 'provision', + 'provision_users_table' => 'users', + 'provision_phones_table' => 'phones', + 'provision_servers_table' => 'servers', + 'numbers_table' => 'number_pool', + 'standard_dialplan' => '(*xx*.|xxx.)', + 'default_domain' => 'hig.no', + 'permit_multiple_e164alias' => false, + ); +} +?> diff --git a/lib/alias_functions.php b/lib/alias_functions.php new file mode 100644 index 0000000..4a6b3aa --- /dev/null +++ b/lib/alias_functions.php @@ -0,0 +1,98 @@ +<?php +require_once('config.php'); +require_once('lib/common_functions.php'); +require_once('lib/db_functions.php'); + + +function get_destination ( $alias_username, $alias_domain ) +{ + global $config; +} +function get_aliases ( $dest_username, $dest_domain ) +{ + global $config; + + $aliases = array(); + $query = sprintf("SELECT CONCAT( alias_username, '@', alias_domain) AS alias FROM %s" + . " WHERE username = '%s' AND domain = '%s'", + $config['kamailio_alias_table'], $dest_username, $dest_domain); + $result = sql_dbquery( $config['kamailio_db'], $query); + // If result is empty, there was either an SQL error, or simply no results. + // At this point, no error checking is performed, instead the empty array is returned. + if ( ! $result ) return $aliases; + + while ( $row = mysql_fetch_assoc( $result ) ) + { + array_push( $aliases, $row['alias'] ); + } + return $aliases; +} + +function get_e164_alias( $dest_username, $dest_domain ) +{ + global $config; + $cur_aliases = get_aliases( $dest_username, $dest_domain ); + foreach ( $cur_aliases as $testalias ) + { + list( $tau, $foo ) = split_sipaddress( $testalias ); + if ( verify_e164 ( $tau ) ) return $testalias; + } + return false; + +} +function alias_exists( $alias_username, $alias_domain ) +{ + global $config; + $query = sprintf("SELECT COUNT(*) AS num FROM %s WHERE alias_username = '%s' AND alias_domain = '%s'", + $config['kamailio_alias_table'], + sql_clean($alias_username), sql_clean($alias_domain)); + $result = sql_dbquery($config['kamailio_db'], $query); + + if ( !$result ) return true; // This is an error. Better to fail claiming alias exists... + $row = mysql_fetch_row($result); + if ( !$row ) return true; // This is an error. Better to fail claiming alias exists... + $num_r = $row[0]; + if ( $num_r == 1 ) + { + $query = sprintf("SELECT CONCAT(username, '@', domain ) AS destination FROM %s WHERE alias_username = '%s' AND alias_domain = '%s'", + $config['kamailio_alias_table'], + sql_clean($alias_username), sql_clean($alias_domain)); + $result = sql_dbquery($config['kamailio_db'], $query); + $row = mysql_fetch_row($result); + if ( is_string( $row[0] ) ) return $row[0]; + return true; // Failure mode.. + } + + return false; +} + +function add_alias( $alias_username, $alias_domain, $dest_username, $dest_domain ) +{ + global $config; + // The following will, in normal cases, lead to a double-test, and double the number of queries... + // Defensive comment: better safe than sorry :P + if ( alias_exists( $alias_username, $alias_domain ) ) + return false; + + $query = sprintf("INSERT INTO %s ( alias_username, alias_domain, username, domain) VALUES ('%s','%s','%s','%s')", + $config['kamailio_alias_table'], + sql_clean($alias_username), sql_clean($alias_domain), + sql_clean($dest_username), sql_clean($dest_domain)); + + return sql_dbexec( $config['kamailio_db'], $query); + +} + +function remove_alias ( $alias_username, $alias_domain ) +{ + global $config; + if (! alias_exists ( $alias_username, $alias_domain ) ) return false; + $query = sprintf ("DELETE FROM %s WHERE alias_username = '%s' AND alias_domain = '%s'", + $config['kamailio_alias_table'], + sql_clean( $alias_username ), + sql_clean( $alias_domain )); + return sql_dbexec( $config['kamailio_db'], $query); + +} + +?> diff --git a/lib/auth.php b/lib/auth.php new file mode 100644 index 0000000..2f8f9cf --- /dev/null +++ b/lib/auth.php @@ -0,0 +1,13 @@ +<?php + +function token_auth( ) +{ + return true; +} + +if (! token_auth() ) +{ + print json_encode( array( 'response' => 'failed', 'cause' => 'unauthorized', 'description' => 'Not authorized') ); + exit; +} +?> diff --git a/lib/check_email.php b/lib/check_email.php new file mode 100644 index 0000000..800f52d --- /dev/null +++ b/lib/check_email.php @@ -0,0 +1,38 @@ +<?php + +function check_email_address($email) { + // First, we check that there's one @ symbol, + // and that the lengths are right. + if (!ereg("^[^@]{1,64}@[^@]{1,255}$", $email)) { + // Email invalid because wrong number of characters + // in one section or wrong number of @ symbols. + return false; + } + // Split it into sections to make life easier + $email_array = explode("@", $email); + $local_array = explode(".", $email_array[0]); + for ($i = 0; $i < sizeof($local_array); $i++) { + if + (!ereg("^(([A-Za-z0-9!#$%&'*+/=?^_`{|}~-][A-Za-z0-9!#$%&'*+/=?^_`{|}~\.-]{0,63})|(\"[^(\\|\")]{0,62}\"))$", + $local_array[$i])) { + return false; + } + } + // Check if domain is IP. If not, + // it should be valid domain name + if (!ereg("^\[?[0-9\.]+\]?$", $email_array[1])) { + $domain_array = explode(".", $email_array[1]); + if (sizeof($domain_array) < 2) { + return false; // Not enough parts to domain + } + for ($i = 0; $i < sizeof($domain_array); $i++) { + if + (!ereg("^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]+))$", + $domain_array[$i])) { + return false; + } + } + } + return true; +} +?> diff --git a/lib/common_functions.php b/lib/common_functions.php new file mode 100644 index 0000000..52d99e8 --- /dev/null +++ b/lib/common_functions.php @@ -0,0 +1,54 @@ +<?php +require_once('config.php'); +require_once('lib/common_functions.php'); +require_once('lib/db_functions.php'); +require_once('lib/check_email.php'); + +$config = get_config(); + +function is_kamailio_domain ( $domain ) +{ + global $config; + $query = sprintf("SELECT id FROM %s WHERE domain = '%s'", + $config['kamailio_domain_table'], + sql_clean( $domain ) + ); + $domain = sql_dbquery_single( $config['kamailio_db'], $query ); + if ( !$domain ) return false; + return $domain['id']; + +} + +function get_servers( $domain ) +{ + global $config; + $query = sprintf("SELECT registrar, r_port, proxy, p_port FROM %s WHERE domain = '%s'", + $config['provision_servers_table'], + sql_clean( $domain )); + $servers = sql_dbquery_single( $config['provision_db'], $query ); + return $servers; +} + +function verify_sipadress( $address ) //TODO: Improve/expand on the test! +{ + return check_email_address($address); // A SIP address follows the requirements for email addresses... + // Except that it permits the use of :portnumber notation! + // TODO: Fix this so it allows :NNNN syntax for portnumbers... +} + +function split_sipaddress ( $address ) +{ + if ( !verify_sipadress( $address ) ) return null; + $data = split('@', $address); + if ( count( $data ) != 2 ) return null; + return $data; +} + +function clean_mac ( $input ) +{ + $mac = strtolower( preg_replace('/:/', '', $input) ); + if ( ! preg_match( '/^[a-f0-9]{1,}$/', $mac ) ) return null; + if (strlen( $mac ) != 12 ) return null; + return $mac; +} +?> diff --git a/lib/db_functions.php b/lib/db_functions.php new file mode 100644 index 0000000..6122257 --- /dev/null +++ b/lib/db_functions.php @@ -0,0 +1,42 @@ +<?php +require_once('config.php'); +require_once('lib/common_functions.php'); + +function sql_dbquery( $db, $query ) +{ + if ( ! mysql_select_db( $db ) ) return false; + $result = mysql_query( $query ); + if ( !$result ) return false; + if (mysql_num_rows($result) == 0) return false; + return $result; +} +function sql_dbexec ( $db, $query ) +{ + if ( ! mysql_select_db( $db ) ) return false; + $result = mysql_query( $query ); + if ( !$result ) return false; + return $result; +} +function sql_dbquery_single( $db, $query ) +{ + $result = sql_dbquery( $db, $query ); + if ( ! $result ) return false; + if ( mysql_num_rows($result) != 1) return false; + $data = mysql_fetch_assoc( $result ); + return $data; +} +function sql_dbtest_numrows ( $db, $query, $numrows ) +{ + $result = sql_dbquery( $db, $query ); + + if ( ! $result ) return false; + if ( mysql_num_rows($result) == $numrows ) return true; + return false; +} +function sql_clean( $string ) +{ + return addslashes( $string ); +} + + +?> diff --git a/lib/number_functions.php b/lib/number_functions.php new file mode 100644 index 0000000..d814a99 --- /dev/null +++ b/lib/number_functions.php @@ -0,0 +1,155 @@ +<?php +require_once('config.php'); +require_once('lib/common_functions.php'); +require_once('lib/db_functions.php'); + +function verify_e164 ( $input ) +{ + // A really paranoid E164 test. Starts out with a regexp, + // where $arr[1] will be set to the numeric part of the + // e164 content, if valid. The rest is paranoid behaviour, + // if the regexp matches, the other tests can not fail... + + // e164 format is: A plus (+) followed by at least four + // digits, and no more than 15 digits total. + if (! preg_match ( '/^\+(\d{4,15})$/', $input, $arr ) ) + return 0; + $number = $arr[1]; + if ( ! $number ) + return 0; + + if ( preg_match( '/\+/', $number )) + return 0; + + if ( !is_numeric( $number ) ) + return 0; + + return $number; + +} + +function number_inpool( $number ) +{ + global $config; + if (! verify_e164( $number ) ) + { + return false; + } + + $test = "SELECT COUNT(*) FROM " . $config['numbers_table'] . " WHERE number = '" . $number . "'"; + $result = sql_dbquery($config['provision_db'], $test); + if ( !$result ) return false; + $row = mysql_fetch_row($result); + if ( !$row ) return false; + $num_r = $row[0]; + if ( $num_r == 1 ) return true; + + return false; +} + +function get_random_numbers ( $limit = 0 ) +{ + global $config; + $query = "SELECT number FROM " . $config['numbers_table'] . " ORDER BY RAND()"; + if ( $limit && is_numeric( $limit ) ) + $query .= " LIMIT " . $limit; + $result = sql_dbquery( $config['provision_db'], $query ); + print mysql_error(); + if ( !$result ) return null; + if (mysql_num_rows($result) < 1 ) return null; + $rows = array(); + while ( $row = mysql_fetch_assoc( $result ) ) + { + array_push( $rows, $row['number'] ); + } + return $rows; + +} + +function get_numbers ( $search=null, $limit = 0 ) +{ + global $config; + $query = "SELECT number FROM " . $config['numbers_table']; + + if ( $search ) + $query .= sprintf(" WHERE number LIKE '%%%s%%'", sql_clean($search)); + + $query .= " ORDER BY number ASC"; + + if ( $limit && is_numeric( $limit ) ) + $query .= " LIMIT " . $limit; + + + $result = sql_dbquery( $config['provision_db'], $query ); + print mysql_error(); + if ( !$result ) return null; + if (mysql_num_rows($result) < 1 ) return null; + $rows = array(); + while ( $row = mysql_fetch_assoc( $result ) ) + { + array_push( $rows, $row['number'] ); + } + return $rows; +} + +function add_range( $start, $end ) +{ + global $config; + $start_numeric = null; + $end_numeric = null; + + // The start and end must be in the same e164 range. Because + // of this, they must hav identical length + if ( strlen( $start ) != strlen( $end )) + return "Start and end have different lengths."; + + // The parameters MUST be in e164 format. + $start_numeric = verify_e164( $start ); + $end_numeric = verify_e164( $end ); + if (! $start_numeric ) + return "Start of range is not a valid e164 number"; + if (! $end_numeric ) + return "End of range is not a valid e164 number"; + + // Significant, a simple sanity check. + if ( ! ($start < $end) ) + return "Start of range is after end of range"; + + // Hard-coded paranoia: We expect ranges to be less than 10k numbers.. + if ( ($end - $start ) > 9999 ) + return "Range is larger than hard limit permits"; + + for ( $num = $start_numeric; $num <= $end_numeric; $num++ ) + { + add_number( '+' . $num ); + } + return 'ok'; +} + +function add_number( $number ) +{ + global $config; + if (! verify_e164( $number ) ) + { + return false; + } + + if (number_inpool( $number ) ) return false; + + $insert = "INSERT INTO " . $config['numbers_table'] . " (number) VALUES ('" . $number . "')"; + return sql_dbexec( $config['provision_db'], $insert); +} + +function remove_number ( $number ) +{ + global $config; + if (! verify_e164( $number ) ) + { + return false; + } + if ( !number_inpool( $number ) ) return false; + $query = "DELETE FROM " . $config['numbers_table'] . " WHERE number = '" . $number . "'"; + return sql_dbexec( $config['provision_db'], $query); + +} +?> diff --git a/lib/phone_functions.php b/lib/phone_functions.php new file mode 100644 index 0000000..17fbe10 --- /dev/null +++ b/lib/phone_functions.php @@ -0,0 +1,120 @@ +<?php +require_once('config.php'); +require_once('lib/common_functions.php'); +require_once('lib/db_functions.php'); + +function get_user_phones ( $username, $domain ) +{ + global $config; + $query = sprintf("SELECT id FROM %s WHERE username = '%s' AND domain = '%s'", + $config['provision_users_table'], + sql_clean( $username ), + sql_clean( $domain ) + ); + + $user = sql_dbquery_single( $config['provision_db'], $query ); + if ( ! $user ) return null; + $user_id = $user['id']; + + $query = sprintf("SELECT mac FROM %s WHERE user_rel = %d", + $config['provision_phones_table'], + $user_id + ); + + $result = sql_dbquery( $config['provision_db'], $query ); + if ( !$result ) return null; + if (mysql_num_rows($result) < 1 ) return null; + $rows = array(); + while ( $row = mysql_fetch_assoc( $result ) ) + array_push( $rows, $row['mac'] ); + return $rows; +} + +function get_phone_users ( $macaddress ) +{ + global $config; + $ptbl = $config['provision_phones_table']; + $utbl = $config['provision_users_table']; + $query = "SELECT ".$ptbl.".mac as mac, CONCAT( ".$utbl.".username, '@', ".$utbl.".domain ) as user + FROM ".$ptbl." + INNER JOIN ".$utbl." ON ".$ptbl.".user_rel = ".$utbl.".id + WHERE ".$ptbl.".mac = '".sql_clean($macaddress ). "'"; + + $result = sql_dbquery( $config['provision_db'], $query ); + if ( !$result ) return null; + if (mysql_num_rows($result) < 1 ) return null; + $rows = array(); + while ( $row = mysql_fetch_assoc( $result ) ) + { + array_push( $rows, $row['user'] ); + } + return $rows; +} + +function add_phone_user( $mac, $username, $domain ) +{ + global $config; + // Get ID of user, for use with user_rel field.. + $user_id = get_provision_userid( $username, $domain ); + if ( !$user_id ) return false; + + // Doublecheck :) + $mac = clean_mac($mac); + if (!$mac) return false; + + // Triplecheck :) + $phones = get_user_phones ( $username, $domain); + if ( $phones && in_array( $mac, $phones ) ) + return false; + + // OK, so we have the User ID, a valid MAC, and no previous registration + // of that combination. Going to add. + $query = sprintf("INSERT INTO %s ( mac, user_rel ) VALUES ( '%s', %d )", + $config['provision_phones_table'], $mac, $user_id); + return sql_dbexec( $config['provision_db'], $query ); +} + +function delete_phone_user( $mac, $username, $domain ) +{ + global $config; + // Get ID of user, for use with user_rel field.. + $user_id = get_provision_userid( $username, $domain ); + if ( !$user_id ) return false; + + // Doublecheck :) + $mac = clean_mac($mac); + if (!$mac) return false; + + // Triplecheck :) + $phones = get_user_phones ( $username, $domain); + if ( !$phones ) return false; + if ( ! in_array( $mac, $phones ) ) return false; + + // OK, so we have the User ID, a valid MAC, and no previous registration + // of that combination. Going to remove. + $query = sprintf("DELETE FROM %s WHERE mac = '%s' AND user_rel = %d", + $config['provision_phones_table'], $mac, $user_id); + return sql_dbexec( $config['provision_db'], $query ); +} + + + +function list_phones ( $search = null ) +{ + global $config; + $query = sprintf("SELECT mac FROM %s", $config['provision_phones_table']); + if ( $search ) + $query .= sprintf(" WHERE mac LIKE '%s%%'", sql_clean($search)); + + $result = sql_dbquery( $config['provision_db'], $query ); + if ( !$result ) return null; + if (mysql_num_rows($result) < 1 ) return null; + $rows = array(); + while ( $row = mysql_fetch_assoc( $result ) ) + { + array_push( $rows, $row['mac'] ); + } + return $rows; +} + +?> diff --git a/lib/user_functions.php b/lib/user_functions.php new file mode 100644 index 0000000..7944551 --- /dev/null +++ b/lib/user_functions.php @@ -0,0 +1,217 @@ +<?php +require_once('config.php'); +require_once('lib/common_functions.php'); +require_once('lib/db_functions.php'); + +$config = get_config(); + +function generate_password( $length = 32 ) +{ + $string = ""; + while ( strlen( $string ) < $length ) + $string .= substr(md5(rand().rand()), 0, $length); + return substr( $string, 0, $length ); + +} + + +function is_kamailio_subscriber ( $user, $domain ) +{ + global $config; + $query = sprintf("SELECT username FROM %s WHERE username = '%s' AND domain = '%s'", + $config['kamailio_subscriber_table'], + sql_clean( $user ), + sql_clean( $domain ) + ); + return sql_dbtest_numrows( $config['kamailio_db'], $query, 1); +} + +function is_provision_user ( $user, $domain ) +{ + global $config; + $query = sprintf("SELECT username FROM %s WHERE username = '%s' AND domain = '%s'", + $config['provision_users_table'], + sql_clean( $user ), + sql_clean( $domain ) + ); + return sql_dbtest_numrows( $config['provision_db'], $query, 1); +} + +function add_kamailio_subscriber( $username, $domain, $password, $email ) +{ + + global $config; + + $ha1 = md5( $username . ":" . $domain . ":" . $password ); + $ha1b = md5( $username . "@" . $domain . ":" . $domain . ":" . $password ); + + $query = sprintf( "INSERT INTO %s (username, domain, password, email_address, ha1, ha1b) VALUES ('%s','%s','%s', '%s', '%s', '%s')", + $config['kamailio_subscriber_table'], + sql_clean($username), + sql_clean($domain), + sql_clean($password), + sql_clean($email), + $ha1, + $ha1b + ); + if ( ! sql_dbexec( $config['kamailio_db'], $query ) ) return false; + return true; +} + +function delete_kamailio_subscriber( $username, $domain ) +{ + global $config; + + + $query = sprintf("SELECT id FROM %s WHERE username = '%s' AND domain = '%s'", + $config['kamailio_subscriber_table'], + sql_clean($username), + sql_clean($domain) + ); + $row = sql_dbquery_single( $config['kamailio_db'], $query ); + if (!$row) return false; + $user_rowid = $row['id']; + if ( !$user_rowid ) return false; + + $query = sprintf( "DELETE FROM %s WHERE id = %d AND username = '%s' AND domain = '%s'", + $config['kamailio_subscriber_table'], + $user_rowid, + sql_clean($username), + sql_clean($domain) + ); + if ( ! sql_dbexec( $config['kamailio_db'], $query ) ) return false; + return true; +} + +function add_provision_user( $username, $password, $domain, $authid, $registrar, $r_port, $proxy, $p_port, $displayname, $dialplan, $linetext ) +{ global $config; + + if ( is_provision_user( $username, $password ) ) return false; + $query = sprintf ("INSERT INTO %s ( username, password, displayname, domain, registrar, r_port, proxy, p_port, dialplan, authid, linetext ) + VALUES ('%s', '%s', '%s', '%s', '%s', %d, '%s', %d, '%s', '%s', '%s')", + $config['provision_users_table'], + sql_clean($username), + sql_clean($password), + sql_clean($displayname), + sql_clean($domain), + sql_clean($registrar), + $r_port, + sql_clean($proxy), + $p_port, + sql_clean($dialplan), + sql_clean($authid), + sql_clean($linetext) + ); + if ( ! sql_dbexec( $config['provision_db'], $query ) ) return false; + return true; +} +function get_provision_userid ( $username, $domain ) +{ + global $config; + + $query = sprintf("SELECT id FROM %s WHERE username = '%s' AND domain = '%s'", + $config['provision_users_table'], + sql_clean($username), + sql_clean($domain) + ); + + $row = sql_dbquery_single( $config['provision_db'], $query ); + if (!$row) return false; + $user_rowid = $row['id']; + return $user_rowid; +} + +function delete_provision_user( $username, $domain ) +{ + global $config; + + $user_rowid = get_provision_userid( $username, $domain ); + if ( !$user_rowid ) return false; + + $query = sprintf( "DELETE FROM %s WHERE id = %d AND username = '%s' AND domain = '%s'", + $config['provision_users_table'], + $user_rowid, + sql_clean($username), + sql_clean($domain) + ); + if ( ! sql_dbexec( $config['provision_db'], $query ) ) return false; + return true; +} + +function list_users ( $search = null ) +{ + global $config; + $query = sprintf("SELECT CONCAT(username, '@', domain) FROM %s ORDER BY username,domain", $config['provision_users_table'] ); + + if ( array_key_exists ( 'search', $_GET ) ) + { + $search = $_GET['search']; // TODO: Add some sanitation and input validation! + $query = sprintf("SELECT CONCAT(username, '@', domain) FROM %s WHERE CONCAT(username, '@', domain) LIKE '%%%s%%' ORDER BY username,domain", $config['provision_users_table'], sql_clean( $search ) ); + } + + $result = sql_dbquery( $config['provision_db'], $query ); + if ( !$result ) return null; + $list = array(); + while ( $row = mysql_fetch_row( $result ) ) + { + array_push( $list, $row[0] ); + } + return $list; + print json_encode( array( 'response' => 'ok', 'list' => $list )); + + +} +function get_userdata( $username, $domain ) +{ + global $config; + if ( is_kamailio_subscriber( $username, $domain ) // User must be present in both! + && is_provision_user( $username, $domain ) ) $type = 'local'; + else if ( is_provision_user( $username, $domain ) ) $type = 'remote'; + else return null; + + $provision_data = null; + $kamailio_data = null; + + $query_provision = sprintf ("SELECT id, username, password, displayname, domain, registrar, r_port, proxy, p_port, dialplan, authid, linetext FROM %s WHERE username = '%s' AND domain = '%s'", + $config['provision_users_table'], + sql_clean($username), + sql_clean($domain)); + + $provision_data = sql_dbquery_single( $config['provision_db'] , $query_provision ); + if ( ! $provision_data ) return false; + + if ( $type == 'local' ) + { + // WARNING: Note the typo in the name of the 'permittedcalls' column! + $query_kamailio = sprintf ("SELECT id, username, domain, password, email_address, ha1, ha1b, rpid, permitedcalls FROM %s WHERE username = '%s' AND domain = '%s'", + $config['kamailio_subscriber_table'], + sql_clean($username), + sql_clean($domain)); + $kamailio_data = sql_dbquery_single( $config['kamailio_db'] , $query_kamailio ); + if ( ! $kamailio_data ) return false; + } + $user['type'] = $type; + $user['username'] = $provision_data['username']; + $user['password'] = $provision_data['password']; + $user['domain'] = $provision_data['domain']; + $user['authid'] = $provision_data['authid']; + $user['registrar'] = $provision_data['registrar']; + $user['r_port'] = $provision_data['r_port']; + $user['proxy'] = $provision_data['proxy']; + $user['p_port'] = $provision_data['p_port']; + $user['dialplan'] = $provision_data['dialplan']; + $user['displayname'] = $provision_data['displayname']; + $user['linetext'] = $provision_data['linetext']; + if ( $type == 'local' ) + { + $user['email'] = $kamailio_data['email']; + $user['ha1'] = $kamailio_data['ha1']; + $user['ha1b'] = $kamailio_data['ha1b']; + $user['rpid'] = $kamailio_data['rpid']; + $user['permittedcalls'] = $kamailio_data['permitedcalls']; + } + + return $user; +} + +?> diff --git a/numbers.php b/numbers.php new file mode 100644 index 0000000..e9fc796 --- /dev/null +++ b/numbers.php @@ -0,0 +1,141 @@ +<?php +require_once('config.php'); +//require_once('lib/user_functions.php'); +require_once('lib/common_functions.php'); +require_once('lib/db_functions.php'); +require_once('lib/number_functions.php'); + +$config = get_config(); + +$config['sql_link'] = @mysql_connect( + $config['sql_server'], + $config['sql_username'], + $config['sql_password'] +); +if ( !$config['sql_link'] ) +{ + print json_encode( array( 'response' => 'failed', 'cause' => 'error', 'detail' => 'Database connection failed.')); + exit; +} + +//************************************************************************************* + switch ( $_SERVER['PATH_INFO'] ) + { + case "/list": + // List all (distinct) phone MAC-adresses registered... + $limit = 0; + $random = false; + $search = null; + if ( array_key_exists('limit', $_GET ) && is_numeric( $_GET['limit'])) + $limit = $_GET['limit']; + + if ( array_key_exists('search', $_GET ) ) + $search = $_GET['search']; + else if ( array_key_exists('random', $_GET ) && (strtolower( $_GET['random'] ) === 'true')) + $random = true; + + $numbers = null; + + if ( $random ) + { + $numbers = get_random_numbers( $limit ); + } + else + { + $numbers = get_numbers ( $search, $limit ); + } + if ( $numbers ) + { + print json_encode( array( 'response' => 'ok', 'list' => $numbers )); + break; + } + + print json_encode( array( 'response' => 'failed', 'cause' => 'empty', 'detail' => 'Empty result.' )); + break; + case "/add_range": + if ( array_key_exists('start', $_GET) && array_key_exists('end', $_GET) ) + { + $start = $_GET['start']; + $end = sql_clean( $_GET['end'] ); + $result = add_range( $start, $end ); + if ( $result === 'ok' ) + { + print json_encode ( array( 'response' => 'ok') ); + } + else + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'rejected', 'detail' => $result ) ); + } + } + break; + case "/add": + // TODO: This should return better responses! + // Currently, it will fail with "invalid" + if ( array_key_exists('number', $_GET)) + { + $number = $_GET['number']; + + if (! verify_e164( $number ) ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'rejected', 'detail' => "Not a valid e164 number" )); + break; + } + if ( number_inpool( $number ) ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'exists', 'detail' => "Number is already in the pool" )); + break; + } + + $result = add_number( $number ); + if ( $result ) + { + print json_encode ( array( 'response' => 'ok', 'detail' => 'Added ' . $number, 'number' => $number ) ); + break; + } + else + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'rejected' )); + } + } + print json_encode ( array( 'response' => 'invalid') ); + break; + case "/remove": + if ( array_key_exists('number', $_GET)) + { + $number = $_GET['number']; + + if (! verify_e164( $number ) ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'rejected', 'detail' => "Not a valid e164 number" )); + break; + } + if ( !number_inpool( $number ) ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'nonexistant', 'detail' => "Number not in pool" )); + break; + } + if ( !remove_number ( $number ) ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'dbfail', 'detail' => "Failed to remove number" )); + break; + } + print json_encode ( array( 'response' => 'ok', 'detail' => 'Removed ' . $number, 'number' => $number ) ); + break; + } + print json_encode ( array( 'response' => 'invalid') ); + break; + case "/inpool": + if ( array_key_exists('number', $_GET)) + { + $number = $_GET['number']; + if ( number_inpool( $number ) ) + print json_encode ( array( 'response' => 'ok', 'number' => $number ) ); + else + print json_encode ( array( 'response' => 'failed', 'cause' => 'nonexistant') ); + break; + } + default: + print json_encode ( array( 'response' => 'invalid') ); + } +mysql_close( $config['sql_link'] ); +?> diff --git a/phone.php b/phone.php new file mode 100644 index 0000000..11dd6db --- /dev/null +++ b/phone.php @@ -0,0 +1,205 @@ +<?php +require_once('config.php'); +require_once('lib/user_functions.php'); +require_once('lib/common_functions.php'); +require_once('lib/db_functions.php'); +require_once('lib/phone_functions.php'); + +$config = get_config(); + +$config['sql_link'] = @mysql_connect( + $config['sql_server'], + $config['sql_username'], + $config['sql_password'] +); +if ( !$config['sql_link'] ) +{ + print json_encode( array( 'response' => 'failed', 'cause' => 'error', 'detail' => 'Database connection failed.')); + exit; +} + +//************************************************************************************* + switch ( $_SERVER['PATH_INFO'] ) + { + case "/get": + // Required GET parameters: + // user: authentication username, SIP-username without domain component + // domain: Domain/realm of the user. username + '@' + domain == SIP address. + if ( array_key_exists( 'mac', $_GET) ) + { + $mac = $_GET['mac']; + $relations = get_phone_users ( $mac ); + if ( $relations ) + { + print json_encode( array( 'response' => 'ok', 'list' => $relations )); + } + else print json_encode( array ( 'response' => 'failed', 'cause' => 'nonexistant', 'detail' => 'No results.')); + } + else if ( array_key_exists( 'user', $_GET ) ) + { + $user = split_sipaddress($_GET['user']); + if (! $user ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'nonexistant', 'detail' => 'Invalid SIP address') ); + break; + } + list ( $username, $domain ) = $user; + $userdata = get_user_phones( $username, $domain ); + if ( $userdata ) + { + print json_encode( array( 'response' => 'ok', 'list' => $userdata )); + } + else print json_encode( array ( 'response' => 'failed', 'cause' => 'nonexistant', 'detail' => 'No results.')); + + } + else + print json_encode ( array( 'response' => 'invalid') ); + break; + case "/list": + // List all (distinct) phone MAC-adresses registered... + $search = null; + if ( array_key_exists('search', $_GET ) ) + $search = $_GET['search']; + + $phones = list_phones( $search ); + print json_encode( array( 'response' => 'ok', 'list' => $phones )); + break; + case "/add": + // Add a MAC+user... + /* + Parameters: + mac The MAC-address of the phone to add an entry for + Either: + user A registered username on user@domain form (SIP address) + Or: + username A registered username, combines with: + domain A valid domain .. to form a registered user@domain combo :) + + */ + if ( array_key_exists('mac', $_GET ) && + ( array_key_exists('user', $_GET) || + ( array_key_exists('username', $_GET) && array_key_exists('domain', $_GET )))) + { + $username = ""; + $domain = ""; + if ( array_key_exists('username', $_GET) ) + { + $username = $_GET['username']; + $domain = $_GET['domain']; + } + else + { + $user = split_sipaddress($_GET['user']); + if ( !$user ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'invalid', 'detail' => 'Invalid SIP address') ); + break; + } + list ( $username, $domain ) = $user; + } + $mac = clean_mac($_GET['mac']); + if ( !$mac ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'invalid', 'detail' => 'No valid MAC address given.') ); + break; + } + + if ( !is_provision_user ( $username, $domain ) ) + { + print json_encode( array ( 'response' => 'failed', 'cause' => 'nonexistant', 'detail' => 'User not registered.')); + break; + } + $phones = get_user_phones ( $username, $domain); + if ( $phones && in_array( $mac, $phones ) ) + { + print json_encode( array ( 'response' => 'failed', 'cause' => 'exists', 'detail' => 'This phone and user combination is already configured..')); + break; + } + $res = add_phone_user ( $mac, $username, $domain ); + if ( !$res ) + { + print json_encode( array ( 'response' => 'failed', 'cause' =>'dbfail', 'detail' => 'Failed to add phone to database.')); + break; + } + else + { + print json_encode( array ( 'response' => 'ok', 'data' => array ( + 'mac' => $mac, 'username' => $username, 'domain' => $domain) )); + break; + } + break; + } + else + print json_encode ( array( 'response' => 'invalid') ); + break; + + + case "/remove": + // Del a MAC+user... + /* + Parameters: + mac The MAC-address of the phone to add an entry for + Either: + user A registered username on user@domain form (SIP address) + Or: + username A registered username, combines with: + domain A valid domain .. to form a registered user@domain combo :) + + */ + if ( array_key_exists('mac', $_GET ) && + ( array_key_exists('user', $_GET) || + ( array_key_exists('username', $_GET) && array_key_exists('domain', $_GET )))) + { + $username = ""; + $domain = ""; + if ( array_key_exists('username', $_GET) ) + { + $username = $_GET['username']; + $domain = $_GET['domain']; + } + else + { + $user = split_sipaddress($_GET['user']); + if ( !$user ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'invalid', 'detail' => 'Invalid SIP address') ); + break; + } + list ( $username, $domain ) = $user; + } + $mac = clean_mac($_GET['mac']); + if ( !$mac ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'invalid', 'detail' => 'No valid MAC address given.') ); + break; + } + + $phones = get_user_phones ( $username, $domain); + if ( ! $phones || !in_array( $mac, $phones ) ) + { + print json_encode( array ( 'response' => 'failed', 'cause' => 'nonexistant', 'detail' => 'Unable to locate requested combination')); + break; + } + $res = delete_phone_user ( $mac, $username, $domain ); + if ( !$res ) + { + print json_encode( array ( 'response' => 'failed', 'cause' =>'dbfail', 'detail' => 'Failed to remove phone from database.')); + break; + } + else + { + print json_encode( array ( 'response' => 'ok' )); + break; + } + break; + } + else + print json_encode ( array( 'response' => 'invalid') ); + break; + + + default: + print json_encode ( array( 'response' => 'invalid') ); + } +mysql_close( $config['sql_link'] ); +?> diff --git a/user.php b/user.php new file mode 100644 index 0000000..ca15955 --- /dev/null +++ b/user.php @@ -0,0 +1,319 @@ +<?php +require_once('config.php'); +require_once('lib/user_functions.php'); +require_once('lib/common_functions.php'); +require_once('lib/db_functions.php'); +require_once('lib/phone_functions.php'); + +$config = get_config(); + +$config['sql_link'] = @mysql_connect( + $config['sql_server'], + $config['sql_username'], + $config['sql_password'] +); +if ( !$config['sql_link'] ) +{ + print json_encode( array( 'response' => 'failed', 'cause' => 'error', 'detail' => 'Database connection failed.')); + exit; +} + +//************************************************************************************* + switch ( $_SERVER['PATH_INFO'] ) + { + case "/get": + // Required GET parameters: + // user: authentication username, SIP-username without domain component + // domain: Domain/realm of the user. username + '@' + domain == SIP address. + if ( array_key_exists( 'username', $_GET) && array_key_exists( 'domain', $_GET ) ) + { + $username = $_GET['username']; + $domain = $_GET['domain']; + // Now, do funky stuff. + /* + Test if user exists in both 'kamailio.subscribers' and 'provision.users' + * Return 'response' => 'ok', 'type' => 'local', 'user' => complete user object. + Test if user exists in 'provision.user' only + * Return 'response' => 'ok', 'type' => 'remote', 'user' => complete user object. + If user does is neither local nor remote + * Return 'response' => 'failed' with 'cause' => 'nonexistant' + On failure, return 'response' => 'failed' with 'cause' => 'error' (may set 'detail' => 'message') + + */ + // Dummy-response: + $userdata = get_userdata( $username, $domain ); + if ( $userdata ) + { + print json_encode( array( 'response' => 'ok', 'user' => $userdata )); + } + else + { + print json_encode( array ( 'response' => 'failed', 'cause' => 'nonexistant', 'detail' => 'Request for user ' . $username . '@' . $domain . ' failed.')); + } + } + else + print json_encode ( array( 'response' => 'invalid') ); + break; + case "/list": + /* + Simply list all users in user@domain format + Perform a search operation if 'search' exists as a GET-parameter + * The search should try to do a "smart search" on SIP-usernames: + * Try to search with names in in username@domain format + * Do the search with wildcards before and after input text. + * The search must be done in the provisioning tables, to be able + to match non-local users. + * SQL SELECT CONCAT() WHERE CONCAT() must be used *shrug* + */ + $search = null; + if ( array_key_exists ( 'search', $_GET ) ) + $search = $_GET['search']; // TODO: Add some sanitation and input validation! + $list = list_users( $search ); + print json_encode( array( 'response' => 'ok', 'list' => $list )); + break; + case "/add_local": + /* + What to do?? + Required parameters should be... + username + domain + displayname + email + + Verify that domain is local (lookup in the 'kamailio.domain' table. + Verify that the username is available (nonexistant for domain in kamilio.subscribers (and provision.users?)) + * Autocreate password + * Add username, domain, email and created password to the 'kamailio.subscriber' table + * Get the registrar+port, proxy+port from the 'provision.servers' table. + * standard dialplan from configuration. + * Add to the 'provision.users' table: + username -> username + password -> generated password + displayname -> displayname + domain -> domain + registrar -> provision.servers.registrar + r_port -> provision.servers.r_port + proxy -> provision.servers.proxy + p_port -> provision.servers.p_port + authid -> username + dialplan -> standard dialplan + linetext -> username + * Return 'response' => 'ok' with a full user object in JSON format. + If any of the tests fail, return 'response' => 'failed' with 'cause' => "description" on JSON format. + + */ + // Test required parameters: + if ( array_key_exists( 'username', $_GET) + && array_key_exists( 'domain', $_GET ) + && array_key_exists( 'displayname', $_GET ) + && array_key_exists( 'email', $_GET ) ) + { + $username = $_GET['username']; + $domain = $_GET['domain']; + $password = generate_password(); + $displayname = $_GET['displayname']; + $email = $_GET['email']; + + if ( !is_kamailio_domain( $domain ) ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'nxdomain', 'detail' => 'The selected domain is not local' )); + break; + } + + $servers = get_servers( $domain ); + if ( !$servers ) + { + print json_encode( array( 'response' => 'failed', 'cause' => 'servfail', 'detail' => 'Servers lookup failed for domain '. $domain ) ); + break; + } + $registrar = $servers['registrar']; + $r_port = $servers['r_port']; + $proxy = $servers['proxy']; + $p_port = $servers['p_port']; + $authid = $username; + $linetext = $username; + $dialplan = $config['standard_dialplan']; + + if ( is_provision_user ( $username, $domain ) ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'exists', 'detail' => 'User already exists in provisioning configuration' )); + break; + } + if ( is_kamailio_subscriber ( $username, $domain ) ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'exists', 'detail' => 'User already exists as a Kamailio subscriber' )); + break; + } + $kam_res = add_kamailio_subscriber( $username, $domain, $password, $email ); + if ( !$kam_res ) + { + print json_encode( array( 'response' => 'failed', 'cause' => 'dbfail', 'detail' => 'Failed to add kamailio subscriber.' ) ); + break; + } + $pro_res = add_provision_user( $username, $password, $domain, $authid, $registrar, $r_port, $proxy, $p_port, $displayname, $dialplan, $linetext ); + if ( !$pro_res ) + { + // Rollback data added to Kamailio! Try to simulate atomicity, or atleast maintain integrity... + delete_kamailio_subscriber( $username, $domain ); + // Give errormessage, and quit. + print json_encode( array( 'response' => 'failed', 'cause' => 'dbfail', 'detail' => 'Failed to add user for provisioning. Rolled back kamailio subscriber' ) ); + break; + } + $userdata = get_userdata( $username, $domain ); + if ( !$userdata ) + { + // Rollback data added to Kamailio! Try to simulate atomicity, or atleast maintain integrity... + delete_kamailio_subscriber( $username, $domain ); + delete_provision_user( $username, $domain ); + // Give errormessage, and quit. + print json_encode( array( 'response' => 'failed', 'cause' => 'dbfail', 'detail' => 'Failed to read recently added data. Operations rolled back' ) ); + + } + print json_encode( array( 'response' => 'ok', 'user' => $userdata )); + } + else + print json_encode( array( 'response' => 'invalid', 'cause' => 'parameters' ) ); + break; + case "/add_remote": + /* + Required parameters should be... + username + password + domain + displayname + registrar + Optional parameters + r_port + proxy + p_port + authid + dialplan + linetext + + Verify that the username+domain is not already registered in 'provision.users'. + * If r_port is empty, set to 5060 + * If proxy/port is empty, set to registrar/port + * If authid is empty, set to username + * If dialplan is empty, set to standard dialplan + * If linetext is empty, set to username@domain + * Add to the 'provision.users' table: + username -> username + password -> supplied password + displayname -> displayname + domain -> domain + registrar -> registrar + r_port -> r_port + proxy -> proxy + p_port -> p_port + authid -> authid + dialplan -> dialplan + linetext -> linetext + * Return 'response' => 'ok' with a full user object in JSON format. + If any of the tests fail, return 'response' => 'failed' with 'cause' => "description" in JSON format. + */ + + + // Test required parameters: + if ( array_key_exists( 'username', $_GET) + && array_key_exists( 'password', $_GET ) + && array_key_exists( 'displayname', $_GET ) + && array_key_exists( 'domain', $_GET ) + && array_key_exists( 'registrar', $_GET ) ) + { + $username = $_GET['username']; + $password = $_GET['password']; + $domain = $_GET['domain']; + $displayname = $_GET['displayname']; + $registrar = $_GET['registrar']; + $r_port = ( array_key_exists('r_port', $_GET) ) ? $_GET['r_port'] : 5060; + + $proxy = ( array_key_exists('proxy', $_GET) ) ? $_GET['proxy'] : $registrar; + $p_port = ( array_key_exists('p_port', $_GET) ) ? $_GET['p_port'] : $r_port; + $authid = ( array_key_exists('authid', $_GET) ) ? $_GET['authid'] : $username; + $dialplan = ( array_key_exists('dialplan', $_GET) ) ? $_GET['dialplan'] : $config['standard_dialplan']; + $linetext = ( array_key_exists('linetext', $_GET) ) ? $_GET['linetext'] : $username . '@' . $domain; + + if ( is_provision_user ( $username, $domain ) ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'exists', 'detail' => 'User already exists in provisioning configuration' )); + break; + } + if ( is_kamailio_subscriber ( $username, $domain ) ) + { + print json_encode ( array( 'response' => 'failed', 'cause' => 'exists', 'detail' => 'User already exists as a Kamailio subscriber' )); + break; + } + + $pro_res = add_provision_user( $username, $password, $domain, $authid, $registrar, $r_port, $proxy, $p_port, $displayname, $dialplan, $linetext ); + if ( !$pro_res ) + { + // Give errormessage, and quit. + print json_encode( array( 'response' => 'failed', 'cause' => 'dbfail', 'detail' => 'Failed to add user for provisioning.' ) ); + break; + } + $userdata = get_userdata( $username, $domain ); + if ( !$userdata ) + { + // Rollback data added! + delete_provision_user( $username, $domain ); + // Give errormessage, and quit. + print json_encode( array( 'response' => 'failed', 'cause' => 'dbfail', 'detail' => 'Failed to read recently added data. Operations rolled back' ) ); + + } + print json_encode( array( 'response' => 'ok', 'user' => $userdata )); + } + else + print json_encode( array( 'response' => 'invalid', 'cause' => 'parameters' ) ); + break; + case "/remove": + /* + Required parameters should be... + username + domain + + * Verify that no associations/relations exist in 'provision.phones' + * Verify that the user exists in 'provision.users' + * Remove from 'provision.users' + * Test to see of user exists in 'kamailio.subscriber'. + * Remove from 'kamailio.subscribers' + * Return response' => 'ok', 'type' => 'local' + * If not in 'kamailio.subscribers' + * Return response' => 'ok', 'type' => 'remote' + * If associations exist, return 'response' => 'failed', 'cause' => 'inuse' + * If no such user exists, return 'response' => 'failed' with 'cause' => 'nonexistant' + * On other failures, return 'response' => 'failed' with 'cause' => 'error' (may set 'detail' => 'message') + */ + if ( array_key_exists( 'username', $_GET) && array_key_exists( 'domain', $_GET ) ) + { + $username = $_GET['username']; + $domain = $_GET['domain']; + if ( get_user_phones ( $username, $domain ) ) + { + print json_encode( array( 'response' => 'failed', 'cause' => 'inuse', 'detail' => 'User has associated provisioning. Remove and retry.' ) ); + break; + } + if ( is_provision_user( $username, $domain ) || is_kamailio_subscriber( $username, $domain ) ) + { + delete_provision_user( $username, $domain ); + delete_kamailio_subscriber( $username, $domain ); + print json_encode( array ( 'response' => 'ok', 'detail' => 'User ' . $username . '@' . $domain . ' deleted.')); + break; + } + else + { + print json_encode( array ( 'response' => 'failed', 'cause' => 'nonexistant', 'detail' => 'Unable to remove nonexistant user.')); + break; + } + + break; + + } + break; + case "/gen_pw": + print generate_password(); + break; + default: + print json_encode ( array( 'response' => 'invalid') ); + } +mysql_close( $config['sql_link'] ); +?>