]> git.defcon.no Git - hermes/commitdiff
Hermes is a framework for managing SIP-accounts, aliases and automatic phone-provisio...
authorjonl <jonl@sippbx.hig.no>
Thu, 12 Jan 2012 14:51:58 +0000 (15:51 +0100)
committerjonl <jonl@sippbx.hig.no>
Thu, 12 Jan 2012 14:51:58 +0000 (15:51 +0100)
14 files changed:
alias.php [new file with mode: 0644]
api-nodes.txt [new file with mode: 0644]
config.php.sample [new file with mode: 0644]
lib/alias_functions.php [new file with mode: 0644]
lib/auth.php [new file with mode: 0644]
lib/check_email.php [new file with mode: 0644]
lib/common_functions.php [new file with mode: 0644]
lib/db_functions.php [new file with mode: 0644]
lib/number_functions.php [new file with mode: 0644]
lib/phone_functions.php [new file with mode: 0644]
lib/user_functions.php [new file with mode: 0644]
numbers.php [new file with mode: 0644]
phone.php [new file with mode: 0644]
user.php [new file with mode: 0644]

diff --git a/alias.php b/alias.php
new file mode 100644 (file)
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 (file)
index 0000000..376beb1
--- /dev/null
@@ -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&registrar=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 (file)
index 0000000..5b0a82b
--- /dev/null
@@ -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 (file)
index 0000000..4a6b3aa
--- /dev/null
@@ -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 (file)
index 0000000..2f8f9cf
--- /dev/null
@@ -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 (file)
index 0000000..800f52d
--- /dev/null
@@ -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 (file)
index 0000000..52d99e8
--- /dev/null
@@ -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 (file)
index 0000000..6122257
--- /dev/null
@@ -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 (file)
index 0000000..d814a99
--- /dev/null
@@ -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 (file)
index 0000000..17fbe10
--- /dev/null
@@ -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 (file)
index 0000000..7944551
--- /dev/null
@@ -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 (file)
index 0000000..e9fc796
--- /dev/null
@@ -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 (file)
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 (file)
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'] );
+?>