mirror of
https://github.com/foxcpp/maddy.git
synced 2025-04-04 21:47:40 +03:00
1. There is only one version for maddy and imapsql-ctl utility. This prevents confusion about compatibility. 2. Modified imapsql-ctl understands maddy config format, this allows it to read needed values from it without the need for lengthy commmand line arguments. Closes #148.
428 lines
11 KiB
Go
428 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
imapsql "github.com/foxcpp/go-imap-sql"
|
|
"github.com/foxcpp/maddy/config"
|
|
"github.com/urfave/cli"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
var backend *imapsql.Backend
|
|
var stdinScnr *bufio.Scanner
|
|
|
|
func connectToDB(ctx *cli.Context) error {
|
|
if ctx.GlobalIsSet("unsafe") && !ctx.GlobalIsSet("quiet") {
|
|
fmt.Fprintln(os.Stderr, "WARNING: Using --unsafe with running server may lead to accidential damage to data due to desynchronization with connected clients.")
|
|
}
|
|
|
|
driver := ctx.GlobalString("driver")
|
|
if driver != "" {
|
|
// Construct artificial config file tree from command line arguments
|
|
// and pass to initialization logic.
|
|
cfg := config.Node{
|
|
Name: "sql",
|
|
Children: []config.Node{
|
|
{
|
|
Name: "driver",
|
|
Args: []string{ctx.GlobalString("driver")},
|
|
},
|
|
{
|
|
Name: "dsn",
|
|
Args: []string{ctx.GlobalString("dsn")},
|
|
},
|
|
{
|
|
Name: "fsstore",
|
|
Args: []string{ctx.GlobalString("fsstore")},
|
|
},
|
|
},
|
|
}
|
|
var err error
|
|
backend, err = backendFromNode(make(map[string]interface{}), cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
cfg := ctx.GlobalString("config")
|
|
|
|
cfgAbs, err := filepath.Abs(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cfgBlock := ctx.GlobalString("cfg-block")
|
|
if err := os.Chdir(ctx.GlobalString("state")); err != nil {
|
|
return err
|
|
}
|
|
|
|
backend, err = backendFromCfg(cfgAbs, cfgBlock)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
backend.EnableSpecialUseExt()
|
|
|
|
return nil
|
|
}
|
|
|
|
func closeBackend(ctx *cli.Context) (err error) {
|
|
if backend != nil {
|
|
return backend.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
stdinScnr = bufio.NewScanner(os.Stdin)
|
|
|
|
app := cli.NewApp()
|
|
app.Name = "imapsql-ctl"
|
|
app.Copyright = "(c) 2019 Max Mazurov <fox.cpp@disroot.org>\n Published under the terms of the MIT license (https://opensource.org/licenses/MIT)"
|
|
app.Usage = "SQL database management utility for maddy"
|
|
app.Version = buildInfo()
|
|
app.After = closeBackend
|
|
|
|
app.Flags = []cli.Flag{
|
|
cli.StringFlag{
|
|
Name: "config",
|
|
Usage: "Configuration file to use",
|
|
EnvVar: "MADDY_CONFIG",
|
|
Value: "/etc/maddy/maddy.conf",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "state",
|
|
Usage: "State directory to use",
|
|
EnvVar: "MADDY_STATE",
|
|
Value: "/var/lib/maddy",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "cfg-block",
|
|
Usage: "SQL module configuration to use",
|
|
EnvVar: "MADDY_CFGBLOCK",
|
|
Value: "local_mailboxes",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "driver",
|
|
Usage: "Directly specify driver value instead of reading it from config",
|
|
EnvVar: "MADDY_SQL_DRIVER",
|
|
Value: "",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "dsn",
|
|
Usage: "Directly specify dsn value instead of reading it from config",
|
|
EnvVar: "MADDY_SQL_DSN",
|
|
Value: "",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "fsstore",
|
|
Usage: "Directly specify fsstore value instead of reading it from config",
|
|
EnvVar: "MADDY_SQL_FSSTORE",
|
|
Value: "",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "quiet,q",
|
|
Usage: "Don't print user-friendly messages to stderr",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "unsafe",
|
|
Usage: "Allow to perform actions that can be safely done only without running server",
|
|
},
|
|
}
|
|
|
|
app.Commands = []cli.Command{
|
|
{
|
|
Name: "mboxes",
|
|
Usage: "Mailboxes (folders) management",
|
|
Subcommands: []cli.Command{
|
|
{
|
|
Name: "list",
|
|
Usage: "Show mailboxes of user",
|
|
ArgsUsage: "USERNAME",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "subscribed,s",
|
|
Usage: "List only subscribed mailboxes",
|
|
},
|
|
},
|
|
Action: mboxesList,
|
|
},
|
|
{
|
|
Name: "create",
|
|
Usage: "Create mailbox",
|
|
ArgsUsage: "USERNAME NAME",
|
|
Action: mboxesCreate,
|
|
Flags: []cli.Flag{
|
|
cli.StringFlag{
|
|
Name: "special",
|
|
Usage: "Set SPECIAL-USE attribute on mailbox; valid values: archive, drafts, junk, sent, trash",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "remove",
|
|
Usage: "Remove mailbox (requires --unsafe)",
|
|
Description: "WARNING: All contents of mailbox will be irrecoverably lost.",
|
|
ArgsUsage: "USERNAME MAILBOX",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "yes,y",
|
|
Usage: "Don't ask for confirmation",
|
|
},
|
|
},
|
|
Action: mboxesRemove,
|
|
},
|
|
{
|
|
Name: "rename",
|
|
Usage: "Rename mailbox (requires --unsafe)",
|
|
Description: "Rename may cause unexpected failures on client-side so be careful.",
|
|
ArgsUsage: "USERNAME OLDNAME NEWNAME",
|
|
Action: mboxesRename,
|
|
},
|
|
{
|
|
Name: "appendlimit",
|
|
Usage: "Query or set user's APPENDLIMIT value",
|
|
ArgsUsage: "USERNAME",
|
|
Flags: []cli.Flag{
|
|
cli.IntFlag{
|
|
Name: "value,v",
|
|
Usage: "Set APPENDLIMIT to specified value (in bytes). Pass -1 to disable limit.",
|
|
},
|
|
},
|
|
Action: mboxesAppendLimit,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "msgs",
|
|
Usage: "Messages management",
|
|
Subcommands: []cli.Command{
|
|
{
|
|
Name: "add",
|
|
Usage: "Add message to mailbox (requires --unsafe)",
|
|
ArgsUsage: "USERNAME MAILBOX",
|
|
Description: "Reads message body (with headers) from stdin. Prints UID of created message on success.",
|
|
Flags: []cli.Flag{
|
|
cli.StringSliceFlag{
|
|
Name: "flag,f",
|
|
Usage: "Add flag to message. Can be specified multiple times",
|
|
},
|
|
cli.Int64Flag{
|
|
Name: "date,d",
|
|
Usage: "Set internal date value to specified UNIX timestamp",
|
|
},
|
|
},
|
|
Action: msgsAdd,
|
|
},
|
|
{
|
|
Name: "add-flags",
|
|
Usage: "Add flags to messages (requires --unsafe)",
|
|
ArgsUsage: "USERNAME MAILBOX SEQ FLAGS...",
|
|
Description: "Add flags to all messages matched by SEQ.",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "uid,u",
|
|
Usage: "Use UIDs for SEQSET instead of sequence numbers",
|
|
},
|
|
},
|
|
Action: msgsFlags,
|
|
},
|
|
{
|
|
Name: "rem-flags",
|
|
Usage: "Remove flags from messages (requires --unsafe)",
|
|
ArgsUsage: "USERNAME MAILBOX SEQ FLAGS...",
|
|
Description: "Remove flags from all messages matched by SEQ.",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "uid,u",
|
|
Usage: "Use UIDs for SEQSET instead of sequence numbers",
|
|
},
|
|
},
|
|
Action: msgsFlags,
|
|
},
|
|
{
|
|
Name: "set-flags",
|
|
Usage: "Set flags on messages (requires --unsafe)",
|
|
ArgsUsage: "USERNAME MAILBOX SEQ FLAGS...",
|
|
Description: "Set flags on all messages matched by SEQ.",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "uid,u",
|
|
Usage: "Use UIDs for SEQSET instead of sequence numbers",
|
|
},
|
|
},
|
|
Action: msgsFlags,
|
|
},
|
|
{
|
|
Name: "remove",
|
|
Usage: "Remove messages from mailbox (requires --unsafe)",
|
|
ArgsUsage: "USERNAME MAILBOX SEQSET",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "uid,u",
|
|
Usage: "Use UIDs for SEQSET instead of sequence numbers",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "yes,y",
|
|
Usage: "Don't ask for confirmation",
|
|
},
|
|
},
|
|
Action: msgsRemove,
|
|
},
|
|
{
|
|
Name: "copy",
|
|
Usage: "Copy messages between mailboxes (requires --unsafe)",
|
|
Description: "Note: You can't copy between mailboxes of different users. APPENDLIMIT of target mailbox is not enforced.",
|
|
ArgsUsage: "USERNAME SRCMAILBOX SEQSET TGTMAILBOX",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "uid,u",
|
|
Usage: "Use UIDs for SEQSET instead of sequence numbers",
|
|
},
|
|
},
|
|
Action: msgsCopy,
|
|
},
|
|
{
|
|
Name: "move",
|
|
Usage: "Move messages between mailboxes (requires --unsafe)",
|
|
Description: "Note: You can't move between mailboxes of different users. APPENDLIMIT of target mailbox is not enforced.",
|
|
ArgsUsage: "USERNAME SRCMAILBOX SEQSET TGTMAILBOX",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "uid,u",
|
|
Usage: "Use UIDs for SEQSET instead of sequence numbers",
|
|
},
|
|
},
|
|
Action: msgsMove,
|
|
},
|
|
{
|
|
Name: "list",
|
|
Usage: "List messages in mailbox",
|
|
Description: "If SEQSET is specified - only show messages that match it.",
|
|
ArgsUsage: "USERNAME MAILBOX [SEQSET]",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "uid,u",
|
|
Usage: "Use UIDs for SEQSET instead of sequence numbers",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "full,f",
|
|
Usage: "Show entire envelope and all server meta-data",
|
|
},
|
|
},
|
|
Action: msgsList,
|
|
},
|
|
{
|
|
Name: "dump",
|
|
Usage: "Dump message body",
|
|
Description: "If passed SEQ matches multiple messages - they will be joined.",
|
|
ArgsUsage: "USERNAME MAILBOX SEQ",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "uid,u",
|
|
Usage: "Use UIDs for SEQ instead of sequence numbers",
|
|
},
|
|
},
|
|
Action: msgsDump,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "users",
|
|
Usage: "User accounts management",
|
|
Subcommands: []cli.Command{
|
|
{
|
|
Name: "list",
|
|
Usage: "List created user accounts",
|
|
Action: usersList,
|
|
},
|
|
{
|
|
Name: "create",
|
|
Usage: "Create user account",
|
|
Description: "Reads password from stdin",
|
|
ArgsUsage: "USERNAME",
|
|
Flags: []cli.Flag{
|
|
cli.StringFlag{
|
|
Name: "password,p",
|
|
Usage: "Use `PASSWORD instead of reading password from stdin.\n\t\tWARNING: Provided only for debugging convenience. Don't leave your passwords in shell history!",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "null,n",
|
|
Usage: "Create account with null password",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "hash",
|
|
Usage: "Use specified hash algorithm. Valid values: sha3-512, bcrypt",
|
|
Value: "bcrypt",
|
|
},
|
|
cli.IntFlag{
|
|
Name: "bcrypt-cost",
|
|
Usage: "Specify bcrypt cost value",
|
|
Value: bcrypt.DefaultCost,
|
|
},
|
|
},
|
|
Action: usersCreate,
|
|
},
|
|
{
|
|
Name: "remove",
|
|
Usage: "Delete user account (requires --unsafe)",
|
|
ArgsUsage: "USERNAME",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "yes,y",
|
|
Usage: "Don't ask for confirmation",
|
|
},
|
|
},
|
|
Action: usersRemove,
|
|
},
|
|
{
|
|
Name: "password",
|
|
Usage: "Change account password",
|
|
Description: "Reads password from stdin",
|
|
ArgsUsage: "USERNAME",
|
|
Flags: []cli.Flag{
|
|
cli.StringFlag{
|
|
Name: "password,p",
|
|
Usage: "Use `PASSWORD` instead of reading password from stdin.\n\t\tWARNING: Provided only for debugging convenience. Don't leave your passwords in shell history!",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "null,n",
|
|
Usage: "Set password to null",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "hash",
|
|
Usage: "Use specified hash algorithm. Valid values: sha3-512, bcrypt",
|
|
Value: "sha3-512",
|
|
},
|
|
cli.IntFlag{
|
|
Name: "bcrypt-cost",
|
|
Usage: "Specify bcrypt cost value",
|
|
Value: bcrypt.DefaultCost,
|
|
},
|
|
},
|
|
Action: usersPassword,
|
|
},
|
|
{
|
|
Name: "appendlimit",
|
|
Usage: "Query or set user's APPENDLIMIT value",
|
|
ArgsUsage: "USERNAME",
|
|
Flags: []cli.Flag{
|
|
cli.IntFlag{
|
|
Name: "value,v",
|
|
Usage: "Set APPENDLIMIT to specified value (in bytes)",
|
|
},
|
|
},
|
|
Action: usersAppendLimit,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
if err := app.Run(os.Args); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
}
|
|
}
|