[tor-commits] [goptlib/master] Bug 28940: add support for LOG.

dcf at torproject.org dcf at torproject.org
Thu Apr 11 18:32:04 UTC 2019


commit 350ea810838a99d9fc9bf7e3523fcc5635691eed
Author: David Fifield <david at 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)
+		}
+	}
+}





More information about the tor-commits mailing list