// misfin - a prototype misfin client in Go // // go build misfin.go // // then see misfind.go for how to generate a certificate; I'm using the // same certificate for both the server and the client: // // echo "some message here" | ./misfin usercert.pem ed@example.org package main import ( "bufio" "crypto/tls" "fmt" "io" "log" "os" "regexp" "strings" // optional, adds pledge on OpenBSD "suah.dev/protect" ) func main() { if len(os.Args) != 3 { fmt.Fprintln(os.Stderr, "Usage: usercert.pem misfin://[user]@[hostname] < ...") os.Exit(64) } recipient := os.Args[2] // be nice and prefix with misfin:// if they forget to put that on if recipient[0:9] != "misfin://" { recipient = "misfin://" + recipient } mfre := regexp.MustCompile("^misfin://[^@]+@([^ ]+)$") match := mfre.FindStringSubmatch(recipient) if match == nil { fmt.Fprintln(os.Stderr, "Usage: usercert.pem misfin://[user]@[hostname] < ...") os.Exit(65) } server_addr := match[1] + ":1958" header := fmt.Sprintf("%s ", recipient) // maximum, minus header, minus trailing \r\n remains := 2048 - len(header) - 2 if remains <= 0 { // not judging, but don't want negative remains to read on fmt.Fprintln(os.Stderr, "misfin: recipient is too long?") os.Exit(1) } wlim := io.LimitReader(os.Stdin, int64(remains)) reader := bufio.NewReader(wlim) var message string for { line, err := reader.ReadString('\n') if err != nil { // KLUGE this tolerate incomplete lines (those // lacking the POSIX-mandated ultimate \n) if err == io.EOF { line = strings.TrimRight(line, "\r\n") message += line break } log.Println(err) return } message += line } // avoid accidental \r\n that would terminate our message early // on the server message = strings.Replace(message, "\r", "", -1) // we'll add this back on in a moment message = strings.TrimRight(message, "\n") if len(message) == 0 { fmt.Fprintln(os.Stderr, "misfin: no message") os.Exit(1) } message += "\r\n" client_cert, lerr := tls.LoadX509KeyPair(os.Args[1], os.Args[1]) if lerr != nil { log.Println(lerr) return } ssl_config := &tls.Config{ // misfin specification calls for a floor of TLS 1.2 // (section 3, TLS) MinVersion: tls.VersionTLS12, Certificates: []tls.Certificate{client_cert}, InsecureSkipVerify: true, } protect.Pledge("inet rpath stdio unveil") protect.Unveil("/etc/resolv.conf", "r") protect.Unveil("/etc/ssl/cert.pem", "r") protect.UnveilBlock() conn, err := tls.Dial("tcp", server_addr, ssl_config) if err != nil { log.Println(err) return } protect.Pledge("stdio") // NOTE this used to be two separate Writes but that confused // the other implementations, probably because they assumed // (incorrectly) that the whole request would be available in a // single read conn.Write([]byte(header + message)) // the reference python implementation doesn't work well when // this call is made here. so let's not do that... //conn.CloseWrite() // hopefully the response fits, the cap is mostly for when the // server is malicious and tries to stream infinite bytes rlim := io.LimitReader(conn, 512) resprdr := bufio.NewReader(rlim) resp, serr := resprdr.ReadString('\n') if serr != nil { fmt.Fprintln(os.Stderr, "misfin: warning: read error: %s", serr.Error()) } resp = strings.Replace(resp, "\r", "", -1) resp = strings.TrimRight(resp, "\n") fmt.Println(resp) conn.Close() }