commit - 5e70998a1d4f415458485d8c6f7f85ba82dc42b3
commit + 8d82f4f7bef520ae81a9e5f9265e9a65cc060b29
blob - 209399933ec9d08d05107ae2b620f31184b0a393
blob + 7587d15c5f7a44e4d14b5ded3d1acaacd9d152f6
--- client.go
+++ client.go
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
+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
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
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
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
-package mailmux
-
-type Server struct {
- users UserStore
-}
-
blob - de55899a1f20f18e75e4296862ee52c1d3e5315b
blob + 86fb14489e0d0003570103a71faaf8434175ff0c
--- user.go
+++ user.go
import (
"errors"
- "os"
- "path"
)
type User struct {
blob - 8f4da24dca9b8cb822e32bb5089ba801748152ef
blob + 80ecd2b252ac071b45975e0e83cff33f46e62d36
--- userdb.go
+++ userdb.go
"database/sql"
"errors"
"fmt"
+ "os"
+ "path"
_ "github.com/mattn/go-sqlite3"
)
}
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
+}