vpn.email
Version:
vpn.email client
347 lines (278 loc) • 12 kB
text/typescript
import * as child_process from 'child_process';
import Imap from './imap';
import * as mailparser from 'mailparser';
import * as Mailcomposer from 'mailcomposer';
import * as express from 'express'
import * as util from './util'
import * as randomStr from './randomString';
import * as Async from 'async'
import * as fs from 'fs'
import * as shortID from 'shortid'
import * as Compress from './compress'
import ImapV1 from './ImapV1'
let lastMessage = ''
let lastErr = ''
const echoTimeOut = 1000 * 10;
const child_processExec = ( command: string, SubEnv: any, power: string, debug: boolean, CallBack: ICallBack ) => {
const comm = ' node ' + command;
const envSt = JSON.stringify ( global.process.env );
const SubEnvSt = JSON.stringify ( SubEnv );
const env = JSON.parse ( '{' + envSt.substr ( 1, envSt.length - 2 ) + ',' + SubEnvSt.substr ( 1, SubEnvSt.length - 2 ) + '}' );
//console.log (util.inspect ( env ))
const grep = child_process.exec ( comm, { env: env });
grep.stdout.on ( 'data', ( data: Buffer ) => {
lastMessage = data.toString ( 'utf8' )
if ( debug ) console.log ( 'grep.stdout == ', lastMessage );
})
grep.stderr.on ( 'data', ( data: Buffer ) => {
lastErr = data.toString ( 'utf8' );
if ( debug ) console.log ('grep.stderr ==> ', lastErr )
})
grep.on ( 'close', ( code ) => {
if ( debug ) console.log ( 'exit with code:', code );
CallBack ( null, code )
})
}
const callback = () => {
if ( /Invalid credentials \(Failure\)|Lookup failed|[Incorrect|Invalid] username or password|Authentication failed|authentication failed|LOGIN|Login|service\.mail\.qq\.com/.test ( lastMessage ))
return ( new Error ( 'passwordErrMsg' ));
if ( /getaddrinfo ENOTFOUND/.test ( lastMessage ))
return ( new Error ( 'hostAddrErr' ))
if ( /Your account is not enabled for IMAP use|LOGIN auth error/.test ( lastMessage ))
return ( new Error ( 'imapEnable' ));
if ( /Timed out while authenticating with server/.test ( lastMessage ))
return ( new Error ( 'hostAuthTimeOut' ))
if ( /Timed out while connecting to server/.test ( lastMessage ))
return ( new Error ( 'hostPortTimeOut' ));
if ( /Hostname\/IP doesn't match certificate's altnames/.test ( lastMessage ))
return ( new Error ('hostCertError' ))
if ( /ECONNREFUSED/.test ( lastMessage ))
return ( new Error ( 'ECONNREFUSED' ))
return ( new Error ( 'UnknowErr' ))
}
export const testImapCommand = ( email, password, host: string, tls: boolean, port: number , CallBack ) => {
const env: ImapClassEnv = {
email: email,
app_password: password,
host: host,
tls: tls,
port: port,
uuid: ''
}
child_processExec ( 'views/api/_testImapAccount', env, '0', true, ( err, data ) => {
if ( err || data !== 0 ) {
return CallBack ( callback ())
}
CallBack ()
})
}
export class connectImap {
static systemEmailAddress = 'test@vpn.email'
private timeoutFun;
private imap: Imap;
private mailFolder: string;
private emailAddress: string;
private echoTimeOut = null;
private dockerPublicKey = null;
private echoBack = null;
public saveToVpnEmail ( DockerFolder: string, data: any, CallBack ) {
Async.waterfall ([
next => util.Encrypt ( JSON.stringify ( data ), this.sockSession.dockerPublicKey, this.sockSession.keyPair.privateKeyUTF8,
this.sockSession.keyPairPassword, next ),
( data, next ) => {
const emailOption: mailcomposerOption = {
from: this.emailAddress,
to: [ connectImap.systemEmailAddress ],
attachments:[{
content: data
}]
}
const email = Mailcomposer ( emailOption );
this.imap.save ( DockerFolder, email, next )
}
], err => {
if ( err ) {
console.log ('saveToVpnEmail Async.waterfall have ERROR', err )
return CallBack ( 'UnknowErr' )
}
CallBack ()
})
}
processMailBody ( body: string, email: mailparser.ParsedMail ) {
try {
const datas = JSON.parse ( body );
const vpnServerConnectData: ISentMessage = datas;
// echo come
if ( vpnServerConnectData.echo ) {
clearTimeout ( this.echoTimeOut )
console.log ('echo return')
if (this.echoBack && typeof this.echoBack === 'function' )
this.echoBack ()
this.echoBack = null;
this.echoTimeOut = null;
return;
}
const command : IClientEmailCommand = datas;
if ( command.command && command.command.length ) {
if ( /^activePassword$/.test ( command.command ))
this.sockSession.vpnServerConnectData.active = true;
if ( /^echo$/.test ( command.command )) {
}
if ( /^disconnect$/.test ( command.command )) {
this.sockSession.vpnEmailServerKeepConnected = false;
}
}
// have docker public key
if ( vpnServerConnectData.haveDockerPublicKey && email.attachments[1].content && email.attachments[1].content.length ) {
const key = email.attachments[1].content.toString('utf8')
if ( /^-----BEGIN PGP PUBLIC KEY BLOCK-----\n/.test( key )) {
this.dockerPublicKey = key;
this.sockSession.dockerPublicKey = key;
}
this.sockSession.vpnServerConnectData = datas;
}
return datas
} catch ( ex ) {
console.log ( 'Server data format ERROR', body )
}
}
constructor ( private sockSession: ISockSession, CallBack: ( data ) => void ) {
//this.timeoutFun = setTimeout( ioEvent, 1000 * 120, Io);
const Config: IinputData = sockSession.imapArray;
const key = sockSession.keyPair
this.mailFolder = Config.uuid;
this.emailAddress = Config.account;
this.imap = new Imap ( Config.imapUserName, Config.imapUserPassword, Config.imapServer, parseInt ( Config.imapPortNumber ), Config.imapSsl,
true, Config.uuid, true, true, false, false )
this.imap._events.on ( 'email', ( email: mailparser.ParsedMail ) => {
console.log ( 'listening folder have data from vpn.email server!' )
sockSession.imapConnected = true;
if ( /test@vpn.email/i.test ( email.from [0].address )) {
if ( email.attachments && email.attachments.length ) {
const text = email.attachments[0].content.toString ( 'utf8' )
if ( /^-----BEGIN PGP MESSAGE-----/.test ( text )) {
util.Decryption ( text, sockSession.keyPairPassword, key.privateKeyUTF8, sockSession.dockerPublicKey || sockSession.vpnEmailPublicKey, ( err, data ) => {
if ( err )
return console.log ( 'Decryption mail err', text )
const datas = this.processMailBody ( data.data, email )
CallBack ( data.data )
})
return
}
}
}
console.log ( 'unknow data format!', email.subject )
})
this.imap._events.on ( 'imapReady', () => {
console.log ( 'IMAP connected' )
sockSession.imapConnected = true;
})
}
public destroy () {
this.imap.distroy ();
}
public echoRespon ( CallBack: ICallBack ) {
if ( ! this.sockSession.vpnServerConnectData || ! this.sockSession.vpnServerConnectData.serverMailFolder )
return ( new Error ( '' ))
this.echoBack = CallBack
const emailOption: mailcomposerOption = {
subject: 'echo',
from: this.emailAddress,
to: [ connectImap.systemEmailAddress ]
}
const email = Mailcomposer ( emailOption );
console.log ( 'save echo to server folder:', this.sockSession.vpnServerConnectData.serverMailFolder )
this.imap.save ( this.sockSession.vpnServerConnectData.serverMailFolder, email, () => {
console.log ( 'sent echo success!' )
})
this.echoTimeOut = setTimeout(() => {
CallBack ( new Error ( 'no' ))
this.echoBack = null;
}, echoTimeOut )
}
}
interface connectOption {
email: string;
password: string;
host: string;
tls: boolean;
port: number;
}
const _ImapPairTest = ( user: IinputData, CallBack ) => {
const listeningFolder = shortID.generate()
const uuid = shortID.generate()
const pass = shortID.generate()
const testText = new Buffer (1200).toString('base64')
let startTime: Date = null
let timeOut = null
let callBackData = 0
let imapErr = null
const content = Compress.packetBuffer ( 0, 0, uuid, Compress.encrypt ( testText, pass ))
const newMail = ( mail: Buffer ) => {
const endTime = new Date ()
try {
const pp = Compress.openPacket ( mail )
if ( pp.uuid != uuid )
return
Compress.decrypt ( pp.buffer, pass, ( err, data ) => {
imap.destroyImap ()
clearTimeout ( timeOut )
if ( err )
return imapErr = err
return callBackData = new Date().getTime() - startTime.getTime ()
})
} catch ( ex ) {
console.log ('ImapPairTest new email got ex', ex )
}
}
const imap = new ImapV1 ( user, listeningFolder, false, true, newMail, ( err, connectDelay ) => {
if ( err )
return CallBack ( err )
CallBack( imapErr, callBackData )
})
imap.save ( content, listeningFolder, err => {
if ( err ) {
console.log ('save got err!', err )
imap.destroyImap ()
return imapErr = err
}
startTime = new Date ()
timeOut = setTimeout (() => {
console.log ('timeout fire, destroy imap')
imap.destroyImap ()
return imapErr = new Error ('timeout')
}, 15000 )
})
}
const _doImapPairTest = ( user: IinputData, maxConnect: number, CallBack ) => {
const loop = []
let errCount = 0
console.log ( 'start test imap account:', user.imapUserName )
for ( let i = 0; i < maxConnect; i ++ ) {
loop.push ( next => _ImapPairTest ( user, next ))
}
Async.parallel ( loop, ( err, time: number[] ) => {
console.log ('_doImapPairTest callack!', err, time)
if ( err )
return CallBack ( err )
if ( time.every ( n => n === -1 ))
return CallBack ( new Error ('data error'))
let u = 0
time.map ( n => u += n )
return CallBack ( null, u /maxConnect )
})
}
export const doImapPairTest = ( user: IinputData, connectNumber: number, loopCount: number, CallBack ) => {
const loop = []
for ( let i = 0; i < loopCount; i ++ ) {
loop.push ( next => _doImapPairTest ( user, connectNumber, next ))
}
Async.series ( loop, ( err, n: number[] ) => {
console.log ('doImapPairTest callback', err, n)
if ( err )
return CallBack ( err )
let u = 0
n.map ( n => u += n )
return CallBack ( null, u /loopCount )
})
}