@push.rocks/smartmta 6.5.1 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/changelog.md +20 -0
  2. package/dist_rust/mailer-bin_linux_amd64 +0 -0
  3. package/dist_rust/mailer-bin_linux_arm64 +0 -0
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/index.d.ts +3 -0
  6. package/dist_ts/index.js +4 -1
  7. package/dist_ts/mail/core/classes.bouncemanager.d.ts +5 -12
  8. package/dist_ts/mail/core/classes.bouncemanager.js +27 -92
  9. package/dist_ts/mail/core/classes.email.js +2 -2
  10. package/dist_ts/mail/core/classes.templatemanager.d.ts +10 -6
  11. package/dist_ts/mail/core/classes.templatemanager.js +35 -51
  12. package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +15 -20
  13. package/dist_ts/mail/delivery/classes.delivery.queue.js +196 -114
  14. package/dist_ts/mail/delivery/classes.delivery.system.js +5 -5
  15. package/dist_ts/mail/interfaces.storage.d.ts +37 -6
  16. package/dist_ts/mail/interfaces.storage.js +33 -3
  17. package/dist_ts/mail/routing/classes.dkim.manager.d.ts +2 -2
  18. package/dist_ts/mail/routing/classes.dkim.manager.js +2 -3
  19. package/dist_ts/mail/routing/classes.dns.manager.d.ts +2 -2
  20. package/dist_ts/mail/routing/classes.dns.manager.js +1 -1
  21. package/dist_ts/mail/routing/classes.email.router.d.ts +2 -2
  22. package/dist_ts/mail/routing/classes.email.router.js +4 -5
  23. package/dist_ts/mail/routing/classes.unified.email.server.d.ts +8 -6
  24. package/dist_ts/mail/routing/classes.unified.email.server.js +12 -41
  25. package/dist_ts/mail/routing/interfaces.d.ts +0 -2
  26. package/dist_ts/mail/security/classes.dkimcreator.d.ts +15 -45
  27. package/dist_ts/mail/security/classes.dkimcreator.js +66 -294
  28. package/dist_ts/paths.d.ts +0 -12
  29. package/dist_ts/paths.js +3 -36
  30. package/dist_ts/plugins.d.ts +2 -5
  31. package/dist_ts/plugins.js +3 -6
  32. package/dist_ts/security/classes.contentscanner.js +1 -2
  33. package/dist_ts/security/classes.ipreputationchecker.d.ts +6 -6
  34. package/dist_ts/security/classes.ipreputationchecker.js +24 -84
  35. package/dist_ts/security/classes.rustsecuritybridge.d.ts +3 -3
  36. package/package.json +2 -3
  37. package/readme.md +12 -6
  38. package/ts/00_commitinfo_data.ts +1 -1
  39. package/ts/index.ts +4 -0
  40. package/ts/mail/core/classes.bouncemanager.ts +31 -110
  41. package/ts/mail/core/classes.email.ts +1 -1
  42. package/ts/mail/core/classes.templatemanager.ts +42 -57
  43. package/ts/mail/delivery/classes.delivery.queue.ts +264 -135
  44. package/ts/mail/delivery/classes.delivery.system.ts +7 -5
  45. package/ts/mail/interfaces.storage.ts +64 -10
  46. package/ts/mail/routing/classes.dkim.manager.ts +3 -3
  47. package/ts/mail/routing/classes.dns.manager.ts +3 -3
  48. package/ts/mail/routing/classes.email.router.ts +6 -6
  49. package/ts/mail/routing/classes.unified.email.server.ts +23 -44
  50. package/ts/mail/routing/interfaces.ts +0 -2
  51. package/ts/mail/security/classes.dkimcreator.ts +104 -352
  52. package/ts/paths.ts +2 -41
  53. package/ts/plugins.ts +1 -6
  54. package/ts/security/classes.contentscanner.ts +0 -1
  55. package/ts/security/classes.ipreputationchecker.ts +26 -90
  56. package/ts/security/classes.rustsecuritybridge.ts +3 -3
@@ -1,19 +1,10 @@
1
1
  import * as plugins from '../../plugins.js';
2
- import * as paths from '../../paths.js';
3
- import { type IStorageManagerLike, hasStorageManagerMethods } from '../interfaces.storage.js';
2
+ import type { IStorageManager } from '../interfaces.storage.js';
4
3
 
5
4
  import { Email } from '../core/classes.email.js';
6
- // MtaService reference removed
7
5
 
8
- const readFile = plugins.util.promisify(plugins.fs.readFile);
9
- const writeFile = plugins.util.promisify(plugins.fs.writeFile);
10
6
  const generateKeyPair = plugins.util.promisify(plugins.crypto.generateKeyPair);
11
7
 
12
- export interface IKeyPaths {
13
- privateKeyPath: string;
14
- publicKeyPath: string;
15
- }
16
-
17
8
  export interface IDkimKeyMetadata {
18
9
  domain: string;
19
10
  selector: string;
@@ -24,79 +15,51 @@ export interface IDkimKeyMetadata {
24
15
  }
25
16
 
26
17
  export class DKIMCreator {
27
- private keysDir: string;
28
- private storageManager?: IStorageManagerLike;
18
+ constructor(private readonly storageManager: IStorageManager) {}
29
19
 
30
- constructor(keysDir = paths.keysDir, storageManager?: IStorageManagerLike) {
31
- this.keysDir = keysDir;
32
- this.storageManager = storageManager;
20
+ private legacyKey(domain: string, filename: 'private.key' | 'public.key'): string {
21
+ return `/email/dkim/${domain}/${filename}`;
33
22
  }
34
23
 
35
- private async writeKeyPairToFilesystem(
36
- privateKeyPath: string,
37
- publicKeyPath: string,
38
- privateKey: string,
39
- publicKey: string,
40
- ): Promise<void> {
41
- await Promise.all([writeFile(privateKeyPath, privateKey), writeFile(publicKeyPath, publicKey)]);
24
+ private selectorKey(
25
+ domain: string,
26
+ selector: string,
27
+ filename: 'private.key' | 'public.key',
28
+ ): string {
29
+ return selector === 'default'
30
+ ? this.legacyKey(domain, filename)
31
+ : `/email/dkim/${domain}/${selector}/${filename}`;
42
32
  }
43
33
 
44
- private async storeLegacyKeysToStorage(domain: string, privateKey: string, publicKey: string): Promise<void> {
45
- if (!hasStorageManagerMethods(this.storageManager, ['set'])) {
46
- return;
47
- }
48
- await Promise.all([
49
- this.storageManager.set(`/email/dkim/${domain}/private.key`, privateKey),
50
- this.storageManager.set(`/email/dkim/${domain}/public.key`, publicKey),
51
- ]);
34
+ private metadataKey(domain: string, selector: string): string {
35
+ return `/email/dkim/${domain}/${selector}/metadata`;
52
36
  }
53
37
 
54
- private async storeSelectorKeysToStorage(
38
+ private async storeKeyPair(
55
39
  domain: string,
56
40
  selector: string,
57
41
  privateKey: string,
58
42
  publicKey: string,
59
43
  ): Promise<void> {
60
- if (!hasStorageManagerMethods(this.storageManager, ['set'])) {
61
- return;
62
- }
63
44
  await Promise.all([
64
- this.storageManager.set(`/email/dkim/${domain}/${selector}/private.key`, privateKey),
65
- this.storageManager.set(`/email/dkim/${domain}/${selector}/public.key`, publicKey),
45
+ this.storageManager.set(this.selectorKey(domain, selector, 'private.key'), privateKey),
46
+ this.storageManager.set(this.selectorKey(domain, selector, 'public.key'), publicKey),
66
47
  ]);
67
48
  }
68
49
 
69
- public async getKeyPathsForDomain(domainArg: string): Promise<IKeyPaths> {
70
- return {
71
- privateKeyPath: plugins.path.join(this.keysDir, `${domainArg}-private.pem`),
72
- publicKeyPath: plugins.path.join(this.keysDir, `${domainArg}-public.pem`),
73
- };
50
+ public async handleDKIMKeysForDomain(domain: string): Promise<void> {
51
+ await this.handleDKIMKeysForSelector(domain, 'default');
74
52
  }
75
53
 
76
- // Check if a DKIM key is present and creates one and stores it to disk otherwise
77
- public async handleDKIMKeysForDomain(domainArg: string): Promise<void> {
78
- try {
79
- await this.readDKIMKeys(domainArg);
80
- } catch (error) {
81
- console.log(`No DKIM keys found for ${domainArg}. Generating...`);
82
- await this.createAndStoreDKIMKeys(domainArg);
83
- const dnsValue = await this.getDNSRecordForDomain(domainArg);
84
- await plugins.smartfs.directory(paths.dnsRecordsDir).recursive().create();
85
- await plugins.smartfs.file(plugins.path.join(paths.dnsRecordsDir, `${domainArg}.dkimrecord.json`)).write(JSON.stringify(dnsValue, null, 2));
86
- }
87
- }
88
-
89
- public async handleDKIMKeysForSelector(domainArg: string, selector: string = 'default', keySize: number = 2048): Promise<void> {
90
- if (selector === 'default') {
91
- await this.handleDKIMKeysForDomain(domainArg);
92
- return;
93
- }
94
-
54
+ public async handleDKIMKeysForSelector(
55
+ domain: string,
56
+ selector = 'default',
57
+ keySize = 2048,
58
+ ): Promise<void> {
95
59
  try {
96
- await this.readDKIMKeysForSelector(domainArg, selector);
60
+ await this.readDKIMKeysForSelector(domain, selector);
97
61
  } catch {
98
- console.log(`No DKIM keys found for ${domainArg}/${selector}. Generating...`);
99
- await this.createAndStoreDKIMKeysForSelector(domainArg, selector, keySize);
62
+ await this.createAndStoreDKIMKeysForSelector(domain, selector, keySize);
100
63
  }
101
64
  }
102
65
 
@@ -105,367 +68,156 @@ export class DKIMCreator {
105
68
  await this.handleDKIMKeysForDomain(domain);
106
69
  }
107
70
 
108
- // Read DKIM keys - always use storage manager, migrate from filesystem if needed
109
- public async readDKIMKeys(domainArg: string): Promise<{ privateKey: string; publicKey: string }> {
110
- // Try to read from storage manager first
111
- if (hasStorageManagerMethods(this.storageManager, ['get', 'set'])) {
112
- try {
113
- const [privateKey, publicKey] = await Promise.all([
114
- this.storageManager.get(`/email/dkim/${domainArg}/private.key`),
115
- this.storageManager.get(`/email/dkim/${domainArg}/public.key`)
116
- ]);
117
-
118
- if (privateKey && publicKey) {
119
- return { privateKey, publicKey };
120
- }
121
- } catch (error) {
122
- // Fall through to migration check
123
- }
124
-
125
- // Check if keys exist in filesystem and migrate them to storage manager
126
- const keyPaths = await this.getKeyPathsForDomain(domainArg);
127
- try {
128
- const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
129
- readFile(keyPaths.privateKeyPath),
130
- readFile(keyPaths.publicKeyPath),
131
- ]);
132
-
133
- // Convert the buffers to strings
134
- const privateKey = privateKeyBuffer.toString();
135
- const publicKey = publicKeyBuffer.toString();
136
-
137
- // Migrate to storage manager
138
- console.log(`Migrating DKIM keys for ${domainArg} from filesystem to StorageManager`);
139
- await this.storeLegacyKeysToStorage(domainArg, privateKey, publicKey);
140
-
141
- return { privateKey, publicKey };
142
- } catch (error) {
143
- if (error.code === 'ENOENT') {
144
- // Keys don't exist anywhere
145
- throw new Error(`DKIM keys not found for domain ${domainArg}`);
146
- }
147
- throw error;
148
- }
149
- } else {
150
- // No storage manager, use filesystem directly
151
- const keyPaths = await this.getKeyPathsForDomain(domainArg);
152
- const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
153
- readFile(keyPaths.privateKeyPath),
154
- readFile(keyPaths.publicKeyPath),
155
- ]);
71
+ public async readDKIMKeys(domain: string): Promise<{ privateKey: string; publicKey: string }> {
72
+ return this.readDKIMKeysForSelector(domain, 'default');
73
+ }
156
74
 
157
- const privateKey = privateKeyBuffer.toString();
158
- const publicKey = publicKeyBuffer.toString();
159
-
160
- return { privateKey, publicKey };
75
+ public async readDKIMKeysForSelector(
76
+ domain: string,
77
+ selector: string,
78
+ ): Promise<{ privateKey: string; publicKey: string }> {
79
+ const [privateKey, publicKey] = await Promise.all([
80
+ this.storageManager.get(this.selectorKey(domain, selector, 'private.key')),
81
+ this.storageManager.get(this.selectorKey(domain, selector, 'public.key')),
82
+ ]);
83
+ if (!privateKey || !publicKey) {
84
+ throw new Error(`DKIM keys not found for domain ${domain} with selector ${selector}`);
161
85
  }
86
+ return { privateKey, publicKey };
162
87
  }
163
88
 
164
- // Create an RSA DKIM key pair - changed to public for API access
165
- public async createDKIMKeys(keySize: number = 2048): Promise<{ privateKey: string; publicKey: string }> {
89
+ public async createDKIMKeys(
90
+ keySize = 2048,
91
+ ): Promise<{ privateKey: string; publicKey: string }> {
166
92
  const { privateKey, publicKey } = await generateKeyPair('rsa', {
167
93
  modulusLength: keySize,
168
94
  publicKeyEncoding: { type: 'spki', format: 'pem' },
169
95
  privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
170
96
  });
171
-
172
97
  return { privateKey, publicKey };
173
98
  }
174
99
 
175
- // Create an Ed25519 DKIM key pair (RFC 8463)
176
100
  public async createEd25519Keys(): Promise<{ privateKey: string; publicKey: string }> {
177
101
  const { privateKey, publicKey } = await generateKeyPair('ed25519', {
178
102
  publicKeyEncoding: { type: 'spki', format: 'pem' },
179
103
  privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
180
104
  });
181
-
182
105
  return { privateKey, publicKey };
183
106
  }
184
107
 
185
- // Create a DKIM key pair and store it to disk - changed to public for API access
186
- public async createAndStoreDKIMKeys(domain: string, keySize: number = 2048): Promise<void> {
187
- const { privateKey, publicKey } = await this.createDKIMKeys(keySize);
188
- const keyPaths = await this.getKeyPathsForDomain(domain);
189
- await this.storeLegacyKeysToStorage(domain, privateKey, publicKey);
190
- await this.writeKeyPairToFilesystem(keyPaths.privateKeyPath, keyPaths.publicKeyPath, privateKey, publicKey);
191
- await this.saveKeyMetadata({
192
- domain,
193
- selector: 'default',
194
- createdAt: Date.now(),
195
- keySize,
196
- });
197
- console.log(`DKIM keys for ${domain} created and stored.`);
108
+ public async createAndStoreDKIMKeys(domain: string, keySize = 2048): Promise<void> {
109
+ await this.createAndStoreDKIMKeysForSelector(domain, 'default', keySize);
198
110
  }
199
111
 
200
112
  public async createAndStoreDKIMKeysForSelector(
201
113
  domain: string,
202
114
  selector: string,
203
- keySize: number = 2048,
115
+ keySize = 2048,
204
116
  ): Promise<void> {
205
- if (selector === 'default') {
206
- await this.createAndStoreDKIMKeys(domain, keySize);
207
- return;
208
- }
209
-
210
117
  const { privateKey, publicKey } = await this.createDKIMKeys(keySize);
211
- const keyPaths = await this.getKeyPathsForSelector(domain, selector);
212
- await this.storeSelectorKeysToStorage(domain, selector, privateKey, publicKey);
213
- await this.writeKeyPairToFilesystem(keyPaths.privateKeyPath, keyPaths.publicKeyPath, privateKey, publicKey);
118
+ await this.storeKeyPair(domain, selector, privateKey, publicKey);
214
119
  await this.saveKeyMetadata({
215
120
  domain,
216
121
  selector,
217
122
  createdAt: Date.now(),
218
123
  keySize,
219
124
  });
220
- console.log(`DKIM keys for ${domain}/${selector} created and stored.`);
221
125
  }
222
126
 
223
- // Changed to public for API access
224
127
  public async getDNSRecordForDomain(
225
- domainArg: string,
226
- selector: string = 'default',
128
+ domain: string,
129
+ selector = 'default',
227
130
  ): Promise<plugins.tsclass.network.IDnsRecord> {
228
- await this.handleDKIMKeysForSelector(domainArg, selector);
229
- return this.getDNSRecordForSelector(domainArg, selector);
131
+ await this.handleDKIMKeysForSelector(domain, selector);
132
+ return this.getDNSRecordForSelector(domain, selector);
230
133
  }
231
134
 
232
- /**
233
- * Get DKIM key metadata for a domain
234
- */
235
- private async getKeyMetadata(domain: string, selector: string = 'default'): Promise<IDkimKeyMetadata | null> {
236
- if (!hasStorageManagerMethods(this.storageManager, ['get'])) {
237
- return null;
238
- }
239
-
240
- const metadataKey = `/email/dkim/${domain}/${selector}/metadata`;
241
- const metadataStr = await this.storageManager.get(metadataKey);
242
-
243
- if (!metadataStr) {
244
- return null;
245
- }
246
-
247
- return JSON.parse(metadataStr) as IDkimKeyMetadata;
135
+ private async getKeyMetadata(
136
+ domain: string,
137
+ selector = 'default',
138
+ ): Promise<IDkimKeyMetadata | null> {
139
+ const metadata = await this.storageManager.get(this.metadataKey(domain, selector));
140
+ return metadata ? JSON.parse(metadata) as IDkimKeyMetadata : null;
248
141
  }
249
142
 
250
- /**
251
- * Save DKIM key metadata
252
- */
253
143
  private async saveKeyMetadata(metadata: IDkimKeyMetadata): Promise<void> {
254
- if (!hasStorageManagerMethods(this.storageManager, ['set'])) {
255
- return;
256
- }
257
-
258
- const metadataKey = `/email/dkim/${metadata.domain}/${metadata.selector}/metadata`;
259
- await this.storageManager.set(metadataKey, JSON.stringify(metadata));
144
+ await this.storageManager.set(
145
+ this.metadataKey(metadata.domain, metadata.selector),
146
+ JSON.stringify(metadata),
147
+ );
260
148
  }
261
149
 
262
- /**
263
- * Check if DKIM keys need rotation
264
- */
265
- public async needsRotation(domain: string, selector: string = 'default', rotationIntervalDays: number = 90): Promise<boolean> {
150
+ public async needsRotation(
151
+ domain: string,
152
+ selector = 'default',
153
+ rotationIntervalDays = 90,
154
+ ): Promise<boolean> {
266
155
  const metadata = await this.getKeyMetadata(domain, selector);
267
-
268
- if (!metadata) {
269
- // No metadata means old keys, should rotate
270
- return true;
271
- }
272
-
273
- const now = Date.now();
274
- const keyAgeMs = now - metadata.createdAt;
275
- const keyAgeDays = keyAgeMs / (1000 * 60 * 60 * 24);
276
-
277
- return keyAgeDays >= rotationIntervalDays;
156
+ if (!metadata) return true;
157
+ return (Date.now() - metadata.createdAt) / (1000 * 60 * 60 * 24) >= rotationIntervalDays;
278
158
  }
279
159
 
280
- /**
281
- * Rotate DKIM keys for a domain
282
- */
283
- public async rotateDkimKeys(domain: string, currentSelector: string = 'default', keySize: number = 2048): Promise<string> {
284
- console.log(`Rotating DKIM keys for ${domain}...`);
285
-
286
- // Generate new selector based on date
160
+ public async rotateDkimKeys(
161
+ domain: string,
162
+ currentSelector = 'default',
163
+ keySize = 2048,
164
+ ): Promise<string> {
287
165
  const now = new Date();
288
166
  const newSelector = `key${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`;
289
-
290
- // Create new keys with custom key size
291
167
  const { privateKey, publicKey } = await this.createDKIMKeys(keySize);
292
-
293
- // Store new keys with new selector
294
- const newKeyPaths = await this.getKeyPathsForSelector(domain, newSelector);
295
-
296
- // Store in storage manager if available
297
- await this.storeSelectorKeysToStorage(domain, newSelector, privateKey, publicKey);
298
-
299
- // Also store to filesystem
300
- await this.writeKeyPairToFilesystem(newKeyPaths.privateKeyPath, newKeyPaths.publicKeyPath, privateKey, publicKey);
301
-
302
- // Save metadata for new keys
303
- const metadata: IDkimKeyMetadata = {
168
+ await this.storeKeyPair(domain, newSelector, privateKey, publicKey);
169
+ await this.saveKeyMetadata({
304
170
  domain,
305
171
  selector: newSelector,
306
172
  createdAt: Date.now(),
307
173
  previousSelector: currentSelector,
308
- keySize
309
- };
310
- await this.saveKeyMetadata(metadata);
311
-
312
- // Update metadata for old keys
174
+ keySize,
175
+ });
176
+
313
177
  const oldMetadata = await this.getKeyMetadata(domain, currentSelector);
314
178
  if (oldMetadata) {
315
179
  oldMetadata.rotatedAt = Date.now();
316
180
  await this.saveKeyMetadata(oldMetadata);
317
181
  }
318
-
319
- console.log(`DKIM keys rotated for ${domain}. New selector: ${newSelector}`);
320
182
  return newSelector;
321
183
  }
322
184
 
323
- /**
324
- * Get key paths for a specific selector
325
- */
326
- public async getKeyPathsForSelector(domain: string, selector: string): Promise<IKeyPaths> {
327
- return {
328
- privateKeyPath: plugins.path.join(this.keysDir, `${domain}-${selector}-private.pem`),
329
- publicKeyPath: plugins.path.join(this.keysDir, `${domain}-${selector}-public.pem`),
330
- };
331
- }
332
-
333
- /**
334
- * Read DKIM keys for a specific selector
335
- */
336
- public async readDKIMKeysForSelector(domain: string, selector: string): Promise<{ privateKey: string; publicKey: string }> {
337
- // Try to read from storage manager first
338
- if (hasStorageManagerMethods(this.storageManager, ['get', 'set'])) {
339
- try {
340
- const [privateKey, publicKey] = await Promise.all([
341
- this.storageManager.get(`/email/dkim/${domain}/${selector}/private.key`),
342
- this.storageManager.get(`/email/dkim/${domain}/${selector}/public.key`)
343
- ]);
344
-
345
- if (privateKey && publicKey) {
346
- return { privateKey, publicKey };
347
- }
348
-
349
- if (selector === 'default') {
350
- return await this.readDKIMKeys(domain);
351
- }
352
- } catch (error) {
353
- // Fall through to migration check
354
- }
355
-
356
- // Check if keys exist in filesystem and migrate them to storage manager
357
- const keyPaths = await this.getKeyPathsForSelector(domain, selector);
358
- try {
359
- const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
360
- readFile(keyPaths.privateKeyPath),
361
- readFile(keyPaths.publicKeyPath),
362
- ]);
363
-
364
- const privateKey = privateKeyBuffer.toString();
365
- const publicKey = publicKeyBuffer.toString();
366
-
367
- // Migrate to storage manager
368
- console.log(`Migrating DKIM keys for ${domain}/${selector} from filesystem to StorageManager`);
369
- await this.storeSelectorKeysToStorage(domain, selector, privateKey, publicKey);
370
-
371
- return { privateKey, publicKey };
372
- } catch (error) {
373
- if (error.code === 'ENOENT') {
374
- throw new Error(`DKIM keys not found for domain ${domain} with selector ${selector}`);
375
- }
376
- throw error;
377
- }
378
- } else {
379
- // No storage manager, use filesystem directly
380
- if (selector === 'default') {
381
- return this.readDKIMKeys(domain);
382
- }
383
- const keyPaths = await this.getKeyPathsForSelector(domain, selector);
384
- const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
385
- readFile(keyPaths.privateKeyPath),
386
- readFile(keyPaths.publicKeyPath),
387
- ]);
388
-
389
- const privateKey = privateKeyBuffer.toString();
390
- const publicKey = publicKeyBuffer.toString();
391
-
392
- return { privateKey, publicKey };
393
- }
394
- }
395
-
396
- /**
397
- * Get DNS record for a specific selector
398
- */
399
- public async getDNSRecordForSelector(domain: string, selector: string): Promise<plugins.tsclass.network.IDnsRecord> {
185
+ public async getDNSRecordForSelector(
186
+ domain: string,
187
+ selector: string,
188
+ ): Promise<plugins.tsclass.network.IDnsRecord> {
400
189
  const keys = await this.readDKIMKeysForSelector(domain, selector);
401
-
402
- // Remove the PEM header and footer and newlines
403
- const pemHeader = '-----BEGIN PUBLIC KEY-----';
404
- const pemFooter = '-----END PUBLIC KEY-----';
405
190
  const keyContents = keys.publicKey
406
- .replace(pemHeader, '')
407
- .replace(pemFooter, '')
408
- .replace(/\n/g, '');
409
-
410
- // Detect key type from PEM header
411
- const keyAlgo = keys.privateKey.includes('ED25519') || keys.publicKey.length < 200 ? 'ed25519' : 'rsa';
412
-
413
- // Generate the DKIM DNS TXT record
414
- const dnsRecordValue = `v=DKIM1; h=sha256; k=${keyAlgo}; p=${keyContents}`;
191
+ .replace('-----BEGIN PUBLIC KEY-----', '')
192
+ .replace('-----END PUBLIC KEY-----', '')
193
+ .replace(/\s/g, '');
194
+ const keyAlgo = keys.privateKey.includes('ED25519') || keys.publicKey.length < 200
195
+ ? 'ed25519'
196
+ : 'rsa';
415
197
 
416
198
  return {
417
199
  name: `${selector}._domainkey.${domain}`,
418
200
  type: 'TXT',
419
201
  dnsSecEnabled: null,
420
- value: dnsRecordValue,
202
+ value: `v=DKIM1; h=sha256; k=${keyAlgo}; p=${keyContents}`,
421
203
  };
422
204
  }
423
205
 
424
- /**
425
- * Clean up old DKIM keys after grace period
426
- */
427
- public async cleanupOldKeys(domain: string, gracePeriodDays: number = 30): Promise<void> {
428
- if (!hasStorageManagerMethods(this.storageManager, ['get', 'list', 'delete'])) {
429
- console.log(`StorageManager for ${domain} does not support list/delete. Skipping DKIM cleanup.`);
430
- return;
431
- }
432
-
433
- // List all selectors for the domain
434
- const metadataKeys = await this.storageManager.list(`/email/dkim/${domain}/`);
435
-
436
- for (const key of metadataKeys) {
437
- if (key.endsWith('/metadata')) {
438
- const metadataStr = await this.storageManager.get(key);
439
- if (metadataStr) {
440
- const metadata = JSON.parse(metadataStr) as IDkimKeyMetadata;
441
-
442
- // Check if key is rotated and past grace period
443
- if (metadata.rotatedAt) {
444
- const gracePeriodMs = gracePeriodDays * 24 * 60 * 60 * 1000;
445
- const now = Date.now();
446
-
447
- if (now - metadata.rotatedAt > gracePeriodMs) {
448
- console.log(`Cleaning up old DKIM keys for ${domain} selector ${metadata.selector}`);
449
-
450
- // Delete key files
451
- const keyPaths = await this.getKeyPathsForSelector(domain, metadata.selector);
452
- try {
453
- await plugins.fs.promises.unlink(keyPaths.privateKeyPath);
454
- await plugins.fs.promises.unlink(keyPaths.publicKeyPath);
455
- } catch (error) {
456
- console.warn(`Failed to delete old key files: ${error.message}`);
457
- }
458
-
459
- // Delete selector-specific storage keys and metadata
460
- await Promise.all([
461
- this.storageManager.delete(`/email/dkim/${domain}/${metadata.selector}/private.key`),
462
- this.storageManager.delete(`/email/dkim/${domain}/${metadata.selector}/public.key`),
463
- ]);
464
- await this.storageManager.delete(key);
465
- }
466
- }
467
- }
468
- }
206
+ public async cleanupOldKeys(domain: string, gracePeriodDays = 30): Promise<void> {
207
+ const cutoff = Date.now() - gracePeriodDays * 24 * 60 * 60 * 1000;
208
+ const keys = await this.storageManager.list(`/email/dkim/${domain}/`);
209
+ const metadataKeys = keys.filter((key) => key.endsWith('/metadata'));
210
+
211
+ for (const metadataKey of metadataKeys) {
212
+ const serialized = await this.storageManager.get(metadataKey);
213
+ if (!serialized) continue;
214
+ const metadata = JSON.parse(serialized) as IDkimKeyMetadata;
215
+ if (!metadata.rotatedAt || metadata.rotatedAt >= cutoff) continue;
216
+ await Promise.all([
217
+ this.storageManager.delete(this.selectorKey(domain, metadata.selector, 'private.key')),
218
+ this.storageManager.delete(this.selectorKey(domain, metadata.selector, 'public.key')),
219
+ this.storageManager.delete(metadataKey),
220
+ ]);
469
221
  }
470
222
  }
471
223
  }
package/ts/paths.ts CHANGED
@@ -1,48 +1,9 @@
1
1
  import * as plugins from './plugins.js';
2
2
 
3
- // Base directories
4
- export const baseDir = process.cwd();
3
+ // Code and packaged executable paths only. SmartMTA data is supplied through
4
+ // managed storage interfaces and never persisted directly to the filesystem.
5
5
  export const packageDir = plugins.path.join(
6
6
  plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
7
7
  '../'
8
8
  );
9
9
  export const distServe = plugins.path.join(packageDir, './dist_serve');
10
-
11
- // Configure data directory with environment variable or default to .nogit/data
12
- const DEFAULT_DATA_PATH = '.nogit/data';
13
- export const dataDir = process.env.DATA_DIR
14
- ? process.env.DATA_DIR
15
- : plugins.path.join(baseDir, DEFAULT_DATA_PATH);
16
-
17
- // MTA directories
18
- export const keysDir = plugins.path.join(dataDir, 'keys');
19
- export const dnsRecordsDir = plugins.path.join(dataDir, 'dns');
20
- export const sentEmailsDir = plugins.path.join(dataDir, 'emails', 'sent');
21
- export const receivedEmailsDir = plugins.path.join(dataDir, 'emails', 'received');
22
- export const failedEmailsDir = plugins.path.join(dataDir, 'emails', 'failed'); // For failed emails
23
- export const logsDir = plugins.path.join(dataDir, 'logs'); // For logs
24
-
25
- // Email template directories
26
- export const emailTemplatesDir = plugins.path.join(dataDir, 'templates', 'email');
27
- export const MtaAttachmentsDir = plugins.path.join(dataDir, 'attachments'); // For email attachments
28
-
29
- // Configuration path
30
- export const configPath = process.env.CONFIG_PATH
31
- ? process.env.CONFIG_PATH
32
- : plugins.path.join(baseDir, 'config.json');
33
-
34
- // Create directories if they don't exist
35
- export async function ensureDirectories() {
36
- // Ensure data directories
37
- await plugins.smartfs.directory(dataDir).recursive().create();
38
- await plugins.smartfs.directory(keysDir).recursive().create();
39
- await plugins.smartfs.directory(dnsRecordsDir).recursive().create();
40
- await plugins.smartfs.directory(sentEmailsDir).recursive().create();
41
- await plugins.smartfs.directory(receivedEmailsDir).recursive().create();
42
- await plugins.smartfs.directory(failedEmailsDir).recursive().create();
43
- await plugins.smartfs.directory(logsDir).recursive().create();
44
-
45
- // Ensure email template directories
46
- await plugins.smartfs.directory(emailTemplatesDir).recursive().create();
47
- await plugins.smartfs.directory(MtaAttachmentsDir).recursive().create();
48
- }
package/ts/plugins.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  // node native
2
2
  import * as dns from 'dns';
3
- import * as fs from 'fs';
4
3
  import * as crypto from 'crypto';
5
4
  import * as http from 'http';
6
5
  import * as net from 'net';
@@ -11,7 +10,6 @@ import * as util from 'util';
11
10
 
12
11
  export {
13
12
  dns,
14
- fs,
15
13
  crypto,
16
14
  http,
17
15
  net,
@@ -23,15 +21,12 @@ export {
23
21
 
24
22
  // @push.rocks scope
25
23
  import * as smartfile from '@push.rocks/smartfile';
26
- import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
27
24
  import * as smartlog from '@push.rocks/smartlog';
28
25
  import * as smartmail from '@push.rocks/smartmail';
29
26
  import * as smartpath from '@push.rocks/smartpath';
30
27
  import * as smartrust from '@push.rocks/smartrust';
31
28
 
32
- export const smartfs = new SmartFs(new SmartFsProviderNode());
33
-
34
- export { smartfile, SmartFs, smartlog, smartmail, smartpath, smartrust };
29
+ export { smartfile, smartlog, smartmail, smartpath, smartrust };
35
30
 
36
31
  // Define SmartLog types for use in error handling
37
32
  export type TLogLevel = 'error' | 'warn' | 'info' | 'success' | 'debug';
@@ -1,5 +1,4 @@
1
1
  import * as plugins from '../plugins.js';
2
- import * as paths from '../paths.js';
3
2
  import { logger } from '../logger.js';
4
3
  import { Email } from '../mail/core/classes.email.js';
5
4
  import type { IAttachment } from '../mail/core/classes.email.js';