package main import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/base32" "encoding/json" "flag" "fmt" "log" "math/rand" "net/http" "os" "strings" ) var apiSecret string type addyForm struct { Alias string `json:"alias"` Description string `json:"description"` Domain string `json:"domain"` } type addyResponse struct { Data addyResponseData `json:"data"` } type addyResponseData struct { Email string `json:"email"` } func init() { if val, ok := os.LookupEnv("ADDY_API_SECRET"); ok { apiSecret = val } flag.StringVar(&apiSecret, "addy-api-secret", apiSecret, "Secret used for the HMAC protecting addy.io-like API") } func AddyAPISignature(username string) []byte { mac := hmac.New(sha256.New224, []byte(apiSecret)) mac.Write([]byte(username)) return mac.Sum(nil) } func AddyAPIToken(username string) string { chain := []byte(username + ":") chain = append(chain, AddyAPISignature(username)...) return base32.StdEncoding.EncodeToString(chain) } func checkAddyApiAuthorization(authorization []byte) *string { fields := bytes.SplitN(authorization, []byte(":"), 2) if len(fields) != 2 { return nil } username := string(fields[0]) expectedSign := AddyAPISignature(username) if !hmac.Equal(expectedSign, fields[1]) { return nil } return &username } func addyAliasAPIAuth(r *http.Request) (*string, error) { // Check authorization header fields := strings.Fields(r.Header.Get("Authorization")) if len(fields) != 2 || fields[0] != "Bearer" { return nil, fmt.Errorf("Authorization header should be a valid Bearer token") } // Decode header authorization, err := base32.StdEncoding.DecodeString(fields[1]) if err != nil { log.Println("Invalid Authorization header: %s", err.Error()) return nil, err } user := checkAddyApiAuthorization(authorization) if user == nil { return nil, fmt.Errorf("Not authorized") } return user, nil } func addyAliasAPI(w http.ResponseWriter, r *http.Request) { user, err := addyAliasAPIAuth(r) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } // Decode body var body addyForm err = json.NewDecoder(r.Body).Decode(&body) if err != nil { http.Error(w, "Invalid body", http.StatusBadRequest) return } conn, err := myLDAP.Connect() if err != nil || conn == nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } err = conn.ServiceBind() if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } dn, err := conn.SearchDN(*user, true) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } if len(body.Alias) == 0 { body.Alias = generateRandomString(10) } email := body.Alias + "@" + body.Domain nb, err := conn.SearchMailAlias(email) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } if nb != 0 { http.Error(w, fmt.Sprintf("The alias %q is already used.", email), http.StatusBadRequest) return } err = conn.AddMailAlias(dn, email) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } log.Printf("New alias created for %s: %s", dn, email) res := addyResponse{ Data: addyResponseData{ Email: email, }, } err = json.NewEncoder(w).Encode(res) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) } } func addyAliasAPIDelete(w http.ResponseWriter, r *http.Request) { user, err := addyAliasAPIAuth(r) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } email := r.PathValue("alias") conn, err := myLDAP.Connect() if err != nil || conn == nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } err = conn.ServiceBind() if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } dn, err := conn.SearchDN(*user, true) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } err = conn.DelMailAlias(dn, email) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } log.Printf("Alias deleted for %s: %s", dn, email) http.Error(w, "", http.StatusOK) } func generateRandomString(length int) string { charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" result := make([]byte, length) for i := range result { result[i] = charset[rand.Intn(len(charset))] } return string(result) }