There is no way to change an existing account's password. create and verify are the only actions on either command surface, so the only route to a password change is editing the database by hand.
Reproducing
On v0.3.12, probing both surfaces:
mail-server user:local create -> Error: username is required (recognised)
mail-server user:local verify -> Error: username is required (recognised)
mail-server user:local password -> Error: Too many arguments provided (not recognised)
mail-server user:local reset -> Error: Too many arguments provided
mail-server user:local update -> Error: Too many arguments provided
mail-server user create -> Error: email address is required (recognised)
mail-server user verify -> Error: username is required (recognised)
mail-server user password -> Error: Too many arguments provided
mail-server user reset -> Error: Too many arguments provided
mail-server user update -> Error: Too many arguments provided--help on user:local prints nothing, so the available actions are not discoverable either.
Why create cannot stand in for it
users.username is UNIQUE, so create on an existing account is rejected rather than upserting. An operator who wants to change a password has three options, and all of them are worse than a command:
- Delete the row and re-create it.
imap_mailboxeshas no foreign key tousersandPRAGMA foreign_keysis 0, so the folder rows and the maildir do survive — but that is a property of the current schema rather than a guarantee, and nothing tells the operator it is safe. It reads like it will destroy the mailbox. - Write a hash directly into the database, which means reproducing the argon2 parameters correctly by hand.
- Create a throwaway user, copy its
password_hashonto the real row, delete the throwaway. This is the only one that uses the server's own hasher without touching the account row, and it takes four commands plus two raw SQL statements.
What this causes in practice
On a production install this led to someone doing a database-level password restore across three accounts at once. It rewrote the hashes for all three and locked those users out of mail they had been using, with no record of what was intended. Recovering it meant diffing the live database against a pre-change backup to work out which rows had moved.
A single supported command would have made that a non-event.
Related: passwords are passed as argv
create and verify take the password as a positional argument, so every password-setting command is recorded in shell history. On the same install, a live mailbox password was sitting in /root/.bash_history in plaintext, months after the account was created, on a host that also accepts public SMTP.
Worth fixing alongside, since a new password command would inherit the same problem.
Proposal
mail-server user:local password <username> [password]
mail-server user password <username> [password]With the password optional:
- omitted, prompt on a TTY with no echo, or read one line from stdin when piped
- provided as argv, keep it working for scripts, since that is what automation needs
The same treatment would suit create and verify. Reading from stdin is what lets an operator do pwgen | mail-server user:local password user@example.com - without the value ever reaching the shell's history.
Two things that would help regardless of whether the command lands:
- make
--helponuseranduser:locallist their actions, so the surface is discoverable - have an unrecognised action say so, rather than
Error: Too many arguments provided, which reads as a syntax problem with the arguments rather than an unknown subcommand