blob: 1e823a1c26d085f34bb5fbc630c252f42796d3a3 [file] [log] [blame]
giolekva1cf54312021-09-23 21:27:20 +04001package main
2
3import (
4 "bufio"
5 "embed"
6 "flag"
7 "fmt"
8 "html/template"
9 "io"
10 "io/ioutil"
11 "log"
12 "net/http"
giolekva7fe15192021-11-19 13:58:16 +040013 "os"
giolekva1cf54312021-09-23 21:27:20 +040014 "os/exec"
15)
16
17var port = flag.Int("port", 8080, "Port to listen on.")
18var maddyConfig = flag.String("maddy-config", "", "Path to the Maddy configuration file.")
giolekva7fe15192021-11-19 13:58:16 +040019var exportDKIM = flag.String("export-dkim", "", "Path to the dkim dns configuration to expose.")
giolekva1cf54312021-09-23 21:27:20 +040020
21//go:embed templates/*
22var tmpls embed.FS
23
24type Templates struct {
25 Index *template.Template
26}
27
28func ParseTemplates(fs embed.FS) (*Templates, error) {
29 index, err := template.ParseFS(fs, "templates/index.html")
30 if err != nil {
31 return nil, err
32 }
33 return &Templates{index}, nil
34}
35
36type MaddyManager struct {
37 configPath string
38}
39
40func (m MaddyManager) ListAccounts() ([]string, error) {
41 cmd := exec.Command("maddyctl", "-config", m.configPath, "creds", "list")
42 stdout, err := cmd.StdoutPipe()
43 if err != nil {
44 return nil, err
45 }
46 if err := cmd.Start(); err != nil {
47 return nil, err
48 }
49 scanner := bufio.NewScanner(stdout)
50 accts := make([]string, 0)
51 for scanner.Scan() {
52 acct := scanner.Text()
53 if len(acct) == 0 {
54 continue
55 }
56 accts = append(accts, acct)
57 }
58 return accts, nil
59
60}
61
62func (m MaddyManager) CreateAccount(username, password string) error {
63 cmd := exec.Command("maddyctl", "-config", m.configPath, "creds", "create", username)
64 stdin, err := cmd.StdinPipe()
65 if err != nil {
66 return err
67 }
68 if err := cmd.Start(); err != nil {
69 return err
70 }
71 go func() {
72 defer stdin.Close()
73 io.WriteString(stdin, password)
74 }()
75 if err := cmd.Wait(); err != nil {
76 return err
77 }
78 // Create IMAP
79 cmd = exec.Command("maddyctl", "-config", m.configPath, "imap-acct", "create", username)
80 return cmd.Run()
81}
82
83type MaddyHandler struct {
84 mgr MaddyManager
85 tmpls *Templates
86}
87
88func (h *MaddyHandler) handleListAccounts(w http.ResponseWriter, r *http.Request) {
89 accts, err := h.mgr.ListAccounts()
90 if err != nil {
91 http.Error(w, err.Error(), http.StatusInternalServerError)
92 return
93 }
94 if err := h.tmpls.Index.Execute(w, accts); err != nil {
95 http.Error(w, err.Error(), http.StatusInternalServerError)
96 }
97}
98
99func (h *MaddyHandler) handleCreateAccount(w http.ResponseWriter, r *http.Request) {
100 if err := r.ParseForm(); err != nil {
101 http.Error(w, err.Error(), http.StatusBadRequest)
102 return
103 }
104
105 err := h.mgr.CreateAccount(r.FormValue("username"), r.FormValue("password"))
106 if err != nil {
107 http.Error(w, err.Error(), http.StatusInternalServerError)
108 return
109 }
110 http.Redirect(w, r, "/", http.StatusSeeOther)
111}
112
giolekva7fe15192021-11-19 13:58:16 +0400113func (h *MaddyHandler) handleDKIM(w http.ResponseWriter, r *http.Request) {
114 d, err := os.Open(*exportDKIM)
115 if err != nil {
116 http.Error(w, err.Error(), http.StatusInternalServerError)
117 return
118 }
119 defer d.Close()
120 if _, err := io.Copy(w, d); err != nil {
121 http.Error(w, err.Error(), http.StatusInternalServerError)
122 return
123 }
124}
125
giolekva1cf54312021-09-23 21:27:20 +0400126func main() {
127 flag.Parse()
128 t, err := ParseTemplates(tmpls)
129 if err != nil {
130 log.Fatal(err)
131 }
132 mgr := MaddyManager{
133 configPath: *maddyConfig,
134 }
135 handler := MaddyHandler{
136 mgr: mgr,
137 tmpls: t,
138 }
139 http.HandleFunc("/", handler.handleListAccounts)
140 http.HandleFunc("/create", handler.handleCreateAccount)
giolekva7fe15192021-11-19 13:58:16 +0400141 if *exportDKIM != "" {
142 http.HandleFunc("/dkim", handler.handleDKIM)
143 }
giolekva1cf54312021-09-23 21:27:20 +0400144 fmt.Printf("Starting HTTP server on port: %d\n", *port)
145 fmt.Printf("Maddy config: %s\n", *maddyConfig)
146 if cfg, err := ioutil.ReadFile(*maddyConfig); err != nil {
147 log.Fatal(err)
148 } else {
149 log.Print(string(cfg))
150 }
151 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
152
153}