author | Alberto Bertogli
<albertito@blitiri.com.ar> 2015-10-25 22:38:17 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2015-10-26 03:40:26 UTC |
.gitignore | +12 | -0 |
chasquid.go | +187 | -0 |
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09b424f --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ + +# Ignore anything beginning with a dot: these are usually temporary or +# unimportant. +.* + +# Exceptions to the rules above: files we care about that would otherwise be +# excluded. +!.gitignore + +# The binary. +chasquid + diff --git a/chasquid.go b/chasquid.go new file mode 100644 index 0000000..b6a9188 --- /dev/null +++ b/chasquid.go @@ -0,0 +1,187 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "io" + "math/rand" + "net" + "net/http" + "net/textproto" + "strings" + + "github.com/golang/glog" + "golang.org/x/net/trace" +) + +func main() { + flag.Parse() + + monAddr := ":1099" + glog.Infof("Monitoring HTTP server listening on %s", monAddr) + go http.ListenAndServe(monAddr, nil) + + ListenAndServe() +} + +const ( + hostname = "charqui.com.ar" +) + +func ListenAndServe() { + addr := ":1025" + l, err := net.Listen("tcp", addr) + if err != nil { + glog.Fatalf("Error listening: %v", err) + } + defer l.Close() + + glog.Infof("Server listening on %s", addr) + for { + conn, err := l.Accept() + if err != nil { + glog.Fatalf("Error accepting: %v", err) + } + + sc := &Conn{ + netconn: conn, + tc: textproto.NewConn(conn), + } + go sc.Handle() + } +} + +type Conn struct { + netconn net.Conn + tc *textproto.Conn +} + +func (c *Conn) Handle() { + defer c.netconn.Close() + + tr := trace.New("SMTP", "connection") + defer tr.Finish() + + c.tc.PrintfLine("220 %s ESMTP charquid", hostname) + + var cmd, params string + var err error + +loop: + for { + cmd, params, err = c.readCommand() + if err != nil { + c.tc.PrintfLine("554 error reading command: %v", err) + break + } + + tr.LazyPrintf("-> %s %s", cmd, params) + + var code int + var msg string + + switch cmd { + case "HELO": + code, msg = c.HELO(params) + case "EHLO": + code, msg = c.EHLO(params) + case "HELP": + code, msg = c.HELP(params) + case "QUIT": + c.writeResponse(221, "Be seeing you...") + break loop + default: + code = 500 + msg = "unknown command" + } + + tr.LazyPrintf("<- %d", code) + + err = c.writeResponse(code, msg) + if err != nil { + break + } + } + + if err != nil { + tr.LazyPrintf("exiting with error: %v", err) + } +} + +func (c *Conn) HELO(params string) (code int, msg string) { + types := []string{ + "general store", "used armor dealership", "second-hand bookstore", + "liquor emporium", "antique weapons outlet", "delicatessen", + "jewelers", "quality apparel and accessories", "hardware", + "rare books", "lighting store"} + t := types[rand.Int()%len(types)] + msg = fmt.Sprintf("Hello my friend, welcome to chasqui's %s!", t) + + return 250, msg +} + +func (c *Conn) EHLO(params string) (code int, msg string) { + buf := bytes.NewBuffer(nil) + fmt.Fprintf(buf, hostname+" - Your hour of destiny has come.\n") + fmt.Fprintf(buf, "8BITMIME\n") + fmt.Fprintf(buf, "PIPELINING\n") + fmt.Fprintf(buf, "SIZE 52428800\n") + fmt.Fprintf(buf, "STARTTLS\n") + fmt.Fprintf(buf, "HELP\n") + return 250, buf.String() +} + +func (c *Conn) HELP(params string) (code int, msg string) { + return 214, "hoy por ti, maƱana por mi" +} + +func (c *Conn) NOOP(params string) (code int, msg string) { + return 250, "noooooooooooooooooooop" +} + +func (c *Conn) readCommand() (cmd, params string, err error) { + var msg string + + msg, err = c.tc.ReadLine() + if err != nil { + return "", "", err + } + + sp := strings.SplitN(msg, " ", 2) + cmd = strings.ToUpper(sp[0]) + if len(sp) > 1 { + params = sp[1] + } + + return cmd, params, err +} + +func (c *Conn) writeResponse(code int, msg string) error { + defer c.tc.W.Flush() + + return writeResponse(c.tc.W, code, msg) +} + +// writeResponse writes a multi-line response to the given writer. +// This is the writing version of textproto.Reader.ReadResponse(). +func writeResponse(w io.Writer, code int, msg string) error { + var i int + lines := strings.Split(msg, "\n") + + // The first N-1 lines use "<code>-<text>". + for i = 0; i < len(lines)-2; i++ { + _, err := w.Write([]byte(fmt.Sprintf("%d-%s\r\n", code, lines[i]))) + if err != nil { + return err + } + } + + // The last line uses "<code> <text>". + _, err := w.Write([]byte(fmt.Sprintf("%d %s\r\n", code, lines[i]))) + if err != nil { + return err + } + + return nil +}