1/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2/* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6/** 7 * This file creates the class AccountConfig, which is a JS object that holds 8 * a configuration for a certain account. It is *not* created in the backend 9 * yet (use aw-createAccount.js for that), and it may be incomplete. 10 * 11 * Several AccountConfig objects may co-exist, e.g. for autoconfig. 12 * One AccountConfig object is used to prefill and read the widgets 13 * in the Wizard UI. 14 * When we autoconfigure, we autoconfig writes the values into a 15 * new object and returns that, and the caller can copy these 16 * values into the object used by the UI. 17 * 18 * See also 19 * <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat> 20 * for values stored. 21 */ 22 23const EXPORTED_SYMBOLS = ["AccountConfig"]; 24 25ChromeUtils.defineModuleGetter( 26 this, 27 "AccountCreationUtils", 28 "resource:///modules/accountcreation/AccountCreationUtils.jsm" 29); 30ChromeUtils.defineModuleGetter( 31 this, 32 "Sanitizer", 33 "resource:///modules/accountcreation/Sanitizer.jsm" 34); 35 36function AccountConfig() { 37 this.incoming = this.createNewIncoming(); 38 this.incomingAlternatives = []; 39 this.outgoing = this.createNewOutgoing(); 40 this.outgoingAlternatives = []; 41 this.identity = { 42 // displayed real name of user 43 realname: "%REALNAME%", 44 // email address of user, as shown in From of outgoing mails 45 emailAddress: "%EMAILADDRESS%", 46 }; 47 this.inputFields = []; 48 this.domains = []; 49} 50AccountConfig.prototype = { 51 // @see createNewIncoming() 52 incoming: null, 53 // @see createNewOutgoing() 54 outgoing: null, 55 /** 56 * Other servers which can be used instead of |incoming|, 57 * in order of decreasing preference. 58 * (|incoming| itself should not be included here.) 59 * { Array of incoming/createNewIncoming() } 60 */ 61 incomingAlternatives: null, 62 outgoingAlternatives: null, 63 // just an internal string to refer to this. Do not show to user. 64 id: null, 65 // who created the config. 66 // { one of kSource* } 67 source: null, 68 /** 69 * Used for telemetry purposes. 70 * - for kSourceXML, subSource is one of xml-from-{disk, db, isp-https, isp-http}. 71 * - for kSourceExchange, subSource is one of exchange-from-urlN[-guess]. 72 */ 73 subSource: null, 74 displayName: null, 75 // { Array of { varname (value without %), displayName, exampleValue } } 76 inputFields: null, 77 // email address domains for which this config is applicable 78 // { Array of Strings } 79 domains: null, 80 81 /** 82 * Factory function for incoming and incomingAlternatives 83 */ 84 createNewIncoming() { 85 return { 86 // { String-enum: "pop3", "imap", "nntp", "exchange" } 87 type: null, 88 hostname: null, 89 // { Integer } 90 port: null, 91 // May be a placeholder (starts and ends with %). { String } 92 username: null, 93 password: null, 94 // { enum: 1 = plain, 2 = SSL/TLS, 3 = STARTTLS always, 0 = not inited } 95 // ('TLS when available' is insecure and not supported here) 96 socketType: 0, 97 /** 98 * true when the cert is invalid (and thus SSL useless), because it's 99 * 1) not from an accepted CA (including self-signed certs) 100 * 2) for a different hostname or 101 * 3) expired. 102 * May go back to false when user explicitly accepted the cert. 103 */ 104 badCert: false, 105 /** 106 * How to log in to the server: plaintext or encrypted pw, GSSAPI etc. 107 * Defined by Ci.nsMsgAuthMethod 108 * Same as server pref "authMethod". 109 */ 110 auth: 0, 111 /** 112 * Other auth methods that we think the server supports. 113 * They are ordered by descreasing preference. 114 * (|auth| itself is not included in |authAlternatives|) 115 * {Array of Ci.nsMsgAuthMethod} (same as .auth) 116 */ 117 authAlternatives: null, 118 // in minutes { Integer } 119 checkInterval: 10, 120 loginAtStartup: true, 121 // POP3 only: 122 // Not yet implemented. { Boolean } 123 useGlobalInbox: false, 124 leaveMessagesOnServer: true, 125 daysToLeaveMessagesOnServer: 14, 126 deleteByAgeFromServer: true, 127 // When user hits delete, delete from local store and from server 128 deleteOnServerWhenLocalDelete: true, 129 downloadOnBiff: true, 130 // Override `addThisServer` for a specific incoming server 131 useGlobalPreferredServer: false, 132 133 // OAuth2 configuration, if needed. 134 oauthSettings: null, 135 136 // for Microsoft Exchange servers. Optional. 137 owaURL: null, 138 ewsURL: null, 139 easURL: null, 140 // for when an addon overrides the account type. Optional. 141 addonAccountType: null, 142 }; 143 }, 144 /** 145 * Factory function for outgoing and outgoingAlternatives 146 */ 147 createNewOutgoing() { 148 return { 149 type: "smtp", 150 hostname: null, 151 port: null, // see incoming 152 username: null, // see incoming. may be null, if auth is 0. 153 password: null, // see incoming. may be null, if auth is 0. 154 socketType: 0, // see incoming 155 badCert: false, // see incoming 156 auth: 0, // see incoming 157 authAlternatives: null, // see incoming 158 addThisServer: true, // if we already have an SMTP server, add this 159 // if we already have an SMTP server, use it. 160 useGlobalPreferredServer: false, 161 // we should reuse an already configured SMTP server. 162 // nsISmtpServer.key 163 existingServerKey: null, 164 // user display value for existingServerKey 165 existingServerLabel: null, 166 167 // OAuth2 configuration, if needed. 168 oauthSettings: null, 169 }; 170 }, 171 172 /** 173 * The configuration needs an addon to handle the account type. 174 * The addon needs to be installed before the account can be created 175 * in the backend. 176 * You can choose one, if there are several addons in the list. 177 * (Optional) 178 * 179 * Array of: 180 * { 181 * id: "owl@example.com" {string}, 182 * 183 * // already localized string 184 * name: "Owl" {string}, 185 * 186 * // already localized string 187 * description: "A third party addon that allows you to connect to Exchange servers" {string} 188 * 189 * // Minimal version of the addon. Needed in case the addon is already installed, 190 * // to verify that the installed version is sufficient. 191 * // The XPI URL below must satisfy this. 192 * // Must satisfy <https://developer.mozilla.org/en-US/docs/Mozilla/Toolkit_version_format> 193 * minVersion: "0.2" {string} 194 * 195 * xpiURL: "https://live.thunderbird.net/autoconfig/owl.xpi" {URL}, 196 * websiteURL: "https://www.beonex.com/owl/" {URL}, 197 * icon32: "https://www.beonex.com/owl/owl-32x32.png" {URL}, 198 * 199 * useType : { 200 * // Type shown as radio button to user in the config result. 201 * // Users won't understand OWA vs. EWS vs. EAS etc., so this is an abstraction 202 * // from the end user perspective. 203 * generalType: "exchange" {string}, 204 * 205 * // Protocol 206 * // Independent of the addon 207 * protocolType: "owa" {string}, 208 * 209 * // Account type in the Thunderbird backend. 210 * // What nsIMsgAccount.type will be set to when creating the account. 211 * // This is specific to the addon. 212 * addonAccountType: "owl-owa" {string}, 213 * } 214 * } 215 */ 216 addons: null, 217 218 /** 219 * Returns a deep copy of this object, 220 * i.e. modifying the copy will not affect the original object. 221 */ 222 copy() { 223 // Workaround: deepCopy() fails to preserve base obj (instanceof) 224 let result = new AccountConfig(); 225 for (let prop in this) { 226 result[prop] = AccountCreationUtils.deepCopy(this[prop]); 227 } 228 229 return result; 230 }, 231 232 isComplete() { 233 return ( 234 !!this.incoming.hostname && 235 !!this.incoming.port && 236 !!this.incoming.socketType && 237 !!this.incoming.auth && 238 !!this.incoming.username && 239 (!!this.outgoing.existingServerKey || 240 this.outgoing.useGlobalPreferredServer || 241 (!!this.outgoing.hostname && 242 !!this.outgoing.port && 243 !!this.outgoing.socketType && 244 !!this.outgoing.auth && 245 !!this.outgoing.username)) 246 ); 247 }, 248 249 toString() { 250 function sslToString(socketType) { 251 switch (socketType) { 252 case 0: 253 return "undefined"; 254 case 1: 255 return "no SSL"; 256 case 2: 257 return "SSL"; 258 case 3: 259 return "STARTTLS"; 260 default: 261 return "invalid"; 262 } 263 } 264 265 function authToString(authMethod) { 266 switch (authMethod) { 267 case 0: 268 return "undefined"; 269 case 1: 270 return "none"; 271 case 2: 272 return "old plain"; 273 case 3: 274 return "plain"; 275 case 4: 276 return "encrypted"; 277 case 5: 278 return "Kerberos"; 279 case 6: 280 return "NTLM"; 281 case 7: 282 return "external/SSL"; 283 case 8: 284 return "any secure"; 285 case 10: 286 return "OAuth2"; 287 default: 288 return "invalid"; 289 } 290 } 291 292 function passwordToString(password) { 293 return password ? "set" : "not set"; 294 } 295 296 function configToString(config) { 297 return ( 298 config.type + 299 ", " + 300 config.hostname + 301 ":" + 302 config.port + 303 ", " + 304 sslToString(config.socketType) + 305 ", auth: " + 306 authToString(config.auth) + 307 ", username: " + 308 (config.username || "(undefined)") + 309 ", password: " + 310 passwordToString(config.password) 311 ); 312 } 313 314 let result = "Incoming: " + configToString(this.incoming) + "\nOutgoing: "; 315 if ( 316 this.outgoing.useGlobalPreferredServer || 317 this.incoming.useGlobalPreferredServer 318 ) { 319 result += "Use global server"; 320 } else if (this.outgoing.existingServerKey) { 321 result += "Use existing server " + this.outgoing.existingServerKey; 322 } else { 323 result += configToString(this.outgoing); 324 } 325 for (let config of this.incomingAlternatives) { 326 result += "\nIncoming alt: " + configToString(config); 327 } 328 for (let config of this.outgoingAlternatives) { 329 result += "\nOutgoing alt: " + configToString(config); 330 } 331 return result; 332 }, 333 334 /** 335 * Sort the config alternatives such that exchange is the last of the 336 * alternatives. 337 */ 338 preferStandardProtocols() { 339 let alternatives = this.incomingAlternatives; 340 // Add default incoming as one alternative. 341 alternatives.unshift(this.incoming); 342 alternatives.sort((a, b) => { 343 if (a.type == "exchange") { 344 return 1; 345 } 346 if (b.type == "exchange") { 347 return -1; 348 } 349 return 0; 350 }); 351 this.incomingAlternatives = alternatives; 352 this.incoming = alternatives.shift(); 353 }, 354}; 355 356// enum consts 357 358// .source 359AccountConfig.kSourceUser = "user"; // user manually entered the config 360AccountConfig.kSourceXML = "xml"; // config from XML from ISP or Mozilla DB 361AccountConfig.kSourceGuess = "guess"; // guessConfig() 362AccountConfig.kSourceExchange = "exchange"; // from Microsoft Exchange AutoDiscover 363 364/** 365 * Some fields on the account config accept placeholders (when coming from XML). 366 * 367 * These are the predefined ones 368 * * %EMAILADDRESS% (full email address of the user, usually entered by user) 369 * * %EMAILLOCALPART% (email address, part before @) 370 * * %EMAILDOMAIN% (email address, part after @) 371 * * %REALNAME% 372 * as well as those defined in account.inputFields.*.varname, with % added 373 * before and after. 374 * 375 * These must replaced with real values, supplied by the user or app, 376 * before the account is created. This is done here. You call this function once 377 * you have all the data - gathered the standard vars mentioned above as well as 378 * all listed in account.inputFields, and pass them in here. This function will 379 * insert them in the fields, returning a fully filled-out account ready to be 380 * created. 381 * 382 * @param account {AccountConfig} 383 * The account data to be modified. It may or may not contain placeholders. 384 * After this function, it should not contain placeholders anymore. 385 * This object will be modified in-place. 386 * 387 * @param emailfull {String} 388 * Full email address of this account, e.g. "joe@example.com". 389 * Empty of incomplete email addresses will/may be rejected. 390 * 391 * @param realname {String} 392 * Real name of user, as will appear in From of outgoing messages 393 * 394 * @param password {String} 395 * The password for the incoming server and (if necessary) the outgoing server 396 */ 397AccountConfig.replaceVariables = function( 398 account, 399 realname, 400 emailfull, 401 password 402) { 403 Sanitizer.nonemptystring(emailfull); 404 let emailsplit = emailfull.split("@"); 405 AccountCreationUtils.assert( 406 emailsplit.length == 2, 407 "email address not in expected format: must contain exactly one @" 408 ); 409 let emaillocal = Sanitizer.nonemptystring(emailsplit[0]); 410 let emaildomain = Sanitizer.hostname(emailsplit[1]); 411 Sanitizer.label(realname); 412 Sanitizer.nonemptystring(realname); 413 414 let otherVariables = {}; 415 otherVariables.EMAILADDRESS = emailfull; 416 otherVariables.EMAILLOCALPART = emaillocal; 417 otherVariables.EMAILDOMAIN = emaildomain; 418 otherVariables.REALNAME = realname; 419 420 if (password) { 421 account.incoming.password = password; 422 account.outgoing.password = password; // set member only if auth required? 423 } 424 account.incoming.username = _replaceVariable( 425 account.incoming.username, 426 otherVariables 427 ); 428 account.outgoing.username = _replaceVariable( 429 account.outgoing.username, 430 otherVariables 431 ); 432 account.incoming.hostname = _replaceVariable( 433 account.incoming.hostname, 434 otherVariables 435 ); 436 if (account.outgoing.hostname) { 437 // will be null if user picked existing server. 438 account.outgoing.hostname = _replaceVariable( 439 account.outgoing.hostname, 440 otherVariables 441 ); 442 } 443 account.identity.realname = _replaceVariable( 444 account.identity.realname, 445 otherVariables 446 ); 447 account.identity.emailAddress = _replaceVariable( 448 account.identity.emailAddress, 449 otherVariables 450 ); 451 account.displayName = _replaceVariable(account.displayName, otherVariables); 452}; 453 454function _replaceVariable(variable, values) { 455 let str = variable; 456 if (typeof str != "string") { 457 return str; 458 } 459 460 for (let varname in values) { 461 str = str.replace("%" + varname + "%", values[varname]); 462 } 463 464 return str; 465} 466