1// Copyright (c) 2012-2014 Jeremy Latt
2// Copyright (c) 2014-2015 Edmund Huber
3// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
4// released under the MIT license
5
6package irc
7
8import (
9	"errors"
10	"net"
11
12	"github.com/ergochat/ergo/irc/flatip"
13	"github.com/ergochat/ergo/irc/modes"
14	"github.com/ergochat/ergo/irc/utils"
15)
16
17var (
18	errBadGatewayAddress = errors.New("PROXY/WEBIRC commands are not accepted from this IP address")
19	errBadProxyLine      = errors.New("Invalid PROXY/WEBIRC command")
20)
21
22const (
23	// https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
24	// "a 108-byte buffer is always enough to store all the line and a trailing zero
25	// for string processing."
26	maxProxyLineLen = 107
27)
28
29type webircConfig struct {
30	PasswordString string  `yaml:"password"`
31	Password       []byte  `yaml:"password-bytes"`
32	Fingerprint    *string // legacy name for certfp, #1050
33	Certfp         string
34	Hosts          []string
35	allowedNets    []net.IPNet
36}
37
38// Populate fills out our password or fingerprint.
39func (wc *webircConfig) Populate() (err error) {
40	if wc.PasswordString != "" {
41		wc.Password, err = decodeLegacyPasswordHash(wc.PasswordString)
42		if err != nil {
43			return
44		}
45	}
46
47	certfp := wc.Certfp
48	if certfp == "" && wc.Fingerprint != nil {
49		certfp = *wc.Fingerprint
50	}
51	if certfp != "" {
52		wc.Certfp, err = utils.NormalizeCertfp(certfp)
53	}
54	if err != nil {
55		return
56	}
57
58	if wc.Certfp == "" && wc.PasswordString == "" {
59		return errors.New("webirc block has no certfp or password specified")
60	}
61
62	wc.allowedNets, err = utils.ParseNetList(wc.Hosts)
63	return err
64}
65
66// ApplyProxiedIP applies the given IP to the client.
67func (client *Client) ApplyProxiedIP(session *Session, proxiedIP net.IP, tls bool) (err error, quitMsg string) {
68	// PROXY and WEBIRC are never accepted from a Tor listener, even if the address itself
69	// is whitelisted. Furthermore, don't accept PROXY or WEBIRC if we already accepted
70	// a proxied IP from any source (PROXY, WEBIRC, or X-Forwarded-For):
71	if session.isTor || session.proxiedIP != nil {
72		return errBadProxyLine, ""
73	}
74
75	// ensure IP is sane
76	if proxiedIP == nil {
77		return errBadProxyLine, "proxied IP is not valid"
78	}
79	proxiedIP = proxiedIP.To16()
80
81	isBanned, requireSASL, banMsg := client.server.checkBans(client.server.Config(), proxiedIP, true)
82	if isBanned {
83		return errBanned, banMsg
84	}
85	client.requireSASL = requireSASL
86	if requireSASL {
87		client.requireSASLMessage = banMsg
88	}
89	// successfully added a limiter entry for the proxied IP;
90	// remove the entry for the real IP if applicable (#197)
91	client.server.connectionLimiter.RemoveClient(flatip.FromNetIP(session.realIP))
92
93	// given IP is sane! override the client's current IP
94	client.server.logger.Info("connect-ip", "Accepted proxy IP for client", proxiedIP.String())
95
96	client.stateMutex.Lock()
97	defer client.stateMutex.Unlock()
98	client.proxiedIP = proxiedIP
99	session.proxiedIP = proxiedIP
100	// nickmask will be updated when the client completes registration
101	// set tls info
102	session.certfp = ""
103	session.peerCerts = nil
104	client.SetMode(modes.TLS, tls)
105
106	return nil, ""
107}
108
109// handle the PROXY command: http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
110// PROXY must be sent as the first message in the session and has the syntax:
111// PROXY TCP[46] SOURCEIP DESTIP SOURCEPORT DESTPORT\r\n
112// unfortunately, an ipv6 SOURCEIP can start with a double colon; in this case,
113// the message is invalid IRC and can't be parsed normally, hence the special handling.
114func handleProxyCommand(server *Server, client *Client, session *Session, line string) (err error) {
115	var quitMsg string
116	defer func() {
117		if err != nil {
118			if quitMsg == "" {
119				quitMsg = client.t("Bad or unauthorized PROXY command")
120			}
121			client.Quit(quitMsg, session)
122		}
123	}()
124
125	ip, err := utils.ParseProxyLineV1(line)
126	if err != nil {
127		return err
128	} else if ip == nil {
129		return nil
130	}
131
132	if utils.IPInNets(client.realIP, server.Config().Server.proxyAllowedFromNets) {
133		// assume PROXY connections are always secure
134		err, quitMsg = client.ApplyProxiedIP(session, ip, true)
135		return
136	} else {
137		// real source IP is not authorized to issue PROXY:
138		return errBadGatewayAddress
139	}
140}
141