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 6const EXPORTED_SYMBOLS = ["CreateInBackend"]; 7 8ChromeUtils.defineModuleGetter( 9 this, 10 "AccountConfig", 11 "resource:///modules/accountcreation/AccountConfig.jsm" 12); 13ChromeUtils.defineModuleGetter( 14 this, 15 "AccountCreationUtils", 16 "resource:///modules/accountcreation/AccountCreationUtils.jsm" 17); 18 19const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); 20const { MailServices } = ChromeUtils.import( 21 "resource:///modules/MailServices.jsm" 22); 23 24/* eslint-disable complexity */ 25/** 26 * Takes an |AccountConfig| JS object and creates that account in the 27 * Thunderbird backend (which also writes it to prefs). 28 * 29 * @param {AccountConfig} config - The account to create 30 * @return {nsIMsgAccount} - the newly created account 31 */ 32function createAccountInBackend(config) { 33 let uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService( 34 Ci.nsIUUIDGenerator 35 ); 36 // incoming server 37 let inServer = MailServices.accounts.createIncomingServer( 38 config.incoming.username, 39 config.incoming.hostname, 40 config.incoming.type 41 ); 42 inServer.port = config.incoming.port; 43 inServer.authMethod = config.incoming.auth; 44 inServer.password = config.incoming.password; 45 // This new CLIENTID is for the outgoing server, and will be applied to the 46 // incoming only if the incoming username and hostname match the outgoing. 47 // We must generate this unconditionally because we cannot determine whether 48 // the outgoing server has clientid enabled yet or not, and we need to do it 49 // here in order to populate the incoming server if the outgoing matches. 50 let newOutgoingClientid = uuidGen 51 .generateUUID() 52 .toString() 53 .replace(/[{}]/g, ""); 54 // Grab the base domain of both incoming and outgoing hostname in order to 55 // compare the two to detect if the base domain is the same. 56 let incomingBaseDomain; 57 let outgoingBaseDomain; 58 try { 59 incomingBaseDomain = Services.eTLD.getBaseDomainFromHost( 60 config.incoming.hostname 61 ); 62 } catch (e) { 63 incomingBaseDomain = config.incoming.hostname; 64 } 65 try { 66 outgoingBaseDomain = Services.eTLD.getBaseDomainFromHost( 67 config.outgoing.hostname 68 ); 69 } catch (e) { 70 outgoingBaseDomain = config.outgoing.hostname; 71 } 72 if ( 73 config.incoming.username == config.outgoing.username && 74 incomingBaseDomain == outgoingBaseDomain 75 ) { 76 inServer.clientid = newOutgoingClientid; 77 } else { 78 // If the username/hostname are different then generate a new CLIENTID. 79 inServer.clientid = uuidGen 80 .generateUUID() 81 .toString() 82 .replace(/[{}]/g, ""); 83 } 84 85 if (config.rememberPassword && config.incoming.password) { 86 rememberPassword(inServer, config.incoming.password); 87 } 88 89 if (inServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) { 90 inServer.setCharValue("oauth2.scope", config.incoming.oauthSettings.scope); 91 inServer.setCharValue( 92 "oauth2.issuer", 93 config.incoming.oauthSettings.issuer 94 ); 95 } 96 97 // SSL 98 if (config.incoming.socketType == 1) { 99 // plain 100 inServer.socketType = Ci.nsMsgSocketType.plain; 101 } else if (config.incoming.socketType == 2) { 102 // SSL / TLS 103 inServer.socketType = Ci.nsMsgSocketType.SSL; 104 } else if (config.incoming.socketType == 3) { 105 // STARTTLS 106 inServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS; 107 } 108 109 // If we already have an account with an identical name, generate a unique 110 // name for the new account to avoid duplicates. 111 inServer.prettyName = checkAccountNameAlreadyExists( 112 config.identity.emailAddress 113 ) 114 ? generateUniqueAccountName(config) 115 : config.identity.emailAddress; 116 117 inServer.doBiff = true; 118 inServer.biffMinutes = config.incoming.checkInterval; 119 inServer.setBoolValue("login_at_startup", config.incoming.loginAtStartup); 120 if (config.incoming.type == "pop3") { 121 inServer.setBoolValue( 122 "leave_on_server", 123 config.incoming.leaveMessagesOnServer 124 ); 125 inServer.setIntValue( 126 "num_days_to_leave_on_server", 127 config.incoming.daysToLeaveMessagesOnServer 128 ); 129 inServer.setBoolValue( 130 "delete_mail_left_on_server", 131 config.incoming.deleteOnServerWhenLocalDelete 132 ); 133 inServer.setBoolValue( 134 "delete_by_age_from_server", 135 config.incoming.deleteByAgeFromServer 136 ); 137 inServer.setBoolValue("download_on_biff", config.incoming.downloadOnBiff); 138 } 139 if (config.incoming.owaURL) { 140 inServer.setUnicharValue("owa_url", config.incoming.owaURL); 141 } 142 if (config.incoming.ewsURL) { 143 inServer.setUnicharValue("ews_url", config.incoming.ewsURL); 144 } 145 if (config.incoming.easURL) { 146 inServer.setUnicharValue("eas_url", config.incoming.easURL); 147 } 148 inServer.valid = true; 149 150 let username = 151 config.outgoing.auth != Ci.nsMsgAuthMethod.none 152 ? config.outgoing.username 153 : null; 154 let outServer = MailServices.smtp.findServer( 155 username, 156 config.outgoing.hostname 157 ); 158 AccountCreationUtils.assert( 159 config.outgoing.addThisServer || 160 config.outgoing.useGlobalPreferredServer || 161 config.outgoing.existingServerKey, 162 "No SMTP server: inconsistent flags" 163 ); 164 165 if ( 166 config.outgoing.addThisServer && 167 !outServer && 168 !config.incoming.useGlobalPreferredServer 169 ) { 170 outServer = MailServices.smtp.createServer(); 171 outServer.hostname = config.outgoing.hostname; 172 outServer.port = config.outgoing.port; 173 outServer.authMethod = config.outgoing.auth; 174 // Populate the clientid if it is enabled for this outgoing server. 175 if (outServer.clientidEnabled) { 176 outServer.clientid = newOutgoingClientid; 177 } 178 if (config.outgoing.auth != Ci.nsMsgAuthMethod.none) { 179 outServer.username = username; 180 outServer.password = config.outgoing.password; 181 if (config.rememberPassword && config.outgoing.password) { 182 rememberPassword(outServer, config.outgoing.password); 183 } 184 } 185 186 if (outServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) { 187 let prefBranch = "mail.smtpserver." + outServer.key + "."; 188 Services.prefs.setCharPref( 189 prefBranch + "oauth2.scope", 190 config.outgoing.oauthSettings.scope 191 ); 192 Services.prefs.setCharPref( 193 prefBranch + "oauth2.issuer", 194 config.outgoing.oauthSettings.issuer 195 ); 196 } 197 198 if (config.outgoing.socketType == 1) { 199 // no SSL 200 outServer.socketType = Ci.nsMsgSocketType.plain; 201 } else if (config.outgoing.socketType == 2) { 202 // SSL / TLS 203 outServer.socketType = Ci.nsMsgSocketType.SSL; 204 } else if (config.outgoing.socketType == 3) { 205 // STARTTLS 206 outServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS; 207 } 208 209 outServer.description = config.displayName; 210 211 // If this is the first SMTP server, set it as default 212 if ( 213 !MailServices.smtp.defaultServer || 214 !MailServices.smtp.defaultServer.hostname 215 ) { 216 MailServices.smtp.defaultServer = outServer; 217 } 218 } 219 220 // identity 221 // TODO accounts without identity? 222 let identity = MailServices.accounts.createIdentity(); 223 identity.fullName = config.identity.realname; 224 identity.email = config.identity.emailAddress; 225 226 // for new accounts, default to replies being positioned above the quote 227 // if a default account is defined already, take its settings instead 228 if (config.incoming.type == "imap" || config.incoming.type == "pop3") { 229 identity.replyOnTop = 1; 230 // identity.sigBottom = false; // don't set this until Bug 218346 is fixed 231 232 if ( 233 MailServices.accounts.accounts.length && 234 MailServices.accounts.defaultAccount 235 ) { 236 let defAccount = MailServices.accounts.defaultAccount; 237 let defIdentity = defAccount.defaultIdentity; 238 if ( 239 defAccount.incomingServer.canBeDefaultServer && 240 defIdentity && 241 defIdentity.valid 242 ) { 243 identity.replyOnTop = defIdentity.replyOnTop; 244 identity.sigBottom = defIdentity.sigBottom; 245 } 246 } 247 } 248 249 // due to accepted conventions, news accounts should default to plain text 250 if (config.incoming.type == "nntp") { 251 identity.composeHtml = false; 252 } 253 254 identity.valid = true; 255 256 if ( 257 !config.outgoing.useGlobalPreferredServer && 258 !config.incoming.useGlobalPreferredServer 259 ) { 260 if (config.outgoing.existingServerKey) { 261 identity.smtpServerKey = config.outgoing.existingServerKey; 262 } else { 263 identity.smtpServerKey = outServer.key; 264 } 265 } 266 267 // account and hook up 268 // Note: Setting incomingServer will cause the AccountManager to refresh 269 // itself, which could be a problem if we came from it and we haven't set 270 // the identity (see bug 521955), so make sure everything else on the 271 // account is set up before you set the incomingServer. 272 let account = MailServices.accounts.createAccount(); 273 account.addIdentity(identity); 274 account.incomingServer = inServer; 275 if ( 276 inServer.canBeDefaultServer && 277 (!MailServices.accounts.defaultAccount || 278 !MailServices.accounts.defaultAccount.incomingServer.canBeDefaultServer) 279 ) { 280 MailServices.accounts.defaultAccount = account; 281 } 282 283 verifyLocalFoldersAccount(MailServices.accounts); 284 setFolders(identity, inServer); 285 286 // save 287 MailServices.accounts.saveAccountInfo(); 288 try { 289 Services.prefs.savePrefFile(null); 290 } catch (ex) { 291 AccountCreationUtils.ddump("Could not write out prefs: " + ex); 292 } 293 return account; 294} 295/* eslint-enable complexity */ 296 297function setFolders(identity, server) { 298 // TODO: support for local folders for global inbox (or use smart search 299 // folder instead) 300 301 var baseURI = server.serverURI + "/"; 302 303 // Names will be localized in UI, not in folder names on server/disk 304 // TODO allow to override these names in the XML config file, 305 // in case e.g. Google or AOL use different names? 306 // Workaround: Let user fix it :) 307 var fccName = "Sent"; 308 var draftName = "Drafts"; 309 var templatesName = "Templates"; 310 311 identity.draftFolder = baseURI + draftName; 312 identity.stationeryFolder = baseURI + templatesName; 313 identity.fccFolder = baseURI + fccName; 314 315 identity.fccFolderPickerMode = 0; 316 identity.draftsFolderPickerMode = 0; 317 identity.tmplFolderPickerMode = 0; 318} 319 320function rememberPassword(server, password) { 321 let passwordURI; 322 if (server instanceof Ci.nsIMsgIncomingServer) { 323 passwordURI = server.localStoreType + "://" + server.hostName; 324 } else if (server instanceof Ci.nsISmtpServer) { 325 passwordURI = "smtp://" + server.hostname; 326 } else { 327 throw new AccountCreationUtils.NotReached("Server type not supported"); 328 } 329 330 let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance( 331 Ci.nsILoginInfo 332 ); 333 login.init(passwordURI, null, passwordURI, server.username, password, "", ""); 334 try { 335 Services.logins.addLogin(login); 336 } catch (e) { 337 if (e.message.includes("This login already exists")) { 338 // TODO modify 339 } else { 340 throw e; 341 } 342 } 343} 344 345/** 346 * Check whether the user's setup already has an incoming server 347 * which matches (hostname, port, username) the primary one 348 * in the config. 349 * (We also check the email address as username.) 350 * 351 * @param config {AccountConfig} filled in (no placeholders) 352 * @return {nsIMsgIncomingServer} If it already exists, the server 353 * object is returned. 354 * If it's a new server, |null| is returned. 355 */ 356function checkIncomingServerAlreadyExists(config) { 357 AccountCreationUtils.assert(config instanceof AccountConfig); 358 let incoming = config.incoming; 359 let existing = MailServices.accounts.findRealServer( 360 incoming.username, 361 incoming.hostname, 362 incoming.type, 363 incoming.port 364 ); 365 366 // if username does not have an '@', also check the e-mail 367 // address form of the name. 368 if (!existing && !incoming.username.includes("@")) { 369 existing = MailServices.accounts.findRealServer( 370 config.identity.emailAddress, 371 incoming.hostname, 372 incoming.type, 373 incoming.port 374 ); 375 } 376 return existing; 377} 378 379/** 380 * Check whether the user's setup already has an outgoing server 381 * which matches (hostname, port, username) the primary one 382 * in the config. 383 * 384 * @param config {AccountConfig} filled in (no placeholders) 385 * @return {nsISmtpServer} If it already exists, the server 386 * object is returned. 387 * If it's a new server, |null| is returned. 388 */ 389function checkOutgoingServerAlreadyExists(config) { 390 AccountCreationUtils.assert(config instanceof AccountConfig); 391 for (let existingServer of MailServices.smtp.servers) { 392 // TODO check username with full email address, too, like for incoming 393 if ( 394 existingServer.hostname == config.outgoing.hostname && 395 existingServer.port == config.outgoing.port && 396 existingServer.username == config.outgoing.username 397 ) { 398 return existingServer; 399 } 400 } 401 return null; 402} 403 404/** 405 * Check whether the user's setup already has an account with the same email 406 * address. This might happen if the user uses the same email for different 407 * protocols (eg. IMAP and POP3). 408 * 409 * @param {string} name - The name or email address of the new account. 410 * @returns {boolean} True if an account with the same name is found. 411 */ 412function checkAccountNameAlreadyExists(name) { 413 return MailServices.accounts.accounts.some( 414 a => a.incomingServer.prettyName == name 415 ); 416} 417 418/** 419 * Generate a unique account name by appending the incoming protocol type, and 420 * a counter if necessary. 421 * 422 * @param {AccountConfig} config - The config data of the account being created. 423 * @returns {string} - The unique account name. 424 */ 425function generateUniqueAccountName(config) { 426 // Generate a potential unique name. e.g. "foo@bar.com (POP3)". 427 let name = `${ 428 config.identity.emailAddress 429 } (${config.incoming.type.toUpperCase()})`; 430 431 // If this name already exists, append a counter until we find a unique name. 432 if (checkAccountNameAlreadyExists(name)) { 433 let counter = 2; 434 while (checkAccountNameAlreadyExists(`${name}_${counter}`)) { 435 counter++; 436 } 437 // e.g. "foo@bar.com (POP3)_1". 438 name = `${name}_${counter}`; 439 } 440 441 return name; 442} 443 444/** 445 * Check if there already is a "Local Folders". If not, create it. 446 * Copied from AccountWizard.js with minor updates. 447 */ 448function verifyLocalFoldersAccount(am) { 449 let localMailServer; 450 try { 451 localMailServer = am.localFoldersServer; 452 } catch (ex) { 453 localMailServer = null; 454 } 455 456 try { 457 if (!localMailServer) { 458 // creates a copy of the identity you pass in 459 am.createLocalMailAccount(); 460 try { 461 localMailServer = am.localFoldersServer; 462 } catch (ex) { 463 AccountCreationUtils.ddump( 464 "Error! we should have found the local mail server " + 465 "after we created it." 466 ); 467 } 468 } 469 } catch (ex) { 470 AccountCreationUtils.ddump("Error in verifyLocalFoldersAccount " + ex); 471 } 472} 473 474var CreateInBackend = { 475 checkIncomingServerAlreadyExists, 476 checkOutgoingServerAlreadyExists, 477 createAccountInBackend, 478}; 479