1/* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5/* 6 * This implements SASL for IRC. 7 * https://raw.github.com/atheme/atheme/master/doc/SASL 8 * https://ircv3.net/specs/extensions/sasl-3.2 9 */ 10 11const EXPORTED_SYMBOLS = ["ircSASL", "capSASL"]; 12 13const { ircHandlers } = ChromeUtils.import( 14 "resource:///modules/ircHandlers.jsm" 15); 16 17var ircSASL = { 18 name: "SASL AUTHENTICATE", 19 priority: ircHandlers.DEFAULT_PRIORITY, 20 isEnabled() { 21 return this._activeCAPs.has("sasl"); 22 }, 23 24 commands: { 25 AUTHENTICATE(aMessage) { 26 // Expect an empty response, if something different is received abort. 27 if (aMessage.params[0] != "+") { 28 this.sendMessage("AUTHENTICATE", "*"); 29 this.WARN( 30 "Aborting SASL authentication, unexpected message " + 31 "received:\n" + 32 aMessage.rawMessage 33 ); 34 return true; 35 } 36 37 // An authentication identity, authorization identity and password are 38 // used, separated by null. 39 let data = [ 40 this._requestedNickname, 41 this._requestedNickname, 42 this.imAccount.password, 43 ].join("\0"); 44 // btoa for Unicode, see https://developer.mozilla.org/en-US/docs/DOM/window.btoa 45 let base64Data = btoa(unescape(encodeURIComponent(data))); 46 this.sendMessage( 47 "AUTHENTICATE", 48 base64Data, 49 "AUTHENTICATE <base64 encoded nick, user and password not logged>" 50 ); 51 return true; 52 }, 53 54 "900": function(aMessage) { 55 // RPL_LOGGEDIN 56 // <nick>!<ident>@<host> <account> :You are now logged in as <user> 57 // Now logged in ("whether by SASL or otherwise"). 58 this.isAuthenticated = true; 59 return true; 60 }, 61 62 "901": function(aMessage) { 63 // RPL_LOGGEDOUT 64 // The user's account name is unset (whether by SASL or otherwise). 65 this.isAuthenticated = false; 66 return true; 67 }, 68 69 "902": function(aMessage) { 70 // ERR_NICKLOCKED 71 // Authentication failed because the account is currently locked out, 72 // held, or otherwise administratively made unavailable. 73 this.WARN( 74 "You must use a nick assigned to you. SASL authentication failed." 75 ); 76 this.removeCAP("sasl"); 77 return true; 78 }, 79 80 "903": function(aMessage) { 81 // RPL_SASLSUCCESS 82 // Authentication was successful. 83 this.isAuthenticated = true; 84 this.LOG("SASL authentication successful."); 85 // We may receive this again while already connected if the user manually 86 // identifies with Nickserv. 87 if (!this.connected) { 88 this.removeCAP("sasl"); 89 } 90 return true; 91 }, 92 93 "904": function(aMessage) { 94 // ERR_SASLFAIL 95 // Sent when the SASL authentication fails because of invalid credentials 96 // or other errors not explicitly mentioned by other numerics. 97 this.WARN("Authentication with SASL failed."); 98 this.removeCAP("sasl"); 99 return true; 100 }, 101 102 "905": function(aMessage) { 103 // ERR_SASLTOOLONG 104 // Sent when credentials are valid, but the SASL authentication fails 105 // because the client-sent `AUTHENTICATE` command was too long. 106 this.ERROR("SASL: AUTHENTICATE command was too long."); 107 this.removeCAP("sasl"); 108 return true; 109 }, 110 111 "906": function(aMessage) { 112 // ERR_SASLABORTED 113 // The client completed registration before SASL authentication completed, 114 // or because we sent `AUTHENTICATE` with `*` as the parameter. 115 // 116 // Freenode sends 906 in addition to 904, ignore 906 in this case. 117 if (this._requestedCAPs.has("sasl")) { 118 this.ERROR( 119 "Registration completed before SASL authentication completed." 120 ); 121 this.removeCAP("sasl"); 122 } 123 return true; 124 }, 125 126 "907": function(aMessage) { 127 // ERR_SASLALREADY 128 // Response if client attempts to AUTHENTICATE after successful 129 // authentication. 130 this.ERROR("Attempting SASL authentication twice?!"); 131 this.removeCAP("sasl"); 132 return true; 133 }, 134 135 "908": function(aMessage) { 136 // RPL_SASLMECHS 137 // <nick> <mechanisms> :are available SASL mechanisms 138 // List of SASL mechanisms supported by the server (or network, services). 139 // The numeric contains a comma-separated list of mechanisms. 140 return false; 141 }, 142 }, 143}; 144 145var capSASL = { 146 name: "SASL CAP", 147 priority: ircHandlers.DEFAULT_PRIORITY, 148 isEnabled: () => true, 149 150 commands: { 151 sasl(aMessage) { 152 // Return early if we are already authenticated (can happen due to cap-notify) 153 if (this.isAuthenticated) { 154 return true; 155 } 156 157 if ( 158 (aMessage.cap.subcommand === "LS" || 159 aMessage.cap.subcommand === "NEW") && 160 this.imAccount.password 161 ) { 162 if (aMessage.cap.value) { 163 const mechanisms = aMessage.cap.value.split(","); 164 // We only support the plain authentication mechanism for now, abort if it's not available. 165 if (!mechanisms.includes("PLAIN")) { 166 return true; 167 } 168 } 169 // If it supports SASL, let the server know we're requiring SASL. 170 this.addCAP("sasl"); 171 this.sendMessage("CAP", ["REQ", "sasl"]); 172 } else if (aMessage.cap.subcommand === "ACK") { 173 // The server acknowledges our choice to use SASL, send the first 174 // message. 175 this.sendMessage("AUTHENTICATE", "PLAIN"); 176 } else if (aMessage.cap.subcommand === "NAK") { 177 this.removeCAP("sasl"); 178 } 179 180 return true; 181 }, 182 }, 183}; 184