Commit Diff


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
+}