commit a3752621ac9e236f622a7f58a50c277744b68527 Author: Johan Pascal Date: Wed Jun 26 17:18:50 2019 +0700 Initial commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2370b65 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +$(eval GIT_DESCRIBE = $(shell sh -c "git describe")) +OUTPUT_DIR = ${CURDIR} +rpm: + rm -rf $(OUTPUT_DIR)/flexisip-account-manager + mkdir $(OUTPUT_DIR)/flexisip-account-manager + mkdir -p $(OUTPUT_DIR)/rpmbuild/SPECS + mkdir -p $(OUTPUT_DIR)/rpmbuild/SOURCES + cp src/*.php $(OUTPUT_DIR)/flexisip-account-manager/ + cp README.md $(OUTPUT_DIR)/flexisip-account-manager/ + cp src/*.conf $(OUTPUT_DIR)/flexisip-account-manager/ + mkdir -p $(OUTPUT_DIR)/flexisip-account-manager/httpd + cp httpd/flexisip-account-manager.conf $(OUTPUT_DIR)/flexisip-account-manager/httpd + cp flexisip-account-manager.spec $(OUTPUT_DIR)/rpmbuild/SPECS/ + tar cvf flexisip-account-manager.tar.gz -C $(OUTPUT_DIR) flexisip-account-manager + mv flexisip-account-manager.tar.gz $(OUTPUT_DIR)/rpmbuild/SOURCES/flexisip-account-manager.tar.gz + rpmbuild -v -bb --define '_topdir $(OUTPUT_DIR)/rpmbuild' --define "_rpmdir $(OUTPUT_DIR)/rpmbuild" $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec + rm -rf $(OUTPUT_DIR)/flexisip-account-manager + +.PHONY: rpm diff --git a/README.packaging.md b/README.packaging.md new file mode 100644 index 0000000..1032ca9 --- /dev/null +++ b/README.packaging.md @@ -0,0 +1,16 @@ +PHP Authenticated Lime server +============================== + +Packaging +--------- + +To build a rpm package on centos7: + +`make rpm` + +To build a rpm package with docker: + +`docker run -v $PWD:/home/bc -it gitlab.linphone.org:4567/bc/public/linphone-sdk/bc-dev-centos:7 make` + +The lime-server rpm package can be found in rpmbuild/RPMS/x86_64/bc-lime-server*.rpm +Installation requires package centos-release-scl-rh to be installed for php7.1 diff --git a/flexisip-account-manager.spec b/flexisip-account-manager.spec new file mode 100644 index 0000000..9aff365 --- /dev/null +++ b/flexisip-account-manager.spec @@ -0,0 +1,73 @@ +# -*- rpm-spec -*- +#%define _prefix @CMAKE_INSTALL_PREFIX@ +#%define pkg_prefix @BC_PACKAGE_NAME_PREFIX@ + +# re-define some directories for older RPMBuild versions which don't. This messes up the doc/ dir +# taken from https://fedoraproject.org/wiki/Packaging:RPMMacros?rd=Packaging/RPMMacros +#%define _datarootdir %{_prefix}/share +#%define _datadir %{_datarootdir} +#%define _docdir %{_datadir}/doc + +%define build_number 1 +#%if %{build_number} +#%define build_number_ext -%{build_number} +#%endif + +Name: bc-flexisip-account-manager +Version: 1.0.2 +Release: %{build_number}%{?dist} +Summary: SIP account management xml-rpc server, for use with flexisip server suite. + +Group: Applications/Communications +License: GPL +URL: http://www.linphone.org +#Source0: %{name}-%{version}%{?build_number_ext}.tar.gz +Source0: flexisip-account-manager.tar.gz +#BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot + +# dependencies +Requires: rh-php71-php rh-php71-php-xmlrpc rh-php71-php-mysqlnd rh-php71-php-mbstring + +%description +PHP server for Linphone and Flexisip providing module for account creation. + + +%prep +%setup -n flexisip-account-manager + +%install +rm -rf "$RPM_BUILD_ROOT" +mkdir -p "$RPM_BUILD_ROOT/opt/belledonne-communications/share/flexisip-account-manager" +cp -R *.php "$RPM_BUILD_ROOT/opt/belledonne-communications/share/flexisip-account-manager" +cp -R README* "$RPM_BUILD_ROOT/opt/belledonne-communications/share/flexisip-account-manager" +mkdir -p "$RPM_BUILD_ROOT/etc/flexisip-account-manager" +cp -R *.conf "$RPM_BUILD_ROOT/etc/flexisip-account-manager" +mkdir -p $RPM_BUILD_ROOT/opt/rh/httpd24/root/etc/httpd/conf.d +cp httpd/flexisip-account-manager.conf "$RPM_BUILD_ROOT/opt/rh/httpd24/root/etc/httpd/conf.d" + + +%post +if [ $1 -eq 1 ] ; then +mkdir -p /var/opt/belledonne-communications/log +touch /var/opt/belledonne-communications/log/account-manager.log +chown apache:apache /var/opt/belledonne-communications/log/account-manager.log +chcon -t httpd_sys_rw_content_t /var/opt/belledonne-communications/log/account-manager.log +setsebool httpd_can_network_connect_db on +fi + + +%files +/opt/belledonne-communications/share/flexisip-account-manager/*.php +/opt/belledonne-communications/share/flexisip-account-manager/README* + +%config(noreplace) /etc/flexisip-account-manager/*.conf +%config(noreplace) /opt/rh/httpd24/root/etc/httpd/conf.d/flexisip-account-manager.conf + +%clean +rm -rf $RPM_BUILD_ROOT + +%changelog +* Fri Jun 28 2019 Johan Pascal +- +* Fri May 18 2018 Matthieu TANON +- Initial RPM release. diff --git a/httpd/flexisip-account-manager.conf b/httpd/flexisip-account-manager.conf new file mode 100644 index 0000000..833280e --- /dev/null +++ b/httpd/flexisip-account-manager.conf @@ -0,0 +1,7 @@ +Alias /flexisip-account-manager /opt/belledonne-communications/share/flexisip-account-manager + + Options FollowSymLinks MultiViews + AllowOverride None + Require all granted + + diff --git a/src/admin.php b/src/admin.php new file mode 100644 index 0000000..0272a86 --- /dev/null +++ b/src/admin.php @@ -0,0 +1,78 @@ +#! /bin/php += 2) { + $action = $argv[1]; + if ($action == "list_accounts") { + $accounts = db_get_accounts(); + foreach ($accounts as $account) { + echo $account['username'] . '@' . $account['domain'] . ' activation status is ' . $account['activated'] . " (activation code is " . $account['activation_code'] . "): IP " . $account['ip_address'] . ", user-agent " . $account['user_agent'] . "\r\n"; + } + } else if ($action == "delete_account") { + if ($argc >= 3) { + $login = $argv[2]; + $domain = SIP_DOMAIN; + if ($argc >= 4) { + $domain = $argv[3]; + } + if (!db_account_is_existing($login, $domain)) { + echo "Error: account " . $login . " on domain " . $domain . " doesn't exist." . "\r\n"; + exit; + } + db_alias_delete($login, $domain); + db_account_delete($login, $domain); + if (startswith($login, "+")) { + db_delete_sms($login); + } + echo "Account " . $login . " successfuly deleted." . "\r\n"; + } else { + echo "Proper way to use is php admin.php delete_account [domain]" . "\r\n"; + } + } else if ($action == "activate_account") { + if ($argc >= 3) { + $login = $argv[2]; + $domain = SIP_DOMAIN; + if ($argc >= 4) { + $domain = $argv[3]; + } + if (!db_account_is_existing($login, $domain)) { + echo "Error: account " . $login . " on domain " . $domain . " doesn't exist." . "\r\n"; + exit; + } + db_account_super_activate($login, $domain); + echo "Account " . $login . " succesfuly super activated." . "\r\n"; + } else { + echo "Proper way to use is php admin.php activate_account [domain]" . "\r\n"; + } + } else if ($action == "help") { + echo "Possible commands are:" . "\r\n"; + echo "help" . "\r\n"; + echo "list_accounts" . "\r\n"; + echo "activate_account" . "\r\n"; + echo "delete_account [domain]" . "\r\n"; + } +} else { + echo "Proper way to use is php admin.php action [params]" . "\r\n"; + echo "Try php admin.php help to see all possible actions." . "\r\n"; + exit; +} + +?> \ No newline at end of file diff --git a/src/authentication.php b/src/authentication.php new file mode 100644 index 0000000..1dc1143 --- /dev/null +++ b/src/authentication.php @@ -0,0 +1,64 @@ + diff --git a/src/hooks.php b/src/hooks.php new file mode 100644 index 0000000..d6a7c8b --- /dev/null +++ b/src/hooks.php @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/src/internationalization.conf b/src/internationalization.conf new file mode 100644 index 0000000..78a4650 --- /dev/null +++ b/src/internationalization.conf @@ -0,0 +1,7 @@ + 'Your Linphone validation code is #CODE#', // This one isn't required but if present it MUST be equal to SMS_OVH_US_TEMPLATE + 'FR' => 'Votre code de validation Linphone est #CODE#', + ); +?> \ No newline at end of file diff --git a/src/logging.php b/src/logging.php new file mode 100644 index 0000000..623ac52 --- /dev/null +++ b/src/logging.php @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/src/mysqli-db.php b/src/mysqli-db.php new file mode 100644 index 0000000..8177d28 --- /dev/null +++ b/src/mysqli-db.php @@ -0,0 +1,46 @@ +connect_errno) { + mylog("[ERROR][DB] Connection failed: " . $conn->connect_errno . " - " . $conn->connect_error); + } + return $conn; +} + +function linphonedb_escape($conn, $param) { + return mysqli_real_escape_string($conn, $param); +} + +function linphonedb_query($queryStr, $conn) { + $result = mysqli_query($conn, $queryStr); + if (! $result) { + mylog("[ERROR][DB] Invalid query: " . $conn->connect_errno . " - " . $conn->connect_error); + return ""; + } + // cannot log result because fetch needed + // mylog("[DB] Query: " . $queryStr); + return $result; +} + +function linphonedb_fetch($result) { + if ($result->num_rows === 0) { + mylog("[ERROR][DB] Result is empty..."); + } + $row = mysqli_fetch_array($result, MYSQLI_NUM); + return $row; +} + +function linphonedb_clean($result) { + mysqli_free_result($result); +} + +function linphonedb_close($conn) { + mysqli_close($conn); +} + +?> diff --git a/src/provisioning.php b/src/provisioning.php new file mode 100644 index 0000000..d14b231 --- /dev/null +++ b/src/provisioning.php @@ -0,0 +1,26 @@ +'; +$xml = $xml . ''; +foreach ($rc_array as $section => $values) { + $xml = $xml . '
'; + foreach ($values as $key => $value) { + if (REMOTE_PROVISIONING_OVERWRITE_ALL) { + $xml = $xml . '' . $value . ''; + } else { + $xml = $xml . '' . $value . ''; + } + } + $xml = $xml . '
'; +} +$xml = $xml . '
'; + +header('Content-type: text/xml'); +echo $xml; + +?> diff --git a/src/sms-sender.php b/src/sms-sender.php new file mode 100644 index 0000000..597d73d --- /dev/null +++ b/src/sms-sender.php @@ -0,0 +1,30 @@ + \r\n"; +} + +?> diff --git a/src/utilities.php b/src/utilities.php new file mode 100644 index 0000000..523ee28 --- /dev/null +++ b/src/utilities.php @@ -0,0 +1,158 @@ += 0 and strpos($hay, $needle, $temp) !== FALSE); +} + +function getIp() { + $ip = $_SERVER['REMOTE_ADDR']; + if (!empty($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; + } + return $ip; +} + +// Internationalization + +function get_lang($param) { + if ($param == NULL || $param == "") { + mylog("[WARN] lang parameter wasn't found, use US"); + return 'US'; + } else if (strlen($param) > 2) { + $param = substr($param, 0, 2); + } + return strtoupper($param); +} + +function get_sms_string_for_lang($lang) { + global $SMS_OVH_TEMPLATE; + if (isset($SMS_OVH_TEMPLATE[$lang])) { + return $SMS_OVH_TEMPLATE[$lang]; + } + mylog("[WARN] SMS template not found for lang " . $lang . ", using US template"); + return SMS_OVH_US_TEMPLATE; +} + +// Password + +function hash_password($user, $password, $domain, $algo) { + if(strcmp($algo,"")==0 || strcmp($algo,"MD5")==0) $hashed_password = hash("md5", $user . ":" . $domain . ":" . $password); + if(strcmp($algo,"SHA-256")==0) $hashed_password = hash("sha256", $user . ":" . $domain . ":" . $password); + return $hashed_password; +} + +function generate_password() { + $generated_password = substr(str_shuffle(GENERATED_PASSWORD_CHARACTERS), 0, GENERATED_PASSWORD_LENGTH); + return $generated_password; +} + +function generate_4_digits_code() { + $generated_password = substr(str_shuffle("0123456789"), 0, 4); + return $generated_password; +} + +function get_trial_expiration_date() { + $expiration_date = new DateTime('now +' . TRIAL_DURATION_DAYS . ' days'); + $expiration = $expiration_date->getTimestamp() * 1000; + return $expiration; +} + +function check_parameter($param, $param_name = "username") { + if ($param == NULL || $param == "") { + mylog("[WARN] " . $param_name . " is missing"); + return false; + } + return true; +} + +function check_algo($algo) { + if (strcmp($algo,"")==0 || strcmp($algo,"MD5")==0 || strcmp($algo,"SHA-256")==0 || strcmp($algo,"clrtxt")==0){ + return true; + } + mylog("[ERROR] Algo " . $algo . " is not supported"); + return false; +} + +function get_domain($param) { + if ($param == NULL || $param == "") { + mylog("[WARN] domain parameter wasn't found, use " . SIP_DOMAIN); + $param = SIP_DOMAIN; + } + return $param; +} + +// Email + +function send_email($email, $subject, $text, $html) { + $site = EMAIL_SITE; + $from = EMAIL_FROM_ADDR; + $name = EMAIL_FROM_NAME; + $to = $email; + $from = $name." <".$from.">"; + + $limite = "_----------=_parties_".md5(uniqid (rand())); + + $headers = "Reply-to: ".$from."\n"; + $headers .= "From: ".$from."\n"; + $headers .= "Return-Path: ".$from."\n"; + $headers .= "X-Sender: <".$site.">\n"; + $headers .= "X-Mailer: PHP\n"; + $headers .= "X-auth-smtp-user: ".$from." \n"; + $headers .= "X-abuse-contact: ".$from." \n"; + $headers .= "X-auth-smtp-user: ".$from." \n"; + $headers .= "X-abuse-contact: ".$from." \n"; + $headers .= "Date: ".date("D, j M Y G:i:s O")."\n"; + $headers .= "MIME-Version: 1.0\n"; + $headers .= "Content-Type: multipart/alternative; boundary=\"".$limite."\""; + + $message = ""; + + $message .= "--".$limite."\n"; + $message .= "Content-Type: text/plain; charset=\"utf-8\"\n"; + $message .= "Content-Transfer-Encoding: 8bit\n\n"; + $message .= $text; + + $message .= "\n\n--".$limite."\n"; + $message .= "Content-Type: text/html; charset=\"utf-8\"\n"; + $message .= "Content-Transfer-Encoding: 8bit;\n\n"; + $message .= $html; + + $message .= "\n--".$limite."--"; + + $params = "-f" . EMAIL_FROM_ADDR . " -O DeliveryMode=b"; + $result = mail($email, $subject, $message, $headers, $params); + if (!$result) { + mylog("[ERROR][EMAIL] Email delivery declined !"); + } +} + +function send_email_with_activation_link($email, $key) { + if( !EMAIL_ENABLED ){ + mylog("[WARN] [EMAIL] Emails are disabled"); + return "WARNING_EMAILS_DISABLED"; + } + + $pageURL = 'http'; + if ($_SERVER["HTTPS"] == "on") {$pageURL .= "s";} + $pageURL .= "://"; + + $link = $pageURL . EMAIL_ACTIVATION_LINK; + $link = str_replace("%key%", $key, $link); + mylog("[EMAIL] Activation link is " . $link); + + $body = str_replace("%link%", $link, EMAIL_ACTIVATION_BODY); + mylog("[EMAIL] Activation body is " . $body); + $body_html = str_replace("%link%", $link, EMAIL_ACTIVATION_BODY_HTML); + mylog("[EMAIL] Activation html body is " . $body_html); + + send_email($email, EMAIL_ACTIVATION_SUBJECT, $body, $body_html); + mylog("[EMAIL] Email sent to email " . $email . " to activate the account"); +} + +?> diff --git a/src/xmlrpc-accounts.php b/src/xmlrpc-accounts.php new file mode 100644 index 0000000..a94ffe4 --- /dev/null +++ b/src/xmlrpc-accounts.php @@ -0,0 +1,866 @@ += 1; + linphonedb_clean($result); + linphonedb_close($conn); + return $is_existing; +} + +function db_account_is_email_in_use($email) { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT count(*) FROM " . ACCOUNTS_DB_TABLE . " WHERE email='" . linphonedb_escape($conn, $email) . "'", $conn); + $row = linphonedb_fetch($result); + $is_in_use = $row[0] >= 1; + linphonedb_clean($result); + linphonedb_close($conn); + return $is_in_use; +} + +function db_account_is_email_or_login_in_use($user_or_email) { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT count(*) FROM " . ACCOUNTS_DB_TABLE . " WHERE login='" . linphonedb_escape($conn, $user_or_email) . "' OR email='" . linphonedb_escape($conn, $user_or_email) . "'", $conn); + $row = linphonedb_fetch($result); + $is_in_use = $row[0] >= 1; + linphonedb_clean($result); + linphonedb_close($conn); + return $is_in_use; +} + +function db_account_get_confirmation_key($user, $domain) { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT confirmation_key FROM " . ACCOUNTS_DB_TABLE . " WHERE login='" . linphonedb_escape($conn, $user) . "' AND domain='" . linphonedb_escape($conn, $domain) . "'", $conn); + $row = linphonedb_fetch($result); + $key = $row[0]; + linphonedb_clean($result); + linphonedb_close($conn); + return $key; +} + +function db_account_get_password($user, $domain, $algo) { + $conn = linphonedb_connect(); + if(!strcmp($algo,"")) + $algo = "MD5"; + $result = linphonedb_query("SELECT password FROM " . ACCOUNTS_ALGO_DB_TABLE . " WHERE account_id=(SELECT id FROM " . ACCOUNTS_DB_TABLE . " WHERE login='" . linphonedb_escape($conn, $user) . "' AND domain='" . linphonedb_escape($conn, $domain) . "') AND algorithm='" . linphonedb_escape($conn, $algo) . "'", $conn); + $row = linphonedb_fetch($result); + $password = $row[0]; + linphonedb_clean($result); + linphonedb_close($conn); + return $password; +} + +function db_account_get_login_from_login_or_email($user_or_email) { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT login FROM " . ACCOUNTS_DB_TABLE . " WHERE login='" . linphonedb_escape($conn, $user_or_email) . "' OR email='" . linphonedb_escape($conn, $user_or_email) . "'", $conn); + $row = linphonedb_fetch($result); + linphonedb_clean($result); + linphonedb_close($conn); + return $row[0]; +} + +function db_account_get_email_from_login_or_email($user_or_email) { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT email FROM " . ACCOUNTS_DB_TABLE . " WHERE login='" . linphonedb_escape($conn, $user_or_email) . "' OR email='" . linphonedb_escape($conn, $user_or_email) . "'", $conn); + $row = linphonedb_fetch($result); + linphonedb_clean($result); + linphonedb_close($conn); + return $row[0]; +} + +function db_account_get_logins_from_email($email) { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT login FROM " . ACCOUNTS_DB_TABLE . " WHERE email='" . linphonedb_escape($conn, $email) . "'", $conn); + $usernames = array(); + $i = 0; + while ($row = linphonedb_fetch($result)) { + $usernames[$i] = $row[0]; + $i = $i + 1; + } + linphonedb_clean($result); + linphonedb_close($conn); + return $usernames; +} + +function db_get_accounts() { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT login, domain, activated, confirmation_key, ip_address, user_agent FROM " . ACCOUNTS_DB_TABLE, $conn); + $accounts = array(); + $i = 0; + while ($row = linphonedb_fetch($result)) { + $account = array(); + $account['username'] = $row[0]; + $account['domain'] = $row[1]; + $account['activated'] = $row[2]; + $account['activation_code'] = $row[3]; + $account['ip_address'] = $row[4]; + $account['user_agent'] = $row[5]; + $accounts[$i] = $account; + $i = $i + 1; + } + linphonedb_clean($result); + linphonedb_close($conn); + return $accounts; +} + +// XMLRPC methods + +// args == [email] +function xmlrpc_recover_usernames_from_email($method, $args) { + $email = $args[0]; + mylog("[XMLRPC] xmlrpc_recover_usernames_from_email(" . $email . ")"); + + if (!check_parameter($email, "email")) { + return "ERROR_EMAIL_PARAMETER_NOT_FOUND"; + } + + if (db_account_is_email_in_use($email)) { + $usernames = db_account_get_logins_from_email($email); + //TODO: send email + return "OK"; + } else { + mylog("[ERROR] email not found in database"); + return "ERROR_EMAIL_DOESNT_EXIST"; + } +} + +// args = [username or email] +function xmlrpc_send_reset_password_email($method, $args) { + $user_or_email = $args[0]; + mylog("[XMLRPC] xmlrpc_send_reset_password_email(" . $user_or_email . ")"); + + if (!check_parameter($user_or_email, "username or email")) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + + if (db_account_is_email_or_login_in_use($user_or_email)) { + $user = db_account_get_login_from_login_or_email($user_or_email); + $email = db_account_get_email_from_login_or_email($user_or_email); + //TODO: send email + return "OK"; + } else { + mylog("[ERROR] username or email not found in database"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } +} + +function xmlrpc_send_activation_email($method, $args) { + $user_or_email = $args[0]; + mylog("[XMLRPC]xmlrpc_send_activation_email(" . $user_or_email . ")" ); + if (!check_parameter($user_or_email, "username or email")) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + + if (db_account_is_email_or_login_in_use($user_or_email)) { + $user = db_account_get_login_from_login_or_email($user_or_email); + $email = db_account_get_email_from_login_or_email($user_or_email); + $hash = get_hash_from_email($email); + delete_link_in_database($hash); + //TODO: send email + return "OK"; + } else { + mylog("[ERROR] username or email not found in database"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } +} + +// args = [username, old hash, new hash, [domain], [algo]] +function xmlrpc_update_hash($method, $args) { + $user = $args[0]; + $hashed_old_password = $args[1]; + $hashed_new_password = $args[2]; + $domain = get_domain($args[3]); + $algo = $args[4]; + mylog("[XMLRPC] xmlrpc_update_hash(" . $user . ", " . $domain . ")"); + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + if (!check_algo($algo)) { + return "ERROR_ALGO_NOT_SUPPORTED"; + } + if (db_account_is_existing($user, $domain)) { + $db_hashed_password = db_account_get_password($user, $domain, $algo); + if (strcmp($db_hashed_password, $hashed_old_password) != 0) { + mylog("[ERROR] old password doesn't match"); + return "ERROR_PASSWORD_DOESNT_MATCH"; + } else { + db_account_update_password($user, $domain, $hashed_new_password, $algo); + mylog("Password updated successfully"); + return "OK"; + } + } else { + mylog("[ERROR] username not found in database"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } +} + +// args = [username, old password, new password, [domain], [algo]] +function xmlrpc_update_password($method, $args) { + $user = $args[0]; + $domain = get_domain($args[3]); + $algo = $algo[4]; + mylog("[XMLRPC] xmlrpc_update_password(" . $user . ", " . $domain . ")"); + if (!check_algo($algo)) { + return "ERROR_ALGO_NOT_SUPPORTED"; + } + $args[1] = hash_password($args[0], $args[1], $algo); + $args[2] = hash_password($args[0], $args[2], $algo); + return xmlrpc_update_hash("xmlrpc_update_password", $args); +} + +// args = [username, password, new email, [domain], [algo]] +function xmlrpc_update_email($method, $args) { + $user = $args[0]; + $password = $args[1]; + $new_email = $args[2]; + $domain = get_domain($args[3]); + $algo = $args[4]; + mylog("[XMLRPC] xmlrpc_update_email(" . $user . ", " . $domain . ", " . $new_email . ")"); + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + if (!check_algo($algo)) { + return "ERROR_ALGO_NOT_SUPPORTED"; + } + if (db_account_is_existing($user, $domain)) { + $db_hashed_password = db_account_get_password($user, $domain, $algo); + $hashed_old_password = hash_password($user, $password, $domain, $algo); + if (strcmp($db_hashed_password, $hashed_old_password) != 0 and strcmp($db_hashed_password, $password) != 0) { + mylog("[ERROR] old password doesn't match"); + return "ERROR_PASSWORD_DOESNT_MATCH"; + } else { + $old_email = db_account_get_email_from_login_or_email($user); + if (strcmp($old_email, $new_email) == 0) { + mylog("New email same as previous one"); + return "ERROR_EMAIL_NEW_SAME_AS_OLD"; + } else { + if (db_account_is_email_in_use($new_email) && !ALLOW_SAME_EMAILS_ON_MULTILPLE_ACCOUNTS) { + return "ERROR_EMAIL_ALREADY_IN_USE"; + } else { + db_account_update_email($user, $domain, $new_email); + mylog("Email updated successfully"); + return "OK"; + } + } + } + } else { + mylog("[ERROR] username not found in database"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } +} + +// args = [username, [domain]] +function xmlrpc_is_account_activated($method, $args) { + $user = $args[0]; + $domain = get_domain($args[1]); + mylog("[XMLRPC] xmlrpc_is_account_activated(" . $user . ", " . $domain . ")"); + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + + if (db_account_is_activated($user, $domain)) { + mylog("User account " . $user . " / " . $domain . " is activated"); + return "OK"; + } else { + if (db_account_is_existing($user, $domain)) { + return "NOK"; + } else { + mylog("[ERROR] User account " . $user . " / " . $domain . " doesn't exist"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } + } +} + +// args = [username, [domain]] +function xmlrpc_is_account_used($method, $args) { + $user = $args[0]; + $domain = get_domain($args[1]); + mylog("[XMLRPC] xmlrpc_is_account_used(" . $user . ", " . $domain . ")"); + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + + if (db_account_is_existing($user, $domain)) { + mylog("[ERROR] User account " . $user . " / " . $domain . " exists"); + return "OK"; + } else { + mylog("[ERROR] User account " . $user . " / " . $domain . " doesn't exist"); + return "NOK"; + } +} + +// args = [email] +function xmlrpc_is_email_used($method, $args) { + $email = $args[0]; + mylog("[XMLRPC] xmlrpc_is_email_used(" . $email . ")"); + + if (!check_parameter($email, "email")) { + return "ERROR_EMAIL_PARAMETER_NOT_FOUND"; + } + + if (db_account_is_email_in_use($email)) { + mylog("[ERROR] User email " . $email . " exists"); + return "OK"; + } else { + mylog("[ERROR] User email " . $email . " doesn't exist"); + return "ERROR_EMAIL_DOESNT_EXIST"; + } +} + +// args = [username, key, [domain], [algo]] +function xmlrpc_activate_email_account($method, $args) { + $user = $args[0]; + $key = $args[1]; + $domain = get_domain($args[2]); + $algo = $args[3]; + + mylog("[XMLRPC] xmlrpc_activate_account(" . $user . ", " . $domain . ", " . $key . ")"); + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + if (!check_algo($algo)) { + return "ERROR_ALGO_NOT_SUPPORTED"; + } + if (!db_account_is_existing($user, $domain)) { + mylog("[ERROR] User account " . $user . " / " . $domain . " doesn't exist"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } else if (db_account_is_activated($user, $domain)) { + mylog("[ERROR] User account " . $user . " / " . $domain . " is already activated"); + return "ERROR_ACCOUNT_ALREADY_ACTIVATED"; + } + + $key_db = db_account_get_confirmation_key($user, $domain); + if (strcmp($key, "ERROR") == 0 or strcmp($key, $key_db) != 0) { + if (strcmp($key_db, "ERROR") != 0) { + db_account_update_confirmation_key($user, $domain, "ERROR"); + } + mylog("[ERROR] Key doesn't match"); + return "ERROR_KEY_DOESNT_MATCH"; + } + + $expiration = NULL; + db_account_activate($user, $domain); + if (USE_IN_APP_PURCHASES) { + $expiration = get_trial_expiration_date(); + db_inapp_add_account($user, $domain, $expiration); + } + + if (CUSTOM_HOOKS) { + hook_on_account_activated($user, $domain, $expiration); + } + + $ha1 = db_account_get_password($user, $domain, $algo); + return $ha1; +} + +// args = [phone, username, key, [domain],[algo]] +function xmlrpc_activate_phone_account($method, $args) { + $phone = $args[0]; + $user = $args[1]; + $key = $args[2]; + $domain = get_domain($args[3]); + $algo = $args[4]; + + mylog("[XMLRPC] xmlrpc_activate_phone_account(" . $user . ", " . $domain . ", " . $key . ")"); + + if (!check_parameter($phone, "phone")) { + return "ERROR_PHONE_PARAMETER_NOT_FOUND"; + } else if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } else if (!startswith($phone, "+")) { + mylog("[ERROR] Phone doesn't start by +"); + return "ERROR_PHONE_ISNT_E164"; + } + if (!check_algo($algo)) { + return "ERROR_ALGO_NOT_SUPPORTED"; + } + if (!db_account_is_existing($user, $domain)) { + mylog("[ERROR] User account " . $user . " / " . $domain . " doesn't exist"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } + + $key_db = db_account_get_confirmation_key($user, $domain); + if (strcmp($key, "ERROR") == 0 or strcmp($key, $key_db) != 0) { + if (strcmp($key_db, "ERROR") != 0) { + db_account_update_confirmation_key($user, $domain, "ERROR"); + } + mylog("[ERROR] Key doesn't match"); + return "ERROR_KEY_DOESNT_MATCH"; + } + + // If this is a recovery, account is already activated, don't go through the following again + if (!db_account_is_activated($user, $domain)) { + $expiration = NULL; + db_account_activate($user, $domain); + db_alias_add($phone, $user, $domain); + if (USE_IN_APP_PURCHASES) { + $expiration = get_trial_expiration_date(); + db_inapp_add_account($user, $domain, $expiration); + } + + if (CUSTOM_HOOKS) { + hook_on_account_activated($user, $domain, $expiration); + } + } + + $ha1 = db_account_get_password($user, $domain, $algo); + return $ha1; +} + +// args = [username, email, [hash], useragent, [domain], [algo]] +function xmlrpc_create_email_account($method, $args) { + $user = $args[0]; + $email = $args[1]; + $domain = get_domain($args[4]); + $algo = $args[5]; + + mylog("[XMLRPC] xmlrpc_create_account(" . $user . ", " . $domain . ", " . $email . ")"); + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } else if (db_account_is_existing($user, $domain)) { + mylog("[XMLRPC] account already in use"); + return "ERROR_ACCOUNT_ALREADY_IN_USE"; + } + + if (!check_parameter($email, "email")) { + return "ERROR_EMAIL_PARAMETER_NOT_FOUND"; + } else if (db_account_is_email_in_use($email) && !ALLOW_SAME_EMAILS_ON_MULTILPLE_ACCOUNTS) { + mylog("[XMLRPC] email already in use"); + return "ERROR_EMAIL_ALREADY_IN_USE"; + } + if (!check_algo($algo)) { + return "ERROR_ALGO_NOT_SUPPORTED"; + } + + if (GENERATE_PASSWORD_ENABLED) { + $hashed_password = hash_password($user, generate_password(), $domain, $algo); + } else { + $hashed_password = $args[2]; + } + + $user_agent = $args[3]; + $key = uniqid(); + + mylog("[XMLRPC] Create account " . $user); + db_account_create($user, $hashed_password, $domain, $email, $key, getIp(), $user_agent, $algo); + + if (CUSTOM_HOOKS) { + hook_on_account_created($user, $domain, $hashed_password, $email); + } + + if (SEND_ACTIVATION_EMAIL) { + send_email_with_activation_link($email, $key); + } else if (AUTO_ACTIVATE_ACCOUNT) { + db_account_activate($user, $domain); + if (USE_IN_APP_PURCHASES) { + $expiration = get_trial_expiration_date(); + db_inapp_add_account($user, $domain, $expiration); + } + } + + return "OK"; +} + +// args = [phone, [username], [password], useragent, [domain], [lang], [algo]] +function xmlrpc_create_phone_account($method, $args) { + $phone = $args[0]; + $user = $args[1]; + $hashed_password = $args[2]; + $domain = get_domain($args[4]); + $lang = get_lang($args[5]); + $algo = $args[6]; + + mylog("[XMLRPC] xmlrpc_create_phone_account(" . $phone . ", " . $domain . ", " . $user . ")"); + + if (!check_parameter($phone, "phone")) { + return "ERROR_PHONE_PARAMETER_NOT_FOUND"; + } else if (!startswith($phone, "+")) { + mylog("[ERROR] Phone doesn't start by +"); + return "ERROR_PHONE_ISNT_E164"; + } + + if (!check_parameter($user)) { + $user = $phone; + } + + if (!check_algo($algo)) { + return "ERROR_ALGO_NOT_SUPPORTED"; + } + + $recover_params = array( + 0 => $phone, + 1 => $domain, + 2 => $lang, + ); + if (db_account_is_existing($user, $domain)) { + if (RECOVER_ACCOUNT_IF_EXISTS) { + $recovered_user = xmlrpc_recover_phone_account($method, $recover_params); + if ($recovered_user == $user) return "OK"; + return "ERROR_CANT_RECOVER_ACCOUNT"; + } else { + return "ERROR_ACCOUNT_ALREADY_IN_USE"; + } + } else if (db_alias_is_in_use($phone, $domain)) { + if (RECOVER_ACCOUNT_IF_EXISTS) { + $recovered_user = xmlrpc_recover_phone_account($method, $recover_params); + if ($recovered_user == $user) return "OK"; + return "ERROR_CANT_RECOVER_ACCOUNT"; + } else { + return "ERROR_ALIAS_ALREADY_IN_USE"; + } + } + + $password = $hashed_password; + if (!check_parameter($hashed_password, "hashed password")) { + $password = generate_password(); + $hashed_password = hash_password($user, $password, $domain, $algo); + } + + $user_agent = $args[3]; + $key = generate_4_digits_code(); + + db_account_create($user, $hashed_password, $domain, NULL, $key, getIp(), $user_agent, $algo); + + if (CUSTOM_HOOKS) { + hook_on_account_created($user, $domain, $hashed_password, NULL); + } + + if (SEND_ACTIVATION_SMS) { + if (!SMS_API_ENABLED) { + // This is a hack to allow testing without sending SMS + return "OK"; + } + $ok = send_sms($phone, $key, $lang); + return $ok; + } else if (AUTO_ACTIVATE_ACCOUNT) { + db_account_activate($user, $domain); + if (USE_IN_APP_PURCHASES) { + $expiration = get_trial_expiration_date(); + db_inapp_add_account($user, $domain, $expiration); + } + } + + return "OK"; +} + +// args = [user, pwd, [domain], [algo]] +// /!\ This method must be used for tests purposes only /!\ +function xmlrpc_get_confirmation_key($method, $args) { + $user = $args[0]; + $password = $args[1]; + $domain = get_domain($args[2]); + $algo = $args[3]; + + mylog("[XMLRPC] xmlrpc_get_confirmation_key(" . $user . ", " . $domain . ")"); + + if (!check_parameter($user)) { + mylog("[ERROR] Username parameter not found"); + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } else if (!ALLOW_TEST_ACCOUNTS) { + mylog("[ERROR] Non test account unauthorized"); + return "ERROR_NON_TEST_ACCOUNTS_UNAUTHORIZED"; + } + + if (!check_algo($algo)) { + mylog("[ERROR] Algo not supported"); + return "ERROR_ALGO_NOT_SUPPORTED"; + } + + if (!db_account_is_existing($user, $domain)) { + mylog("[ERROR] User account " . $user . " / " . $domain . " doesn't exist"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } else { + $hashed_password = hash_password($user, $password, $domain, $algo); + $db_hashed_password = db_account_get_password($user, $domain, $algo); + if (strcmp($hashed_password, $db_hashed_password) != 0 and strcmp($password, $db_hashed_password) != 0) { + mylog("[ERROR] Password doesn't match"); + return "ERROR_PASSWORD_DOESNT_MATCH"; + } + } + + $key = db_account_get_confirmation_key($user, $domain); + mylog("[XMLRPC] returning key = " . $key); + return $key; +} + +// args = [user, pwd, [domain], [algo]] +// /!\ This method must be used for tests purposes only /!\ +function xmlrpc_delete_account($method, $args) { + $user = $args[0]; + $password = $args[1]; + $domain = get_domain($args[2]); + $algo = $args[3]; + + mylog("[XMLRPC] xmlrpc_delete_account(" . $user . ", " . $domain . ")"); + if (!check_algo($algo)) { + return "ERROR_ALGO_NOT_SUPPORTED"; + } + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } else if (!ALLOW_TEST_ACCOUNTS) { + return "ERROR_NON_TEST_ACCOUNTS_UNAUTHORIZED"; + } + + if (!db_account_is_existing($user, $domain)) { + mylog("[ERROR] User account " . $user . " / " . $domain . " doesn't exist"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } else { + $hashed_password = hash_password($user, $password, $domain, $algo); + $db_hashed_password = db_account_get_password($user, $domain, $algo); + if (strcmp($hashed_password, $db_hashed_password) != 0 and strcmp($password, $db_hashed_password) != 0) { + mylog("[ERROR] Password doesn't match"); + return "ERROR_PASSWORD_DOESNT_MATCH"; + } + } + db_alias_delete($user, $domain); + db_account_delete($user, $domain); + return "OK"; +} + +// args = [phone, [domain]] +function xmlrpc_is_phone_number_used($method, $args) { + $phone = $args[0]; + $domain = get_domain($args[1]); + + mylog("[XMLRPC] xmlrpc_is_phone_number_used(" . $phone . ")"); + + if (!check_parameter($phone, "phone")) { + return "ERROR_PHONE_PARAMETER_NOT_FOUND"; + } else if (!startswith($phone, "+")) { + return "ERROR_PHONE_ISNT_E164"; + } + + if (db_account_is_existing($phone, $domain)) { + return "OK_ACCOUNT"; + } else if (db_alias_is_in_use($phone, $domain)) { + return "OK_ALIAS"; + } + + return "NOK"; +} + +// args = [username, [domain]] +function xmlrpc_get_phone_number_for_account($method, $args) { + $user = $args[0]; + $domain = get_domain($args[1]); + + mylog("[XMLRPC] xmlrpc_get_phone_number_for_account(" . $user . ")"); + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + + if (!db_account_is_existing($user, $domain)) { + if (db_alias_is_in_use($user, $domain)) { + return $user; + } + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } + + $phone = db_alias_get_reverse($user, $domain); + if ($phone == NULL) { + return "ERROR_ALIAS_DOESNT_EXIST"; + } + + if (RECOVER_ACCOUNT_IF_EXISTS) { + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } + + return $phone; +} + +// args = [phone, [domain], [lang]] +function xmlrpc_recover_phone_account($method, $args) { + $phone = $args[0]; + $domain = get_domain($args[1]); + $lang = get_lang($args[2]); + + mylog("[XMLRPC] xmlrpc_recover_phone_account(" . $phone . ")"); + + if (!check_parameter($phone, "phone")) { + return "ERROR_PHONE_PARAMETER_NOT_FOUND"; + } else if (!startswith($phone, "+")) { + return "ERROR_PHONE_ISNT_E164"; + } + + $user = NULL; + if (db_alias_is_in_use($phone, $domain)) { + $user = db_alias_get($phone, $domain); + } + if ($user != NULL || db_account_is_existing($phone, $domain)) { + if ($user == NULL) { + $user = $phone; + } + if (SEND_ACTIVATION_SMS) { + $key = generate_4_digits_code(); + db_account_update_confirmation_key($user, $domain, $key); + + $ok = send_sms($phone, $key, $lang); + if ($ok != "OK") { + return $ok; + } + } + return $user; + } + return "ERROR_ACCOUNT_DOESNT_EXIST"; +} + +function xmlrpc_accounts_register_methods($server) { + //TODO FIXME remove this methods later as it's a security risk + // /!\ This methods must be used for tests purposes only /!\ + xmlrpc_server_register_method($server, 'get_confirmation_key', 'xmlrpc_get_confirmation_key');// args = [user, pwd, [domain]], return confirmation_key + xmlrpc_server_register_method($server, 'delete_account', 'xmlrpc_delete_account');// args = [user, pwd, [domain]] + + xmlrpc_server_register_method($server, 'is_account_used', 'xmlrpc_is_account_used');// args = [username, [domain]], return "OK" or "NOK" + xmlrpc_server_register_method($server, 'is_account_activated', 'xmlrpc_is_account_activated');// args = [username, [domain]], return "OK" or "NOK" + xmlrpc_server_register_method($server, 'is_phone_number_used', 'xmlrpc_is_phone_number_used');// args = [phone], return "OK_ACCOUNT", "OK_ALIAS" or "NOK" + xmlrpc_server_register_method($server, 'activate_phone_account', 'xmlrpc_activate_phone_account');// args = [phone, username, key, [domain]], return ha1_password + xmlrpc_server_register_method($server, 'create_phone_account', 'xmlrpc_create_phone_account');// args = [phone, [username], [password], useragent, [domain], [lang]], return "OK" + xmlrpc_server_register_method($server, 'activate_email_account', 'xmlrpc_activate_email_account');// args = [username, key, [domain]], return ha1_password + xmlrpc_server_register_method($server, 'create_email_account', 'xmlrpc_create_email_account');// args = [username, email, [hash], useragent, [domain]], return "OK" + xmlrpc_server_register_method($server, 'get_phone_number_for_account', 'xmlrpc_get_phone_number_for_account');// args = [username, [domain]], return a phone number or an error + xmlrpc_server_register_method($server, 'recover_phone_account', 'xmlrpc_recover_phone_account');// args = [phone, [domain], [lang]], return username + + xmlrpc_server_register_method($server, 'update_password', 'xmlrpc_update_password');// args = [username, old password, new password, [domain]], return "OK" + xmlrpc_server_register_method($server, 'update_hash', 'xmlrpc_update_hash');// args = [username, old hash, new hash, [domain]], return "OK" + xmlrpc_server_register_method($server, 'update_email', 'xmlrpc_update_email');// args = [username, password, new email, [domain]], return "OK" +} + +?> diff --git a/src/xmlrpc-aliases.php b/src/xmlrpc-aliases.php new file mode 100644 index 0000000..237587e --- /dev/null +++ b/src/xmlrpc-aliases.php @@ -0,0 +1,203 @@ + diff --git a/src/xmlrpc-compatibility.php b/src/xmlrpc-compatibility.php new file mode 100644 index 0000000..1accfaf --- /dev/null +++ b/src/xmlrpc-compatibility.php @@ -0,0 +1,75 @@ + \ No newline at end of file diff --git a/src/xmlrpc-devices.php b/src/xmlrpc-devices.php new file mode 100644 index 0000000..f85d9ef --- /dev/null +++ b/src/xmlrpc-devices.php @@ -0,0 +1,52 @@ + \ No newline at end of file diff --git a/src/xmlrpc-inapp.php b/src/xmlrpc-inapp.php new file mode 100644 index 0000000..cac9452 --- /dev/null +++ b/src/xmlrpc-inapp.php @@ -0,0 +1,476 @@ += 1; + linphonedb_clean($result); + linphonedb_close($conn); + return $is_account; +} + +function db_inapp_is_account_trial($user, $domain) { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT trial FROM " . INAPP_DB_TABLE . " WHERE account_id=(SELECT id FROM " . ACCOUNTS_DB_TABLE . " WHERE login='" . linphonedb_escape($conn, $user) . "' AND domain='" . linphonedb_escape($conn, $domain) . "')", $conn); + $row = linphonedb_fetch($result); + $is_account_trial = $row[0] == 1; + linphonedb_clean($result); + linphonedb_close($conn); + return $is_account_trial; +} + +function db_inapp_get_last_used_field($user, $domain) { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT last_used FROM " . INAPP_DB_TABLE . " WHERE account_id=(SELECT id FROM " . ACCOUNTS_DB_TABLE . " WHERE login='" . linphonedb_escape($conn, $user) . "' AND domain='" . linphonedb_escape($conn, $domain) . "')", $conn); + $row = linphonedb_fetch($result); + $last_used_field = $row[0]; + linphonedb_clean($result); + linphonedb_close($conn); + return $last_used_field; +} + +function db_inapp_get_expiration_date($user, $domain) { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT expire FROM " . INAPP_DB_TABLE . " WHERE account_id=(SELECT id FROM " . ACCOUNTS_DB_TABLE . " WHERE login='" . linphonedb_escape($conn, $user) . "' AND domain='" . linphonedb_escape($conn, $domain) . "')", $conn); + $row = linphonedb_fetch($result); + $expiration_date = $row[0]; + linphonedb_clean($result); + linphonedb_close($conn); + return $expiration_date; +} + +// Google/Android specifics + +// Get an access token to access Google APIs +function get_google_access_token() { + $ch = curl_init(GOOGLE_API_OAUTH_URL); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FAILONERROR, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/x-www-form-urlencoded' + )); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + 'client_id' => GOOGLE_PROJECT_ID, + 'client_secret' => GOOGLE_PROJECT_PASSWORD, + 'refresh_token' => GOOGLE_PROJECT_REFRESH_TOKEN, + 'grant_type' => "refresh_token", + ))); + $result = curl_exec($ch); + curl_close($ch); + + $json = json_decode($result, true); + $token = $json["access_token"]; + mylog("[GOOGLE] Access token is " . $token); + return $token; +} + +// Query Google for the expiration time given the transaction token as described here: https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/get +function get_expiration_for_android_token_and_subscription($token, $subscription) { + $google_access_token = get_google_access_token(); + $url = "https://www.googleapis.com/androidpublisher/v2/applications/" . ANDROID_PACKAGE . "/purchases/subscriptions/" . $subscription . "/tokens/" . $token . "?access_token=" . $google_access_token; + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + $result = curl_exec($ch); + curl_close($ch); + + $json = json_decode($result, true); + $expiration = $json["expiryTimeMillis"]; + mylog("[GOOGLE] expire timestamp for token = " . $token . " and product id = " . $subscription . " is " . $expiration); + return $expiration . ""; +} + +// Returns 1 if the payload/signature has been issued by Google. +function check_google_signature($payload, $signature) { + $certFile = fopen(ANDROID_PUB_KEY_PATH, "r"); + $cert = fread($certFile, 8192); + fclose($certFile); + $pubKeyId = openssl_get_publickey($cert); + + $ok = openssl_verify($payload, base64_decode($signature), $pubKeyId, OPENSSL_ALGO_SHA1); + mylog("[GOOGLE] signature verification result is " . $ok); + return $ok; +} + +// End of Google/Android specifics + +// Apple/iOS specifics + +function get_apple_receipt($payload) { + $ch = curl_init(APPLE_URL); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FAILONERROR, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array('receipt-data' => $payload, 'password' => APPLE_SECRET))); + $result = curl_exec($ch); + curl_close($ch); + + mylog("[APPLE] decoded receipt is " . $result); + $json = json_decode($result, true); + + $status_code = $json["status"]; + if ($status_code == 21007) { + mylog("[APPLE] Error 21007 found, sending receipt to sandbox instead of production"); + $ch = curl_init(APPLE_SANDBOX_URL); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FAILONERROR, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array('receipt-data' => $payload, 'password' => APPLE_SECRET))); + $result = curl_exec($ch); + curl_close($ch); + + mylog("[APPLE] decoded receipt is " . $result); + $json = json_decode($result, true); + } + + return $json; +} + +// Returns 1 if the payload/signature has been signed by Apple, else will return the error code as described here: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1 +function check_apple_signature($payload) { + $status = -1; + + $status = $payload["status"]; + mylog("[APPLE] Status in apple receipt is " . $status); + if ($status == 0) { + return 1; + } + + return $status; +} + +function parse_apple_receipt_get_expiration($user, $domain, $json) { + $last_used = db_inapp_get_last_used_field($user, $domain); + + $days = 0; + $receipt = $json["receipt"]; + $in_app = $receipt["in_app"]; + foreach($in_app as $item => $value) { + if (array_key_exists("original_purchase_date_ms", $value) and array_key_exists("product_id", $value)) { + $purchase_date = $value["original_purchase_date_ms"]; + $product_id = $value["product_id"]; + + if ($purchase_date > $last_used) { + $days_bought = 0; + if (endswith($product_id, "1_month")) { + $days_bought = 30; + } else if (endswith($product_id, "1_year")) { + $days_bought = 365; + } else { + mylog("[ERROR] Unknown duration for product ID " . $product_id); + continue; + } + + if (startswith($product_id, "test.")) { + mylog("[APPLE] Test mode detected, time accelerated (1 month => 1 minute)"); + $days_bought /= 43200; + } + + if ($days_bought > 0) { + $days = $days + $days_bought; + db_inapp_update_last_used_field($user, $domain, $purchase_date); + } + } + } + } + + if ($days <= 0) { + mylog("[WARN] [APPLE] Either no receipt or all receipts have already been consumed"); + return 0; + } + $millis = 86400000 * $days; + + $now = get_trial_expiration_date(); + $expiration = db_inapp_get_expiration_date($user, $domain); + + $max = max($now, $expiration); + $expiration_date = $max + $millis; + + mylog("[APPLE] Adding " . $days . " days to current expiration date (= " . $millis . " ms). New expiration date is " . $expiration_date); + + return $expiration_date; +} + +// End of Apple/iOS specifics + +// XMLRPC methods + +// Returns 1 if the payload/signature has been signed by either Google or Apple, depending on $os. +function check_signature($os, $payload, $signature) { + if (strcmp($os, "google") == 0) { + return check_google_signature($payload, $signature); + } elseif (strcmp($os, "apple") == 0) { + return check_apple_signature($payload); + } + return -2; +} + +// args = [username, ha1, [domain]] +function xmlrpc_is_account_trial($method, $args) { + $user = $args[0]; + $password = $args[1]; + $domain = get_domain($args[2]); + + mylog("[XMLRPC] xmlrpc_is_account_trial(" . $user . ", " . $domain . ")"); + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + if (!db_account_is_existing($user, $domain)) { + mylog("[ERROR] User account " . $user . " / " . $domain . " doesn't exist"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } else { + $hashed_password = hash_password($user, $password, $domain); + $db_hashed_password = db_account_get_password($user, $domain); + if (strcmp($hashed_password, $db_hashed_password) != 0 and strcmp($password, $db_hashed_password) != 0) { + mylog("[ERROR] Password doesn't match"); + return "ERROR_PASSWORD_DOESNT_MATCH"; + } + } + + if (!USE_IN_APP_PURCHASES || !db_inapp_is_account($user, $domain)) { + return "ERROR_NO_EXPIRATION"; + } + + if (db_inapp_is_account_trial($user, $domain)) { + return "OK"; + } else { + return "NOK"; + } +} + +// args = [username, ha1, [domain]] +function xmlrpc_is_account_expired($method, $args) { + $user = $args[0]; + $password = $args[1]; + $domain = get_domain($args[2]); + + mylog("[XMLRPC] xmlrpc_is_account_expired(" . $user . ", " . $domain . ")"); + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + if (!db_account_is_existing($user, $domain)) { + mylog("[ERROR] User account " . $user . " / " . $domain . " doesn't exist"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } else { + $hashed_password = hash_password($user, $password, $domain); + $db_hashed_password = db_account_get_password($user, $domain); + if (strcmp($hashed_password, $db_hashed_password) != 0 and strcmp($password, $db_hashed_password) != 0) { + mylog("[ERROR] Password doesn't match"); + return "ERROR_PASSWORD_DOESNT_MATCH"; + } + } + + if (!USE_IN_APP_PURCHASES || !db_inapp_is_account($user, $domain)) { + return "ERROR_NO_EXPIRATION"; + } + + $expiration = db_inapp_get_expiration_date($user, $domain); + $now_date = new DateTime('now'); + $now = $now_date->getTimestamp() * 1000; + if ($now > $expiration) { + return "OK"; + } else { + return "NOK"; + } +} + +// args = [payload, signature] +function xmlrpc_check_payload_signature($method, $args) { + $payload = $args[0]; + $signature = $args[1]; + + mylog("[XMLRPC] xmlrpc_check_payload_signature(payload, signature)"); + + $result = 0; + $os = "google"; + $payloadJson = $payload; + if ($signature == "") { + $payloadJson = get_apple_receipt($payload); + $os = "apple"; + } + + $result = check_signature($os, $payloadJson, $signature); + + if ($result == 1) { + return "OK"; + } + return "NOK"; +} + +// args = [username, ha1, [domain], payload, signature=""] +function xmlrpc_update_expiration_date($method, $args) { + $user = $args[0]; + $password = $args[1]; + $payload = $args[3]; + $signature = $args[4]; + $domain = get_domain($args[2]); + + mylog("[XMLRPC] xmlrpc_update_expiration_date(" . $user . ", " . $domain . ", payload, signature)"); + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + if (!db_account_is_existing($user, $domain)) { + mylog("[ERROR] User account " . $user . " / " . $domain . " doesn't exist"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } else if (!db_account_is_activated($user, $domain)) { + mylog("[ERROR] User account " . $user . " / " . $domain . " isn't activated"); + return "ERROR_ACCOUNT_NOT_ACTIVATED"; + } else { + $hashed_password = hash_password($user, $password, $domain); + $db_hashed_password = db_account_get_password($user, $domain); + if (strcmp($hashed_password, $db_hashed_password) != 0 and strcmp($password, $db_hashed_password) != 0) { + mylog("[ERROR] Password doesn't match"); + return "ERROR_PASSWORD_DOESNT_MATCH"; + } + } + + if (!USE_IN_APP_PURCHASES || !db_inapp_is_account($user, $domain)) { + return "ERROR_NO_EXPIRATION"; + } + + $result = 0; + $os = "google"; + $payloadJson = $payload; + if ($signature == "") { + $payloadJson = get_apple_receipt($payload); + $os = "apple"; + } + $result = check_signature($os, $payloadJson, $signature); + + if ($result == 1) { + $expiration_date = 0; + if (strcmp($os,"google") == 0) { + $json = json_decode($payload, true); + $token = $json["purchaseToken"]; + $subscription = $json["productId"]; + $expiration_date = get_expiration_for_android_token_and_subscription($token, $subscription); + } else if (strcmp($os, "apple") == 0) { + $expiration_date = parse_apple_receipt_get_expiration($user, $domain, $payloadJson); + } + + if ($expiration_date >= 0) { + if ($expiration_date > 0) { + db_inapp_update_trial($user, $domain, 0); + db_inapp_update_expiration_date($user, $domain, $expiration_date); + + if (CUSTOM_HOOKS) { + hook_on_expiration_date_updated($user, $domain, $expiration_date, $payloadJson, $os); + } + return $expiration_date . ""; + } else { + return db_inapp_get_expiration_date($user, $domain) . ""; + } + } else { + mylog("[ERROR] Expiration is " . $expiration_date); + } + } + mylog("[ERROR] Couldn't verify signature of payload..."); + return "ERROR_SIGNATURE_VERIFICATION_FAILED"; +} + +// args = [username, ha1, [domain]] +function xmlrpc_get_account_expiration($method, $args) { + $user = $args[0]; + $password = $args[1]; + $domain = get_domain($args[2]); + + mylog("[XMLRPC] xmlrpc_get_account_expiration(" . $user . ")"); + + if (!check_parameter($user)) { + return "ERROR_USERNAME_PARAMETER_NOT_FOUND"; + } + + if (!db_account_is_existing($user, $domain)) { + mylog("[ERROR] User account " . $user . " doesn't exist"); + return "ERROR_ACCOUNT_DOESNT_EXIST"; + } else if (!db_account_is_activated($user, $domain)) { + mylog("[ERROR] User account " . $user . " isn't activated"); + return "ERROR_ACCOUNT_NOT_ACTIVATED"; + } else { + $hashed_password = hash_password($user, $password, $domain); + $db_hashed_password = db_account_get_password($user, $domain); + if (strcmp($hashed_password, $db_hashed_password) != 0 and strcmp($password, $db_hashed_password) != 0) { + mylog("[ERROR] Password doesn't match"); + return "ERROR_PASSWORD_DOESNT_MATCH"; + } + } + + if (!USE_IN_APP_PURCHASES || !db_inapp_is_account($user, $domain)) { + return "ERROR_NO_EXPIRATION"; + } + + $expiration = db_inapp_get_expiration_date($user, $domain); + return $expiration . ""; +} + +function xmlrpc_inapp_register_methods($server) { + xmlrpc_server_register_method($server, 'is_account_trial', 'xmlrpc_is_account_trial');// args = [username, ha1, [domain]] + xmlrpc_server_register_method($server, 'is_account_expired', 'xmlrpc_is_account_expired');// args = [username, ha1, [domain]] + xmlrpc_server_register_method($server, 'get_account_expiration', 'xmlrpc_get_account_expiration');// args = [username, ha1, [domain]] + xmlrpc_server_register_method($server, 'update_expiration_date', 'xmlrpc_update_expiration_date');// args = [username, ha1, [domain], payload, [signature]] + xmlrpc_server_register_method($server, 'check_payload_signature', 'xmlrpc_check_payload_signature');// args = [payload, signature] +} + +?> \ No newline at end of file diff --git a/src/xmlrpc-sms.php b/src/xmlrpc-sms.php new file mode 100644 index 0000000..8ae7e9b --- /dev/null +++ b/src/xmlrpc-sms.php @@ -0,0 +1,208 @@ += 1; + linphonedb_clean($result); + linphonedb_close($conn); + return $already_sent; +} + +function db_insert_sms($phone, $time) { + $conn = linphonedb_connect(); + $result = linphonedb_query("INSERT INTO " . SMS_DB_TABLE . "(phone, last_sms, count) VALUES('" . linphonedb_escape($conn, $phone) . "', " . linphonedb_escape($conn, $time) . ", 1)", $conn); + linphonedb_clean($result); + linphonedb_close($conn); +} + +function db_get_sms_count($phone) { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT count FROM " . SMS_DB_TABLE . " WHERE phone='" . linphonedb_escape($conn, $phone) . "'", $conn); + $row = linphonedb_fetch($result); + $sms_count = $row[0]; + linphonedb_clean($result); + linphonedb_close($conn); + return $sms_count; +} + +function db_get_last_sms($phone) { + $conn = linphonedb_connect(); + $result = linphonedb_query("SELECT last_sms FROM " . SMS_DB_TABLE . " WHERE phone='" . linphonedb_escape($conn, $phone) . "'", $conn); + $row = linphonedb_fetch($result); + $last_sms = $row[0]; + linphonedb_clean($result); + linphonedb_close($conn); + return $last_sms; +} + +function db_update_sms($phone, $time, $count) { + $conn = linphonedb_connect(); + $result = linphonedb_query("UPDATE " . SMS_DB_TABLE . " SET last_sms=" . linphonedb_escape($conn, $time) . ", count=" . linphonedb_escape($conn, $count) . " WHERE phone='" . linphonedb_escape($conn, $phone) . "'", $conn); + linphonedb_clean($result); + linphonedb_close($conn); +} + +function db_delete_sms($phone) { + $conn = linphonedb_connect(); + $result = linphonedb_query("DELETE FROM " . SMS_DB_TABLE . " WHERE phone='" . linphonedb_escape($conn, $phone) . "'", $conn); + linphonedb_clean($result); + linphonedb_close($conn); +} + +function send_sms_ovh($phone, $key, $lang) { + if (!SMS_API_ENABLED) { + mylog("[WARN][SMS] SMS API disabled"); + return "WARNING_SMS_API_DISABLED"; + } + + $sms = new SmsApi(SMS_OVH_API_KEY, SMS_OVH_API_SECRET, SMS_OVH_ENDPOINT, SMS_OVH_CONSUMER_KEY); + $accounts = $sms->getAccounts(); + $sms->setAccount($accounts[0]); + if (SMS_USE_SENDER) { + $senders = $sms->getSenders(); + + /* The account must be validated in the OVH interface and by OVH itself */ + if (count($senders) == 0) { + mylog("[WARN][SMS] No sender found, creating one " . SMS_OVH_SENDER . " / " . SMS_OVH_REASON . " : " . SMS_OVH_DESC); + $sms->addSender(SMS_OVH_SENDER, SMS_OVH_REASON, SMS_OVH_DESC); + $senders = $sms->getSenders(); + } + } + + $message = $sms->createMessage(); + if (SMS_USE_SENDER && count($senders) > 0) { + foreach ($senders as $sender) { + if ($sender == SMS_OVH_SENDER) { + if ($sms->checkSender($sender)) { + // Check if sender exists and is valid, otherwise it will create an exception and sms won't be sent + mylog("[SMS] Found valid sender " . $sender . ", using it"); + $message->setSender($sender); + break; + } else { + mylog("[ERROR][SMS] Found sender " . $sender . " but it is not valid"); + } + } + } + } + $message->addReceiver($phone); + $message->setIsMarketing(FALSE); + + $text = get_sms_string_for_lang($lang); + $text = str_replace("#CODE#", $key, $text); + $result = $message->send($text); + + $credits_removed = $result['totalCreditsRemoved']; + mylog("[SMS] " . $credits_removed . " credit removed"); + $invalid_receiver = $result['invalidReceivers']; + $valid_receiver = $result['validReceivers']; + if (count($invalid_receiver) > 0) { + mylog("[ERROR][SMS] phone number " . $phone . " seems invalid"); + } else if (count($valid_receiver) > 0) { + mylog("[SMS] " . $text . " sent to " . $phone); + } else { + mylog("[WARN][SMS] Both valid and invalid receiver lists are empty..."); + } +} + +function send_sms_legacy($phone, $password) { + if (!SMS_API_ENABLED) { + mylog("[WARN][SMS] SMS API disabled"); + return "WARNING_SMS_API_DISABLED"; + } + + $url = SMS_API_URL; + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FAILONERROR, false); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($ch, CURLOPT_USERPWD, SMS_API_USERNAME . ":" . SMS_API_PASSWORD); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/x-www-form-urlencoded' + )); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + 'mobile' => $phone, + 'password' => $password, + ))); + $result = curl_exec($ch); + mylog("[SMS] SMS confirmation sent to " . $phone . " using password " . $password . ", request result is " . $result); + curl_close($ch); +} + +function send_sms($phone, $key, $lang, $password) { + if (!SMS_API_ENABLED) { + mylog("[WARN][SMS] SMS API disabled"); + return "WARNING_SMS_API_DISABLED"; + } + + if (startswith($phone, TESTS_PHONE_PREFIX)) { + mylog("[ERROR][SMS] Not sending sms to fake number used for tests purposes: " . $phone); + return "ERROR_NON_TEST_ACCOUNTS_UNAUTHORIZED"; + } + + $now_date = new DateTime('now'); + $now = $now_date->getTimestamp() * 1000; + + if (db_has_sms_already_been_sent_to($phone)) { + $count = db_get_sms_count($phone); + $time = db_get_last_sms($phone); + $diff = $now - $time; + if ($count >= SMS_COUNT_LIMIT_IN_PERIOD and $diff < SMS_TIME_PERIOD) { + mylog("[ERROR][SMS] Last sms was sent at " . $time . ", time elapsed since then is " . $diff . "ms which is less than the configured time period " . SMS_TIME_PERIOD); + return "ERROR_MAX_SMS_EXCEEDED"; + } else if ($diff >= SMS_TIME_PERIOD) { + db_update_sms($phone, $now, 1); + } else { + $count = $count + 1; + db_update_sms($phone, $now, $count); + } + } else { + db_insert_sms($phone, $now); + } + + + if (SMS_OVH_API_KEY != NULL && SMS_OVH_API_KEY != "" && SMS_OVH_API_SECRET != NULL && SMS_OVH_API_SECRET != "" && SMS_OVH_CONSUMER_KEY != NULL && SMS_OVH_CONSUMER_KEY != "" && SMS_OVH_ENDPOINT != NULL && SMS_OVH_ENDPOINT != "") { + try { + send_sms_ovh($phone, $key, $lang); + return "OK"; + } catch (Exception $e) { + mylog("[ERROR][OVH-SMS] Exception: " . $e->getMessage()); + } + } else if (SMS_API_URL != NULL && SMS_API_URL != "" && SMS_API_USERNAME != NULL && SMS_API_USERNAME != "" && SMS_API_PASSWORD != NULL && SMS_API_PASSWORD != "") { + send_sms_legacy($phone, $password); + return "OK"; + } else { + mylog("[ERROR][SMS] No SMS API configured, discarding sms..."); + return "OK"; + } + return "ERROR_CANT_SEND_SMS"; +} + +?> diff --git a/src/xmlrpc.conf b/src/xmlrpc.conf new file mode 100644 index 0000000..df683ac --- /dev/null +++ b/src/xmlrpc.conf @@ -0,0 +1,466 @@ +?;:[]{}\| + */ +define("GENERATED_PASSWORD_CHARACTERS", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789``-=~!@#$%^&*()_+,./<>?;:[]{}\|"); + +/* + * The length of the passwords that will be generated. + * + * Default value: 8 + */ +define("GENERATED_PASSWORD_LENGTH", 8); + +/* + * If set to True, a created account will automatically be activated and it's expiration date set to now + TRIAL_DURATION_DAYS, + * otherwise expiration date for trial will be set when account is activated via a different xml rpc call. + */ +define('AUTO_ACTIVATE_ACCOUNT', False); + +/* + * If set to True, a created account will be flagged as trial with an expiration date set in TRIAL_DURATION_DAYS days in the future. + */ +define ('USE_IN_APP_PURCHASES', False); + +/* + * This value determines the number of days for trial starting when the account will be activated. + * + * Default value: 365 + */ +define('TRIAL_DURATION_DAYS', 365); + +/* + * This values sets the default timezone to use when computing the expiration dates when using inapp purchases + * + * Default value: 'Europe/Paris' + */ +define('DEFAULT_TIMEZONE', 'Europe/Paris'); + +/* + * Send an email to activate the account when it is created. + */ +define('SEND_ACTIVATION_EMAIL', True); + +/* + * Send a sms to activate the phone account when it is created. + */ +define('SEND_ACTIVATION_SMS', True); + +/* + * If false, creating an account with an email that is already used for another account will trigger an error + */ +define('ALLOW_SAME_EMAILS_ON_MULTILPLE_ACCOUNTS', True); + +/* + * If true, when an account creation request is received for an existing number, assumes recover procedure + */ +define('RECOVER_ACCOUNT_IF_EXISTS', False); + +/* + * If true, more features are available for test purposes + * + * Default value: False + */ +define('ALLOW_TEST_ACCOUNTS', False); + +/* ### Logs configuration ### */ + +/* + * Whever or not to log each function called. + * Passwords are never logged. + * + * Default value: True + */ +define("LOGS_ENABLED", True); + +/* + * The file in which to log XMLRPC calls. + * + * Default value: xmlrpc.log + */ +define("LOG_FILE", "xmlrpc.log"); + +/* ### Authentication configuration ### */ + +/* + * Realm used for digest authentication + * + * Default value: sip.example.org + */ +define("AUTH_REALM", "sip.example.org"); + +/* ### Database configuration ### */ + +/* + * If set to True, use Mysqli API instead of Mysql one. + * + * Default value: False + */ +define("USE_MYSQLI", False); + +/* + * Whether or not use use persistent connections. + * ! It only works if USE_MYSQLI is set to True ! + * See http://php.net/manual/fr/function.mysqli-connect.php and http://php.net/manual/fr/mysqli.quickstart.connections.php + * + * Default value: False + */ +define("USE_PERSISTENT_CONNECTIONS", False); + +/* + * The host on which the database is located. + * + * Default value: localhost + */ +define("DB_HOST", "localhost"); + +/* + * The database username. + * + * Default value: flexisip + */ +define("DB_USER", "flexisip"); + +/* + * The database user's password. + * + * Default value: + */ +define("DB_PASSWORD", ""); + +/* + * The name of the database. + * + * Default value: flexisip + */ +define("DB_NAME", "flexisip"); + +/* + * The name of the accounts table. + * + * Default value: accounts + */ +define("ACCOUNTS_DB_TABLE", "accounts"); + +/* + * The name of the accounts_algo table. + * + * Default value: accounts_algo + */ +define("ACCOUNTS_ALGO_DB_TABLE", "accounts_algo"); + +/* + * The name of the inapp table. + * It is used to store informations about in-app purchases, accounts expiration, etc... + * + * Default value: inapp_purchases + */ +define("INAPP_DB_TABLE", "inapp_purchases"); + +/* + * The name of the aliases table. + * It is used to store links between an alias (phone number, facebook id, google email, ...) and a SIP address + * + * Default value: aliases + */ +define("ALIAS_DB_TABLE", "aliases"); + +/* + * The name of the devices table. + * It is used to store hardware information about devices running linphone + * + * Default value: devices + */ +define("DEVICES_DB_TABLE", "devices"); + +/* + * The name of the sms table. + * It is used to keep track of sent SMS + * + * Default value: sms + */ +define("SMS_DB_TABLE", "sms"); + +/* + * The delay in minutes before test account expiration. + * It is used to delete old test accounts from database; + * + * Default value: 180 + */ +define("EXPIRATION_DELAY", 180); + +/* ### Apple/ioS configuration ### */ + +/* + * The URL to use to validate an Apple in app purchase receipts. + * + * Default value: https://buy.itunes.apple.com/verifyReceipt + */ +define("APPLE_URL", "https://buy.itunes.apple.com/verifyReceipt"); + +/* + * The URL to use to validate an Apple in app purchase receipts while app is in development. + * + * Default value: https://buy.itunes.apple.com/verifyReceipt + */ +define("APPLE_SANDBOX_URL", "https://sandbox.itunes.apple.com/verifyReceipt"); + +/* + * The shared secret for your application. + * Used to validate in app purchase receipts. + * + * Default value: + */ +define("APPLE_SECRET", ""); + +/* ### Google/Android configuration ### */ + +/* + * The package name of your Android application. + * Used to validate in app purchase receipts. + * + * Default value: org.linphone + */ +define("ANDROID_PACKAGE", "org.linphone"); + +/* + * The path to the public key generated by the Android Play Store. + * See the documentation to know how to get it. + * + * Default value: google.pem + */ +define("ANDROID_PUB_KEY_PATH", "google.pem"); + +/* + * The URL to use to get the authentication token to make calls to Google API server. + * Used to validate in app purchase receipts. + * + * Default value: https://accounts.google.com/o/oauth2/token + */ +define("GOOGLE_API_OAUTH_URL", "https://accounts.google.com/o/oauth2/token"); + +/* + * The project ID with the access to the Android Developer Console API + * Used to validate in app purchase receipts. + * + * Default value: + */ +define("GOOGLE_PROJECT_ID", ""); + +/* + * The previous project ID's password + * Used to validate in app purchase receipts. + * + * Default value: + */ +define("GOOGLE_PROJECT_PASSWORD", ""); + +/* + * The refresh token generated by the Google Developer server. + * See documentation to know how to get it. + * Used to validate in app purchase receipts. + * + * Default value: + */ +define("GOOGLE_PROJECT_REFRESH_TOKEN", ""); + +/* ### Email configuration ### */ + +/* + * Whever or not enable the send email feature. + * Used to send link to generate random password if user forgot it, or the newly generated email once the link has been clicked. + * + * Default value: False + */ +define("EMAIL_ENABLED", False); + +/* + * The website address to display in the email header. + * + * Default value: https://linphone.org + */ +define("EMAIL_SITE", "https://linphone.org"); + +/* + * The link to open when click on activation + * + * Default value: www.linphone.org + */ +define("EMAIL_ACTIVATION_LINK", "www.linphone.org"); + +/* + * The FROM address to set in the email header. + * + * Default value: no.reply@linphone.org + */ +define("EMAIL_FROM_ADDR", "no.reply@linphone.org"); + +/* + * The FROM display name to set in the email header. + * + * Default value: No reply at Linphone.org + */ +define("EMAIL_FROM_NAME", "No reply at Linphone.org"); + +/* + * The subject of the activation account email. + */ +define("EMAIL_ACTIVATION_SUBJECT", "Start your sip.linphone.org service"); + +/* + * The body (as text) of the activation account email. + */ +define("EMAIL_ACTIVATION_BODY", "Hello,\nActivation pending for using your Linphone account.\nPlease use the link bellow to activate your account :\n\n%link%\n\nRegards,\nThe Linphone team.\n"); + +/* + * The body (as html) of the activation account email. + */ +define("EMAIL_ACTIVATION_BODY_HTML", 'Start your sip.linphone.org service

Hello,

Activation pending for using your Linphone account.
Please use the link bellow to activate your account :

%link%

 

Regards,
The Linphone team.

'); + + +/* ### SMS API configuration ### */ + +/* + * Whever or not enable the send SMS feature. + * Used to verify phone number when used as SIP username. + * + * Default value: False + */ +define("SMS_API_ENABLED", False); + +/* + * The application key for OVH SMS platform + * + * Default value: + */ +define("SMS_OVH_API_KEY", ""); + +/* + * The application secret for OVH SMS platform + * + * Default value: + */ +define("SMS_OVH_API_SECRET", ""); + +/* + * The consumer key for OVH SMS platform + * + * Default value: + */ +define("SMS_OVH_CONSUMER_KEY", ""); + +/* + * The sender alias for OVH SMS + * + * Default value: "Linphone" + */ +define("SMS_OVH_SENDER", "Linphone"); + +/* + * Whever or not to use a sender to send the SMS. + * When using sender you can customize the name of the sender, otherwise it will be a phone number. + * To disable for clients using our own OVH SMS account. + * + * Default value: True + */ +define("SMS_USE_SENDER", True); + +/* + * The sender reason for OVH SMS + * + * Default value: "created Linphone SMS sender" + */ +define("SMS_OVH_REASON", "created Linphone SMS sender"); + +/* + * The sender description for OVH SMS + * + * Default value: "Linphone SMS sender" + */ +define("SMS_OVH_DESC", "Linphone SMS sender"); + +/* + * The template to use to send SMS to the US + * + * Default value: "Your Linphone validation code is #CODE#" + */ +define("SMS_OVH_US_TEMPLATE", "Your Linphone validation code is #CODE#"); + +/* + * The OVH endpoint + * + * Default value: ovh-eu + */ +define("SMS_OVH_ENDPOINT", "ovh-eu"); + +/* + * The URL at which the SMS API is available. + * + * Default value: + */ +define("SMS_API_URL", ""); + +/* + * The username to authenticate to the SMS API if needed. + * + * Default value: + */ +define("SMS_API_USERNAME", ""); + +/* + * The username's password to authenticate to the SMS API if needed. + * + * Default value: + */ +define("SMS_API_PASSWORD", ""); + +/* + * The period of time (in milli seconds) over which we compute the number of sent sms + * + * Default value: 86400000 (24 hours) + */ +define("SMS_TIME_PERIOD", 86400000); + +/* + * The maximum number of allowed SMS to be sent over the period + * + * Default value: 3 + */ +define("SMS_COUNT_LIMIT_IN_PERIOD", 3); +?> diff --git a/src/xmlrpc.php b/src/xmlrpc.php new file mode 100644 index 0000000..ef04bf2 --- /dev/null +++ b/src/xmlrpc.php @@ -0,0 +1,135 @@ += 2) { + $arg1 = $argv[1]; + if (strcmp($arg1, "create_tables") == 0) { + db_create_inapp_table(); + db_create_alias_table(); + db_create_accounts_table(); + db_create_accounts_algo_table(); + db_create_devices_table(); + db_create_sms_table(); + echo "Tables have been created!\r\n"; + } else if (strcmp($arg1, "delete_tables") == 0) { + db_drop_inapp_table(); + db_drop_alias_table(); + db_drop_accounts_table(); + db_drop_accounts_algo_table(); + db_drop_devices_table(); + db_drop_sms_table(); + echo "Tables have been deleted!\r\n"; + } else if (strcmp($arg1, "create_algo_table") == 0) { + db_create_accounts_algo_table(); + echo "Algo table has been created!\r\n"; + } else if (strcmp($arg1, "drop_algo_table") == 0) { + db_drop_accounts_algo_table(); + echo "Algo table has been deleted!\r\n"; + } + exit; + } + mylog("[DEBUG] No request found"); + exit; +} + +// XMLRPC requests that do not require authentication +$unauthenticated_requests = array( + // account + 0 => 'create_email_account', + 1 => 'create_phone_account', + 2 => 'get_confirmation_key', + 3 => 'activate_email_account', + 4 => 'activate_phone_account', + 5 => 'recover_phone_account', + 6 => 'get_phone_number_for_account', + 7 => 'is_account_activated', + + // aliases + 8 => 'is_alias_used', + + // inapp + 9 => 'check_payload_signature', + + // misc + 10 => 'add_ec_calibration_result', + + // compatibility + 11 => 'create_account', + 12 => 'create_account_with_useragent', +); + +$headers = getallheaders(); +$xml = simplexml_load_string($request); +$request_type = $xml->methodName; + +// Get authentication header if there is one +if (!empty($headers['Auth-Digest'])) { + mylog("Auth-Digest = " . $headers['Auth-Digest']); + $authorization = $headers['Auth-Digest']; +} elseif (!empty($headers['Authorization'])) { + mylog("Authorization = " . $headers['Authorization']); + $authorization = $headers['Authorization']; +} + +// Authentication +if (in_array($request_type, $unauthenticated_requests) == FALSE) { + if (!empty($authorization)) { + $authentication_status = authenticate(AUTH_REALM); + + if ($authentication_status == TRUE) { + mylog("[DEBUG] Authentication successful for " . $headers['From']); + } else { + mylog("[DEBUG] Authentication failed for " . $headers['From']); + request_authentication(AUTH_REALM); + } + } else { + mylog("[DEBUG] No authentication header for " . $headers['From']); + request_authentication(AUTH_REALM); + } +} + +xmlrpc_accounts_register_methods($server); +xmlrpc_aliases_register_methods($server); +xmlrpc_inapp_register_methods($server); +xmlrpc_misc_register_methods($server); +xmlrpc_compatibility_register_methods($server); + +if ($request) { + $options = array('output_type' => 'xml', 'version' => 'auto'); + echo xmlrpc_server_call_method($server, $request, null, $options); +} + +?>