commit 350ea810838a99d9fc9bf7e3523fcc5635691eed Author: David Fifield david@bamsoftware.com Date: Thu Feb 7 19:56:13 2019 -0700
Bug 28940: add support for LOG. --- pt.go | 65 +++++++++++++++++++++++++++++++ pt_test.go | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+)
diff --git a/pt.go b/pt.go index f7929e6..23893f4 100644 --- a/pt.go +++ b/pt.go @@ -24,6 +24,7 @@ // conn, err := ln.AcceptSocks() // if err != nil { // if e, ok := err.(net.Error); ok && e.Temporary() { +// pt.Log(pt.LogSeverityError, "accept error: " + err.Error()) // continue // } // return err @@ -83,6 +84,7 @@ // if e, ok := err.(net.Error); ok && e.Temporary() { // continue // } +// pt.Log(pt.LogSeverityError, "accept error: " + err.Error()) // return err // } // go handler(conn) @@ -347,6 +349,69 @@ func ProxyDone() { fmt.Fprintf(Stdout, "PROXY DONE\n") }
+// Unexported type to represent log severities, preventing external callers from +// inventing new severity strings that may violate quoting rules. +// +// pt-spec.txt section 3.3.4 specifies quoting for MESSAGE, but not for +// SEVERITY, and the example shows an unquoted "SEVERITY=debug". While we know +// tor's parser permits quoting of SEVERITY, it's not actually specified. +// Therefore we we need to guard against callers passing a string that violates +// the global protocol constraint of "any US-ASCII character but NUL or NL." So +// here, we instantiate exactly the five supported severities, using a type that +// cannot be constructed outside the package. +type logSeverity struct { + string +} + +// Severity levels for the Log function. +var ( + LogSeverityError = logSeverity{"error"} + LogSeverityWarning = logSeverity{"warning"} + LogSeverityNotice = logSeverity{"notice"} + LogSeverityInfo = logSeverity{"info"} + LogSeverityDebug = logSeverity{"debug"} +) + +// Encode a string according to the CString rules of section 2.1.1 in +// control-spec.txt. +// CString = DQUOTE *qcontent DQUOTE +// "...in a CString, the escapes '\n', '\t', '\r', and the octal escapes '\0' +// ... '\377' represent newline, tab, carriage return, and the 256 possible +// octet values respectively." +// RFC 2822 section 3.2.5 in turn defines what byte values we need to escape: +// everything but +// NO-WS-CTL / ; Non white space controls +// %d33 / ; The rest of the US-ASCII +// %d35-91 / ; characters not including "" +// %d93-126 ; or the quote character +// Technically control-spec.txt requires us to escape the space character (32), +// but it is an error in the spec: https://bugs.torproject.org/29432. +// +// We additionally need to ensure that whatever we return passes argIsSafe, +// because strings encoded by this function are printed verbatim by Log. +func encodeCString(s string) string { + result := bytes.NewBuffer([]byte{}) + result.WriteByte('"') + for _, c := range []byte(s) { + if c == 32 || c == 33 || (35 <= c && c <= 91) || (93 <= c && c <= 126) { + result.WriteByte(c) + } else { + fmt.Fprintf(result, "\%03o", c) + } + } + result.WriteByte('"') + return result.String() +} + +// Emit a LOG message with the given severity (one of LogSeverityError, +// LogSeverityWarning, LogSeverityNotice, LogSeverityInfo, or LogSeverityDebug). +func Log(severity logSeverity, message string) { + // "<Message> contains the log message which can be a String or CString..." + // encodeCString always makes the string safe to emit; i.e., it + // satisfies argIsSafe. + line("LOG", "SEVERITY="+severity.string, "MESSAGE="+encodeCString(message)) +} + // Get a pluggable transports version offered by Tor and understood by us, if // any. The only version we understand is "1". This function reads the // environment variable TOR_PT_MANAGED_TRANSPORT_VER. diff --git a/pt_test.go b/pt_test.go index 70f5533..d74d6d7 100644 --- a/pt_test.go +++ b/pt_test.go @@ -959,3 +959,131 @@ func TestMakeStateDir(t *testing.T) { t.Errorf("MakeStateDir with a subdirectory of a file unexpectedly succeeded") } } + +// Compare with unescape_string in tor's src/lib/encoding/cstring.c. That +// function additionally allows hex escapes, but control-spec.txt's CString +// doesn't say anything about that. +func decodeCString(enc string) (string, error) { + var result bytes.Buffer + b := []byte(enc) + state := "^" + number := 0 + i := 0 + for i < len(b) { + c := b[i] + switch state { + case "^": + if c != '"' { + return "", fmt.Errorf("missing start quote") + } + state = "." + case ".": + switch c { + case '\': + state = "\" + case '"': + state = "$" + default: + result.WriteByte(c) + } + case "\": + switch c { + case 'n': + result.WriteByte('\n') + state = "." + case 't': + result.WriteByte('\t') + state = "." + case 'r': + result.WriteByte('\r') + state = "." + case '"', '\': + result.WriteByte(c) + state = "." + case '0', '1', '2', '3', '4', '5', '6', '7': + number = int(c - '0') + state = "o1" + default: + return "", fmt.Errorf("unknown escape \%c", c) + } + case "o1": // 1 octal digit read + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7': + number = number*8 + int(c-'0') + state = "o2" + default: + if number > 255 { + return "", fmt.Errorf("invalid octal escape") + } + result.WriteByte(byte(number)) + state = "." + continue // process the current byte again + } + case "o2": // 2 octal digits read + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7': + number = number*8 + int(c-'0') + if number > 255 { + return "", fmt.Errorf("invalid octal escape") + } + result.WriteByte(byte(number)) + state = "." + default: + if number > 255 { + return "", fmt.Errorf("invalid octal escape") + } + result.WriteByte(byte(number)) + state = "." + continue // process the current byte again + } + case "$": + return "", fmt.Errorf("trailing garbage") + } + i++ + } + if state != "$" { + return "", fmt.Errorf("unexpected end of string") + } + return result.String(), nil +} + +func roundtripCString(src string) (string, error) { + enc := encodeCString(src) + dec, err := decodeCString(enc) + if err != nil { + return enc, fmt.Errorf("failed to decode: %+q → %+q: %v", src, enc, err) + } + if dec != src { + return enc, fmt.Errorf("roundtrip failed: %+q → %+q → %+q", src, enc, dec) + } + return enc, nil +} + +func TestEncodeCString(t *testing.T) { + tests := []string{ + "", + """, + """", + "abc"def", + "\", + "\\", + "\x0123abc", // trap here is if you encode '\x01' as "\1"; it would join with the following digits: "\123abc". + "\n\r\t\x7f", + "\377", + } + allBytes := make([]byte, 256) + for i := 0; i < len(allBytes); i++ { + allBytes[i] = byte(i) + } + tests = append(tests, string(allBytes)) + + for _, test := range tests { + enc, err := roundtripCString(test) + if err != nil { + t.Error(err) + } + if !argIsSafe(enc) { + t.Errorf("escaping %+q resulted in non-safe %+q", test, enc) + } + } +}
tor-commits@lists.torproject.org