1package xmpp 2 3import ( 4 "encoding/xml" 5 "io" 6 "net" 7 "strings" 8 9 "github.com/coyim/coyim/tls" 10 "github.com/coyim/coyim/xmpp/data" 11 "github.com/coyim/coyim/xmpp/interfaces" 12 13 "golang.org/x/net/proxy" 14) 15 16// A dialer connects and authenticates to an XMPP server 17type dialer struct { 18 // JID represents the user's "bare JID" as specified in RFC 6120 19 JID string 20 21 // password used to authenticate to the server 22 password string 23 24 // serverAddress associates a particular FQDN with the origin domain specified by the JID. 25 serverAddress string 26 27 // proxy configures a proxy used to connect to the server 28 proxy proxy.Dialer 29 30 // config configures the XMPP protocol 31 config data.Config 32 33 verifier tls.Verifier 34 tlsConnFactory tls.Factory 35} 36 37// DialerFactory returns a new xmpp dialer 38func DialerFactory(verifier tls.Verifier, connFactory tls.Factory) interfaces.Dialer { 39 return &dialer{verifier: verifier, tlsConnFactory: connFactory} 40} 41 42func (d *dialer) SetJID(v string) { 43 d.JID = v 44} 45 46func (d *dialer) SetServerAddress(v string) { 47 d.serverAddress = v 48} 49 50func (d *dialer) SetPassword(v string) { 51 d.password = v 52} 53 54func (d *dialer) SetProxy(v proxy.Dialer) { 55 d.proxy = v 56} 57 58func (d *dialer) SetConfig(v data.Config) { 59 d.config = v 60} 61 62func (d *dialer) Config() data.Config { 63 return d.config 64} 65 66func (d *dialer) ServerAddress() string { 67 return d.serverAddress 68} 69 70func (d *dialer) hasCustomServer() bool { 71 return d.serverAddress != "" 72} 73 74func (d *dialer) getJIDLocalpart() string { 75 parts := strings.SplitN(d.JID, "@", 2) 76 return parts[0] 77} 78 79func (d *dialer) getJIDDomainpart() string { 80 //TODO: remove any existing resourcepart although our doc says it is a bare JID (without resourcepart) but it would be nice 81 parts := strings.SplitN(d.JID, "@", 2) 82 return parts[1] 83} 84 85// GetServer returns the "hardcoded" server chosen if available, otherwise returns the domainpart from the JID. The server contains port information 86func (d *dialer) GetServer() string { 87 if d.hasCustomServer() { 88 return d.serverAddress 89 } 90 91 return d.getFallbackServer() 92} 93 94func (d *dialer) getFallbackServer() string { 95 return net.JoinHostPort(d.getJIDDomainpart(), "5222") 96} 97 98// RegisterAccount registers an account on the server. The formCallback is used to handle XMPP forms. 99func (d *dialer) RegisterAccount(formCallback data.FormCallback) (interfaces.Conn, error) { 100 //TODO: notify in case the feature is not supported 101 d.config.CreateCallback = formCallback 102 return d.Dial() 103} 104 105// Dial creates a new connection to an XMPP server with the given proxy 106// and authenticates as the given user. 107func (d *dialer) Dial() (interfaces.Conn, error) { 108 // Starting an XMPP connection comprises two parts: 109 // - Opening a transport channel (TCP) 110 // - Opening an XML stream over the transport channel 111 112 // RFC 6120, section 3 113 conn, err := d.newTCPConn() 114 if err != nil { 115 return nil, err 116 } 117 118 // RFC 6120, section 4 119 return d.setupStream(conn) 120} 121 122// RFC 6120, Section 4.2 123func (d *dialer) setupStream(conn net.Conn) (interfaces.Conn, error) { 124 c := newConn() 125 c.config = d.config 126 c.originDomain = d.getJIDDomainpart() 127 d.bindTransport(c, conn) 128 129 if err := d.negotiateStreamFeatures(c, conn); err != nil { 130 return nil, err 131 } 132 133 go c.watchKeepAlive(conn) 134 go c.watchPings() 135 136 return c, nil 137} 138 139// RFC 6120, section 4.3.2 140func (d *dialer) negotiateStreamFeatures(c interfaces.Conn, conn net.Conn) error { 141 if err := c.SendInitialStreamHeader(); err != nil { 142 return err 143 } 144 145 // STARTTLS MUST be the first feature to be negotiated 146 if err := d.negotiateSTARTTLS(c, conn); err != nil { 147 return err 148 } 149 150 if registered, err := d.negotiateInBandRegistration(c); err != nil || registered { 151 return err 152 } 153 154 // SASL negotiation. RFC 6120, section 6 155 if err := d.negotiateSASL(c); err != nil { 156 return err 157 } 158 159 //TODO: negotiate other features 160 161 return nil 162} 163 164func (d *dialer) bindTransport(c interfaces.Conn, conn net.Conn) { 165 c.SetInOut(makeInOut(conn, d.config)) 166 c.SetRawOut(conn) 167 c.SetKeepaliveOut(&timeoutableConn{conn, keepaliveTimeout}) 168 c.SetServerAddress(d.serverAddress) 169} 170 171func makeInOut(conn io.ReadWriter, config data.Config) (in *xml.Decoder, out io.Writer) { 172 if config.InLog != nil { 173 in = xml.NewDecoder(io.TeeReader(conn, config.InLog)) 174 } else { 175 in = xml.NewDecoder(conn) 176 } 177 178 if config.OutLog != nil { 179 out = io.MultiWriter(conn, config.OutLog) 180 } else { 181 out = conn 182 } 183 184 return 185} 186