also looking at this
feat(config): add hosted_domains for multi-domain hosting
#1
7 files
+153
-42
| @@ -672,19 +672,12 @@ pub const Session = struct { | ||
| 672 | 672 | return; |
| 673 | 673 | } |
| 674 | 674 | |
| 675 | // Prevent open relay: unauthenticated clients can only send to local domain | |
| 675 | // Prevent open relay: unauthenticated clients can only send to a local domain | |
| 676 | // (hostname, its parent, or any entry in config.hosted_domains). | |
| 676 | 677 | if (!self.authenticated) { |
| 677 | 678 | if (std.mem.indexOf(u8, addr, "@")) |at_pos| { |
| 678 | 679 | const recipient_domain = addr[at_pos + 1 ..]; |
| 679 | // Check if recipient domain matches hostname or is the parent domain | |
| 680 | // e.g. hostname "mail.stacksjs.com" should accept mail for "stacksjs.com" | |
| 681 | const is_local = std.ascii.eqlIgnoreCase(recipient_domain, self.config.hostname) or blk: { | |
| 682 | if (std.mem.indexOf(u8, self.config.hostname, ".")) |dot_pos| { | |
| 683 | break :blk std.ascii.eqlIgnoreCase(recipient_domain, self.config.hostname[dot_pos + 1 ..]); | |
| 684 | } | |
| 685 | break :blk false; | |
| 686 | }; | |
| 687 | if (!is_local) { | |
| 680 | if (!self.config.isLocalDomain(recipient_domain)) { | |
| 688 | 681 | self.logger.logSecurityEvent(self.remote_addr, "Relay access denied for unauthenticated sender"); |
| 689 | 682 | try self.sendResponse(writer, 550, "5.7.1 Relay access denied", null); |
| 690 | 683 | return; |
| @@ -1357,18 +1350,12 @@ pub const Session = struct { | ||
| 1357 | 1350 | const sender = self.mail_from orelse "unknown"; |
| 1358 | 1351 | |
| 1359 | 1352 | for (self.rcpt_to.items) |rcpt| { |
| 1360 | // Determine if recipient is local or external | |
| 1361 | // Compare against hostname and its parent domain (mail.stacksjs.com -> stacksjs.com) | |
| 1362 | const is_local = if (std.mem.indexOf(u8, rcpt, "@")) |at_pos| blk: { | |
| 1363 | const rcpt_domain = rcpt[at_pos + 1 ..]; | |
| 1364 | if (std.mem.eql(u8, rcpt_domain, self.config.hostname)) break :blk true; | |
| 1365 | // Also check parent domain: if hostname is "mail.X", accept "X" as local | |
| 1366 | if (std.mem.indexOf(u8, self.config.hostname, ".")) |dot_pos| { | |
| 1367 | const parent_domain = self.config.hostname[dot_pos + 1 ..]; | |
| 1368 | if (std.mem.eql(u8, rcpt_domain, parent_domain)) break :blk true; | |
| 1369 | } | |
| 1370 | break :blk false; | |
| 1371 | } else true; | |
| 1353 | // Determine if recipient is local or external. A bare address with | |
| 1354 | // no '@' is treated as local (legacy local-user delivery). | |
| 1355 | const is_local = if (std.mem.indexOf(u8, rcpt, "@")) |at_pos| | |
| 1356 | self.config.isLocalDomain(rcpt[at_pos + 1 ..]) | |
| 1357 | else | |
| 1358 | true; | |
| 1372 | 1359 | |
| 1373 | 1360 | if (is_local) { |
| 1374 | 1361 | // Local delivery: save to Maildir |
| @@ -1500,16 +1487,11 @@ pub const Session = struct { | ||
| 1500 | 1487 | |
| 1501 | 1488 | self.logger.info("Auto-forwarding from {s} to {s}", .{ username, forward_to }); |
| 1502 | 1489 | |
| 1503 | // Check if forward target is a local address | |
| 1504 | const is_local_forward = if (std.mem.indexOf(u8, forward_to, "@")) |at_pos| blk: { | |
| 1505 | const fwd_domain = forward_to[at_pos + 1 ..]; | |
| 1506 | if (std.mem.eql(u8, fwd_domain, self.config.hostname)) break :blk true; | |
| 1507 | if (std.mem.indexOf(u8, self.config.hostname, ".")) |dot_pos| { | |
| 1508 | const parent_domain = self.config.hostname[dot_pos + 1 ..]; | |
| 1509 | if (std.mem.eql(u8, fwd_domain, parent_domain)) break :blk true; | |
| 1510 | } | |
| 1511 | break :blk false; | |
| 1512 | } else true; | |
| 1490 | // Check if forward target is a local address. | |
| 1491 | const is_local_forward = if (std.mem.indexOf(u8, forward_to, "@")) |at_pos| | |
| 1492 | self.config.isLocalDomain(forward_to[at_pos + 1 ..]) | |
| 1493 | else | |
| 1494 | true; | |
| 1513 | 1495 | |
| 1514 | 1496 | if (is_local_forward) { |
| 1515 | 1497 | // Local forward: save directly to recipient's mailbox |