diff --git a/cmd/ximap/proxy/proxy b/cmd/ximap/proxy/proxy new file mode 100755 index 0000000..085dfb9 Binary files /dev/null and b/cmd/ximap/proxy/proxy differ diff --git a/cmd/ximap/proxy/proxy.go b/cmd/ximap/proxy/proxy.go new file mode 100644 index 0000000..d5ceac6 --- /dev/null +++ b/cmd/ximap/proxy/proxy.go @@ -0,0 +1,83 @@ +package main + +import ( + "bufio" + "bytes" + "flag" + "fmt" + "net" + + "github.com/golang/glog" +) + +var ( + saddr = flag.String("saddr", ":11143", "address to listen on") + caddr = flag.String("caddr", "localhost:10143", "remote address to connect to") +) + +// scanLines reimplements bufio.ScanLines without eating '\r' in the input. +func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + if i := bytes.IndexByte(data, '\n'); i >= 0 { + // We have a full newline-terminated line. + return i + 1, data[0:i], nil + } + // If we're at EOF, we have a final, non-terminated line. Return it. + if atEOF { + return len(data), data, nil + } + // Request more data. + return 0, nil, nil +} + +func prefixCopy(prefix string, src, dst net.Conn) { + defer src.Close() + defer dst.Close() + + glog.Infof("%s proxying %s -> %s", prefix, src.LocalAddr(), dst.RemoteAddr()) + scanner := bufio.NewScanner(src) + scanner.Split(scanLines) + for scanner.Scan() { + glog.Infoln(prefix, scanner.Text()) // Println will add back the final '\n' + if _, err := fmt.Fprintln(dst, scanner.Text()); err != nil { + glog.Errorf("%s error writing to dst: %v", prefix, err) + return + } + } + if err := scanner.Err(); err != nil { + glog.Errorf("%s error reading from src: %v", prefix, err) + return + } +} + +func proxy(id int, client net.Conn, remoteAddr string) { + remote, err := net.Dial("tcp", remoteAddr) + if err != nil { + glog.Errorf("Failed to dial %s: %v", remoteAddr, err) + return + } + go prefixCopy(fmt.Sprintf("[%d] S:", id), remote, client) + go prefixCopy(fmt.Sprintf("[%d] C:", id), client, remote) +} + +func main() { + flag.Parse() + defer glog.Flush() + + glog.Infof("Proxying %s -> %s", *saddr, *caddr) + ln, err := net.Listen("tcp", *saddr) + if err != nil { + glog.Exitf("Failed to listen on %s: %v", *saddr, err) + } + var id int + for { + conn, err := ln.Accept() + if err != nil { + glog.Exitf("Failed to accept on %s: %v", *saddr, err) + } + proxy(id, conn, *caddr) + id++ + } +} diff --git a/cmd/ximap/testclient/testclient b/cmd/ximap/testclient/testclient new file mode 100755 index 0000000..192f9b3 Binary files /dev/null and b/cmd/ximap/testclient/testclient differ diff --git a/cmd/ximap/testclient/testclient.go b/cmd/ximap/testclient/testclient.go new file mode 100644 index 0000000..d4930bd --- /dev/null +++ b/cmd/ximap/testclient/testclient.go @@ -0,0 +1,62 @@ +package main + +import ( + "bytes" + "encoding/base64" + "flag" + "fmt" + "io" + "net" + "os" + "strings" + + "github.com/golang/glog" +) + +var addr = flag.String("addr", "localhost:10143", "IMAP addr") + +type client struct { + net.Conn + msgCount int +} + +func (c *client) cmd(tagged bool, msg ...string) { + w := io.MultiWriter(c.Conn, os.Stdout) + var b bytes.Buffer + if tagged { + fmt.Fprintf(&b, "a%04d ", c.msgCount) + c.msgCount++ + } + fmt.Fprintln(&b, strings.Join(msg, " ")) + if _, err := io.Copy(w, &b); err != nil { + panic(err) + } +} + +func (c *client) Close() error { + return c.Conn.Close() +} + +func main() { + flag.Parse() + defer glog.Flush() + + conn, err := net.Dial("tcp", *addr) + if err != nil { + glog.Exitf("Failed to connect to %q: %v", *addr, err) + } + c := &client{Conn: conn} + defer c.Close() + defer func() { + if _, err := io.Copy(os.Stdout, c); err != nil { + glog.Exitf("Failed to read from connection: %v", err) + } + }() + + c.cmd(true, "AUTHENTICATE PLAIN ") + c.cmd(false, base64.StdEncoding.EncodeToString([]byte("\x00username\x00password"))) + c.cmd(true, "SELECT INBOX") + c.cmd(true, "FETCH 1:* (FLAGS INTERNALDATE RFC822.SIZE BODY[HEADER])") + c.cmd(true, "CLOSE") + c.cmd(true, "LOGOUT") +}