commit 8d82f4f7bef520ae81a9e5f9265e9a65cc060b29 from: Oliver Lowe date: Tue Apr 12 06:41:29 2022 UTC wip commit - 5e70998a1d4f415458485d8c6f7f85ba82dc42b3 commit + 8d82f4f7bef520ae81a9e5f9265e9a65cc060b29 blob - 209399933ec9d08d05107ae2b620f31184b0a393 blob + 7587d15c5f7a44e4d14b5ded3d1acaacd9d152f6 --- client.go +++ client.go @@ -20,7 +20,7 @@ func NewClient(user, token string) *Client { func (c *Client) NewAlias() (Alias, error) { v := url.Values{} - v.Add("user", c.user) + v.Add("username", c.user) v.Add("token", c.token) resp, err := http.PostForm(apiurl, v) if err != nil { blob - /dev/null blob + db77054712eef39bc2a689bd2b78b6c4f884d921 (mode 644) --- /dev/null +++ cmd/mailmux/mailmux.go @@ -0,0 +1,186 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "os" + "path" + + mailmux "mailmux.net" + "mailmux.net/aliases" +) + +type server struct { + seedfile string + aliaspath string + aliases aliases.Aliases + users mailmux.UserStore +} + +func (srv *server) registerUser(name string, pw mailmux.Password) error { + _, err := srv.users.Lookup(name) + if err == nil { + return mailmux.ErrUserExist + } + if errors.Is(err, mailmux.ErrUnknownUser) { + return srv.users.Change(name, pw) + } + return err +} + +func (srv *server) registerHandler(w http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + if err := req.ParseForm(); err != nil { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + log.Println(err) + return + } + + username := req.PostForm.Get("username") + if username == "" { + http.Error(w, "empty username", http.StatusBadRequest) + return + } + + pw := req.PostForm.Get("password") + if pw == "" { + http.Error(w, "empty password", http.StatusBadRequest) + return + } + // TODO hash password + password := mailmux.Password(pw) + + err := srv.registerUser(username, password) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Println(err) + } +} + +func (srv *server) aliasHandler(w http.ResponseWriter, req *http.Request) { + if err := req.ParseForm(); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + user := req.Form.Get("username") + if user == "" { + http.Error(w, "empty user", http.StatusBadRequest) + return + } + token := req.Form.Get("token") + if token == "" { + http.Error(w, "empty token", http.StatusBadRequest) + return + } + _, err := srv.users.Lookup(user) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + log.Println(err) + return + } + + switch req.Method { + case http.MethodGet: + srv.listAliasHandler(w, req) + return + case http.MethodPost: + srv.newAliasHandler(w, req) + return + case http.MethodDelete: + srv.deleteAlias(w, req) + return + } + w.WriteHeader(http.StatusMethodNotAllowed) +} + +func (srv *server) listAliasHandler(w http.ResponseWriter, req *http.Request) { + username := req.Form.Get("username") + filtered := make(aliases.Aliases) + for rcpt, dest := range srv.aliases { + if dest == username { + filtered[rcpt] = dest + } + } + if err := json.NewEncoder(w).Encode(filtered); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func (srv *server) newAliasHandler(w http.ResponseWriter, req *http.Request) { + username := req.Form.Get("username") + rcpt, err := mailmux.RandomUsername(srv.seedfile) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + srv.aliases[rcpt] = username + if err := aliases.Put(srv.aliases, srv.aliaspath); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + rcpt = rcpt + "@mailmux.net" + if err := json.NewEncoder(w).Encode(rcpt); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func (srv *server) deleteAlias(w http.ResponseWriter, req *http.Request) { + username := req.Form.Get("username") + recipient := req.Form.Get("recipient") + if recipient == "" { + http.Error(w, "empty recipient", http.StatusBadRequest) + return + } + + for rcpt, dest := range srv.aliases { + if rcpt == recipient && dest == username { + delete(srv.aliases, rcpt) + w.WriteHeader(http.StatusNoContent) + return + } + } + msg := fmt.Sprintf("no alias for recpient %s with destination %s", recipient, username) + http.Error(w, msg, http.StatusNotFound) +} + +func main() { + cdir, err := os.UserCacheDir() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + ticketDir := path.Join(cdir, "/mailmux/ticket") + if err := os.MkdirAll(ticketDir, 0777); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + db, err := mailmux.OpenUserDB(path.Join(cdir, "/mailmux/db"), ticketDir) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + srv := &server{users: db} + + srv.aliaspath = "/tmp/aliases" + srv.aliases, err = aliases.Load(srv.aliaspath) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + srv.seedfile = "/usr/share/dict/words" + + http.HandleFunc("/register", srv.registerHandler) + http.HandleFunc("/aliases", srv.aliasHandler) + + log.Fatal(http.ListenAndServe("[::1]:6969", nil)) +} blob - 85add4b7a714a0bd3b1e17208a80dbeac77d655a blob + ac0acf3fb67527493f9e8fcd29421a54ca208098 --- main.go +++ main.go @@ -21,7 +21,7 @@ func main() { os.Exit(1) } dest := os.Args[1] - username, err := randomUsername("/usr/share/dict/words") + username, err := RandomUsername("/usr/share/dict/words") if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) blob - ebd59c328ae9e25be1ffc97a44ea3aaa8e62de1e blob + 267fb4c56be888c434d071a18e64935683b16f2d --- random.go +++ random.go @@ -46,7 +46,7 @@ func randomLine(f *os.File) (string, error) { return line, nil } -func randomUsername(dictpath string) (string, error) { +func RandomUsername(dictpath string) (string, error) { f, err := os.Open(dictpath) if err != nil { return "", fmt.Errorf("open dictionary: %w", err) blob - 1497bc5a1f39bd6dc7ebb079808e3cf6e2462799 blob + 76f7e936a468264c9c20770906e584c0cebde45f --- random_test.go +++ random_test.go @@ -3,7 +3,7 @@ package mailmux import "testing" func TestRandomAlias(t *testing.T) { - _, err := randomUsername("/usr/share/dict/words") + _, err := RandomUsername("/usr/share/dict/words") if err != nil { t.Error(err) } blob - b6e3281a1a144cb517529cf376cc9e2ff3232613 (mode 644) blob + /dev/null --- server.go +++ /dev/null @@ -1,6 +0,0 @@ -package mailmux - -type Server struct { - users UserStore -} - blob - de55899a1f20f18e75e4296862ee52c1d3e5315b blob + 86fb14489e0d0003570103a71faaf8434175ff0c --- user.go +++ user.go @@ -2,8 +2,6 @@ package mailmux import ( "errors" - "os" - "path" ) type User struct { blob - 8f4da24dca9b8cb822e32bb5089ba801748152ef blob + 80ecd2b252ac071b45975e0e83cff33f46e62d36 --- userdb.go +++ userdb.go @@ -4,6 +4,8 @@ import ( "database/sql" "errors" "fmt" + "os" + "path" _ "github.com/mattn/go-sqlite3" ) @@ -75,3 +77,19 @@ func (db *UserDB) Delete(name string) error { } return nil } + +// TODO tickets aren't implemented yet +func createTicket(dir, username string, pw Password) error { + f, err := os.Create(path.Join(dir, username)) + if err != nil { + return err + } + defer f.Close() + + // TODO hash username, password, random bytes + // _, err := f.Write(b) + if _, err := f.Write(testTicket); err != nil { + return err + } + return nil +}