@push.rocks/smartmta 5.1.2 → 5.2.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 (99) hide show
  1. package/changelog.md +14 -0
  2. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  3. package/dist_ts/00_commitinfo_data.js +9 -0
  4. package/dist_ts/index.d.ts +3 -0
  5. package/dist_ts/index.js +4 -0
  6. package/dist_ts/logger.d.ts +17 -0
  7. package/dist_ts/logger.js +76 -0
  8. package/dist_ts/mail/core/classes.bouncemanager.d.ts +185 -0
  9. package/dist_ts/mail/core/classes.bouncemanager.js +569 -0
  10. package/dist_ts/mail/core/classes.email.d.ts +291 -0
  11. package/dist_ts/mail/core/classes.email.js +802 -0
  12. package/dist_ts/mail/core/classes.emailvalidator.d.ts +61 -0
  13. package/dist_ts/mail/core/classes.emailvalidator.js +184 -0
  14. package/dist_ts/mail/core/classes.templatemanager.d.ts +95 -0
  15. package/dist_ts/mail/core/classes.templatemanager.js +240 -0
  16. package/dist_ts/mail/core/index.d.ts +4 -0
  17. package/dist_ts/mail/core/index.js +6 -0
  18. package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +163 -0
  19. package/dist_ts/mail/delivery/classes.delivery.queue.js +488 -0
  20. package/dist_ts/mail/delivery/classes.delivery.system.d.ts +160 -0
  21. package/dist_ts/mail/delivery/classes.delivery.system.js +630 -0
  22. package/dist_ts/mail/delivery/classes.unified.rate.limiter.d.ts +200 -0
  23. package/dist_ts/mail/delivery/classes.unified.rate.limiter.js +820 -0
  24. package/dist_ts/mail/delivery/index.d.ts +4 -0
  25. package/dist_ts/mail/delivery/index.js +6 -0
  26. package/dist_ts/mail/delivery/interfaces.d.ts +140 -0
  27. package/dist_ts/mail/delivery/interfaces.js +17 -0
  28. package/dist_ts/mail/index.d.ts +7 -0
  29. package/dist_ts/mail/index.js +12 -0
  30. package/dist_ts/mail/routing/classes.dkim.manager.d.ts +25 -0
  31. package/dist_ts/mail/routing/classes.dkim.manager.js +127 -0
  32. package/dist_ts/mail/routing/classes.dns.manager.d.ts +79 -0
  33. package/dist_ts/mail/routing/classes.dns.manager.js +415 -0
  34. package/dist_ts/mail/routing/classes.domain.registry.d.ts +54 -0
  35. package/dist_ts/mail/routing/classes.domain.registry.js +119 -0
  36. package/dist_ts/mail/routing/classes.email.action.executor.d.ts +33 -0
  37. package/dist_ts/mail/routing/classes.email.action.executor.js +137 -0
  38. package/dist_ts/mail/routing/classes.email.router.d.ts +171 -0
  39. package/dist_ts/mail/routing/classes.email.router.js +494 -0
  40. package/dist_ts/mail/routing/classes.unified.email.server.d.ts +241 -0
  41. package/dist_ts/mail/routing/classes.unified.email.server.js +935 -0
  42. package/dist_ts/mail/routing/index.d.ts +7 -0
  43. package/dist_ts/mail/routing/index.js +9 -0
  44. package/dist_ts/mail/routing/interfaces.d.ts +187 -0
  45. package/dist_ts/mail/routing/interfaces.js +2 -0
  46. package/dist_ts/mail/security/classes.dkimcreator.d.ts +72 -0
  47. package/dist_ts/mail/security/classes.dkimcreator.js +360 -0
  48. package/dist_ts/mail/security/classes.spfverifier.d.ts +62 -0
  49. package/dist_ts/mail/security/classes.spfverifier.js +87 -0
  50. package/dist_ts/mail/security/index.d.ts +2 -0
  51. package/dist_ts/mail/security/index.js +4 -0
  52. package/dist_ts/paths.d.ts +14 -0
  53. package/dist_ts/paths.js +39 -0
  54. package/dist_ts/plugins.d.ts +24 -0
  55. package/dist_ts/plugins.js +28 -0
  56. package/dist_ts/security/classes.contentscanner.d.ts +130 -0
  57. package/dist_ts/security/classes.contentscanner.js +338 -0
  58. package/dist_ts/security/classes.ipreputationchecker.d.ts +73 -0
  59. package/dist_ts/security/classes.ipreputationchecker.js +263 -0
  60. package/dist_ts/security/classes.rustsecuritybridge.d.ts +398 -0
  61. package/dist_ts/security/classes.rustsecuritybridge.js +484 -0
  62. package/dist_ts/security/classes.securitylogger.d.ts +140 -0
  63. package/dist_ts/security/classes.securitylogger.js +235 -0
  64. package/dist_ts/security/index.d.ts +4 -0
  65. package/dist_ts/security/index.js +5 -0
  66. package/package.json +6 -1
  67. package/readme.md +52 -9
  68. package/ts/00_commitinfo_data.ts +8 -0
  69. package/ts/index.ts +3 -0
  70. package/ts/logger.ts +91 -0
  71. package/ts/mail/core/classes.bouncemanager.ts +731 -0
  72. package/ts/mail/core/classes.email.ts +942 -0
  73. package/ts/mail/core/classes.emailvalidator.ts +239 -0
  74. package/ts/mail/core/classes.templatemanager.ts +320 -0
  75. package/ts/mail/core/index.ts +5 -0
  76. package/ts/mail/delivery/classes.delivery.queue.ts +645 -0
  77. package/ts/mail/delivery/classes.delivery.system.ts +816 -0
  78. package/ts/mail/delivery/classes.unified.rate.limiter.ts +1053 -0
  79. package/ts/mail/delivery/index.ts +5 -0
  80. package/ts/mail/delivery/interfaces.ts +167 -0
  81. package/ts/mail/index.ts +17 -0
  82. package/ts/mail/routing/classes.dkim.manager.ts +157 -0
  83. package/ts/mail/routing/classes.dns.manager.ts +573 -0
  84. package/ts/mail/routing/classes.domain.registry.ts +139 -0
  85. package/ts/mail/routing/classes.email.action.executor.ts +175 -0
  86. package/ts/mail/routing/classes.email.router.ts +575 -0
  87. package/ts/mail/routing/classes.unified.email.server.ts +1207 -0
  88. package/ts/mail/routing/index.ts +9 -0
  89. package/ts/mail/routing/interfaces.ts +202 -0
  90. package/ts/mail/security/classes.dkimcreator.ts +447 -0
  91. package/ts/mail/security/classes.spfverifier.ts +126 -0
  92. package/ts/mail/security/index.ts +3 -0
  93. package/ts/paths.ts +48 -0
  94. package/ts/plugins.ts +53 -0
  95. package/ts/security/classes.contentscanner.ts +400 -0
  96. package/ts/security/classes.ipreputationchecker.ts +315 -0
  97. package/ts/security/classes.rustsecuritybridge.ts +943 -0
  98. package/ts/security/classes.securitylogger.ts +299 -0
  99. package/ts/security/index.ts +40 -0
@@ -0,0 +1,7 @@
1
+ export * from './classes.email.router.js';
2
+ export * from './classes.unified.email.server.js';
3
+ export * from './classes.dns.manager.js';
4
+ export * from './interfaces.js';
5
+ export * from './classes.domain.registry.js';
6
+ export * from './classes.email.action.executor.js';
7
+ export * from './classes.dkim.manager.js';
@@ -0,0 +1,9 @@
1
+ // Email routing components
2
+ export * from './classes.email.router.js';
3
+ export * from './classes.unified.email.server.js';
4
+ export * from './classes.dns.manager.js';
5
+ export * from './interfaces.js';
6
+ export * from './classes.domain.registry.js';
7
+ export * from './classes.email.action.executor.js';
8
+ export * from './classes.dkim.manager.js';
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9tYWlsL3JvdXRpbmcvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsMkJBQTJCO0FBQzNCLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyxtQ0FBbUMsQ0FBQztBQUNsRCxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMsaUJBQWlCLENBQUM7QUFDaEMsY0FBYyw4QkFBOEIsQ0FBQztBQUM3QyxjQUFjLG9DQUFvQyxDQUFDO0FBQ25ELGNBQWMsMkJBQTJCLENBQUMifQ==
@@ -0,0 +1,187 @@
1
+ import type { Email } from '../core/classes.email.js';
2
+ import type { IExtendedSmtpSession } from './classes.unified.email.server.js';
3
+ /**
4
+ * Route configuration for email routing
5
+ */
6
+ export interface IEmailRoute {
7
+ /** Route identifier */
8
+ name: string;
9
+ /** Order of evaluation (higher priority evaluated first, default: 0) */
10
+ priority?: number;
11
+ /** Conditions to match */
12
+ match: IEmailMatch;
13
+ /** Action to take when matched */
14
+ action: IEmailAction;
15
+ }
16
+ /**
17
+ * Match criteria for email routing
18
+ */
19
+ export interface IEmailMatch {
20
+ /** Email patterns to match recipients: "*@example.com", "admin@*" */
21
+ recipients?: string | string[];
22
+ /** Email patterns to match senders */
23
+ senders?: string | string[];
24
+ /** IP addresses or CIDR ranges to match */
25
+ clientIp?: string | string[];
26
+ /** Require authentication status */
27
+ authenticated?: boolean;
28
+ /** Headers to match */
29
+ headers?: Record<string, string | RegExp>;
30
+ /** Message size range */
31
+ sizeRange?: {
32
+ min?: number;
33
+ max?: number;
34
+ };
35
+ /** Subject line patterns */
36
+ subject?: string | RegExp;
37
+ /** Has attachments */
38
+ hasAttachments?: boolean;
39
+ }
40
+ /**
41
+ * Action to take when route matches
42
+ */
43
+ export interface IEmailAction {
44
+ /** Type of action to perform */
45
+ type: 'forward' | 'deliver' | 'reject' | 'process';
46
+ /** Forward action configuration */
47
+ forward?: {
48
+ /** Target host to forward to */
49
+ host: string;
50
+ /** Target port (default: 25) */
51
+ port?: number;
52
+ /** Authentication credentials */
53
+ auth?: {
54
+ user: string;
55
+ pass: string;
56
+ };
57
+ /** Preserve original headers */
58
+ preserveHeaders?: boolean;
59
+ /** Additional headers to add */
60
+ addHeaders?: Record<string, string>;
61
+ };
62
+ /** Reject action configuration */
63
+ reject?: {
64
+ /** SMTP response code */
65
+ code: number;
66
+ /** SMTP response message */
67
+ message: string;
68
+ };
69
+ /** Process action configuration */
70
+ process?: {
71
+ /** Enable content scanning */
72
+ scan?: boolean;
73
+ /** Enable DKIM signing */
74
+ dkim?: boolean;
75
+ /** Delivery queue priority */
76
+ queue?: 'normal' | 'priority' | 'bulk';
77
+ };
78
+ /** Options for various action types */
79
+ options?: {
80
+ /** MTA specific options */
81
+ mtaOptions?: {
82
+ domain?: string;
83
+ allowLocalDelivery?: boolean;
84
+ localDeliveryPath?: string;
85
+ dkimSign?: boolean;
86
+ dkimOptions?: {
87
+ domainName: string;
88
+ keySelector: string;
89
+ privateKey?: string;
90
+ };
91
+ smtpBanner?: string;
92
+ maxConnections?: number;
93
+ connTimeout?: number;
94
+ spoolDir?: string;
95
+ };
96
+ /** Content scanning configuration */
97
+ contentScanning?: boolean;
98
+ scanners?: Array<{
99
+ type: 'spam' | 'virus' | 'attachment';
100
+ threshold?: number;
101
+ action: 'tag' | 'reject';
102
+ blockedExtensions?: string[];
103
+ }>;
104
+ /** Email transformations */
105
+ transformations?: Array<{
106
+ type: string;
107
+ header?: string;
108
+ value?: string;
109
+ domains?: string[];
110
+ append?: boolean;
111
+ [key: string]: any;
112
+ }>;
113
+ };
114
+ /** Delivery options (applies to forward/process/deliver) */
115
+ delivery?: {
116
+ /** Rate limit (messages per minute) */
117
+ rateLimit?: number;
118
+ /** Number of retry attempts */
119
+ retries?: number;
120
+ };
121
+ }
122
+ /**
123
+ * Context for route evaluation
124
+ */
125
+ export interface IEmailContext {
126
+ /** The email being routed */
127
+ email: Email;
128
+ /** The SMTP session */
129
+ session: IExtendedSmtpSession;
130
+ }
131
+ /**
132
+ * Email domain configuration
133
+ */
134
+ export interface IEmailDomainConfig {
135
+ /** Domain name */
136
+ domain: string;
137
+ /** DNS handling mode */
138
+ dnsMode: 'forward' | 'internal-dns' | 'external-dns';
139
+ /** DNS configuration based on mode */
140
+ dns?: {
141
+ /** For 'forward' mode */
142
+ forward?: {
143
+ /** Skip DNS validation (default: false) */
144
+ skipDnsValidation?: boolean;
145
+ /** Target server's expected domain */
146
+ targetDomain?: string;
147
+ };
148
+ /** For 'internal-dns' mode */
149
+ internal?: {
150
+ /** TTL for DNS records in seconds (default: 3600) */
151
+ ttl?: number;
152
+ /** MX record priority (default: 10) */
153
+ mxPriority?: number;
154
+ };
155
+ /** For 'external-dns' mode */
156
+ external?: {
157
+ /** Custom DNS servers (default: system DNS) */
158
+ servers?: string[];
159
+ /** Which records to validate (default: ['MX', 'SPF', 'DKIM', 'DMARC']) */
160
+ requiredRecords?: ('MX' | 'SPF' | 'DKIM' | 'DMARC')[];
161
+ };
162
+ };
163
+ /** Per-domain DKIM settings (DKIM always enabled) */
164
+ dkim?: {
165
+ /** DKIM selector (default: 'default') */
166
+ selector?: string;
167
+ /** Key size in bits (default: 2048) */
168
+ keySize?: number;
169
+ /** Automatically rotate keys (default: false) */
170
+ rotateKeys?: boolean;
171
+ /** Days between key rotations (default: 90) */
172
+ rotationInterval?: number;
173
+ };
174
+ /** Per-domain rate limits */
175
+ rateLimits?: {
176
+ outbound?: {
177
+ messagesPerMinute?: number;
178
+ messagesPerHour?: number;
179
+ messagesPerDay?: number;
180
+ };
181
+ inbound?: {
182
+ messagesPerMinute?: number;
183
+ connectionsPerIp?: number;
184
+ recipientsPerMessage?: number;
185
+ };
186
+ };
187
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvcm91dGluZy9pbnRlcmZhY2VzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiIifQ==
@@ -0,0 +1,72 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { Email } from '../core/classes.email.js';
3
+ export interface IKeyPaths {
4
+ privateKeyPath: string;
5
+ publicKeyPath: string;
6
+ }
7
+ export interface IDkimKeyMetadata {
8
+ domain: string;
9
+ selector: string;
10
+ createdAt: number;
11
+ rotatedAt?: number;
12
+ previousSelector?: string;
13
+ keySize: number;
14
+ }
15
+ export declare class DKIMCreator {
16
+ private keysDir;
17
+ private storageManager?;
18
+ constructor(keysDir?: string, storageManager?: any);
19
+ getKeyPathsForDomain(domainArg: string): Promise<IKeyPaths>;
20
+ handleDKIMKeysForDomain(domainArg: string): Promise<void>;
21
+ handleDKIMKeysForEmail(email: Email): Promise<void>;
22
+ readDKIMKeys(domainArg: string): Promise<{
23
+ privateKey: string;
24
+ publicKey: string;
25
+ }>;
26
+ createDKIMKeys(): Promise<{
27
+ privateKey: string;
28
+ publicKey: string;
29
+ }>;
30
+ createEd25519Keys(): Promise<{
31
+ privateKey: string;
32
+ publicKey: string;
33
+ }>;
34
+ storeDKIMKeys(privateKey: string, publicKey: string, privateKeyPath: string, publicKeyPath: string): Promise<void>;
35
+ createAndStoreDKIMKeys(domain: string): Promise<void>;
36
+ getDNSRecordForDomain(domainArg: string): Promise<plugins.tsclass.network.IDnsRecord>;
37
+ /**
38
+ * Get DKIM key metadata for a domain
39
+ */
40
+ private getKeyMetadata;
41
+ /**
42
+ * Save DKIM key metadata
43
+ */
44
+ private saveKeyMetadata;
45
+ /**
46
+ * Check if DKIM keys need rotation
47
+ */
48
+ needsRotation(domain: string, selector?: string, rotationIntervalDays?: number): Promise<boolean>;
49
+ /**
50
+ * Rotate DKIM keys for a domain
51
+ */
52
+ rotateDkimKeys(domain: string, currentSelector?: string, keySize?: number): Promise<string>;
53
+ /**
54
+ * Get key paths for a specific selector
55
+ */
56
+ getKeyPathsForSelector(domain: string, selector: string): Promise<IKeyPaths>;
57
+ /**
58
+ * Read DKIM keys for a specific selector
59
+ */
60
+ readDKIMKeysForSelector(domain: string, selector: string): Promise<{
61
+ privateKey: string;
62
+ publicKey: string;
63
+ }>;
64
+ /**
65
+ * Get DNS record for a specific selector
66
+ */
67
+ getDNSRecordForSelector(domain: string, selector: string): Promise<plugins.tsclass.network.IDnsRecord>;
68
+ /**
69
+ * Clean up old DKIM keys after grace period
70
+ */
71
+ cleanupOldKeys(domain: string, gracePeriodDays?: number): Promise<void>;
72
+ }
@@ -0,0 +1,360 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import * as paths from '../../paths.js';
3
+ import { Email } from '../core/classes.email.js';
4
+ // MtaService reference removed
5
+ const readFile = plugins.util.promisify(plugins.fs.readFile);
6
+ const writeFile = plugins.util.promisify(plugins.fs.writeFile);
7
+ const generateKeyPair = plugins.util.promisify(plugins.crypto.generateKeyPair);
8
+ export class DKIMCreator {
9
+ keysDir;
10
+ storageManager; // StorageManager instance
11
+ constructor(keysDir = paths.keysDir, storageManager) {
12
+ this.keysDir = keysDir;
13
+ this.storageManager = storageManager;
14
+ }
15
+ async getKeyPathsForDomain(domainArg) {
16
+ return {
17
+ privateKeyPath: plugins.path.join(this.keysDir, `${domainArg}-private.pem`),
18
+ publicKeyPath: plugins.path.join(this.keysDir, `${domainArg}-public.pem`),
19
+ };
20
+ }
21
+ // Check if a DKIM key is present and creates one and stores it to disk otherwise
22
+ async handleDKIMKeysForDomain(domainArg) {
23
+ try {
24
+ await this.readDKIMKeys(domainArg);
25
+ }
26
+ catch (error) {
27
+ console.log(`No DKIM keys found for ${domainArg}. Generating...`);
28
+ await this.createAndStoreDKIMKeys(domainArg);
29
+ const dnsValue = await this.getDNSRecordForDomain(domainArg);
30
+ await plugins.smartfs.directory(paths.dnsRecordsDir).recursive().create();
31
+ await plugins.smartfs.file(plugins.path.join(paths.dnsRecordsDir, `${domainArg}.dkimrecord.json`)).write(JSON.stringify(dnsValue, null, 2));
32
+ }
33
+ }
34
+ async handleDKIMKeysForEmail(email) {
35
+ const domain = email.from.split('@')[1];
36
+ await this.handleDKIMKeysForDomain(domain);
37
+ }
38
+ // Read DKIM keys - always use storage manager, migrate from filesystem if needed
39
+ async readDKIMKeys(domainArg) {
40
+ // Try to read from storage manager first
41
+ if (this.storageManager) {
42
+ try {
43
+ const [privateKey, publicKey] = await Promise.all([
44
+ this.storageManager.get(`/email/dkim/${domainArg}/private.key`),
45
+ this.storageManager.get(`/email/dkim/${domainArg}/public.key`)
46
+ ]);
47
+ if (privateKey && publicKey) {
48
+ return { privateKey, publicKey };
49
+ }
50
+ }
51
+ catch (error) {
52
+ // Fall through to migration check
53
+ }
54
+ // Check if keys exist in filesystem and migrate them to storage manager
55
+ const keyPaths = await this.getKeyPathsForDomain(domainArg);
56
+ try {
57
+ const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
58
+ readFile(keyPaths.privateKeyPath),
59
+ readFile(keyPaths.publicKeyPath),
60
+ ]);
61
+ // Convert the buffers to strings
62
+ const privateKey = privateKeyBuffer.toString();
63
+ const publicKey = publicKeyBuffer.toString();
64
+ // Migrate to storage manager
65
+ console.log(`Migrating DKIM keys for ${domainArg} from filesystem to StorageManager`);
66
+ await Promise.all([
67
+ this.storageManager.set(`/email/dkim/${domainArg}/private.key`, privateKey),
68
+ this.storageManager.set(`/email/dkim/${domainArg}/public.key`, publicKey)
69
+ ]);
70
+ return { privateKey, publicKey };
71
+ }
72
+ catch (error) {
73
+ if (error.code === 'ENOENT') {
74
+ // Keys don't exist anywhere
75
+ throw new Error(`DKIM keys not found for domain ${domainArg}`);
76
+ }
77
+ throw error;
78
+ }
79
+ }
80
+ else {
81
+ // No storage manager, use filesystem directly
82
+ const keyPaths = await this.getKeyPathsForDomain(domainArg);
83
+ const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
84
+ readFile(keyPaths.privateKeyPath),
85
+ readFile(keyPaths.publicKeyPath),
86
+ ]);
87
+ const privateKey = privateKeyBuffer.toString();
88
+ const publicKey = publicKeyBuffer.toString();
89
+ return { privateKey, publicKey };
90
+ }
91
+ }
92
+ // Create an RSA DKIM key pair - changed to public for API access
93
+ async createDKIMKeys() {
94
+ const { privateKey, publicKey } = await generateKeyPair('rsa', {
95
+ modulusLength: 2048,
96
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
97
+ privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
98
+ });
99
+ return { privateKey, publicKey };
100
+ }
101
+ // Create an Ed25519 DKIM key pair (RFC 8463)
102
+ async createEd25519Keys() {
103
+ const { privateKey, publicKey } = await generateKeyPair('ed25519', {
104
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
105
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
106
+ });
107
+ return { privateKey, publicKey };
108
+ }
109
+ // Store a DKIM key pair - uses storage manager if available, else disk
110
+ async storeDKIMKeys(privateKey, publicKey, privateKeyPath, publicKeyPath) {
111
+ // Store in storage manager if available
112
+ if (this.storageManager) {
113
+ // Extract domain from path (e.g., /path/to/keys/example.com-private.pem -> example.com)
114
+ const match = privateKeyPath.match(/\/([^\/]+)-private\.pem$/);
115
+ if (match) {
116
+ const domain = match[1];
117
+ await Promise.all([
118
+ this.storageManager.set(`/email/dkim/${domain}/private.key`, privateKey),
119
+ this.storageManager.set(`/email/dkim/${domain}/public.key`, publicKey)
120
+ ]);
121
+ }
122
+ }
123
+ // Also store to filesystem for backward compatibility
124
+ await Promise.all([writeFile(privateKeyPath, privateKey), writeFile(publicKeyPath, publicKey)]);
125
+ }
126
+ // Create a DKIM key pair and store it to disk - changed to public for API access
127
+ async createAndStoreDKIMKeys(domain) {
128
+ const { privateKey, publicKey } = await this.createDKIMKeys();
129
+ const keyPaths = await this.getKeyPathsForDomain(domain);
130
+ await this.storeDKIMKeys(privateKey, publicKey, keyPaths.privateKeyPath, keyPaths.publicKeyPath);
131
+ console.log(`DKIM keys for ${domain} created and stored.`);
132
+ }
133
+ // Changed to public for API access
134
+ async getDNSRecordForDomain(domainArg) {
135
+ await this.handleDKIMKeysForDomain(domainArg);
136
+ const keys = await this.readDKIMKeys(domainArg);
137
+ // Remove the PEM header and footer and newlines
138
+ const pemHeader = '-----BEGIN PUBLIC KEY-----';
139
+ const pemFooter = '-----END PUBLIC KEY-----';
140
+ const keyContents = keys.publicKey
141
+ .replace(pemHeader, '')
142
+ .replace(pemFooter, '')
143
+ .replace(/\n/g, '');
144
+ // Detect key type from PEM header
145
+ const keyAlgo = keys.privateKey.includes('ED25519') || keys.publicKey.length < 200 ? 'ed25519' : 'rsa';
146
+ // Now generate the DKIM DNS TXT record
147
+ const dnsRecordValue = `v=DKIM1; h=sha256; k=${keyAlgo}; p=${keyContents}`;
148
+ return {
149
+ name: `mta._domainkey.${domainArg}`,
150
+ type: 'TXT',
151
+ dnsSecEnabled: null,
152
+ value: dnsRecordValue,
153
+ };
154
+ }
155
+ /**
156
+ * Get DKIM key metadata for a domain
157
+ */
158
+ async getKeyMetadata(domain, selector = 'default') {
159
+ if (!this.storageManager) {
160
+ return null;
161
+ }
162
+ const metadataKey = `/email/dkim/${domain}/${selector}/metadata`;
163
+ const metadataStr = await this.storageManager.get(metadataKey);
164
+ if (!metadataStr) {
165
+ return null;
166
+ }
167
+ return JSON.parse(metadataStr);
168
+ }
169
+ /**
170
+ * Save DKIM key metadata
171
+ */
172
+ async saveKeyMetadata(metadata) {
173
+ if (!this.storageManager) {
174
+ return;
175
+ }
176
+ const metadataKey = `/email/dkim/${metadata.domain}/${metadata.selector}/metadata`;
177
+ await this.storageManager.set(metadataKey, JSON.stringify(metadata));
178
+ }
179
+ /**
180
+ * Check if DKIM keys need rotation
181
+ */
182
+ async needsRotation(domain, selector = 'default', rotationIntervalDays = 90) {
183
+ const metadata = await this.getKeyMetadata(domain, selector);
184
+ if (!metadata) {
185
+ // No metadata means old keys, should rotate
186
+ return true;
187
+ }
188
+ const now = Date.now();
189
+ const keyAgeMs = now - metadata.createdAt;
190
+ const keyAgeDays = keyAgeMs / (1000 * 60 * 60 * 24);
191
+ return keyAgeDays >= rotationIntervalDays;
192
+ }
193
+ /**
194
+ * Rotate DKIM keys for a domain
195
+ */
196
+ async rotateDkimKeys(domain, currentSelector = 'default', keySize = 2048) {
197
+ console.log(`Rotating DKIM keys for ${domain}...`);
198
+ // Generate new selector based on date
199
+ const now = new Date();
200
+ const newSelector = `key${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`;
201
+ // Create new keys with custom key size
202
+ const { privateKey, publicKey } = await generateKeyPair('rsa', {
203
+ modulusLength: keySize,
204
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
205
+ privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
206
+ });
207
+ // Store new keys with new selector
208
+ const newKeyPaths = await this.getKeyPathsForSelector(domain, newSelector);
209
+ // Store in storage manager if available
210
+ if (this.storageManager) {
211
+ await Promise.all([
212
+ this.storageManager.set(`/email/dkim/${domain}/${newSelector}/private.key`, privateKey),
213
+ this.storageManager.set(`/email/dkim/${domain}/${newSelector}/public.key`, publicKey)
214
+ ]);
215
+ }
216
+ // Also store to filesystem
217
+ await this.storeDKIMKeys(privateKey, publicKey, newKeyPaths.privateKeyPath, newKeyPaths.publicKeyPath);
218
+ // Save metadata for new keys
219
+ const metadata = {
220
+ domain,
221
+ selector: newSelector,
222
+ createdAt: Date.now(),
223
+ previousSelector: currentSelector,
224
+ keySize
225
+ };
226
+ await this.saveKeyMetadata(metadata);
227
+ // Update metadata for old keys
228
+ const oldMetadata = await this.getKeyMetadata(domain, currentSelector);
229
+ if (oldMetadata) {
230
+ oldMetadata.rotatedAt = Date.now();
231
+ await this.saveKeyMetadata(oldMetadata);
232
+ }
233
+ console.log(`DKIM keys rotated for ${domain}. New selector: ${newSelector}`);
234
+ return newSelector;
235
+ }
236
+ /**
237
+ * Get key paths for a specific selector
238
+ */
239
+ async getKeyPathsForSelector(domain, selector) {
240
+ return {
241
+ privateKeyPath: plugins.path.join(this.keysDir, `${domain}-${selector}-private.pem`),
242
+ publicKeyPath: plugins.path.join(this.keysDir, `${domain}-${selector}-public.pem`),
243
+ };
244
+ }
245
+ /**
246
+ * Read DKIM keys for a specific selector
247
+ */
248
+ async readDKIMKeysForSelector(domain, selector) {
249
+ // Try to read from storage manager first
250
+ if (this.storageManager) {
251
+ try {
252
+ const [privateKey, publicKey] = await Promise.all([
253
+ this.storageManager.get(`/email/dkim/${domain}/${selector}/private.key`),
254
+ this.storageManager.get(`/email/dkim/${domain}/${selector}/public.key`)
255
+ ]);
256
+ if (privateKey && publicKey) {
257
+ return { privateKey, publicKey };
258
+ }
259
+ }
260
+ catch (error) {
261
+ // Fall through to migration check
262
+ }
263
+ // Check if keys exist in filesystem and migrate them to storage manager
264
+ const keyPaths = await this.getKeyPathsForSelector(domain, selector);
265
+ try {
266
+ const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
267
+ readFile(keyPaths.privateKeyPath),
268
+ readFile(keyPaths.publicKeyPath),
269
+ ]);
270
+ const privateKey = privateKeyBuffer.toString();
271
+ const publicKey = publicKeyBuffer.toString();
272
+ // Migrate to storage manager
273
+ console.log(`Migrating DKIM keys for ${domain}/${selector} from filesystem to StorageManager`);
274
+ await Promise.all([
275
+ this.storageManager.set(`/email/dkim/${domain}/${selector}/private.key`, privateKey),
276
+ this.storageManager.set(`/email/dkim/${domain}/${selector}/public.key`, publicKey)
277
+ ]);
278
+ return { privateKey, publicKey };
279
+ }
280
+ catch (error) {
281
+ if (error.code === 'ENOENT') {
282
+ throw new Error(`DKIM keys not found for domain ${domain} with selector ${selector}`);
283
+ }
284
+ throw error;
285
+ }
286
+ }
287
+ else {
288
+ // No storage manager, use filesystem directly
289
+ const keyPaths = await this.getKeyPathsForSelector(domain, selector);
290
+ const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
291
+ readFile(keyPaths.privateKeyPath),
292
+ readFile(keyPaths.publicKeyPath),
293
+ ]);
294
+ const privateKey = privateKeyBuffer.toString();
295
+ const publicKey = publicKeyBuffer.toString();
296
+ return { privateKey, publicKey };
297
+ }
298
+ }
299
+ /**
300
+ * Get DNS record for a specific selector
301
+ */
302
+ async getDNSRecordForSelector(domain, selector) {
303
+ const keys = await this.readDKIMKeysForSelector(domain, selector);
304
+ // Remove the PEM header and footer and newlines
305
+ const pemHeader = '-----BEGIN PUBLIC KEY-----';
306
+ const pemFooter = '-----END PUBLIC KEY-----';
307
+ const keyContents = keys.publicKey
308
+ .replace(pemHeader, '')
309
+ .replace(pemFooter, '')
310
+ .replace(/\n/g, '');
311
+ // Detect key type from PEM header
312
+ const keyAlgo = keys.privateKey.includes('ED25519') || keys.publicKey.length < 200 ? 'ed25519' : 'rsa';
313
+ // Generate the DKIM DNS TXT record
314
+ const dnsRecordValue = `v=DKIM1; h=sha256; k=${keyAlgo}; p=${keyContents}`;
315
+ return {
316
+ name: `${selector}._domainkey.${domain}`,
317
+ type: 'TXT',
318
+ dnsSecEnabled: null,
319
+ value: dnsRecordValue,
320
+ };
321
+ }
322
+ /**
323
+ * Clean up old DKIM keys after grace period
324
+ */
325
+ async cleanupOldKeys(domain, gracePeriodDays = 30) {
326
+ if (!this.storageManager) {
327
+ return;
328
+ }
329
+ // List all selectors for the domain
330
+ const metadataKeys = await this.storageManager.list(`/email/dkim/${domain}/`);
331
+ for (const key of metadataKeys) {
332
+ if (key.endsWith('/metadata')) {
333
+ const metadataStr = await this.storageManager.get(key);
334
+ if (metadataStr) {
335
+ const metadata = JSON.parse(metadataStr);
336
+ // Check if key is rotated and past grace period
337
+ if (metadata.rotatedAt) {
338
+ const gracePeriodMs = gracePeriodDays * 24 * 60 * 60 * 1000;
339
+ const now = Date.now();
340
+ if (now - metadata.rotatedAt > gracePeriodMs) {
341
+ console.log(`Cleaning up old DKIM keys for ${domain} selector ${metadata.selector}`);
342
+ // Delete key files
343
+ const keyPaths = await this.getKeyPathsForSelector(domain, metadata.selector);
344
+ try {
345
+ await plugins.fs.promises.unlink(keyPaths.privateKeyPath);
346
+ await plugins.fs.promises.unlink(keyPaths.publicKeyPath);
347
+ }
348
+ catch (error) {
349
+ console.warn(`Failed to delete old key files: ${error.message}`);
350
+ }
351
+ // Delete metadata
352
+ await this.storageManager.delete(key);
353
+ }
354
+ }
355
+ }
356
+ }
357
+ }
358
+ }
359
+ }
360
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ka2ltY3JlYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvc2VjdXJpdHkvY2xhc3Nlcy5ka2ltY3JlYXRvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sS0FBSyxLQUFLLE1BQU0sZ0JBQWdCLENBQUM7QUFFeEMsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ2pELCtCQUErQjtBQUUvQixNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQzdELE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUFDLENBQUM7QUFDL0QsTUFBTSxlQUFlLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsQ0FBQztBQWdCL0UsTUFBTSxPQUFPLFdBQVc7SUFDZCxPQUFPLENBQVM7SUFDaEIsY0FBYyxDQUFPLENBQUMsMEJBQTBCO0lBRXhELFlBQVksT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLEVBQUUsY0FBb0I7UUFDdkQsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7UUFDdkIsSUFBSSxDQUFDLGNBQWMsR0FBRyxjQUFjLENBQUM7SUFDdkMsQ0FBQztJQUVNLEtBQUssQ0FBQyxvQkFBb0IsQ0FBQyxTQUFpQjtRQUNqRCxPQUFPO1lBQ0wsY0FBYyxFQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsR0FBRyxTQUFTLGNBQWMsQ0FBQztZQUMzRSxhQUFhLEVBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJ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