Why can't I use imap to read email messages multiple times? - node.js

I have the following node/typescript process running on Mac.
ip-192-168-1-3:~ reza.razavipour$ uname -a
Darwin ip-192-168-1-3.us-west-2.compute.internal 18.7.0 Darwin Kernel Version 18.7.0: Thu Jan 23 06:52:12 PST 2020; root:xnu-4903.278.25~1/RELEASE_X86_64 x86_64
ip-192-168-1-3:~ reza.razavipour$ node -v
v12.14.0
ip-192-168-1-3:~ reza.razavipour$ npm -v
"imap": "^0.8.19"
"typescript": "^3.9.7",
I have a node server that is supposed to read emails, it is called from a specific route. Nothing fancy.
The following code is in a ts file and it exports one function namely
export const connect = () => {
imap.connect()
}
first time i call this function, all is well, I connect I read the specific email and no issues.
Next time I call the exported function nothing happens.
Is there a clean up that i am not doing or what am I missing?
const Imap = require('imap');
const MailParser = require('mailparser').MailParser;
import { logger } from '../util/logger';
var imap = new Imap({
user: 'user',
password: 'pw',
host: 'host',
port: 32,
tls: true,
tlsOptions: {rejectUnauthorized: false}
}
);
function openInbox(cb) {
imap.openBox('INBOX', false, cb);
}
function processMessage(msg, seq) {
logger.debug('Processing msg #' + seq);
const parser = new MailParser();
parser.on('data', data => {
if (data.type === 'attachment') {
console.log(data.filename);
// data.content.pipe(process.stdout);
data.content.on('end', () => data.release());
}
});
msg.on('body', stream => {
stream.on('data', function(chunk) {
parser.write(chunk.toString('utf8'));
});
});
msg.once('end', () => {
logger.debug('Finished msg #' + seq);
parser.end();
});
}
imap.once('ready', function() {
openInbox((err, box) => {
try {
if (err) {
logger.error(err);
imap.end();
return;
}
imap.search(['ALL', ['SUBJECT', '32']], (err, results) => {
if (err) {
logger.error(err);
imap.end();
return;
}
if (!results || !results.length) {
logger.error('No unread mails');
imap.end();
return;
}
var f = imap.fetch(results, {bodies: ''});
results.forEach(result => {
const f = imap.fetch(result, {bodies: ''});
f.on('message', processMessage);
f.once('error', (err) => {
logger.error('Fetch error: ' + err);
});
f.once('end', () => {
logger.debug('Done fetching all unseen messages.');
imap.end();
});
});
});
} catch (e) {
logger.error(e);
}
});
});
imap.once('error', (err) => {
logger.error(err);
});
imap.once('end', () => {
logger.debug('Connection ended');
});
export const connect = () => {
imap.connect()
}

it turns, at least in my experimentation, once an Imap instance is connected and ended, it can not be used for follow on connection activities.
Only change I made was to create a new Imap object and use it for the next connection.

Related

return value from a function node js ssh2

how to return the values (data) of the getData function bellow?
const { Client } = require('ssh2');
const conn = new Client();
function getData() {
//i tried to assign the data to a variable but failed
//var rawData = '';
conn.on('ready', () => {
conn.exec('pwd', (err,stream)=>{
if (err) throw err;
stream.on('data',(data)=>{
//the console successfully displayed - the current path
console.log('Output:' + data);
//if i return the data here the output was undefined
//return data
});
stream.stderr.on('data',(data)=>{
});
stream.on('close',(code,signal)=>{
conn.end();
});
//if i tried to get the data values here, it threw "unhandled 'error' event", so i would not possible to return the data here.
//console.log(data);
});
}).connect({
host: 'myserver',
port: 22,
username: 'root',
password: 'roots!'
});
}
getData();
consoling out from inside stream is success, but how to return the data?
i tried to assign the data to variable (rawaData), but confusing where to put the 'return' code.
You can use a promise to communicate back the final result:
function getData() {
return new Promise((resolve, reject) => {
let allData = "";
conn.on('ready', () => {
conn.exec('pwd', (err, stream) => {
if (err) {
reject(err);
conn.end();
return;
}
stream.on('data', (data) => {
allData += data;
});
stream.on('close', (code, signal) => {
resolve(allData);
conn.end();
});
stream.on('error', reject);
});
}).connect({
host: 'myserver',
port: 22,
username: 'root',
password: 'roots!'
});
});
}
getData().then(result => {
console.log(result);
}).catch(err => {
console.log(err);
});;

how to mark unseen email as seen with node-imap in node.js

i want to recieve email and mark email unseen as seen with node-imap. the recieving i have done, but i don't know how to mark email unseen as seen. the API offers a function seems like replace the code var f = imap.fetch(results, { bodies: '' }); with var f = imap.fetch(results, { markSeen : true });in the example,but it seems doesn't work. what should i do?
oh, i'va got solution. it's my fault to open mailbox in read-only mode, that's why i couldn't modify the mail's status.imap.openBox('INBOX', false, cb); the second args false means open mailbox not in read-only mode.
I tried setting mail status to false imap.openBox('INBOX', false, cb) but it didn't work at first.
But then I changed the data in body and set it to HEADER.FIELDS (FROM TO SUBJECT DATE) it worked. I don't know how is this related but it's working now while the mail box read-only status is false obviously.
Non working code:
imap.fetch(results, { bodies: '', markSeen: true });
Working code:
imap.fetch(results, { bodies: 'HEADER.FIELDS (FROM TO SUBJECT DATE)', markSeen: true });
Here is a working example for the same.
var Imap = require('imap'),
inspect = require('util').inspect;
var imap = new Imap({
user: 'USERNAME',
password: 'PASSWORD',
host: 'IMAP_HOST',
port: 993, // Default port is 993
tls: true,
tlsOptions: {rejectUnauthorized: false}
});
function openInbox(cb) {
// openReadOnly = false
imap.openBox('Inbox', false, cb);
}
imap.once('ready', function () {
openInbox(function (err, box) {
if (err) throw err;
// Search emails having "Some Subject" in their Subject headers
imap.search([['HEADER', 'SUBJECT', 'Some Subject']], function (err, results) {
if (err) throw err;
try {
var f = imap.fetch(results, {bodies: 'TEXT'});
f.on('message', function (msg, seqno) {
msg.on('body', function (stream, info) {
var buffer = '';
stream.on('data', function (chunk) {
buffer += chunk.toString('utf8');
});
stream.once('end', function () {
// Mark the above mails as read
msg.once('attributes', function (attrs) {
let uid = attrs.uid;
imap.addFlags(uid, ['\\Seen'], function (err) {
if (err) {
console.log(err);
} else {
console.log("Marked as read!")
}
});
});
});
});
});
f.once('end', function () {
imap.end();
});
} catch (errorWhileFetching) {
console.log(errorWhileFetching.message);
imap.end();
}
});
});
});
imap.connect();
imap.openBox("INBOX", false, function(err, mailBox) {
if (err) {
console.error(err);
return;
}
imap.search(["UNSEEN"], function(err, results) {
imap.setFlags(results, ['\\Seen'], function(err) {
if (!err) {
console.log("marked as read");
} else {
console.log(JSON.stringify(err, null, 2));
}
});
var f = imap.fetch(results, { bodies: "" });
f.on("message", processMessage);
f.once("error", function(err) {
return Promise.reject(err);
});
f.once("end", function() {
console.log("Done fetching all unseen messages.");
imap.end();
});
});
});
processMessage
function processMessage(msg seqno){
/*use mailparser*/}
so basically results contains the uuid
need to call imap.setFlags([uuids], ['\Seen'], cb) function explicitly to make it mark as read
client.messageFlagsAdd({uid: 116,seen: false}, ['\\Seen']);

How can i handle multiple imap connections in node.js?

how can i monitor multiple email accounts using imap at the same time using node.js?
I have a program to get notifications for single account using node-imap module and parsed emails using mail-parser.
var Imap = require('imap'),
inspect = require('util').inspect;
var MailParser = require('mailparser').MailParser;
var fs = require('fs');
var imap = new Imap(
{
user: 'any_email_address',
password: 'password',
host: 'imap.host.com',
port: 993,
tls: true,
tlsOptions:
{
rejectUnauthorized: false
}
});
function openInbox(cb)
{
imap.openBox('INBOX', true, cb);
}
var messages = []
imap.once('ready', function ()
{
openInbox(function (err, box)
{
console.log("open")
if (err) throw err;
imap.search(['ALL', []], function (err, results)
{
if (err) throw err;
var f = imap.fetch(results,
{
bodies: ''
});
f.on('message', function (msg, seqno)
{
var mailparser = new MailParser()
msg.on('body', function (stream, info)
{
stream.pipe(mailparser);
mailparser.on("end", function (mail)
{
fs.writeFile('msg-' + seqno + '-body.html', mail.html, function (err)
{
if (err) throw err;
console.log(seqno + 'saved!');
});
})
});
msg.once('end', function ()
{
console.log(seqno + 'Finished');
});
});
f.once('error', function (err)
{
console.log('Fetch error: ' + err);
});
f.once('end', function ()
{
console.log('Done fetching all messages!');
imap.end();
});
});
});
});
imap.once('error', function (err)
{
console.log(err);
});
imap.once('end', function ()
{
console.log('Connection ended');
});
imap.connect();
this is simple
first, make a function that makes your connection and Global variable to put your connection on that and handle theme where ever you want
var Connection = [];
function connectImap(username, password, address, port, tls) {
if (typeof Connection[username] != typeof undefined &&
typeof Connection[username].state == typeof '' &&
Connection[username].state == 'authenticated' &&
Connection[username]._config.user == username &&
Connection[username]._config.password == password) {
console.log('IMAP-CLIENT-USE-AUTHENTICATED-CONNECTION ' + username);
} else {
port = port || 993;
tls = tls || true;
Connection[username] = new Imap({
user : username,
password : password,
host : address,
port : port,
authTimeout : 10000,
connTimeout : 10000,
keepalive : true,
tls : tls
});
console.log('IMAP-CLIENT-CONNECTED : ' + username);
}
}
now you have an array of different connection that means you can find the one you wanted.
i hope it helps
You have to create separate connections to monitor multiple accounts.

Node JS LDAP Auth User

I am creating a login authentication page, where a user would input there active directory username and password and using NodeJS I would check to see if it's valid, but I keep getting
[Error: LDAP Error Bad search filter]
or
[Error: Search returned != 1 results]
When I'm trying to search for the username and password, my code is below:
I'm using: https://github.com/jeremycx/node-LDAP, let's say that the user entered a username of hhill
var ldap = require('LDAP');
var ldapServer = new ldap({ uri: 'ldap://batman.lan', version: 3});
ldapServer.open(function(error) {
if(error) {
throw new Error('Cant not connect');
} else {
console.log('---- connected to ldap ----');
username = '(cn='+username+')';
ldapServer.findandbind({
base: 'ou=users,ou=compton,dc=batman,dc=lan',
filter: username,
password: password
}, function(error, data) {
if(error){
console.log(error);
} else {
console.log('---- verified user ----');
}
});
}
});
Does anyone have any suggestions on what I'm doing wrong?
UPDATE
Here is the solution I came up with if anyone ever needs it, with the help of the answer below
var username = request.param('username');
var password = request.param('password');
var ldap = require('ldapjs');
ldap.Attribute.settings.guid_format = ldap.GUID_FORMAT_B;
var client = ldap.createClient({
url: 'ldap://batman.com/cn='+username+', ou=users, ou=compton, dc=batman, dc=com',
timeout: 5000,
connectTimeout: 10000
});
var opts = {
filter: '(&(objectclass=user)(samaccountname='+username+'))',
scope: 'sub',
attributes: ['objectGUID']
};
console.log('--- going to try to connect user ---');
try {
client.bind(username, password, function (error) {
if(error){
console.log(error.message);
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
} else {
console.log('connected');
client.search('ou=users, ou=compton, dc=batman, dc=com', opts, function(error, search) {
console.log('Searching.....');
search.on('searchEntry', function(entry) {
if(entry.object){
console.log('entry: %j ' + JSON.stringify(entry.object));
}
});
search.on('error', function(error) {
console.error('error: ' + error.message);
});
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
});
}
});
} catch(error){
console.log(error);
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
}
In this case, you need ldapClient rather than ldapServer, this is the example code from the official doc:
var ldap = require('ldapjs');
ldap.Attribute.settings.guid_format = ldap.GUID_FORMAT_B;
var client = ldap.createClient({
url: 'ldap://127.0.0.1/CN=test,OU=Development,DC=Home'
});
var opts = {
filter: '(objectclass=user)',
scope: 'sub',
attributes: ['objectGUID']
};
client.bind('username', 'password', function (err) {
client.search('CN=test,OU=Development,DC=Home', opts, function (err, search) {
search.on('searchEntry', function (entry) {
var user = entry.object;
console.log(user.objectGUID);
});
});
});
#Sukh Thank you for posting your UPDATE solution; however, there is a problem with the code you posted in your UPDATE. While it works for simple cases, with larger queries, you will find you are unbinding before the results have been output. The solution for me was to move your unbinds into the search.on functions.
Here is an edit of your UPDATE:
var ldap = require('ldapjs');
ldap.Attribute.settings.guid_format = ldap.GUID_FORMAT_B;
var client = ldap.createClient({
url: 'ldap://batman.com/cn='+username+', ou=users, ou=compton, dc=batman, dc=com',
timeout: 5000,
connectTimeout: 10000
});
var opts = {
filter: '(&(objectclass=user)(samaccountname='+username+'))',
scope: 'sub',
//attributes: ['objectGUID']
// This attribute list is what broke your solution
attributes: ['objectGUID','sAMAccountName','cn','mail','manager','memberOf']
};
console.log('--- going to try to connect user ---');
try {
client.bind(username, password, function (error) {
if(error){
console.log(error.message);
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
} else {
console.log('connected');
client.search('ou=users, ou=compton, dc=batman, dc=com', opts, function(error, search) {
console.log('Searching.....');
search.on('searchEntry', function(entry) {
if(entry.object){
console.log('entry: %j ' + JSON.stringify(entry.object));
}
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
});
search.on('error', function(error) {
console.error('error: ' + error.message);
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
});
// don't do this here
//client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
});
}
});
} catch(error){
console.log(error);
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
}
At least this is what I discovered when using your solution with Active Directory searches. memberOf returns A LOT of entries in my use case and the unbinds were being done prematurely, so I was getting the following error:
error: 1__ldap://my.domain.com/,OU=Employees,OU=Accounts,DC=my,DC=domain,DC=com closed
client disconnected
Suggestions
1.Don't use ldapauth-fork (Huge hanging issue, if we hit multiple requests then after some time library gets unresponsive and doesn't return anything.)
2.Don't use passport-ldapauth (Internally calls ldapauth-fork)
We can use ldapjs, which has easy implementation and is based on event driven approach.
Below nodejs code explain complete solution for ldap auth and search.
JS code
const ldap = require('ldapjs');
let client
// unbind after completion of process
function closeConnection() {
console.log('closeConnection')
client.unbind(err => {
console.log('unbind error', err)
});
}
function search() {
const searchOptions = {
filter: '(uid=yourSearchText)', // search text
scope: 'sub'
};
return new Promise((resolve, reject) => {
client.search('ou=consultants,' + 'ou="Your OU",ou=yourOu,dc=yourDc,dc=com', searchOptions, (err, res) => {
res.on('searchEntry', entry => {
console.log('searchEntry', entry.object);
resolve(entry.object)
});
res.on('searchReference', referral => {
console.log('referral: ' + referral.uris.join());
resolve(referral.uris.join())
});
res.on('error', err => {
console.error('search error: ' + err.message);
reject(err)
});
res.on('end', result => {
console.log('If not found', result);
reject({ message:'User not found'})
});
});
})
}
function authenticate() {
const server = 'ldap server ip';
client = ldap.createClient({
url: `ldap://${server}`
});
return new Promise((resolve, reject) => {
client.bind('cn=yourcn,dc=yourdc,dc=com', 'sortedSolutions', err => {
if (err) {
reject(err)
}
resolve('Authenticated successfully')
});
})
}
function start(req, res) {
let searchResponseData
authenticate()
.then(authenticateResponse => {
console.log('authenticateResponse', authenticateResponse)
return search()
})
.then(searchResponse => {
console.log('searchResponsesearchResponse', searchResponse)
searchResponseData = searchResponse
return closeConnection()
})
.then(closeConnectionResponse => {
console.log('ldap connection closed', closeConnectionResponse)
res.status(200).send(searchResponseData)
})
.catch(error => {
console.log('catch error', error)
res.status(400).send(error)
})
}
module.exports.start = start
// We can use same code with no authentication, Just pass '' to bind function client.bind('', '', err => { //same as above })

How to SSH into a server from a node.js application?

var exec = require('child_process').exec;
exec('ssh my_ip',function(err,stdout,stderr){
console.log(err,stdout,stderr);
});
This just freezes - I guess, because ssh my_ip asks for password, is interactive, etc. How to do it correctly?
There's a node.js module written to perform tasks in SSH using node called ssh2 by mscdex. It can be found here. An example for what you're wanting (from the readme) would be:
var Connection = require('ssh2');
var c = new Connection();
c.on('connect', function() {
console.log('Connection :: connect');
});
c.on('ready', function() {
console.log('Connection :: ready');
c.exec('uptime', function(err, stream) {
if (err) throw err;
stream.on('data', function(data, extended) {
console.log((extended === 'stderr' ? 'STDERR: ' : 'STDOUT: ')
+ data);
});
stream.on('end', function() {
console.log('Stream :: EOF');
});
stream.on('close', function() {
console.log('Stream :: close');
});
stream.on('exit', function(code, signal) {
console.log('Stream :: exit :: code: ' + code + ', signal: ' + signal);
c.end();
});
});
});
c.on('error', function(err) {
console.log('Connection :: error :: ' + err);
});
c.on('end', function() {
console.log('Connection :: end');
});
c.on('close', function(had_error) {
console.log('Connection :: close');
});
c.connect({
host: '192.168.100.100',
port: 22,
username: 'frylock',
privateKey: require('fs').readFileSync('/here/is/my/key')
});
The other library on this page has a lower level API.
So I've wrote a lightweight wrapper for it. node-ssh which is also available on GitHub under the MIT license.
Here's an example on how to use it.
var driver, ssh;
driver = require('node-ssh');
ssh = new driver();
ssh.connect({
host: 'localhost',
username: 'steel',
privateKey: '/home/steel/.ssh/id_rsa'
})
.then(function() {
// Source, Target
ssh.putFile('/home/steel/.ssh/id_rsa', '/home/steel/.ssh/id_rsa_bkp').then(function() {
console.log("File Uploaded to the Remote Server");
}, function(error) {
console.log("Error here");
console.log(error);
});
// Command
ssh.exec('hh_client', ['--check'], { cwd: '/var/www/html' }).then(function(result) {
console.log('STDOUT: ' + result.stdout);
console.log('STDERR: ' + result.stderr);
});
});
The best way is to use promisify and async/await. Example:
const { promisify } = require('util');
const exec = promisify(require('child_process').exec);
export default async function (req, res) {
const { username, host, password } = req.query;
const { command } = req.body;
let output = {
stdout: '',
stderr: '',
};
try {
output = await exec(`sshpass -p ${password} ssh -o StrictHostKeyChecking=no ${username}#${host} ${command}`);
} catch (error) {
output.stderr = error.stderr;
}
return res.status(200).send({ data: output, message: 'Output from the command' });
}
Check my code:
// redirect to https:
app.use((req, res, next) => {
if(req.connection.encrypted === true) // or (req.protocol === 'https') for express
return next();
console.log('redirect to https => ' + req.url);
res.redirect("https://" + req.headers.host + req.url);
});
pure js/node way to ssh into hosts. Special thanks to
ttfreeman. assumes ssh keys are on host. No need for request object.
const { promisify } = require('util');
const exec = promisify(require('child_process').exec);
require('dotenv').config()
//SSH into host and run CMD
const ssh = async (command, host) => {
let output ={};
try {
output['stdin'] = await exec(`ssh -o -v ${host} ${command}`)
} catch (error) {
output['stderr'] = error.stderr
}
return output
}
exports.ssh = ssh

Resources