source: node_modules/nodemailer/lib/dkim/index.js

main
Last change on this file was 81bc7da, checked in by Klimentina Efremova <klimentina08642@…>, 3 months ago

Initial commit

  • Property mode set to 100644
File size: 7.1 KB
RevLine 
[81bc7da]1'use strict';
2
3// FIXME:
4// replace this Transform mess with a method that pipes input argument to output argument
5
6const MessageParser = require('./message-parser');
7const RelaxedBody = require('./relaxed-body');
8const sign = require('./sign');
9const PassThrough = require('stream').PassThrough;
10const fs = require('fs');
11const path = require('path');
12const crypto = require('crypto');
13
14const DKIM_ALGO = 'sha256';
15const MAX_MESSAGE_SIZE = 128 * 1024; // buffer messages larger than this to disk
16
17/*
18// Usage:
19
20let dkim = new DKIM({
21 domainName: 'example.com',
22 keySelector: 'key-selector',
23 privateKey,
24 cacheDir: '/tmp'
25});
26dkim.sign(input).pipe(process.stdout);
27
28// Where inputStream is a rfc822 message (either a stream, string or Buffer)
29// and outputStream is a DKIM signed rfc822 message
30*/
31
32class DKIMSigner {
33 constructor(options, keys, input, output) {
34 this.options = options || {};
35 this.keys = keys;
36
37 this.cacheTreshold = Number(this.options.cacheTreshold) || MAX_MESSAGE_SIZE;
38 this.hashAlgo = this.options.hashAlgo || DKIM_ALGO;
39
40 this.cacheDir = this.options.cacheDir || false;
41
42 this.chunks = [];
43 this.chunklen = 0;
44 this.readPos = 0;
45 this.cachePath = this.cacheDir ? path.join(this.cacheDir, 'message.' + Date.now() + '-' + crypto.randomBytes(14).toString('hex')) : false;
46 this.cache = false;
47
48 this.headers = false;
49 this.bodyHash = false;
50 this.parser = false;
51 this.relaxedBody = false;
52
53 this.input = input;
54 this.output = output;
55 this.output.usingCache = false;
56
57 this.hasErrored = false;
58
59 this.input.on('error', err => {
60 this.hasErrored = true;
61 this.cleanup();
62 output.emit('error', err);
63 });
64 }
65
66 cleanup() {
67 if (!this.cache || !this.cachePath) {
68 return;
69 }
70 fs.unlink(this.cachePath, () => false);
71 }
72
73 createReadCache() {
74 // pipe remainings to cache file
75 this.cache = fs.createReadStream(this.cachePath);
76 this.cache.once('error', err => {
77 this.cleanup();
78 this.output.emit('error', err);
79 });
80 this.cache.once('close', () => {
81 this.cleanup();
82 });
83 this.cache.pipe(this.output);
84 }
85
86 sendNextChunk() {
87 if (this.hasErrored) {
88 return;
89 }
90
91 if (this.readPos >= this.chunks.length) {
92 if (!this.cache) {
93 return this.output.end();
94 }
95 return this.createReadCache();
96 }
97 let chunk = this.chunks[this.readPos++];
98 if (this.output.write(chunk) === false) {
99 return this.output.once('drain', () => {
100 this.sendNextChunk();
101 });
102 }
103 setImmediate(() => this.sendNextChunk());
104 }
105
106 sendSignedOutput() {
107 let keyPos = 0;
108 let signNextKey = () => {
109 if (keyPos >= this.keys.length) {
110 this.output.write(this.parser.rawHeaders);
111 return setImmediate(() => this.sendNextChunk());
112 }
113 let key = this.keys[keyPos++];
114 let dkimField = sign(this.headers, this.hashAlgo, this.bodyHash, {
115 domainName: key.domainName,
116 keySelector: key.keySelector,
117 privateKey: key.privateKey,
118 headerFieldNames: this.options.headerFieldNames,
119 skipFields: this.options.skipFields
120 });
121 if (dkimField) {
122 this.output.write(Buffer.from(dkimField + '\r\n'));
123 }
124 return setImmediate(signNextKey);
125 };
126
127 if (this.bodyHash && this.headers) {
128 return signNextKey();
129 }
130
131 this.output.write(this.parser.rawHeaders);
132 this.sendNextChunk();
133 }
134
135 createWriteCache() {
136 this.output.usingCache = true;
137 // pipe remainings to cache file
138 this.cache = fs.createWriteStream(this.cachePath);
139 this.cache.once('error', err => {
140 this.cleanup();
141 // drain input
142 this.relaxedBody.unpipe(this.cache);
143 this.relaxedBody.on('readable', () => {
144 while (this.relaxedBody.read() !== null) {
145 // do nothing
146 }
147 });
148 this.hasErrored = true;
149 // emit error
150 this.output.emit('error', err);
151 });
152 this.cache.once('close', () => {
153 this.sendSignedOutput();
154 });
155 this.relaxedBody.removeAllListeners('readable');
156 this.relaxedBody.pipe(this.cache);
157 }
158
159 signStream() {
160 this.parser = new MessageParser();
161 this.relaxedBody = new RelaxedBody({
162 hashAlgo: this.hashAlgo
163 });
164
165 this.parser.on('headers', value => {
166 this.headers = value;
167 });
168
169 this.relaxedBody.on('hash', value => {
170 this.bodyHash = value;
171 });
172
173 this.relaxedBody.on('readable', () => {
174 let chunk;
175 if (this.cache) {
176 return;
177 }
178 while ((chunk = this.relaxedBody.read()) !== null) {
179 this.chunks.push(chunk);
180 this.chunklen += chunk.length;
181 if (this.chunklen >= this.cacheTreshold && this.cachePath) {
182 return this.createWriteCache();
183 }
184 }
185 });
186
187 this.relaxedBody.on('end', () => {
188 if (this.cache) {
189 return;
190 }
191 this.sendSignedOutput();
192 });
193
194 this.parser.pipe(this.relaxedBody);
195 setImmediate(() => this.input.pipe(this.parser));
196 }
197}
198
199class DKIM {
200 constructor(options) {
201 this.options = options || {};
202 this.keys = [].concat(
203 this.options.keys || {
204 domainName: options.domainName,
205 keySelector: options.keySelector,
206 privateKey: options.privateKey
207 }
208 );
209 }
210
211 sign(input, extraOptions) {
212 let output = new PassThrough();
213 let inputStream = input;
214 let writeValue = false;
215
216 if (Buffer.isBuffer(input)) {
217 writeValue = input;
218 inputStream = new PassThrough();
219 } else if (typeof input === 'string') {
220 writeValue = Buffer.from(input);
221 inputStream = new PassThrough();
222 }
223
224 let options = this.options;
225 if (extraOptions && Object.keys(extraOptions).length) {
226 options = {};
227 Object.keys(this.options || {}).forEach(key => {
228 options[key] = this.options[key];
229 });
230 Object.keys(extraOptions || {}).forEach(key => {
231 if (!(key in options)) {
232 options[key] = extraOptions[key];
233 }
234 });
235 }
236
237 let signer = new DKIMSigner(options, this.keys, inputStream, output);
238 setImmediate(() => {
239 signer.signStream();
240 if (writeValue) {
241 setImmediate(() => {
242 inputStream.end(writeValue);
243 });
244 }
245 });
246
247 return output;
248 }
249}
250
251module.exports = DKIM;
Note: See TracBrowser for help on using the repository browser.