1/*
2 * Copyright (c) 2014, Yawning Angel <yawning at schwanenlied dot me>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *  * Redistributions of source code must retain the above copyright notice,
9 *    this list of conditions and the following disclaimer.
10 *
11 *  * Redistributions in binary form must reproduce the above copyright notice,
12 *    this list of conditions and the following disclaimer in the documentation
13 *    and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 * POSSIBILITY OF SUCH DAMAGE.
26 */
27
28package main
29
30import (
31	"errors"
32	"fmt"
33	"net"
34	"net/url"
35	"os"
36	"strconv"
37
38	"git.torproject.org/pluggable-transports/goptlib.git"
39)
40
41// This file contains things that probably should be in goptlib but are not
42// yet or are not finalized.
43
44func ptEnvError(msg string) error {
45	line := []byte(fmt.Sprintf("ENV-ERROR %s\n", msg))
46	_, _ = pt.Stdout.Write(line)
47	return errors.New(msg)
48}
49
50func ptProxyError(msg string) error {
51	line := []byte(fmt.Sprintf("PROXY-ERROR %s\n", msg))
52	_, _ = pt.Stdout.Write(line)
53	return errors.New(msg)
54}
55
56func ptProxyDone() {
57	line := []byte("PROXY DONE\n")
58	_, _ = pt.Stdout.Write(line)
59}
60
61func ptIsClient() (bool, error) {
62	clientEnv := os.Getenv("TOR_PT_CLIENT_TRANSPORTS")
63	serverEnv := os.Getenv("TOR_PT_SERVER_TRANSPORTS")
64	if clientEnv != "" && serverEnv != "" {
65		return false, ptEnvError("TOR_PT_[CLIENT,SERVER]_TRANSPORTS both set")
66	} else if clientEnv != "" {
67		return true, nil
68	} else if serverEnv != "" {
69		return false, nil
70	}
71	return false, errors.New("not launched as a managed transport")
72}
73
74func ptGetProxy() (*url.URL, error) {
75	specString := os.Getenv("TOR_PT_PROXY")
76	if specString == "" {
77		return nil, nil
78	}
79	spec, err := url.Parse(specString)
80	if err != nil {
81		return nil, ptProxyError(fmt.Sprintf("failed to parse proxy config: %s", err))
82	}
83
84	// Validate the TOR_PT_PROXY uri.
85	if !spec.IsAbs() {
86		return nil, ptProxyError("proxy URI is relative, must be absolute")
87	}
88	if spec.Path != "" {
89		return nil, ptProxyError("proxy URI has a path defined")
90	}
91	if spec.RawQuery != "" {
92		return nil, ptProxyError("proxy URI has a query defined")
93	}
94	if spec.Fragment != "" {
95		return nil, ptProxyError("proxy URI has a fragment defined")
96	}
97
98	switch spec.Scheme {
99	case "http":
100		// The most forgiving of proxies.
101
102	case "socks4a":
103		if spec.User != nil {
104			_, isSet := spec.User.Password()
105			if isSet {
106				return nil, ptProxyError("proxy URI specified SOCKS4a and a password")
107			}
108		}
109
110	case "socks5":
111		if spec.User != nil {
112			// UNAME/PASSWD both must be between 1 and 255 bytes long. (RFC1929)
113			user := spec.User.Username()
114			passwd, isSet := spec.User.Password()
115			if len(user) < 1 || len(user) > 255 {
116				return nil, ptProxyError("proxy URI specified a invalid SOCKS5 username")
117			}
118			if !isSet || len(passwd) < 1 || len(passwd) > 255 {
119				return nil, ptProxyError("proxy URI specified a invalid SOCKS5 password")
120			}
121		}
122
123	default:
124		return nil, ptProxyError(fmt.Sprintf("proxy URI has invalid scheme: %s", spec.Scheme))
125	}
126
127	_, err = resolveAddrStr(spec.Host)
128	if err != nil {
129		return nil, ptProxyError(fmt.Sprintf("proxy URI has invalid host: %s", err))
130	}
131
132	return spec, nil
133}
134
135// Sigh, pt.resolveAddr() isn't exported.  Include our own getto version that
136// doesn't work around #7011, because we don't work with pre-0.2.5.x tor, and
137// all we care about is validation anyway.
138func resolveAddrStr(addrStr string) (*net.TCPAddr, error) {
139	ipStr, portStr, err := net.SplitHostPort(addrStr)
140	if err != nil {
141		return nil, err
142	}
143
144	if ipStr == "" {
145		return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a host part", addrStr))
146	}
147	if portStr == "" {
148		return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a port part", addrStr))
149	}
150	ip := net.ParseIP(ipStr)
151	if ip == nil {
152		return nil, net.InvalidAddrError(fmt.Sprintf("not an IP string: %q", ipStr))
153	}
154	port, err := strconv.ParseUint(portStr, 10, 16)
155	if err != nil {
156		return nil, net.InvalidAddrError(fmt.Sprintf("not a Port string: %q", portStr))
157	}
158
159	return &net.TCPAddr{IP: ip, Port: int(port), Zone: ""}, nil
160}
161
162// Feature #15435 adds a new env var for determining if Tor keeps stdin
163// open for use in termination detection.
164func ptShouldExitOnStdinClose() bool {
165	return os.Getenv("TOR_PT_EXIT_ON_STDIN_CLOSE") == "1"
166}
167