From 38bf6a78c7ce8b53a71098fbc55ce5a082120884 Mon Sep 17 00:00:00 2001 From: yo Date: Wed, 30 Dec 2020 15:51:20 +0100 Subject: [PATCH] Commit initial --- .gitignore | 2 + mxrouter.go | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 .gitignore create mode 100644 mxrouter.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fd4f50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +config.txt +mxrouter diff --git a/mxrouter.go b/mxrouter.go new file mode 100644 index 0000000..dec7b1b --- /dev/null +++ b/mxrouter.go @@ -0,0 +1,178 @@ +// mxrouter +// Copyright (c) 2020 yo000 +// +// + +// checkmx : postfix tcp_table server which returns second-level domain name of the MX of a domain +// ex : +// "get cfnews.net" +// returns : 200 google.com +// +// Use a single LDAP connection protected by a mutex + +package main + +import ( + "fmt" + "log" + "log/syslog" + "net" + "os" + "flag" + "strings" + "sync" + + "github.com/go-ldap/ldap/v3" + "github.com/peterbourgon/ff" +) + +const ( + version = "0.8" +) + +var ( + logstream *syslog.Writer + mutex sync.Mutex + debug *bool + listen *string + ldapURL *string + ldapBaseDN *string + ldapUser *string + ldapPass *string +) + +func handleConnection(connClt net.Conn, conLdap *ldap.Conn) { + buf := make([]byte, 1024) + + defer connClt.Close() + readlen, err := connClt.Read(buf) + if err != nil { + fmt.Println("Error reading:", err.Error()) + } + + // 1) Recupere le MX du domaine de l'adresse mail recue + if strings.HasPrefix(string(buf[:readlen-1]), "get ") && strings.Contains(string(buf[:readlen-1]), "@") { + mail := string(buf[4:readlen-1]) + domain := strings.Split(mail, "@")[1] + mxs, err := net.LookupMX(domain) + if err != nil { + logstream.Err(fmt.Sprintln("Error lookup mx: ", err)) + s := strings.Replace(fmt.Sprintf("Error lookup MX: %s", err.Error()), " ", "%20", -1) + response := fmt.Sprintf("500 %s\n", s) + connClt.Write([]byte(response)) + return + } + // 2) Requete LDAP pour voir si le domaine de second niveau du MX possede un routage particulier + // Protection contre ca : example.org. 72 IN MX 0 . + // Considerons qu'il n'y aura jamais de "one letter TLD" + if len(mxs[0].Host) < 4 { + logstream.Err(fmt.Sprintln("No usable mx found")) + s := strings.Replace("No usable MX found", " ", "%20", -1) + resp := fmt.Sprintf("500 %s\n", s) + connClt.Write([]byte(resp)) + return + } + + mxslic := strings.Split(mxs[0].Host, ".") + // les MXs terminent par '.', on le retire + mxdom := strings.Join(mxslic[len(mxslic)-3:len(mxslic)-1], ".") + + filter := fmt.Sprintf("(dc=%s)", ldap.EscapeFilter(mxdom)) + searchReq := ldap.NewSearchRequest(*ldapBaseDN, ldap.ScopeWholeSubtree, 0, 0, 0, + false, filter, []string{"relayName"}, []ldap.Control{}) + + mutex.Lock() + result, err := conLdap.Search(searchReq) + mutex.Unlock() + if err != nil { + logstream.Err(fmt.Sprintln("Error searching into LDAP: ", err)) + s := strings.Replace(err.Error(), " ", "%20", -1) + response := fmt.Sprintf("500 %s\n", s) + connClt.Write([]byte(response)) + return + } + + if len(result.Entries) != 1 { + if *debug { + logstream.Debug("Got no result, returning 500") + } + s := strings.Replace("No route defined", " ", "%20", -1) + response := fmt.Sprintf("500 %s\n", s) + connClt.Write([]byte(response)) + return + } + if *debug { + logstream.Debug("Got result, returning " + result.Entries[0].Attributes[0].Values[0]) + } + s := strings.Replace(fmt.Sprintf("FILTER relay:[%s]", result.Entries[0].Attributes[0].Values[0]), " ", "%20", -1) + connClt.Write([]byte(fmt.Sprintf("200 %s\n", s))) + } else { + if *debug { + logstream.Debug("Incorrect input format : " + string(buf[:readlen-1])) + } + s := strings.Replace("Incorrect input format", " ", "%20", -1) + response := fmt.Sprintf("500 %s\n", s) + connClt.Write([]byte(response)) + return + + } +} + +func run() { + logstream.Info("start") + defer logstream.Info("exit") + listener, err := net.Listen("tcp", *listen) + if err != nil { + log.Fatal(fmt.Sprintln("Error listening: ", err)) + } + + conLdap, err := ldap.DialURL(*ldapURL) + defer conLdap.Close() + + err = conLdap.Bind(*ldapUser, *ldapPass) + if err != nil { + logstream.Err(fmt.Sprintln("Error binding LDAP: ", err)) + return + } + + for { + connClt, err := listener.Accept() + if err != nil { + logstream.Err(fmt.Sprintln("Error accepting: ", err)) + } + go handleConnection(connClt, conLdap) + } +} + +func main() { + var e error + + fs := flag.NewFlagSet("mxrouter", flag.ExitOnError) + listen = fs.String("listen-addr", "127.0.0.1:8080", "listen address for server (also via LISTEN env var)") + debug = fs.Bool("debug", false, "log debug information (also via DEBUG env var)") + ldapURL = fs.String("ldap", "", "LDAP Server URL (also via LDAP env var)") + ldapBaseDN = fs.String("ldapDN", "", "LDAP base DN (also via LDAPDN env var)") + ldapUser = fs.String("ldapUser", "", "LDAP user DN (also via LDAPUSER env var)") + ldapPass = fs.String("ldapPass", "", "LDAP user password (also via LDAPPASS env var)") + _ = fs.String("config", "", "config file (optional)") + + // Surcharge de la fonction Usage() + fs.Usage = func() { + fmt.Fprintf(flag.CommandLine.Output(), "%s version %s\n", os.Args[0], version) + fmt.Fprintf(flag.CommandLine.Output(), "Usage:\n") + fs.PrintDefaults() + } + ff.Parse(fs, os.Args[1:], ff.WithEnvVarNoPrefix(), ff.WithConfigFileFlag("config"), ff.WithConfigFileParser(ff.PlainParser)) + + if len(*ldapURL) == 0 || len(*ldapBaseDN) == 0 || len (*ldapUser) == 0 || len(*ldapPass) == 0 { + fs.Usage() + return + } + + if logstream, e = syslog.New(syslog.LOG_MAIL, "mxrouter"); e != nil { + log.Fatal(e) + } + defer logstream.Close() + run() +} +