diff --git a/CHANGELOG b/CHANGELOG index cfab02465..15a4a4bcc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -33,6 +33,7 @@ CHANGELOG Roundcube Webmail - Increase maximum size of contact jobtitle and department fields to 128 characters - Fix missing newline after the logged line when writing to stdout (#7418) - Elastic: Fix context menu (paste) on the recipient input (#7431) +- Add httpapi password change driver to connect to generic HTTP/HTTPS APIs RELEASE 1.4.6 ------------- diff --git a/plugins/password/README b/plugins/password/README index 43f0e3494..a8af64845 100644 --- a/plugins/password/README +++ b/plugins/password/README @@ -49,6 +49,7 @@ 2.1.21. Kpasswd 2.1.22. Modoboa 2.1.23. LDAP - Password Modify Extended Operation (ldap_exop) + 2.1.24. HTTP-API 2.2. Password Strength Drivers 2.2.1. Zxcvbn 2.2.2. Have I been pwned? (pwned) @@ -385,7 +386,24 @@ Modified version of ldap_simple. Password is changed using ldap_exop_passwd operation. PHP >= 7.2 required. - + + 2.1.24. HTTP-API + ---------------- + + Driver to change the user's password via any HTTP/HTTPS POST/GET API in a + generic manner. It passes any of the username, curpass and newpass variables + to the configured POST or GET parameters at the configured URL. + + Any 4xx error code (except 404) is assumed to mean the password change failed. + 404 or any other non-2xx error code, or failure to connect, is assumed to be a + connection error and is reported as such. + + A 2xx response is generally assumed to mean the password changed succeeded. + Optionally, you can configure a regular expression to check the response as + well to verify this. + + curl is required by this driver. + 2.2. Password Strength Drivers ------------------------------ diff --git a/plugins/password/config.inc.php.dist b/plugins/password/config.inc.php.dist index 8d3ca7275..d0f0ad28f 100644 --- a/plugins/password/config.inc.php.dist +++ b/plugins/password/config.inc.php.dist @@ -492,3 +492,33 @@ $config['password_kpasswd_cmd'] = '/usr/bin/kpasswd'; // --------------------- // put token number from Modoboa server $config['password_modoboa_api_token'] = ''; + + +// HTTP-API Driver options +// --------------------- +// Changes password via any HTTP/HTTPS API. + +// Base URL of password change API. HTTPS recommended. +$config['password_httpapi_url'] = 'https://passwordserver.example.org'; + +// Method (also affects how vars are sent). Default: POST. +// GET is not recommended as passwords will appears in the remote webserver's access log +$config['password_httpapi_method'] = 'POST'; + +// Whether to verify SSL certificates. Default: true +$config['password_httpapi_ssl_verify'] = true; + +// GET or POST variable in which to put the username +$config['password_httpapi_var_user'] = 'user'; + +// GET or POST variable in which to put the current password +$config['password_httpapi_var_curpass'] = 'curpass'; + +// GET or POST variable in which to put the new password +$config['password_httpapi_var_newpass'] = 'newpass'; + +// HTTP codes other than 2xx are assumed to mean the password changed failed. +// Optionally, if set, this variable additionally checks the body of the 2xx response to +// confirm the change. It's a preg_match regular expression. +$config['password_httpapi_expect'] = '/^ok$/i'; + diff --git a/plugins/password/drivers/httpapi.php b/plugins/password/drivers/httpapi.php new file mode 100644 index 000000000..d5f75b0d1 --- /dev/null +++ b/plugins/password/drivers/httpapi.php @@ -0,0 +1,156 @@ +config->get('password_httpapi_url'); + $method = $rcmail->config->get('password_httpapi_method', 'POST'); + $ssl_verify = $rcmail->config->get('password_httpapi_ssl_verify', true); + $var_user = $rcmail->config->get('password_httpapi_var_user'); + $var_curpass = $rcmail->config->get('password_httpapi_var_curpass'); + $var_newpass = $rcmail->config->get('password_httpapi_var_newpass'); + $expect = $rcmail->config->get('password_httpapi_expect'); + + // Initialise curl options + + $curl = curl_init(); + + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT , 5); + if ($ssl_verify) { + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST , 2); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER , true); + } else { + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST , 0); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER , false); + } + + // Set the variables on the GET query string or POST vars + + $vars = array(); + if ($var_user) $vars[$var_user] = $username; + if ($var_curpass) $vars[$var_curpass] = $curpass; + if ($var_newpass) $vars[$var_newpass] = $newpass; + + if ($method == 'POST') { + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $vars); + } elseif ($method == 'GET') { + $query = http_build_query($vars); + if (parse_url($url, PHP_URL_QUERY)) { // Does URL already include a query string? + $url .= '&' . $query; + } + else { + $url .= '?' . $query; + } + } else { + rcube::raise_error(array( + 'code' => 600, + 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Password plugin: Invalid httpapi method" + ), true, false); + + return PASSWORD_CONNECT_ERROR; + } + + curl_setopt($curl, CURLOPT_URL , $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER , true); + + // Execute the query and check the results + + $result = curl_exec($curl); + + if ($result === false) { + rcube::raise_error(array( + 'code' => 600, + 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Password plugin: Failed to exec curl: " . curl_error($curl) + ), true, false); + + return PASSWORD_CONNECT_ERROR; + } + + $response_code = curl_getinfo($curl, CURLINFO_RESPONSE_CODE); + + curl_close($curl); + + // Non-2xx response codes mean the password change failed + + if ($response_code < 200 || $response_code > 299) { + rcube::raise_error(array( + 'code' => 600, + 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Password plugin: Unexpected response code ${response_code}: \"" . substr($result, 0, 1024) . "\"" + ), true, false); + + return ($response_code == 404 || $response_code > 499) ? PASSWORD_CONNECT_ERROR : PASSWORD_ERROR; + } + + // If configured, check the body of the response + + if ($expect && !preg_match($expect, $result)) { + rcube::raise_error(array( + 'code' => 600, + 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Password plugin: Expected success message was not received, got \"" . substr($result, 0, 1024) . "\"" + ), true, false); + + return PASSWORD_ERROR; + } + + return PASSWORD_SUCCESS; + } +} +