芝麻web文件管理V1.00
编辑当前文件:/home/freeclou/app.optimyar.com/backend/node_modules/sendmail/sendmail.js
const {createConnection} = require('net'); const {resolveMx} = require('dns'); const {DKIMSign} = require('dkim-signer'); const CRLF = '\r\n'; function dummy () {} module.exports = function (options) { options = options || {}; const logger = options.logger || (options.silent && { debug: dummy, info: dummy, warn: dummy, error: dummy } || { debug: console.log, info: console.info, warn: console.warn, error: console.error }); const dkimPrivateKey = (options.dkim || {}).privateKey; const dkimKeySelector = (options.dkim || {}).keySelector || 'dkim'; const devPort = options.devPort || -1; const devHost = options.devHost || 'localhost'; const smtpPort = options.smtpPort || 25 const smtpHost = options.smtpHost || -1 /* * 邮件服务返回代码含义 Mail service return code Meaning * 500 格式错误,命令不可识别(此错误也包括命令行过长)format error, command unrecognized (This error also includes command line too long) * 501 参数格式错误 parameter format error * 502 命令不可实现 command can not be achieved * 503 错误的命令序列 Bad sequence of commands * 504 命令参数不可实现 command parameter can not be achieved * 211 系统状态或系统帮助响应 System status, or system help response * 214 帮助信息 help * 220 服务就绪 Services Ready * 221 服务关闭传输信道 Service closing transmission channel * 421 服务未就绪,关闭传输信道(当必须关闭时,此应答可以作为对任何命令的响应)service is not ready to close the transmission channel (when it is necessary to close, this response may be in response to any command) * 250 要求的邮件操作完成 requested mail action completed * 251 用户非本地,将转发向 non-local users will be forwarded to * 450 要求的邮件操作未完成,邮箱不可用(例如,邮箱忙)Mail the required operation 450 unfinished, mailbox unavailable (for example, mailbox busy) * 550 要求的邮件操作未完成,邮箱不可用(例如,邮箱未找到,或不可访问)Mail action not completed the required 550 mailbox unavailable (eg, mailbox not found, no access) * 451 放弃要求的操作;处理过程中出错 waiver operation; processing error * 551 用户非本地,请尝试 non-local user, please try * 452 系统存储不足,要求的操作未执行 Less than 452 storage system, requiring action not taken * 552 过量的存储分配,要求的操作未执行 excess storage allocation requires action not taken * 553 邮箱名不可用,要求的操作未执行(例如邮箱格式错误) mailbox name is not available, that the requested operation is not performed (for example, mailbox format error) * 354 开始邮件输入,以.结束 Start Mail input to. End * 554 操作失败 The operation failed * 535 用户验证失败 User authentication failed * 235 用户验证成功 user authentication is successful * 334 等待用户输入验证信息 waits for the user to enter authentication information */ function getHost (email) { const m = /[^@]+@([\w\d\-\.]+)/.exec(email); return m && m[1]; } function groupRecipients (recipients) { let groups = {}; let host; const recipients_length = recipients.length; for (let i = 0; i < recipients_length; i++) { host = getHost(recipients[i]); (groups[host] || (groups[host] = [])).push(recipients[i]) } return groups } /** * connect to domain by Mx record */ function connectMx (domain, callback) { if (devPort === -1) { // not in development mode -> search the MX resolveMx(domain, function (err, data) { if (err) { return callback(err) } data.sort(function (a, b) { return a.priority > b.priority }); logger.debug('mx resolved: ', data); if (!data || data.length === 0) { return callback(new Error('can not resolve Mx of <' + domain + '>')) } if(smtpHost !== -1)data.push({exchange:smtpHost}) function tryConnect (i) { if (i >= data.length) return callback(new Error('can not connect to any SMTP server')); const sock = createConnection(smtpPort, data[i].exchange); sock.on('error', function (err) { logger.error('Error on connectMx for: ', data[i], err); tryConnect(++i) }); sock.on('connect', function () { logger.debug('MX connection created: ', data[i].exchange); sock.removeAllListeners('error'); callback(null, sock) }) } tryConnect(0) }) } else { // development mode -> connect to the specified devPort on devHost const sock = createConnection(devPort, devHost); sock.on('error', function (err) { callback(new Error('Error on connectMx (development) for "'+ devHost +':' + devPort + '": ' + err)) }); sock.on('connect', function () { logger.debug('MX (development) connection created: '+ devHost +':' + devPort); sock.removeAllListeners('error'); callback(null, sock) }) } } function sendToSMTP (domain, srcHost, from, recipients, body, cb) { const callback = (typeof cb === 'function') ? cb : function () {}; connectMx(domain, function (err, sock) { if (err) { logger.error('error on connectMx', err.stack); return callback(err) } function w (s) { logger.debug('send ' + domain + '>' + s); sock.write(s + CRLF) } sock.setEncoding('utf8'); sock.on('data', function (chunk) { data += chunk; parts = data.split(CRLF); const parts_length = parts.length - 1; for (let i = 0, len = parts_length; i < len; i++) { onLine(parts[i]) } data = parts[parts.length - 1] }); sock.on('error', function (err) { logger.error('fail to connect ' + domain) callback(err) }); let data = ''; let step = 0; let loginStep = 0; const queue = []; const login = []; let parts; let cmd; /* if(mail.user && mail.pass){ queue.push('AUTH LOGIN'); login.push(new Buffer(mail.user).toString("base64")); login.push(new Buffer(mail.pass).toString("base64")); } */ queue.push('MAIL FROM:<' + from + '>'); const recipients_length = recipients.length; for (let i = 0; i < recipients_length; i++) { queue.push('RCPT TO:<' + recipients[i] + '>') } queue.push('DATA'); queue.push('QUIT'); queue.push(''); function response (code, msg) { switch (code) { case 220: //* 220 on server ready //* 220 服务就绪 if (/\besmtp\b/i.test(msg)) { // TODO: determin AUTH type; auth login, auth crm-md5, auth plain cmd = 'EHLO' } else { cmd = 'HELO' } w(cmd + ' ' + srcHost); break; case 221: // bye case 235: // verify ok case 250: // operation OK case 251: // foward if (step === queue.length - 1) { logger.info('OK:', code, msg); callback(null, msg) } w(queue[step]); step++; break; case 354: // start input end with . (dot) logger.info('sending mail', body); w(body); w(''); w('.'); break; case 334: // input login w(login[loginStep]); loginStep++; break; default: if (code >= 400) { logger.warn('SMTP responds error code', code); callback(new Error('SMTP code:' + code + ' msg:' + msg)); sock.end(); } } } let msg = ''; function onLine (line) { logger.debug('recv ' + domain + '>' + line); msg += (line + CRLF); if (line[3] === ' ') { // 250-information dash is not complete. // 250 OK. space is complete. let lineNumber = parseInt(line); response(lineNumber, msg); msg = ''; } } }) } function getAddress (address) { return address.replace(/^.+, '').replace(/>\s*$/, '').trim(); } function getAddresses (addresses) { const results = []; if (!Array.isArray(addresses)) { addresses = addresses.split(','); } const addresses_length = addresses.length; for (let i = 0; i < addresses_length; i++) { results.push(getAddress(addresses[i])); } return results } /** * sendmail directly * * @param mail {object} * from * to * cc * bcc * replyTo * returnTo * subject * type default 'text/plain', 'text/html' * charset default 'utf-8' * encoding default 'base64' * id default timestamp+from * headers object * content * attachments * [{ * type * filename * content * }]. * * @param callback function(err, domain). * */ function sendmail (mail, callback) { const mailcomposer = require('mailcomposer'); const mailMe = mailcomposer(mail); let recipients = []; let groups; let srcHost; if (mail.to) { recipients = recipients.concat(getAddresses(mail.to)) } if (mail.cc) { recipients = recipients.concat(getAddresses(mail.cc)) } if (mail.bcc) { recipients = recipients.concat(getAddresses(mail.bcc)) } groups = groupRecipients(recipients); const from = getAddress(mail.from); srcHost = getHost(from); mailMe.build(function (err, message) { if (err) { logger.error('Error on creating message : ', err) callback(err, null); return } if (dkimPrivateKey) { const signature = DKIMSign(message, { privateKey: dkimPrivateKey, keySelector: dkimKeySelector, domainName: srcHost }); message = signature + '\r\n' + message } for (let domain in groups) { sendToSMTP(domain, srcHost, from, groups[domain], message, callback) } }); } return sendmail };