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