[PATCH] Support display name substitution in customheaders

Lumin Etherlight <[email protected]> Fri, 27 Mar 2026 21:14:16 +0300
Newsgroups org.mlmmj.mlmmj
Organization Etherlight Systems - https://etherlight.link
Message-ID <83Su95j/[email protected]>
Allow substitution of  the variable $postername$ for
the display name  of the user.  The  display name is
extracted from the sender's From: header.  If a name
is not found, then  this variable is substituted for
user's address, i.e. the same value as $posteraddr$.
Proper handling of comments,  and quoted strings, in
the From: address is also included.

This allows the following `customheaders' use-case:

    From: $postername$ via Project-List <[email protected]>

Which would render, for example, as:

    From: John Doe via Project-List <[email protected]>

For the user:

    John Doe <[email protected]>.
---
 AUTHORS                      |  1 +
 include/find_email_adr.h     |  1 +
 include/wrappers.h           |  2 +-
 src/do_all_the_voodoo_here.c | 22 ++++++++++++---
 src/dumpfd2fd.c              | 14 +++++++++-
 src/find_email_adr.c         | 52 ++++++++++++++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 6 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index be21ced..9d9b7df 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,3 +1,4 @@
 Mads Martin Joergensen <[email protected]>
 Morten K. Poulsen <[email protected]>
 Ben Schmidt <[email protected]>
+Lumin Etherlight <[email protected]>
diff --git a/include/find_email_adr.h b/include/find_email_adr.h
index e5da89b..707fdd6 100644
--- a/include/find_email_adr.h
+++ b/include/find_email_adr.h
@@ -27,5 +27,6 @@
 #include <stddef.h>
 
 strlist *find_email_adr(const char *str, strlist *retval);
+char    *find_display_name(char *name);
 
 #endif /* FIND_EMAIL_ADR_H */
diff --git a/include/wrappers.h b/include/wrappers.h
index 2953bd2..5a2c144 100644
--- a/include/wrappers.h
+++ b/include/wrappers.h
@@ -31,6 +31,6 @@ ssize_t readn(int fd, void *vptr, size_t n);
 int random_int(void);
 int dumpfd2fd(int infd, int outfd);
 int copy_file(int infd, int outfd, size_t bufsiz);
-int process_headers(int infd, FILE *outf, const char *from);
+int process_headers(int infd, FILE *outf, const char *from, const char *name);
 
 #endif /* WRAPPERS_H */
diff --git a/src/do_all_the_voodoo_here.c b/src/do_all_the_voodoo_here.c
index 13aeb74..14214a8 100644
--- a/src/do_all_the_voodoo_here.c
+++ b/src/do_all_the_voodoo_here.c
@@ -80,6 +80,7 @@ int do_all_the_voodoo_here(int infd, int outfd, int hdrfd, int footfd,
 	char *hdrline, *unfolded, *unqp;
 	strlist allunfoldeds = tll_init();
 	char *posteraddr = NULL;
+	char *postername = NULL;
 	bool hdrsadded = false;
 	bool subject_present = false;
 	int dupfd = dup(infd);
@@ -111,9 +112,16 @@ int do_all_the_voodoo_here(int infd, int outfd, int hdrfd, int footfd,
 	if(hdrfd >= 0) {
 		strlist fromemails = tll_init();
 		if ( readhdrs[0].valuecount == 1 ) {
-		        find_email_adr(readhdrs[0].values[0], &fromemails);
-			if (tll_length(fromemails) > 0)
+			find_email_adr(readhdrs[0].values[0], &fromemails);
+			if (tll_length(fromemails) > 0) {
 				posteraddr = xstrdup(tll_front(fromemails));
+				postername = find_display_name(readhdrs[0].values[0]);
+				if (!postername) {
+					postername = posteraddr ?
+						xstrdup(posteraddr) :
+						xcalloc(1, 1);
+				}
+			}
 		}
 		tll_free_and_free(fromemails, free);
 	}
@@ -129,12 +137,13 @@ int do_all_the_voodoo_here(int infd, int outfd, int hdrfd, int footfd,
 		if(!hdrsadded &&
 		    ( strncasecmp(hdrline, "mime", 4) == 0)) {
 			if(hdrfd >= 0) {
-			        if(process_headers(hdrfd,outf,posteraddr ? posteraddr : "") < 0) {
+				if(process_headers(hdrfd,outf,posteraddr ? posteraddr : "", postername ) < 0) {
 					log_error(LOG_ARGS, "Could not "
 						"add extra headers");
 					free(unfolded);
 					tll_free_and_free(allunfoldeds, free);
 					free(posteraddr);
+					free(postername);
 					fclose(outf);
 					fclose(f);
 					return -1;
@@ -174,11 +183,12 @@ int do_all_the_voodoo_here(int infd, int outfd, int hdrfd, int footfd,
 
 	if(!hdrsadded ) {
 		if(hdrfd >= 0) {
-		  if(process_headers(hdrfd,outf,posteraddr ? posteraddr : "") < 0) {
+		  if(process_headers(hdrfd,outf,posteraddr ? posteraddr : "", postername) < 0) {
 				log_error(LOG_ARGS, "Could not "
 					"add extra headers");
 				tll_free_and_free(allunfoldeds, free);
 				free(posteraddr);
+				free(postername);
 				fclose(outf);
 				fclose(f);
 				return -1;
@@ -195,6 +205,7 @@ int do_all_the_voodoo_here(int infd, int outfd, int hdrfd, int footfd,
 	        tll_free_and_free(allunfoldeds, free);
 		log_error(LOG_ARGS, "Error writing hdrs.");
 		free(posteraddr);
+		free(postername);
 		fclose(outf);
 		fclose(f);
 		return -1;
@@ -209,6 +220,7 @@ int do_all_the_voodoo_here(int infd, int outfd, int hdrfd, int footfd,
 	if(dumpfd2fd(infd, outfd) < 0) {
 		log_error(LOG_ARGS, "Error when dumping rest of mail");
 		free(posteraddr);
+		free(postername);
 		fclose(f);
 		return -1;
 	}
@@ -219,11 +231,13 @@ int do_all_the_voodoo_here(int infd, int outfd, int hdrfd, int footfd,
 		if(dumpfd2fd(footfd, outfd) < 0) {
 			log_error(LOG_ARGS, "Error when adding footer");
 			free(posteraddr);
+			free(postername);
 			return -1;
 		}
 
 	fsync(outfd);
 
 	free(posteraddr);
+	free(postername);
 	return 0;
 }
diff --git a/src/dumpfd2fd.c b/src/dumpfd2fd.c
index 674e2b0..431bac2 100644
--- a/src/dumpfd2fd.c
+++ b/src/dumpfd2fd.c
@@ -84,7 +84,7 @@ int dumpfd2fd(int from, int to)
 	return (r);
 }
 
-int process_headers(int infd, FILE *outf, const char *from)
+int process_headers(int infd, FILE *outf, const char *from, const char *name)
 {
 	char *line = NULL, *p, *to_be_subst, *new_line;
 	size_t linecap = 0;
@@ -120,6 +120,18 @@ int process_headers(int infd, FILE *outf, const char *from)
 				return -1;
 			}
 			free(new_line);
+		} else if ((to_be_subst = strstr(p, "$postername$")) != NULL) {
+			*to_be_subst = '\0';
+			to_be_subst += 12;
+			xasprintf(&new_line, "%s%s%s", p, name, to_be_subst);
+			if (fprintf(outf, "%s", new_line) < 0) {
+				log_error(LOG_ARGS, "Could not write headers");
+				free(new_line);
+				free(line);
+				fclose(f);
+				return -1;
+			}
+			free(new_line);
 		} else {
 			if (fprintf(outf, "%s", p) < 0) {
 				log_error(LOG_ARGS, "Could not write headers");
diff --git a/src/find_email_adr.c b/src/find_email_adr.c
index 0830098..77b7cef 100644
--- a/src/find_email_adr.c
+++ b/src/find_email_adr.c
@@ -239,3 +239,55 @@ oncemore:
 
 	return retstruct;
 }
+
+/* ---------------------------------------------------------------------------*\
+ * Extract the display name of the first user,
+ * removing comments, for example:
+ *
+ * for the input string:
+ *	"John (Amazing) Smith" <[email protected]>,
+ *	  "Jane Doe" <[email protected]
+ *
+ * returns: "John Smith"
+\* ---------------------------------------------------------------------------*/
+char *
+find_display_name (char *name)
+{
+	char *p, *nbuf, c;
+	int nbuf_len, saw_angled_brackets;
+
+	if (!name) return NULL;
+	nbuf = xmalloc(strlen(name));
+	saw_angled_brackets = 0;
+	nbuf_len = 0;
+
+	for (p = name; (c = *p) != '\0' && isspace(c); p++);
+	while ((c = *p++) != '\0') {
+		switch (c) {
+		case '(':
+			p = skip_comment(p);
+			/* -------------------------------*\
+			 * Copy at most one space in place
+			 * of the comment.  Don't copy any
+			 * whitespace if we are still at
+			 * the beginning of the string.
+			 */
+			if (nbuf_len > 0 && isspace(*p) &&
+			    !isspace(nbuf[nbuf_len-1])) {
+				nbuf[nbuf_len++] = *p++;
+			}
+			while(isspace(*p)) p++;
+			/* -------------------------------*/
+			break;
+		case '<': saw_angled_brackets = 1; goto finished;
+		case ',':			   goto finished;
+		default:  nbuf[nbuf_len++] = c;	   break;
+		}
+	}
+
+ finished:
+	if (!saw_angled_brackets || nbuf_len == 0) { free(nbuf); return NULL; }
+	for (p--, p--; (c = *p) && isspace(c); p--, nbuf_len--);
+	nbuf[nbuf_len++] = '\0';
+	return nbuf;
+}
-- 
2.46.4