Roundcube Password (reset) Button for Virtualmin Setup Steps Ubuntu 24.04 - slight tweak

This is a small tweak regarding an older (now closed) post I made in which I slightly adapted LeeWells’ (a Roundcube forum poster) altered Virtualmin driver for Roundcube’s password (resetting) plugin to work with my current Virtualmin. This tweak allows for the plugin to work even with a self-signed certificate on :20000

I record it here because this is where I came to look for that version when I had forgotten the exact details (full instructions are in the old post):

https://forum.virtualmin.com/t/roundcube-password-reset-button-for-virtualmin-setup-steps-ubuntu-24-04/133164

Tweaks are to add:

        curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, false);

to each of the url visit stages (removing the requirement for a real SSL):

<?php

/**
 * Virtualmin Password Driver Fixed
 *
 *
 * Adapted and fixed by LeeWells
 * Tweaked to only expect "successful" by Ron E James DO (and allow self-signed SSL on port 20000)
 *
 * This script can run the server from ANY host, just replace localhost with the remote host
 * that virtualmin runs on.
 *
 * @version 1.0
 * @author LeeWells
 *
 * Copyright (C) 2005-2013, LeeWells
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
 */

class rcube_virtualmin_password
{
    function save($currpass, $newpass)
    {
        $rcmail   = rcmail::get_instance();
        $format   = $rcmail->config->get('password_virtualmin_format', 0);
        $username = $_SESSION['username'];
        $cook     = md5($username);

        $username = escapeshellcmd($username);
        $newpass  = escapeshellcmd($newpass);
        $curdir   = RCUBE_PLUGINS_DIR . 'password/helpers';

        // First cURL request
        $curl_handle = curl_init("https://localhost:20000/session_login.cgi?user=$username&pass=$currpass");

        curl_setopt($curl_handle, CURLOPT_COOKIEJAR, 'usermin.'.$cook.'.txt');
        curl_setopt($curl_handle, CURLOPT_COOKIEFILE, 'usermin.'.$cook.'.txt');
        curl_setopt($curl_handle, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0');
        curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl_handle, CURLOPT_HEADER, 1);
        curl_setopt($curl_handle, CURLOPT_STDERR, fopen('php://stdout', 'w'));
        curl_setopt($curl_handle, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, false);

        $output = curl_exec($curl_handle);
        curl_close($curl_handle);

        // Second cURL request
        $post_array2 = array('old' => $currpass, 'new1' => $newpass, 'new2' => $newpass);
        $curl_handle = curl_init("https://localhost:20000/changepass/changepass.cgi");
        curl_setopt($curl_handle, CURLOPT_POST, 3);
        curl_setopt($curl_handle, CURLOPT_POSTFIELDS, 'old='.$currpass.'&new1='.$newpass.'&new2='.$newpass);
        curl_setopt($curl_handle, CURLOPT_COOKIEJAR, 'usermin.'.$cook.'.txt');
        curl_setopt($curl_handle, CURLOPT_COOKIEFILE, 'usermin.'.$cook.'.txt');
        curl_setopt($curl_handle, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0');
        curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl_handle, CURLOPT_REFERER, 'https://localhost:20000/changepass/');
        curl_setopt($curl_handle, CURLOPT_HEADER, 1);
        curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, false);

        $content = curl_exec($curl_handle);

        // Check if cURL execution was successful
        if ($content === false) {
            rcube::raise_error(array(
                'code' => 601,
                'type' => 'php',
                'file' => __FILE__,
                'line' => __LINE__,
                'message' => 'cURL error: ' . curl_error($curl_handle)
            ), true, false);
            curl_close($curl_handle); // Close the handle before returning
            return PASSWORD_ERROR;
        }

        // Check if content is not empty
        if (empty($content)) {
            rcube::raise_error(array(
                'code' => 602,
                'type' => 'php',
                'file' => __FILE__,
                'line' => __LINE__,
                'message' => 'No content returned from cURL request.'
            ), true, false);
            curl_close($curl_handle); // Close the handle before returning
            return PASSWORD_ERROR;
        }

        curl_close($curl_handle); // Close the handle after all checks

        if (stripos($content, 'successful') !== false) {
            return PASSWORD_SUCCESS;
        } else {
            rcube::raise_error(array(
                'code' => 600,
                'type' => 'php',
                'file' => __FILE__,
                'line' => __LINE__,
                'message' => 'Password change failed. Response: ' . $content
            ), true, false);
            return PASSWORD_ERROR;
        }
    }
}

// Check if the script is run from the command line
if (php_sapi_name() == "cli") {
    if ($argc != 3) {
        echo "Usage: php script.php <current_password> <new_password>\n";
        exit(1);
    }

    $currpass = $argv[1];
    $newpass = $argv[2];

    $passwordChanger = new rcube_virtualmin_password();
    $result = $passwordChanger->save($currpass, $newpass);

    if ($result === PASSWORD_SUCCESS) {
        echo "Password changed successfully.\n";
    } else {
        echo "Failed to change password.\n";
    }
}
SYSTEM INFORMATION
OS type and version REQUIRED
Virtualmin version REQUIRED

Hello,

We have built-in password reset functionality in Webmin starting from the 2.402 release. You can enable it in the “Webmin ⇾ Webmin Configuration: Authentication” and “Webmin ⇾ Usermin Configuration: Authentication” pages using the “Allow forgotten password recovery” option.

This particular Roundcube solution regards resetting the password when logged into Roundcube (as described in the first part of the referenced post), not the “Forgot Password” button. It only changes that item to disable needing the SSL to :20000 to actually work when it makes its in-the-background visit to https://localhost:20000/changepass/changepass.cgi (and scrapes the answer it gives for “Success”).

The “Forgot Password” button really belonged in its own thread (but I was pushing my luck talking about Roundcube on the Virtualmin forum already by then). I mentioned it on May 6 (my original post got a lot of responses and started some conversation threads of its own). That solution (and I don’t know for sure, skimming the original post, that I even mentioned) relied on the 3rd-party “forgot password” plugin, which the official version you bring up works as a better replacement. That section regards adding a link here:

That sends you to something like this:

https://anothertest.com:20000/?return=https%3A%2F%2Fanothertest%2Ecom%3A20000%2

Which (now) takes you right to this:

Which works well.

Besides being able to alter the text and change the word “Username” to “Email address” in the form, one thing that I think would make this new one better would be if the “Forgot Password?” button that ordinarily gets you there (from your Usermin/Webmin :20000/:10000 prompt) were only visible once login had failed.

That’s the way the Roundcube plugin works, and having a button there (in my view) invites more hacker clicks (and conceivably could make users so used to seeing/ignoring it that they forget it and call when they forget their password anyway). Having the button only appear on a password failure on the other hand might reduce both.

Ron

1 Like

Real hackers don’t use buttons, and those who do aren’t dangerous.

Well spotted.