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 updatepipe2021import (22 "bufio"23 "fmt"24 "io"25 "net"26 "os"2728 mess "github.com/foxcpp/go-imap-mess"29 "github.com/foxcpp/maddy/framework/log"30)3132// UnixSockPipe implements the UpdatePipe interface by serializating updates33// to/from a Unix domain socket. Due to the way Unix sockets work, only one34// Listen goroutine can be running.35//36// The socket is stream-oriented and consists of the following messages:37//38// SENDER_ID;JSON_SERIALIZED_INTERNAL_OBJECT\n39//40// And SENDER_ID is Process ID and UnixSockPipe address concated as a string.41// It is used to deduplicate updates sent to Push and recevied via Listen.42//43// The SockPath field specifies the socket path to use. The actual socket44// is initialized on the first call to Listen or (Init)Push.45type UnixSockPipe struct {46 SockPath string47 Log log.Logger4849 listener net.Listener50 sender net.Conn51}5253var _ P = &UnixSockPipe{}5455func (usp *UnixSockPipe) myID() string {56 return fmt.Sprintf("%d-%p", os.Getpid(), usp)57}5859func (usp *UnixSockPipe) readUpdates(conn net.Conn, updCh chan<- mess.Update) {60 scnr := bufio.NewScanner(conn)61 for scnr.Scan() {62 id, upd, err := parseUpdate(scnr.Text())63 if err != nil {64 usp.Log.Error("malformed update received", err, "str", scnr.Text())65 }6667 // It is our own update, skip.68 if id == usp.myID() {69 continue70 }7172 updCh <- *upd73 }74}7576func (usp *UnixSockPipe) Listen(upd chan<- mess.Update) error {77 l, err := net.Listen("unix", usp.SockPath)78 if err != nil {79 return err80 }81 usp.listener = l82 go func() {83 for {84 conn, err := l.Accept()85 if err != nil {86 return87 }88 go usp.readUpdates(conn, upd)89 }90 }()91 return nil92}9394func (usp *UnixSockPipe) InitPush() error {95 sock, err := net.Dial("unix", usp.SockPath)96 if err != nil {97 return err98 }99100 usp.sender = sock101 return nil102}103104func (usp *UnixSockPipe) Push(upd mess.Update) error {105 if usp.sender == nil {106 if err := usp.InitPush(); err != nil {107 return err108 }109 }110111 updStr, err := formatUpdate(usp.myID(), upd)112 if err != nil {113 return err114 }115116 _, err = io.WriteString(usp.sender, updStr)117 return err118}119120func (usp *UnixSockPipe) Close() error {121 if usp.sender != nil {122 usp.sender.Close()123 }124 if usp.listener != nil {125 usp.listener.Close()126 os.Remove(usp.SockPath)127 }128 return nil129}