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 "io"25 "os"2627 "github.com/foxcpp/maddy"28 parser "github.com/foxcpp/maddy/framework/cfgparser"29 "github.com/foxcpp/maddy/framework/config"30 "github.com/foxcpp/maddy/framework/hooks"31 "github.com/foxcpp/maddy/framework/module"32 "github.com/foxcpp/maddy/internal/updatepipe"33 "github.com/urfave/cli/v2"34)3536func closeIfNeeded(i interface{}) {37 if c, ok := i.(io.Closer); ok {38 c.Close()39 }40}4142func getCfgBlockModule(ctx *cli.Context) (map[string]interface{}, *maddy.ModInfo, error) {43 cfgPath := ctx.String("config")44 if cfgPath == "" {45 return nil, nil, cli.Exit("Error: config is required", 2)46 }47 cfgFile, err := os.Open(cfgPath)48 if err != nil {49 return nil, nil, cli.Exit(fmt.Sprintf("Error: failed to open config: %v", err), 2)50 }51 defer cfgFile.Close()52 cfgNodes, err := parser.Read(cfgFile, cfgFile.Name())53 if err != nil {54 return nil, nil, cli.Exit(fmt.Sprintf("Error: failed to parse config: %v", err), 2)55 }5657 globals, cfgNodes, err := maddy.ReadGlobals(cfgNodes)58 if err != nil {59 return nil, nil, err60 }6162 if err := maddy.InitDirs(); err != nil {63 return nil, nil, err64 }6566 module.NoRun = true67 _, mods, err := maddy.RegisterModules(globals, cfgNodes)68 if err != nil {69 return nil, nil, err70 }71 defer hooks.RunHooks(hooks.EventShutdown)7273 cfgBlock := ctx.String("cfg-block")74 if cfgBlock == "" {75 return nil, nil, cli.Exit("Error: cfg-block is required", 2)76 }77 var mod maddy.ModInfo78 for _, m := range mods {79 if m.Instance.InstanceName() == cfgBlock {80 mod = m81 break82 }83 }84 if mod.Instance == nil {85 return nil, nil, cli.Exit(fmt.Sprintf("Error: unknown configuration block: %s", cfgBlock), 2)86 }8788 return globals, &mod, nil89}9091func openStorage(ctx *cli.Context) (module.Storage, error) {92 globals, mod, err := getCfgBlockModule(ctx)93 if err != nil {94 return nil, err95 }9697 storage, ok := mod.Instance.(module.Storage)98 if !ok {99 return nil, cli.Exit(fmt.Sprintf("Error: configuration block %s is not an IMAP storage", ctx.String("cfg-block")), 2)100 }101102 if err := mod.Instance.Init(config.NewMap(globals, mod.Cfg)); err != nil {103 return nil, fmt.Errorf("Error: module initialization failed: %w", err)104 }105106 if updStore, ok := mod.Instance.(updatepipe.Backend); ok {107 if err := updStore.EnableUpdatePipe(updatepipe.ModePush); err != nil && !errors.Is(err, os.ErrNotExist) {108 fmt.Fprintf(os.Stderr, "Failed to initialize update pipe, do not remove messages from mailboxes open by clients: %v\n", err)109 }110 } else {111 fmt.Fprintf(os.Stderr, "No update pipe support, do not remove messages from mailboxes open by clients\n")112 }113114 return storage, nil115}116117func openUserDB(ctx *cli.Context) (module.PlainUserDB, error) {118 globals, mod, err := getCfgBlockModule(ctx)119 if err != nil {120 return nil, err121 }122123 userDB, ok := mod.Instance.(module.PlainUserDB)124 if !ok {125 return nil, cli.Exit(fmt.Sprintf("Error: configuration block %s is not a local credentials store", ctx.String("cfg-block")), 2)126 }127128 if err := mod.Instance.Init(config.NewMap(globals, mod.Cfg)); err != nil {129 return nil, fmt.Errorf("Error: module initialization failed: %w", err)130 }131132 return userDB, nil133}