From be86684e967507606074407752f33aed936b3f2b Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 26 Feb 2018 21:44:03 -0500 Subject: [PATCH] implement #199 --- irc/client.go | 1 + irc/config.go | 1 + irc/getters.go | 24 ++++++++++++++++++++++++ irc/handlers.go | 39 ++++++++++++++++++--------------------- irc/nickname.go | 12 ++++++------ irc/server.go | 25 ++++++++++++++++++++++--- oragono.yaml | 7 +++++++ 7 files changed, 79 insertions(+), 30 deletions(-) diff --git a/irc/client.go b/irc/client.go index e632b3c0..09b60c29 100644 --- a/irc/client.go +++ b/irc/client.go @@ -65,6 +65,7 @@ type Client struct { nickMaskString string // cache for nickmask string since it's used with lots of replies nickTimer *NickTimer operName string + preregNick string proxiedIP net.IP // actual remote IP if using the PROXY protocol quitMessage string rawHostname string diff --git a/irc/config.go b/irc/config.go index 34155a47..a5600663 100644 --- a/irc/config.go +++ b/irc/config.go @@ -61,6 +61,7 @@ func (conf *PassConfig) PasswordBytes() []byte { type AccountConfig struct { Registration AccountRegistrationConfig AuthenticationEnabled bool `yaml:"authentication-enabled"` + SkipServerPassword bool `yaml:"skip-server-password"` NickReservation NickReservationConfig `yaml:"nick-reservation"` } diff --git a/irc/getters.go b/irc/getters.go index 4d37057d..29e32361 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -142,6 +142,30 @@ func (client *Client) SetAccountName(account string) (changed bool) { return } +func (client *Client) Authorized() bool { + client.stateMutex.RLock() + defer client.stateMutex.RUnlock() + return client.authorized +} + +func (client *Client) SetAuthorized(authorized bool) { + client.stateMutex.Lock() + defer client.stateMutex.Unlock() + client.authorized = authorized +} + +func (client *Client) PreregNick() string { + client.stateMutex.RLock() + defer client.stateMutex.RUnlock() + return client.preregNick +} + +func (client *Client) SetPreregNick(preregNick string) { + client.stateMutex.Lock() + defer client.stateMutex.Unlock() + client.preregNick = preregNick +} + func (client *Client) HasMode(mode modes.Mode) bool { client.stateMutex.RLock() defer client.stateMutex.RUnlock() diff --git a/irc/handlers.go b/irc/handlers.go index fecabba0..fbb574a5 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -319,6 +319,10 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, // let the SASL handler do its thing exiting := handler(server, client, client.saslMechanism, data, rb) + if client.LoggedIntoAccount() && server.AccountConfig().SkipServerPassword { + client.SetAuthorized(true) + } + // wait 'til SASL is done before emptying the sasl vars client.saslInProgress = false client.saslMechanism = "" @@ -491,9 +495,8 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo rb.Add(nil, server.name, "CAP", client.nick, "ACK", capString) case "END": - if !client.registered { + if !client.Registered() { client.capState = caps.NegotiatedState - server.tryRegister(client) } default: @@ -1633,12 +1636,12 @@ func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res // NICK func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { - if !client.authorized { - client.Quit("Bad password") - return true + if client.Registered() { + performNickChange(server, client, client, msg.Params[0], rb) + } else { + client.SetPreregNick(msg.Params[0]) } - - return performNickChange(server, client, client, msg.Params[0], rb) + return false } // NOTICE {,} @@ -1831,14 +1834,14 @@ func partHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp // PASS func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { - if client.registered { + if client.Registered() { rb.Add(nil, server.name, ERR_ALREADYREGISTRED, client.nick, client.t("You may not reregister")) return false } // if no password exists, skip checking if len(server.password) == 0 { - client.authorized = true + client.SetAuthorized(true) return false } @@ -1850,7 +1853,7 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp return true } - client.authorized = true + client.SetAuthorized(true) return false } @@ -1942,7 +1945,7 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R // http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { // only allow unregistered clients to use this command - if client.registered || client.proxiedIP != nil { + if client.Registered() || client.proxiedIP != nil { return false } @@ -2106,7 +2109,8 @@ func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re rb.Add(nil, server.name, ERR_NOSUCHNICK, client.nick, msg.Params[0], client.t("No such nick")) return false } - return performNickChange(server, client, target, msg.Params[1], rb) + performNickChange(server, client, target, msg.Params[1], rb) + return false } // SCENE @@ -2320,16 +2324,11 @@ func unKLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R // USER * 0 func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { - if client.registered { + if client.Registered() { rb.Add(nil, server.name, ERR_ALREADYREGISTRED, client.nick, client.t("You may not reregister")) return false } - if !client.authorized { - client.Quit("Bad password") - return true - } - if client.username != "" && client.realname != "" { return false } @@ -2350,8 +2349,6 @@ func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp client.realname = msg.Params[3] } - server.tryRegister(client) - return false } @@ -2403,7 +2400,7 @@ func versionHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R // WEBIRC [:flag1 flag2=x flag3] func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { // only allow unregistered clients to use this command - if client.registered || client.proxiedIP != nil { + if client.Registered() || client.proxiedIP != nil { return false } diff --git a/irc/nickname.go b/irc/nickname.go index b0728b7a..4251e387 100644 --- a/irc/nickname.go +++ b/irc/nickname.go @@ -23,6 +23,7 @@ var ( } ) +// returns whether the change succeeded or failed func performNickChange(server *Server, client *Client, target *Client, newnick string, rb *ResponseBuffer) bool { nickname := strings.TrimSpace(newnick) cfnick, err := CasefoldName(nickname) @@ -38,7 +39,7 @@ func performNickChange(server *Server, client *Client, target *Client, newnick s } if target.Nick() == nickname { - return false + return true } hadNick := target.HasNick() @@ -49,7 +50,7 @@ func performNickChange(server *Server, client *Client, target *Client, newnick s rb.Add(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, client.t("Nickname is already in use")) return false } else if err == errNicknameReserved { - client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, client.t("Nickname is reserved by a different account")) + rb.Add(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, client.t("Nickname is reserved by a different account")) return false } else if err != nil { rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "NICK", fmt.Sprintf(client.t("Could not set or change nickname: %s"), err.Error())) @@ -67,12 +68,11 @@ func performNickChange(server *Server, client *Client, target *Client, newnick s } } - if target.registered { + if target.Registered() { client.server.monitorManager.AlertAbout(target, true) - } else { - server.tryRegister(target) } - return false + // else: Run() will attempt registration immediately after this + return true } func (server *Server) RandomlyRename(client *Client) { diff --git a/irc/server.go b/irc/server.go index ee1628d5..21712fce 100644 --- a/irc/server.go +++ b/irc/server.go @@ -416,8 +416,27 @@ func (server *Server) generateMessageID() string { // func (server *Server) tryRegister(c *Client) { - if c.registered || !c.HasNick() || !c.HasUsername() || - (c.capState == caps.NegotiatingState) { + if c.Registered() { + return + } + + preregNick := c.PreregNick() + if preregNick == "" || !c.HasUsername() || c.capState == caps.NegotiatingState { + return + } + + // client MUST send PASS (or AUTHENTICATE, if skip-server-password is set) + // before completing the other registration commands + if !c.Authorized() { + c.Quit(c.t("Bad password")) + c.destroy(false) + return + } + + rb := NewResponseBuffer(c) + nickAssigned := performNickChange(server, c, c, preregNick, rb) + rb.Send() + if !nickAssigned { return } @@ -447,7 +466,7 @@ func (server *Server) tryRegister(c *Client) { //TODO(dan): Look at adding last optional [] parameter c.Send(nil, server.name, RPL_MYINFO, c.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString) - rb := NewResponseBuffer(c) + rb = NewResponseBuffer(c) c.RplISupport(rb) server.MOTD(c, rb) rb.Send() diff --git a/oragono.yaml b/oragono.yaml index d32a50db..9b6a94be 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -170,6 +170,13 @@ accounts: # is account authentication enabled? authentication-enabled: true + # some clients (notably Pidgin and Hexchat) offer only a single password field, + # which makes it impossible to specify a separate server password (for the PASS + # command) and SASL password. if this option is set to true, a client that + # successfully authenticates with SASL will not be required to send + # PASS as well, so it can be configured to authenticate with SASL only. + skip-server-password: false + # nick-reservation controls how, and whether, nicknames are linked to accounts nick-reservation: # is there any enforcement of reserved nicknames?