maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19package maddy
 20
 21import (
 22	"errors"
 23	"fmt"
 24	"os"
 25	"path/filepath"
 26
 27	"github.com/foxcpp/maddy/framework/config"
 28	"github.com/foxcpp/maddy/framework/log"
 29)
 30
 31/*
 32Config matchers for module interfaces.
 33*/
 34
 35// logOut structure wraps log.Output and preserves
 36// configuration directive it was constructed from, allowing
 37// dynamic reinitialization for purposes of log file rotation.
 38type logOut struct {
 39	args []string
 40	log.Output
 41}
 42
 43func logOutput(_ *config.Map, node config.Node) (interface{}, error) {
 44	if len(node.Args) == 0 {
 45		return nil, config.NodeErr(node, "expected at least 1 argument")
 46	}
 47	if len(node.Children) != 0 {
 48		return nil, config.NodeErr(node, "can't declare block here")
 49	}
 50
 51	return LogOutputOption(node.Args)
 52}
 53
 54func LogOutputOption(args []string) (log.Output, error) {
 55	outs := make([]log.Output, 0, len(args))
 56	for i, arg := range args {
 57		switch arg {
 58		case "stderr":
 59			outs = append(outs, log.WriterOutput(os.Stderr, false))
 60		case "stderr_ts":
 61			outs = append(outs, log.WriterOutput(os.Stderr, true))
 62		case "syslog":
 63			syslogOut, err := log.SyslogOutput()
 64			if err != nil {
 65				return nil, fmt.Errorf("failed to connect to syslog daemon: %v", err)
 66			}
 67			outs = append(outs, syslogOut)
 68		case "off":
 69			if len(args) != 1 {
 70				return nil, errors.New("'off' can't be combined with other log targets")
 71			}
 72			return log.NopOutput{}, nil
 73		default:
 74			// Log file paths are converted to absolute to make sure
 75			// we will be able to recreate them in right location
 76			// after changing working directory to the state dir.
 77			absPath, err := filepath.Abs(arg)
 78			if err != nil {
 79				return nil, err
 80			}
 81			// We change the actual argument, so logOut object will
 82			// keep the absolute path for reinitialization.
 83			args[i] = absPath
 84
 85			w, err := os.OpenFile(absPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666)
 86			if err != nil {
 87				return nil, fmt.Errorf("failed to create log file: %v", err)
 88			}
 89
 90			outs = append(outs, log.WriteCloserOutput(w, true))
 91		}
 92	}
 93
 94	if len(outs) == 1 {
 95		return logOut{args, outs[0]}, nil
 96	}
 97	return logOut{args, log.MultiOutput(outs...)}, nil
 98}
 99
100func defaultLogOutput() (interface{}, error) {
101	return log.DefaultLogger.Out, nil
102}
103
104func reinitLogging() {
105	out, ok := log.DefaultLogger.Out.(logOut)
106	if !ok {
107		log.Println("Can't reinitialize logger because it was replaced before, this is a bug")
108		return
109	}
110
111	newOut, err := LogOutputOption(out.args)
112	if err != nil {
113		log.Println("Can't reinitialize logger:", err)
114		return
115	}
116
117	out.Close()
118
119	log.DefaultLogger.Out = newOut
120}