2024-05-31 12:08:05 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-05-31 15:08:15 +00:00
|
|
|
func addyAliasAPIAuth(r *http.Request) (*string, error) {
|
2024-05-31 12:08:05 +00:00
|
|
|
// Check authorization header
|
|
|
|
fields := strings.Fields(r.Header.Get("Authorization"))
|
|
|
|
if len(fields) != 2 || fields[0] != "Bearer" {
|
2024-05-31 15:08:15 +00:00
|
|
|
return nil, fmt.Errorf("Authorization header should be a valid Bearer token")
|
2024-05-31 12:08:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Decode header
|
|
|
|
authorization, err := base32.StdEncoding.DecodeString(fields[1])
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Invalid Authorization header: %s", err.Error())
|
2024-05-31 15:08:15 +00:00
|
|
|
return nil, err
|
2024-05-31 12:08:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
user := checkAddyApiAuthorization(authorization)
|
|
|
|
if user == nil {
|
2024-05-31 15:08:15 +00:00
|
|
|
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)
|
2024-05-31 12:08:05 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-31 15:08:15 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-05-31 12:08:05 +00:00
|
|
|
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)
|
|
|
|
}
|