1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package ctl2021import (22 "errors"23 "fmt"24 "os"2526 "github.com/emersion/go-imap"27 "github.com/foxcpp/maddy/framework/module"28 maddycli "github.com/foxcpp/maddy/internal/cli"29 clitools2 "github.com/foxcpp/maddy/internal/cli/clitools"30 "github.com/urfave/cli/v2"31)3233func init() {34 maddycli.AddSubcommand(35 &cli.Command{36 Name: "imap-acct",37 Usage: "IMAP storage accounts management",38 Description: `These subcommands can be used to list/create/delete IMAP storage39accounts for any storage backend supported by maddy.4041The corresponding storage backend should be configured in maddy.conf and be42defined in a top-level configuration block. By default, the name of that43block should be local_mailboxes but this can be changed using --cfg-block44flag for subcommands.4546Note that in default configuration it is not enough to create an IMAP storage47account to grant server access. Additionally, user credentials should48be created using 'creds' subcommand.49`,50 Subcommands: []*cli.Command{51 {52 Name: "list",53 Usage: "List storage accounts",54 Flags: []cli.Flag{55 &cli.StringFlag{56 Name: "cfg-block",57 Usage: "Module configuration block to use",58 EnvVars: []string{"MADDY_CFGBLOCK"},59 Value: "local_mailboxes",60 },61 },62 Action: func(ctx *cli.Context) error {63 be, err := openStorage(ctx)64 if err != nil {65 return err66 }67 defer closeIfNeeded(be)68 return imapAcctList(be, ctx)69 },70 },71 {72 Name: "create",73 Usage: "Create IMAP storage account",74 Description: `In addition to account creation, this command75creates a set of default folder (mailboxes) with special-use attribute set.`,76 ArgsUsage: "USERNAME",77 Flags: []cli.Flag{78 &cli.StringFlag{79 Name: "cfg-block",80 Usage: "Module configuration block to use",81 EnvVars: []string{"MADDY_CFGBLOCK"},82 Value: "local_mailboxes",83 },84 &cli.BoolFlag{85 Name: "no-specialuse",86 Usage: "Do not create special-use folders",87 Value: false,88 },89 &cli.StringFlag{90 Name: "sent-name",91 Usage: "Name of special mailbox for sent messages, use empty string to not create any",92 Value: "Sent",93 },94 &cli.StringFlag{95 Name: "trash-name",96 Usage: "Name of special mailbox for trash, use empty string to not create any",97 Value: "Trash",98 },99 &cli.StringFlag{100 Name: "junk-name",101 Usage: "Name of special mailbox for 'junk' (spam), use empty string to not create any",102 Value: "Junk",103 },104 &cli.StringFlag{105 Name: "drafts-name",106 Usage: "Name of special mailbox for drafts, use empty string to not create any",107 Value: "Drafts",108 },109 &cli.StringFlag{110 Name: "archive-name",111 Usage: "Name of special mailbox for archive, use empty string to not create any",112 Value: "Archive",113 },114 },115 Action: func(ctx *cli.Context) error {116 be, err := openStorage(ctx)117 if err != nil {118 return err119 }120 defer closeIfNeeded(be)121 return imapAcctCreate(be, ctx)122 },123 },124 {125 Name: "remove",126 Usage: "Delete IMAP storage account",127 Description: `If IMAP connections are open and using the specified account,128messages access will be killed off immediately though connection will remain open. No cache129or other buffering takes effect.`,130 ArgsUsage: "USERNAME",131 Flags: []cli.Flag{132 &cli.StringFlag{133 Name: "cfg-block",134 Usage: "Module configuration block to use",135 EnvVars: []string{"MADDY_CFGBLOCK"},136 Value: "local_mailboxes",137 },138 &cli.BoolFlag{139 Name: "yes",140 Aliases: []string{"y"},141 Usage: "Don't ask for confirmation",142 },143 },144 Action: func(ctx *cli.Context) error {145 be, err := openStorage(ctx)146 if err != nil {147 return err148 }149 defer closeIfNeeded(be)150 return imapAcctRemove(be, ctx)151 },152 },153 {154 Name: "appendlimit",155 Usage: "Query or set accounts's APPENDLIMIT value",156 Description: `APPENDLIMIT value determines the size of a message that157can be saved into a mailbox using IMAP APPEND command. This does not affect the size158of messages that can be delivered to the mailbox from non-IMAP sources (e.g. SMTP).159160Global APPENDLIMIT value set via server configuration takes precedence over161per-account values configured using this command.162163APPENDLIMIT value (either global or per-account) cannot be larger than1644 GiB due to IMAP protocol limitations.165`,166 ArgsUsage: "USERNAME",167 Flags: []cli.Flag{168 &cli.StringFlag{169 Name: "cfg-block",170 Usage: "Module configuration block to use",171 EnvVars: []string{"MADDY_CFGBLOCK"},172 Value: "local_mailboxes",173 },174 &cli.IntFlag{175 Name: "value",176 Aliases: []string{"v"},177 Usage: "Set APPENDLIMIT to specified value (in bytes)",178 },179 },180 Action: func(ctx *cli.Context) error {181 be, err := openStorage(ctx)182 if err != nil {183 return err184 }185 defer closeIfNeeded(be)186 return imapAcctAppendlimit(be, ctx)187 },188 },189 },190 })191}192193type SpecialUseUser interface {194 CreateMailboxSpecial(name, specialUseAttr string) error195}196197func imapAcctList(be module.Storage, ctx *cli.Context) error {198 mbe, ok := be.(module.ManageableStorage)199 if !ok {200 return cli.Exit("Error: storage backend does not support accounts management using maddy command", 2)201 }202203 list, err := mbe.ListIMAPAccts()204 if err != nil {205 return err206 }207208 if len(list) == 0 && !ctx.Bool("quiet") {209 fmt.Fprintln(os.Stderr, "No users.")210 }211212 for _, user := range list {213 fmt.Println(user)214 }215 return nil216}217218func imapAcctCreate(be module.Storage, ctx *cli.Context) error {219 mbe, ok := be.(module.ManageableStorage)220 if !ok {221 return cli.Exit("Error: storage backend does not support accounts management using maddy command", 2)222 }223224 username := ctx.Args().First()225 if username == "" {226 return cli.Exit("Error: USERNAME is required", 2)227 }228229 if err := mbe.CreateIMAPAcct(username); err != nil {230 return err231 }232233 act, err := mbe.GetIMAPAcct(username)234 if err != nil {235 return fmt.Errorf("failed to get user: %w", err)236 }237238 suu, ok := act.(SpecialUseUser)239 if !ok {240 fmt.Fprintf(os.Stderr, "Note: Storage backend does not support SPECIAL-USE IMAP extension")241 }242243 if ctx.Bool("no-specialuse") {244 return nil245 }246247 createMbox := func(name, specialUseAttr string) error {248 if suu == nil {249 return act.CreateMailbox(name)250 }251 return suu.CreateMailboxSpecial(name, specialUseAttr)252 }253254 if name := ctx.String("sent-name"); name != "" {255 if err := createMbox(name, imap.SentAttr); err != nil {256 fmt.Fprintf(os.Stderr, "Failed to create sent folder: %v", err)257 }258 }259 if name := ctx.String("trash-name"); name != "" {260 if err := createMbox(name, imap.TrashAttr); err != nil {261 fmt.Fprintf(os.Stderr, "Failed to create trash folder: %v", err)262 }263 }264 if name := ctx.String("junk-name"); name != "" {265 if err := createMbox(name, imap.JunkAttr); err != nil {266 fmt.Fprintf(os.Stderr, "Failed to create junk folder: %v", err)267 }268 }269 if name := ctx.String("drafts-name"); name != "" {270 if err := createMbox(name, imap.DraftsAttr); err != nil {271 fmt.Fprintf(os.Stderr, "Failed to create drafts folder: %v", err)272 }273 }274 if name := ctx.String("archive-name"); name != "" {275 if err := createMbox(name, imap.ArchiveAttr); err != nil {276 fmt.Fprintf(os.Stderr, "Failed to create archive folder: %v", err)277 }278 }279280 return nil281}282283func imapAcctRemove(be module.Storage, ctx *cli.Context) error {284 mbe, ok := be.(module.ManageableStorage)285 if !ok {286 return cli.Exit("Error: storage backend does not support accounts management using maddy command", 2)287 }288289 username := ctx.Args().First()290 if username == "" {291 return cli.Exit("Error: USERNAME is required", 2)292 }293294 if !ctx.Bool("yes") {295 if !clitools2.Confirmation("Are you sure you want to delete this user account?", false) {296 return errors.New("Cancelled")297 }298 }299300 return mbe.DeleteIMAPAcct(username)301}