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

I might get flagged by the admins as this is more of a Roundcube thing than a Virtualmin thing but the virtualmin.php driver included with Roundcube’s (optional) Password button did not work for me out of the box and neither did the LeeWells version posted on their forum in 2013 (so I tweaked that one). Since I don’t have a Roundcube forum account, since this only applies to Virtualmin users, and since I am more likely to find it again here than anywhere else if I ever forget how it works, I am recording it here in case it helps someone else. (Also since I am not particularly qualified to be tweaking any such thing, someone might come along and say “Oh no, you musn’t do it that way, no no no”, which would be good for me to know.)

To make the Password button appear (I guess on a live site you might want to do this part last) once you have added Roundcube to your Virtualmin site (under a virtual’s “Manage Web Apps”) you should find a file here:

/home/[domain’s-home-folder]/public_html/config/config.inc.php

or here (depending if you installed roundcube in the root or the suggested folder):

/home/[domain’s-home-folder]/public_html/roundcube/config/config.inc.php

Edit this file to include: ‘password’ in its $config[‘plugins’] array, mine looks like this (the other two things were already there):

// List of active plugins (in plugins/ directory)
$config['plugins'] = [
    'archive',
    'zipdownload',
    'password',
];

Which gives you this:

It then needs to know which driver it should use to change passwords (Roundcube being a generic webmail client and not Virtualmin specific, obviously) which you set by copying or renaming this file to remove the “.inc” at the end:

/home/[domain’s-home-folder]/public_html/plugins/password/config.inc.php.inc

or here:

/home/[domain’s-home-folder]/public_html/roundcube/plugins/password/config.inc.php.inc

Edit the resulting /plugins/password/config.inc.php file’s $config[‘password_driver’] line thusly:

// Password Plugin options
// -----------------------
// A driver to use for password change. Default: "sql".
// See README file for list of supported driver names.
$config['password_driver'] = 'virtualmin';

(It will have defaulted to ‘sql’, type over that.)

Note that his adds confirmation of the current password to the form:

Then edit:

/home/[domain’s-home-folder]/public_html/plugins/password/drivers/virtualmin.php

or here:

/home/[domain’s-home-folder]/public_html/roundcube/plugins/password/drivers/virtualmin.php

Replacing its text with the following, but note that you must replace the instances of “localhost” with your-mail-domain (whatever you would put here to use virtualmin’s webmail: “https://your-mail-domain:20000”):

<?php

/**
 * Virtualmin Password Driver Fixed
 *
 *
 * Adapted and fixed by LeeWells
 * Tweaked to only expect "successful" by Ron E James DO
 *
 * 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';

                $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);
                $output = curl_exec ($curl_handle);
                curl_close($curl_handle);

                $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);

                $content = curl_exec($curl_handle);

                // Check if cURL execution was successful
                if ($content === false) {
                        // Log the cURL error
                        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;
                }


    }
}

The driver in the box apparently requires some compiling and/or some permissions stuff… it didn’t sound like a good idea to LeeWells so he made an alternative that uses curl to visit the Virtualmin web interface and make the change for you. His version, from here down curl_setopt($curl_handle, CURLOPT_HEADER, 1); looks like this:

		curl_setopt($curl_handle, CURLOPT_HEADER, 1);
 
		$content = curl_exec ($curl_handle);
		curl_close($curl_handle);
		
		$newcontent = explode('</tt>', $content);
		$success = explode('.', $newcontent[1]);
		if($success[0] == ' has been changed successfully')
			{
			return PASSWORD_SUCCESS;
			} else {
            rcube::raise_error(array(
                'code' => 600,
                'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => $success[0]
                ), true, false);
			return PASSWORD_ERROR;
			}
    }
}

(So overwriting my version from that same line to the bottom would switch this back to the original.)

I am not skilled enough to go line-by-line through that but generally I understand it to be looking for " has been changed successfully" in a specific spot in the output of Virtualmin’s password change response in order to declare the operation a success. That response must have changed somewhere along the way (or between his site’s response and mine anyway) because if I use it as-is it says “Could not save new password” even though it does change the password (and since it didn’t declare success the plugin doesn’t automatically switch to the new password in the background so you also find yourself kicked out and weren’t told that your password was changed correctly).

My version looks only for “successful” anywhere in the response. (I suppose Virtualmin could someday change its response to “You were successful in reaching the site but the password change failed” or “Password change happily completed” and that would break this my version as well, but today it works.)

No warranty is implied.

Ron

SYSTEM INFORMATION
OS type and version Ubuntu 24.04.2
Virtualmin version 7.30.8
2 Likes

I don’t know much about RoundCube, but I do know that using CURL in this way is the wrong approach.

Virtualmin has an API, which includes the ability to change a users password (modify-user | Virtualmin — Open Source Web Hosting Control Panel) - I expect updating the RoundCube Driver to use that would be a better approach, and lead to a less fragile solution, probably will fewer lines of code.

No idea what your talking about, can you expand on what you are doing and give your system details

I would call what is described there as a CLI not an API. The API is the https://localhost:20000/changepass/changepass.cgi that this uses curl to access.

The CLI requires root-level permissions to do anything (I expect)… which means that I have a Roundcube (that I know nothing about) with Internet-facing PHP-code (itself an absolute security nightmare if you’ve ever let users host Wordpress sites) that executes something on the command line as root… which I think is back to the original problem that LeeWells set out to solve (though I did not look closely at what the original driver actually tried to do… I only found that it didn’t work out-of-the-box and went looking around for what I might do about it).

Roundcube cannot use (and should not use) the virtualmin CLI, at least not without some extra steps, because Roundcube does not run as root. sudo could configured to allow the specific command, but that feels like a bad idea.

1 Like

It’s kind of semantics really, but I described the CLI there as an API as that’s what Virtualmin call it. A Command Line API I suppose?

You have a good point regarding it needing to be run as root. I don’t know if there are commands which can run as non-root user. I have seen references to ‘webmin passwd’ which may do the trick if it acts in a similar way to the linux ‘passwd’ utility and will allow non-root users to change their own passwords.

Is RoundCube already running as root, or does it run under the user account?

There is a HTTP API also - but this can only be used as the ‘master administrator’ (I think that’s referring to the account owner, rather than root, but may be mistaken).

The changepass.cgi isn’t really an API at all as far as I’m aware, it’s a script designed to work with the Virtualmin GUI. Using CURL to interact with it with spoofed user agents smells really bad, and is likely to break as/when Virtualmin make internal changes to how they do things.

I’m not saying what it’s currently doing is ideal, either, but Virtualmin is an administrative level tool. Most email users won’t have access to Virtualmin, so Roundcube can’t reasonably try to use it for email user actions.

Holy hell, no. That’d be a catastrophic security risk. And, it does not run under the user account either (we’re talking about email users!). It is a web application that runs under the domain owner’s PHP-FPM service.

With the ‘Remote API’ - https://www.virtualmin.com/docs/development/remote-api/

Is ‘master administrator’ on that page referring to root, or the account owner?

If the account owner, that seems like the best place for RoundCube to try to change users passwords, I think? But it may mean storing the account owners credentials in the RoundCube configuration…

Or does Usermin have an equivalent API?

Don’t bother just use usermin which has been developed to allow the user to change the password safely, why are you trying to add something insecure to an application that you have no control over ?

It is referring to root. The Virtualmin API (whether CLI or remote, they are the same, only root can use it) is not appropriate for this task.

Usermin is also not a great fit, but it is not insecure when used the way the above plugin is using it, at least not in any way I can see. It’s a little clumsy looking because Usermin doesn’t have an API, but it is not dangerous, as far as I know. Giving every user root access, even for one specific task, feels much more dangerous.

So that email(-only) users can look at nice pretty Roundcube and not administratory Webmin/Usermin (which seems quite a bit more likely to confuse them).

I’m not the OP, so I’m not trying to do anything here.

I can sympathise with OP’s problem however. Users who are just using email services should:

a) Be able to change their own password.
b) Not have to login to a separate system to do so.

The code the OP posted which I believe is already part of RoundCube looked fairly fragile to me, so I suggested they may be a better approach.

Granted, some of my ideas weren’t fully thought through from a security point of view. I’d assumed Virtualmin would have CLI/APIs which operated at lower privilege levels, but it appears not.

If that’s out of scope of what Virtualmin is intended to do, that’s fair enough, but does encourage hacky workarounds.

The best solution here I think would be a Usermin ‘Remote API’ which operates with the privileges of the authenticated user, allowing users do to things like change their own passwords. I appreciate there is a fair bit of development effort in building and maintaining that.

usermin is an email client , so you could change your password at the same time as reading mail.
it also allows filtering at the server level (not at the email client level) so mail is sorted into email folders and any email client will see them in the correct folders, it has message retention policies, auto reply options, folder management, signatures and more. Nice part about it is doesn’t need any dependencies (php) to work and is updated via a package manager so will always work with a virtualmin mail stack.
Downside is perhaps, it’s layout I would guess there would be more up take if it resembles any popular email client rather than looking like virtualmin

We don’t maintain Roundcube. We’re not trying to build Roundcube tools. How Roundcube resets a user’s system password is not our business.

We maintain our own webmail client, Usermin. I guess we’re not doing a great job of making it friendly, because a lot of folks prefer other options like Roundcube. But, Usermin doesn’t need any hacks to allow a user to change their password, it’s a built-in feature, and since it runs as the logged in user, it has the privileges needed to do so.

Hi Joe,

I totally understand. I’m slightly regretting getting involved in this thread now :slight_smile:

Putting RoundCube aside, I suppose it just may be useful to have a Remote API for Usermin in general, for allowing any kind of third party software to do things at the user-level rather than the system-level like the Virtualmin Remote API allows for.

Of course, it would be totally reasonable for you to say that’s not what Webmin/Virtualmin/Usermin is intended for, and leave third parties to come up with their own workarounds.

Can you explain how mail users can change their own passwords? Do they need to know and enter their current password to do it?

Hi Iila,

I’m not the OP here, but if you look at their first post they give an example of how this works, and yes the user does need to enter their current and new password.

I think that makes sense and would be a reasonable security feature, even if it wasn’t technically needed.

Thanks for the feedback! @Jamie and I have been talking about adding the ability to change mailbox user passwords through a reset link. We’ll see if we can make it.

@wheeler Would it work for you if, instead of confirming the password directly, users received a reset link in their inbox to set a new one?

I think that’s a question for u/Ron_E_James_D.O who was the one who originally raised this topic.

Ah, right! @Ron_E_James_D.O what are your thoughts on this?