From 665e8f51f50d2f09069e870a71826d3b8295ae34 Mon Sep 17 00:00:00 2001 From: jonl Date: Thu, 12 Jan 2012 15:51:58 +0100 Subject: [PATCH 1/1] Hermes is a framework for managing SIP-accounts, aliases and automatic phone-provisioning, based on Kamailio. This is the initial import. --- alias.php | 133 ++++++++++++++++ api-nodes.txt | 168 +++++++++++++++++++++ config.php.sample | 24 +++ lib/alias_functions.php | 98 ++++++++++++ lib/auth.php | 13 ++ lib/check_email.php | 38 +++++ lib/common_functions.php | 54 +++++++ lib/db_functions.php | 42 ++++++ lib/number_functions.php | 155 +++++++++++++++++++ lib/phone_functions.php | 120 +++++++++++++++ lib/user_functions.php | 217 ++++++++++++++++++++++++++ numbers.php | 141 +++++++++++++++++ phone.php | 205 +++++++++++++++++++++++++ user.php | 319 +++++++++++++++++++++++++++++++++++++++ 14 files changed, 1727 insertions(+) create mode 100644 alias.php create mode 100644 api-nodes.txt create mode 100644 config.php.sample create mode 100644 lib/alias_functions.php create mode 100644 lib/auth.php create mode 100644 lib/check_email.php create mode 100644 lib/common_functions.php create mode 100644 lib/db_functions.php create mode 100644 lib/number_functions.php create mode 100644 lib/phone_functions.php create mode 100644 lib/user_functions.php create mode 100644 numbers.php create mode 100644 phone.php create mode 100644 user.php diff --git a/alias.php b/alias.php new file mode 100644 index 0000000..398c12a --- /dev/null +++ b/alias.php @@ -0,0 +1,133 @@ + '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 " 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' = '' 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 @@ + '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 @@ + 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 @@ + '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 @@ + 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 @@ + 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 @@ + 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 @@ + 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 @@ + 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 @@ + '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 @@ + '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 @@ + '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 @@ + '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'] ); +?> -- 2.39.2