commit 350ea810838a99d9fc9bf7e3523fcc5635691eed
Author: David Fifield <da...@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 mailing list
tor-commits@lists.torproject.org
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to