Browse Source

merge upstream

Fabian Peter Hammerle 1 tuần trước cách đây
mục cha
commit
035b1afa04
2 tập tin đã thay đổi với 136 bổ sung34 xóa
  1. 17 0
      CHANGELOG.md
  2. 119 34
      src/milterfrom.c

+ 17 - 0
CHANGELOG.md

@@ -0,0 +1,17 @@
+#milterfrom CHANGELOG
+
+Version 1.0.1  2023-08-30
+
+* Add logging to log mismatch event using syslog (mail.notice)
+* Add VERSION declaration and help/version command line options
+* Add CHANGELOG.md to repository for tracking
+
+Version 1.0.2  2023-09-18
+
+* Fixed bug on null sender to SMFIS_ACCEPT and no more filtering
+* Added syslog on null sender event with connection details (IP,name)
+* Changed syslog to be from log_event routine
+
+Version 1.0.3 2025-09-24
+* Added verification of no control characters inside carrots.
+[See references https://blog.slonser.info/posts/email-attacks/ ]

+ 119 - 34
src/milterfrom.c

@@ -42,6 +42,10 @@
 #include <pwd.h>
 #include <grp.h>
 #include <stdint.h>
+#include <syslog.h>
+
+#include <ctype.h>
+#include <stddef.h>
 
 #include "libmilter/mfapi.h"
 #include "libmilter/mfdef.h"
@@ -54,6 +58,9 @@ struct mlfiPriv {
 };
 
 #define MLFIPRIV ((struct mlfiPriv*)smfi_getpriv(ctx))
+#define VERSION "1.0.3"
+
+extern const char *__progname;
 
 static unsigned long mta_caps = 0;
 
@@ -61,25 +68,63 @@ static unsigned long mta_caps = 0;
 // contains a < with a subsequent >, the inner part is used. If not, the whole
 // header field is used. This allows matching "Max Mustermann
 // <max.mustermann@example.invalid>".
-const char *parse_address(const char *address, size_t *len)
-{
-	size_t inlen = strlen(address);
-	size_t pos_open = SIZE_MAX, pos_close = SIZE_MAX;
-	size_t i;
-	for (i = 0; i < inlen; ++i) {
-		if (address[i] == '<') pos_open = i;
-		else if (address[i] == '>') pos_close = i;
-	}
-
-	if (pos_open != SIZE_MAX && pos_close != SIZE_MAX && pos_open < pos_close) {
-		*len = pos_close - pos_open - 1;
-		return address + pos_open + 1;
-	} else {
-		*len = inlen;
-		return address;
-	}
+const char *parse_address(const char *header, size_t *len) {
+    if (!header || !len) return NULL;
+
+    const char *start = NULL;
+    const char *end = NULL;
+    size_t i;
+
+    /* Find the last '<' and the corresponding '>' */
+    for (i = 0; header[i]; i++) {
+        if (header[i] == '<') start = header + i + 1;
+        else if (header[i] == '>') end = header + i;
+    }
+
+    if (start && end && start < end) {
+        /* Trim whitespace from start */
+        while (start < end && isspace((unsigned char)*start)) start++;
+        /* Trim whitespace from end */
+        while (end > start && isspace((unsigned char)*(end-1))) end--;
+
+        /* Reject if empty */
+        if (end <= start) return NULL;
+
+        /* Reject if any control chars (\r, \n, 0x00-0x1F, 0x7F) */
+        for (const char *p = start; p < end; p++) {
+            if ((unsigned char)*p < 32 || *p == 127) return NULL;
+        }
+
+        *len = (size_t)(end - start);
+        return start;
+    }
+
+    /* No angle brackets: treat the entire header as address */
+    start = header;
+    end = header + strlen(header);
+
+    /* Trim whitespace */
+    while (start < end && isspace((unsigned char)*start)) start++;
+    while (end > start && isspace((unsigned char)*(end-1))) end--;
+
+    if (end <= start) return NULL;
+
+    /* Reject control characters */
+    for (const char *p = start; p < end; p++) {
+        if ((unsigned char)*p < 32 || *p == 127) return NULL;
+    }
+
+    *len = (size_t)(end - start);
+    return start;
 }
 
+void log_event(SMFICTX *ctx, char *msg) {
+	/* Log event with additional information */
+	const char *author = smfi_getsymval(ctx, "{auth_authen}");
+	const char *info = smfi_getsymval(ctx,"_");
+	syslog(LOG_INFO,"%s for authenticated user (%s) from (%s)", msg, author, info);
+} 
+
 void mlfi_cleanup(SMFICTX *ctx)
 {
 	struct mlfiPriv *priv = MLFIPRIV;
@@ -99,20 +144,21 @@ sfsistat mlfi_envfrom(SMFICTX *ctx, char **envfrom)
 	// Allocate some private memory.
 	priv = calloc(1, sizeof(*priv));
 	if (priv == NULL) {
-		goto fail;
+		return SMFIS_TEMPFAIL;
 	}
 
 	// Parse envelope from.
 	size_t len = 0;
 	const char *from = parse_address(*envfrom, &len);
 	if (len == 0) {
-		/* The strndup call below with a length of 0 will allocate a string of size
-		 * 0 so avoid that entirely and fail. */
-		goto fail;
+		/* A 0 length from address means a "null reverse-path", which is valid per
+		 * RFC5321. */
+		log_event(ctx, "Accepting message as envelope sender is null");
+		return SMFIS_ACCEPT;
 	}
 	fromcp = strndup(from, len);
 	if (fromcp == NULL) {
-		goto fail;
+		return SMFIS_TEMPFAIL;
 	}
 
 	// Set private values.
@@ -124,15 +170,14 @@ sfsistat mlfi_envfrom(SMFICTX *ctx, char **envfrom)
 	smfi_setpriv(ctx, priv);
 
 	return SMFIS_CONTINUE;
-fail:
-	free(fromcp);
-	return SMFIS_TEMPFAIL;
 }
 
 sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
 {
 	struct mlfiPriv *priv = MLFIPRIV;
 
+	if (priv == NULL) return SMFIS_CONTINUE;
+
 	// Perform checks if the sender is authenticated and the message is not rejected yet (the mail may contain multiple from tags, all have to match!).
 	if (priv->is_auth && !priv->reject) {
 		if (strcasecmp(headerf, "from") == 0) {
@@ -140,7 +185,19 @@ sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
 			const char *from = parse_address(headerv, &len);
 
 			// Check whether header from matches envelope from and reject if not.
-			if (len != priv->env_from_len || strncasecmp(from, priv->env_from, len) != 0) priv->reject = 1;
+			if (len != priv->env_from_len || strncasecmp(from, priv->env_from, len) != 0) {
+				char *msg;
+				size_t msg_len = 0;
+
+				priv->reject = 1;
+				msg_len = 55 + len + priv->env_from_len;
+				msg = malloc(msg_len);
+				if (msg != NULL) { 
+					snprintf(msg, msg_len, "Rejecting Envelope From (%s) and Header From (%s) mismatch", priv->env_from, from);
+					log_event(ctx, msg);
+				}
+				free(msg);
+			}
 		}
 	}
 
@@ -150,6 +207,9 @@ sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
 sfsistat mlfi_eom(SMFICTX *ctx)
 {
 	struct mlfiPriv *priv = MLFIPRIV;
+
+	if (priv == NULL) return SMFIS_CONTINUE;
+
 	if (priv->reject) {
 		smfi_setreply(ctx, "550", "5.7.1", "Rejected due to unmatching envelope and header sender.");
 		mlfi_cleanup(ctx);
@@ -200,13 +260,24 @@ struct smfiDesc smfilter =
 
 uid_t get_uid(const char *name)
 {
-    struct passwd *pwd = getpwnam(name);
-    return pwd == NULL ? -1 : pwd->pw_uid;
+	struct passwd *pwd = getpwnam(name);
+	return pwd == NULL ? -1 : pwd->pw_uid;
 }
 gid_t get_gid(const char *name)
 {
-    struct group *grp = getgrnam(name);
-    return grp == NULL ? -1 : grp->gr_gid;
+	struct group *grp = getgrnam(name);
+	return grp == NULL ? -1 : grp->gr_gid;
+}
+
+static void usage(FILE *stream) {
+	fprintf(stream, "%s: A Milter program version %s to reject emails that have a mismatch between Envelope Sender and email Header From fields for authenticated users. This prevents spoofing that is currently not possible with \"reject_authenticated_sender_login_mismatch\" in Postfix\n", __progname, VERSION);
+	fprintf(stream, "%s: usage: %s -s socketfile [options]\n"
+		"\t-p pidfile  \twrite process ID to pidfile name\n"
+		"\t-d          \tdaemonize to background and exit\n"
+		"\t-u userid   \tchange to specified userid\n"
+		"\t-g groupid  \tchange to specific groupid\n"
+		"\t-v          \tprint version number and terminate\n",
+		__progname, __progname);
 }
 
 int main(int argc, char **argv)
@@ -216,8 +287,11 @@ int main(int argc, char **argv)
 	mode_t um = -1;
 	char *pidfilename = NULL, *sockname = NULL;
 	FILE *pidfile = NULL;
+	u_int mvmajor;
+	u_int mvminor;
+	u_int mvrelease;
 
-	while ((c = getopt(argc, argv, "ds:p:u:g:m:")) != -1) {
+	while ((c = getopt(argc, argv, "dhvs:p:u:g:m:")) != -1) {
 		switch (c) {
 		case 's':
 			sockname = strdup(optarg);
@@ -237,19 +311,29 @@ int main(int argc, char **argv)
 		case 'm':
 			um = strtol(optarg, 0, 8);
 			break;
+		case 'h':
+			usage(stdout);
+			return EXIT_SUCCESS;
+		case 'v':
+			printf("%s: v%s\n", __progname, VERSION);
+			printf("\tSMFI_VERSION 0x%x\n", SMFI_VERSION);
+
+			(void)smfi_version(&mvmajor, &mvminor, &mvrelease);
+			printf("\tlibmilter version %d.%d.%d\n", mvmajor, mvminor, mvrelease);
+			return EXIT_SUCCESS;
 		}
 	}
 
 	if (!sockname) {
 		fprintf(stderr, "%s: Missing required -s argument\n", argv[0]);
-		exit(EX_USAGE);
+		usage(stderr);
+		return EX_USAGE;
 	}
 
 	if (pidfilename) {
 		unlink(pidfilename);
 		pidfile = fopen(pidfilename, "w");
-		if (!pidfile)
-		{
+		if (!pidfile) {
 			fprintf(stderr, "Could not open pidfile: %s\n", strerror(errno));
 			exit(1);
 		}
@@ -280,5 +364,6 @@ int main(int argc, char **argv)
 		fprintf(stderr, "smfi_register failed\n");
 		exit(EX_UNAVAILABLE);
 	}
+        openlog("milterfrom", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_MAIL);
 	return smfi_main();
 }