New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@carlgo11/smtp-server

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@carlgo11/smtp-server - npm Package Compare versions

Comparing version 0.0.6 to 0.0.7

.prettierrc.json

2

package.json
{
"name": "@carlgo11/smtp-server",
"version": "0.0.6",
"version": "0.0.7",
"description": "Simple lightweight SMTP server written in Node.js",

@@ -5,0 +5,0 @@ "main": "src/core/SMTPServer.js",

@@ -11,6 +11,2 @@ import Response from '../models/Response.js';

export function clearCommands() {
commandHandlers = {};
}
export async function handleCommand(message, session) {

@@ -21,3 +17,3 @@ const command = message.split(' ')[0].toUpperCase();

if (handler) {
await handler(args, session)
await handler(args, session);
} else {

@@ -24,0 +20,0 @@ session.unknownCommands += 1;

@@ -26,2 +26,3 @@ import context from '../core/ServerContext.js';

cleanup(false);
session.socket.end();
}, DATA_TIMEOUT);

@@ -39,3 +40,3 @@ };

session.transitionTo(
result ? session.states.DATA_DONE:session.states.RCPT_TO,
result ? session.states.DATA_DONE : session.states.RCPT_TO
);

@@ -51,12 +52,15 @@ };

// Pass the message data to the consumer's onDATA handler
context.onDATA(messageData, session).then((result) => {
if (result instanceof Response) session.send(result);
else session.send('Message accepted', [250, 2, 6, 0]);
context
.onDATA(messageData, session)
.then((result) => {
if (result instanceof Response) session.send(result);
else session.send('Message accepted', [250, 2, 6, 0]);
cleanup(true);
}).catch((result) => {
if (result instanceof Response) session.send(result);
else session.send(`${result || 'Message rejected'}`, [550, 5, 1, 0]);
cleanup(false);
});
cleanup(true);
})
.catch((result) => {
if (result instanceof Response) session.send(result);
else session.send(`${result || 'Message rejected'}`, [550, 5, 1, 0]);
cleanup(false);
});
};

@@ -71,3 +75,3 @@

resetTimeout();
dataBuffer += chunk.toString();
dataBuffer += chunk.toString(session.utf8 ? 'utf-8' : 'ascii');

@@ -77,4 +81,5 @@ // Check for maximum message size

session.send(
'Message size exceeds fixed maximum message size',
[552, 5, 3, 4]);
'Message size exceeds fixed maximum message size',
[552, 5, 3, 4]
);
cleanup(false);

@@ -81,0 +86,0 @@ return;

@@ -7,3 +7,2 @@ import context from '../core/ServerContext.js';

export default function EHLO(args, session) {
if (!session.isValidTransition(['NEW', 'STARTTLS']))

@@ -17,3 +16,3 @@ return session.send(new Response(null, 503, [5, 5, 1]));

if(!isValidEHLO(domain))
if (!isValidEHLO(domain))
return session.send(new Response(null, 501, [5, 5, 2]));

@@ -25,11 +24,12 @@

context.onEHLO(domain, session).then((result) => {
if (result instanceof Response)
return session.send(result);
if (result instanceof Response) return session.send(result);
session.ehlo = domain;
session.send(`250-${session.greeting} Hello, ${domain}`);
const { extensions } = context;
if (session.tls) {
session.send('250-ENHANCEDSTATUSCODES');
session.send('250-PIPELINING');
extensions.forEach((extension) =>
session.send(`250-${extension.toUpperCase()}`),
);
session.send(`SIZE ${context.maxMessageSize}`, 250);

@@ -40,5 +40,7 @@ } else {

}
}).catch((err) => session.send(err instanceof Response ?
err:
new Response(null, 451, [4, 3, 0])));
}).catch((err) =>
session.send(
err instanceof Response ? err:new Response(null, 451, [4, 3, 0]),
),
);
}

@@ -7,8 +7,10 @@ import context from '../core/ServerContext.js';

if (session.state !== session.states.STARTTLS)
return session.send(session.tls ?
new Response(null, 501, [5, 5, 1]):
new Response('Must issue a STARTTLS command first.', 530, [5, 7, 0]));
return session.send(
session.tls
? new Response(null, 501, [5, 5, 1])
: new Response('Must issue a STARTTLS command first.', 530, [5, 7, 0])
);
// Validate command is MAIL FROM:
if (args.length !== 1 || !args[0].toUpperCase().startsWith('FROM:'))
if (!args[0].toUpperCase().startsWith('FROM:'))
return session.send(new Response(null, 501, [5, 5, 2]));

@@ -28,18 +30,25 @@

const [_, ...extensions] = args;
// Wait on external validation
context.onMAILFROM(sender, session).then(result => {
context
.onMAILFROM(sender, session, extensions)
.then((result) => {
// Save the sender's address in the session
session.mailFrom = sender;
// Save the sender's address in the session
session.mailFrom = sender;
// Transition to MAIL_FROM state
session.transitionTo(session.states.MAIL_FROM);
// Transition to MAIL_FROM state
session.transitionTo(session.states.MAIL_FROM);
session.send(result instanceof Response ?
result:
new Response(`Originator <${sender}> ok`, 250, [2, 1, 0]));
}).catch(err => session.send(err instanceof Response ?
err:
new Response(null, 451, [4, 3, 0])),
);
session.send(
result instanceof Response
? result
: new Response(`Originator <${sender}> ok`, 250, [2, 1, 0])
);
})
.catch((err) =>
session.send(
err instanceof Response ? err : new Response(null, 451, [4, 3, 0])
)
);
}

@@ -6,2 +6,2 @@ import Response from '../models/Response.js';

session.socket.end();
}
}

@@ -25,16 +25,23 @@ import context from '../core/ServerContext.js';

return context.onRCPTTO(recipient, session).then((result) => {
// Save the recipient's address in the session
session.rcptTo.push(recipient);
return context
.onRCPTTO(recipient, session)
.then((result) => {
// Save the recipient's address in the session
session.rcptTo.push(recipient);
// Transition to RCPT_TO state
session.transitionTo(session.states.RCPT_TO);
// Transition to RCPT_TO state
session.transitionTo(session.states.RCPT_TO);
// Send positive response
session.send(result instanceof Response ?
result:
new Response(`Recipient <${recipient}> ok`, 250, [2, 1, 5]));
}).catch(err => session.send(err instanceof Response ?
err:
new Response(null, 451, [4, 1, 1])));
// Send positive response
session.send(
result instanceof Response
? result
: new Response(`Recipient <${recipient}> ok`, 250, [2, 1, 5])
);
})
.catch((err) =>
session.send(
err instanceof Response ? err : new Response(null, 451, [4, 1, 1])
)
);
}

@@ -1,2 +0,2 @@

import {handleTLSConnection} from '../core/TLSServer.js';
import { handleTLSConnection } from '../core/TLSServer.js';
import Logger from '../utils/Logger.js';

@@ -3,0 +3,0 @@ import Response from '../models/Response.js';

@@ -1,3 +0,3 @@

import {EventEmitter} from 'events';
import { EventEmitter } from 'events';
export default new EventEmitter();
export default new EventEmitter();

@@ -1,2 +0,2 @@

import {hostname} from 'os';
import { hostname } from 'os';

@@ -12,2 +12,3 @@ class ServerContext {

this.timeout = 60 * 1000;
this.extensions = ['ENHANCEDSTATUSCODES', 'PIPELINING'];
// Default hooks

@@ -25,4 +26,4 @@ this.onConnect = async () => {};

setOptions(options = {}) {
Object.assign(this, options)
}
Object.assign(this, options);
}
}

@@ -29,0 +30,0 @@

@@ -6,3 +6,3 @@ import net from 'net';

import reverseDNS from '../utils/reverseDNS.js';
import {handleCommand, registerCommand} from '../commands/CommandHandler.js';
import { handleCommand, registerCommand } from '../commands/CommandHandler.js';
import context from './ServerContext.js';

@@ -40,4 +40,4 @@ import events from './Event.js';

session.rDNS = await reverseDNS(session.clientIP);
Logger.setLevel(context.logLevel)
const rDNS = `<${session.rDNS}>`
Logger.setLevel(context.logLevel);
const rDNS = `<${session.rDNS}>`;
Logger.info(`${session.clientIP} connected ${rDNS}`, session.id);

@@ -58,4 +58,3 @@

session.send(new Response('Line too long', 500, [5, 5, 2]));
else
handleCommand(message, session);
else handleCommand(message, session);
});

@@ -70,4 +69,6 @@

socket.on('error', (err) => {
Logger.error(`Error occurred with ${session.clientIP}: ${err.message}`,
session.id);
Logger.error(
`Error occurred with ${session.clientIP}: ${err.message}`,
session.id
);
activeSessions.delete(session);

@@ -80,5 +81,4 @@ });

Logger.info('Server is shutting down...');
server.close(() => {
Logger.info('Server closed, no longer accepting connections.');
});
server.close(
() => Logger.info('Server closed, no longer accepting connections.'));

@@ -101,2 +101,2 @@ // Gracefully close all active sessions

export const Listen = events;
export const Log = Logger;
export const Log = Logger;

@@ -5,7 +5,7 @@ import tls from 'tls';

import events from './Event.js';
import {handleCommand} from '../commands/CommandHandler.js';
import { handleCommand } from '../commands/CommandHandler.js';
export function handleTLSConnection(session) {
// Create a new TLS socket from the existing socket
const {tlsOptions} = context;
const { tlsOptions } = context;

@@ -44,4 +44,4 @@ const tlsSocket = new tls.TLSSocket(session.socket, {

async function processCommandQueue() {
if (processing) return; // Exit if another command is being processed
processing = true; // Mark processing state
if (processing) return; // Exit if another command is being processed
processing = true; // Mark processing state

@@ -52,4 +52,3 @@ while (commandQueue.length > 0) {

// Skip empty lines (possible with consecutive CRLF)
if (command.length === 0)
continue;
if (command.length === 0) continue;

@@ -93,6 +92,9 @@ Logger.debug(`C: ${command}`, session.id);

case 'ERR_SSL_UNSUPPORTED_PROTOCOL':
Logger.warn(`No shared TLS versions`, session.id);
Logger.warn(
`No shared TLS versions (Client wants ${tlsSocket.getProtocol()})`,
session.id
);
break;
case 'ERR_SSL_NO_SHARED_CIPHER':
Logger.warn(`No shared TLS ciphers`, session.id);
Logger.warn('No shared TLS ciphers.', session.id);
break;

@@ -106,19 +108,18 @@ default:

tlsSocket.on('secure', () => {
// Replace the plain socket with the secure TLS socket
session.socket = tlsSocket;
const protocol = tlsSocket.getProtocol();
const cipher = tlsSocket.getCipher().standardName;
// Set TLS connection data
session.tls = {
enabled: true,
version: tlsSocket.getProtocol(), // Get the TLS protocol version
cipher: tlsSocket.getCipher().standardName, // Get the cipher info (now it will be defined)
authorized: tlsSocket.getPeerCertificate() || false, // Check if the connection is authorized
version: protocol, // Get the TLS protocol version
cipher, // Get the cipher info (now it will be defined)
};
Logger.info(
`Connection upgraded to ${tlsSocket.getProtocol()} (${tlsSocket.getCipher().standardName})`,
session.id);
Logger.info(`Connection upgraded to ${protocol} (${cipher})`, session.id);
events.emit('SECURE');
context.onSecure(session).then(r => r);
context.onSecure(session).then((r) => r);
});

@@ -125,0 +126,0 @@

@@ -32,9 +32,13 @@ const statuses = {

const eStatus = this.enhancedStatus.join('.');
return `${this.basicStatus}${eStatusCodes ? ` ${eStatus}`: ''} ${this.message}`;
return `${this.basicStatus}${eStatusCodes ?
` ${eStatus}`:
''} ${this.message}`;
}
fetchMessage(enhancedStatus) {
return statuses[enhancedStatus.join('.')] ||
statuses[enhancedStatus.slice(1).join('.')];
return (
statuses[enhancedStatus.join('.')] ||
statuses[enhancedStatus.slice(1).join('.')]
);
}
}
}

@@ -9,8 +9,9 @@ import os from 'os';

this.socket = socket;
this.clientIP = socket.remoteAddress.startsWith('::ffff:') ?
socket.remoteAddress.slice(7):
socket.remoteAddress;
this.clientIP = socket.remoteAddress.startsWith('::ffff:')
? socket.remoteAddress.slice(7)
: socket.remoteAddress;
this.greeting = os.hostname();
this.id = crypto.randomBytes(8).toString('hex');
this.rDNS = null;
this.utf8 = false;
this.ehlo = null;

@@ -24,10 +25,10 @@ this.unknownCommands = 0;

this.states = {
NEW: 'NEW', // Just connected
NEW: 'NEW', // Just connected
EHLO_RECEIVED: 'EHLO_RECEIVED', // EHLO completed
STARTTLS: 'STARTTLS', // STARTTLS completed
MAIL_FROM: 'MAIL_FROM', // MAIL FROM received
RCPT_TO: 'RCPT_TO', // RCPT TO received
DATA_READY: 'DATA_READY', // Data received
DATA_DONE: 'DATA_DONE', // Data received
QUIT: 'QUIT', // Client has quit
STARTTLS: 'STARTTLS', // STARTTLS completed
MAIL_FROM: 'MAIL_FROM', // MAIL FROM received
RCPT_TO: 'RCPT_TO', // RCPT TO received
DATA_READY: 'DATA_READY', // Data received
DATA_DONE: 'DATA_DONE', // Data received
QUIT: 'QUIT', // Client has quit
};

@@ -41,22 +42,19 @@

* @param {String|Error|Response} message
* @param code {Number|Array}
* @param code {Number|Array|undefined}
*/
send(message, code = undefined) {
let output = '';
if (message instanceof Response) {
if (message instanceof Response)
output = message.toString(this.tls);
} else if (message instanceof Error) {
else if (message instanceof Error)
output = `${message.responseCode} ${message.message}`;
} else if (code === undefined) {
else if (code === undefined)
output = message;
} else if (code instanceof Array) {
const basic = code.shift();
const enhanced = code.join('.');
output = `${basic} ${enhanced} ${message}`;
} else if (Number.isInteger(code)) {
else if (code instanceof Array)
output = `${code.shift()} ${code.join('.')} ${message}`;
else if (Number.isInteger(code))
output = `${code} ${message}`;
}
Logger.debug(`S: ${output}`, this.id);
this.socket.write(`${output}\r\n`);
}

@@ -77,5 +75,4 @@

return false;
} else
return this.state === expectedState;
} else return this.state === expectedState;
}
}

@@ -7,3 +7,3 @@ import context from '../core/ServerContext.js';

WARN: 'WARN',
ERROR: 'ERROR'
ERROR: 'ERROR',
};

@@ -13,6 +13,6 @@

DEBUG: '\x1b[36m', // Cyan
INFO: '\x1b[32m', // Green
WARN: '\x1b[33m', // Yellow
INFO: '\x1b[32m', // Green
WARN: '\x1b[33m', // Yellow
ERROR: '\x1b[31m', // Red
RESET: '\x1b[0m' // Reset color
RESET: '\x1b[0m', // Reset color
};

@@ -43,3 +43,2 @@

return `${colors[level]}[${level}]${colors.RESET} ${' '.repeat(5 - level.length)}[${time}]${session}${message}`;

@@ -76,3 +75,3 @@ }

[levels.WARN]: 3,
[levels.ERROR]: 4
[levels.ERROR]: 4,
};

@@ -79,0 +78,0 @@

@@ -16,2 +16,2 @@ import dns from 'node:dns/promises';

}
}
}

@@ -13,3 +13,3 @@ import { isIPv4, isIPv6 } from 'net'; // Node.js built-in module for IP validation

const ip = ehloValue.slice(1, -1); // Remove the brackets
return isIPv4(ip) || isIPv6(ip); // Check if it's a valid IPv4 or IPv6
return isIPv4(ip) || isIPv6(ip); // Check if it's a valid IPv4 or IPv6
}

@@ -24,3 +24,6 @@

// Ensure no label starts or ends with a hyphen and no label is longer than 63 characters
return labels.every(label => !label.startsWith('-') && !label.endsWith('-') && label.length <= 63);
return labels.every(
(label) =>
!label.startsWith('-') && !label.endsWith('-') && label.length <= 63
);
}

@@ -27,0 +30,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc