// 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()
}
Response:
20 (Success), text/plain