changeset 0:7e0d08176f32

f-demime starting code
author Mychaela Falconia <falcon@freecalypso.org>
date Sat, 06 May 2023 06:14:03 +0000
parents
children 05651a1b8ba8
files f-demime/attach_out.c f-demime/b2q_in.c f-demime/b2q_out.c f-demime/base64.c f-demime/defs.h f-demime/finish.c f-demime/header.c f-demime/header_end.c f-demime/initconv.c f-demime/main.c f-demime/msgstate.c f-demime/ptext_in.c f-demime/ptext_out.c f-demime/qpdec.c
diffstat 14 files changed, 1554 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/attach_out.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,57 @@
+/*
+ * This module contains code for creating (writing) secondary output files
+ * intended for storing attached base64 blobs.
+ */
+
+#include <sys/file.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include "defs.h"
+
+extern char *att_filename_buf, *att_filename_tail;
+extern void (*dec_outf)();
+
+static unsigned att_count;
+static FILE *att_outf;
+static int giveup_flag;
+
+static void
+output_func(ch)
+{
+	putc(ch, att_outf);
+}
+
+init_attach_out()
+{
+	int fd;
+
+	if (giveup_flag)
+		return(-1);
+	for (;;) {
+		if (att_count >= 10000) {
+			giveup_flag = 1;
+			return(-1);
+		}
+		sprintf(att_filename_tail, "%04u", att_count++);
+		fd = open(att_filename_buf, O_WRONLY|O_CREAT|O_EXCL, 0644);
+		if (fd >= 0)
+			break;
+	}
+	att_outf = fdopen(fd, "w");
+	if (!att_outf) {
+		perror("fdopen");
+		close(fd);
+		return(-1);
+	}
+	dec_outf = output_func;
+	return(0);
+}
+
+void
+attach_out_finish()
+{
+	fclose(att_outf);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/b2q_in.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,45 @@
+/*
+ * This module implements the input side of base64-to-QP conversion.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "defs.h"
+
+extern void (*dec_outf)();
+extern FILE *tempfile;
+
+static int cr_state;
+
+static void
+output_func(ch)
+{
+	if (cr_state) {
+		cr_state = 0;
+		if (ch == '\n') {
+			putc('\n', tempfile);
+			return;
+		} else
+			putc('\r', tempfile);
+	}
+	if (ch == '\r')
+		cr_state = 1;
+	else
+		putc(ch, tempfile);
+}
+
+void
+b2q_conv_init()
+{
+	dec_outf = output_func;
+	cr_state = 0;
+}
+
+void
+b2q_conv_finish()
+{
+	if (cr_state)
+		putc('\r', tempfile);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/b2q_out.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,90 @@
+/*
+ * This module implements the output side of base64-to-QP conversion.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "defs.h"
+
+extern FILE *tempfile;
+
+static char linebuf[QP_MAX_LINE];
+static unsigned line_len, trailing_newline;
+
+static void
+add_plain_char(ch)
+{
+	linebuf[line_len++] = ch;
+	linebuf[line_len] = '\0';
+	if (!strcmp(linebuf, "From ")) {
+		strcpy(linebuf, "=46rom ");
+		line_len = 7;
+	} else if (!strcmp(linebuf, "--")) {
+		strcpy(linebuf, "=2D-");
+		line_len = 4;
+	} else if (!strcmp(linebuf, ".")) {
+		strcpy(linebuf, "=2E");
+		line_len = 3;
+	}
+}
+
+static void
+add_octet_char(ch)
+{
+	sprintf(linebuf + line_len, "=%02X", ch);
+	line_len += 3;
+}
+
+static void
+process_newline()
+{
+	if (line_len) {
+		if (linebuf[line_len-1] == ' ')
+			strcpy(linebuf + line_len - 1, "=20");
+		fputs(linebuf, stdout);
+		line_len = 0;
+	}
+	putchar('\n');
+}
+
+static void
+process_char(ch)
+{
+	if (ch == '\n') {
+		process_newline();
+		trailing_newline++;
+		if (trailing_newline > 2)
+			trailing_newline = 2;
+		return;
+	}
+	if (line_len >= QP_MAX_LINE - 2) {
+		printf("%s=\n", linebuf);
+		line_len = 0;
+	}
+	if (ch == '=')
+		add_octet_char(ch);
+	else if (ch >= ' ' && ch <= '~')
+		add_plain_char(ch);
+	else
+		add_octet_char(ch);
+	trailing_newline = 0;
+}
+
+void
+b2q_emit_output()
+{
+	int c;
+
+	rewind(tempfile);
+	line_len = 0;
+	trailing_newline = 1;
+	while ((c = getc(tempfile)) != EOF)
+		process_char(c);
+	fclose(tempfile);
+	while (trailing_newline < 2) {
+		process_newline();
+		trailing_newline++;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/base64.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,112 @@
+/*
+ * This module implements base64 decoding.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "defs.h"
+
+extern void (*dec_outf)();
+
+static unsigned b64_accum, b64_eq_flag;
+static int b64_state, last_partial;
+int b64_err_flag, b64_nonempty;
+
+void
+base64_dec_init()
+{
+	b64_accum = 0;
+	b64_eq_flag = 0;
+	b64_state = 0;
+	b64_err_flag = 0;
+	b64_nonempty = 0;
+	last_partial = 0;
+}
+
+static void
+process_unit()
+{
+	if (last_partial)
+		b64_err_flag = 1;
+	switch (b64_eq_flag) {
+	case 0:
+		dec_outf(b64_accum >> 16);
+		dec_outf((b64_accum >> 8) & 0xFF);
+		dec_outf(b64_accum & 0xFF);
+		last_partial = 0;
+		break;
+	case 1:
+		dec_outf(b64_accum >> 16);
+		dec_outf((b64_accum >> 8) & 0xFF);
+		last_partial = 1;
+		break;
+	case 3:
+		dec_outf(b64_accum >> 16);
+		last_partial = 1;
+		break;
+	default:
+		b64_err_flag = 1;
+	}
+	b64_accum = 0;
+	b64_eq_flag = 0;
+	b64_state = 0;
+}
+
+static void
+base64_input_char(ch)
+{
+	int code;
+
+	b64_nonempty = 1;
+	if (ch >= 'A' && ch <= 'Z')
+		code = ch - 'A';
+	else if (ch >= 'a' && ch <= 'z')
+		code = ch - 'a' + 26;
+	else if (ch >= '0' && ch <= '9')
+		code = ch - '0' + 52;
+	else switch (ch) {
+	case '+':
+		code = 62;
+		break;
+	case '/':
+		code = 63;
+		break;
+	case '=':
+		code = 64;
+		break;
+	default:
+		b64_err_flag = 1;
+		return;
+	}
+	b64_accum <<= 6;
+	b64_accum |= (code & 63);
+	b64_eq_flag <<= 1;
+	b64_eq_flag |= (code >> 6);
+	b64_state++;
+	if (b64_state >= 4)
+		process_unit();
+}
+
+void
+base64_input_line(line)
+	char *line;
+{
+	char *cp;
+	int c;
+
+	for (cp = line; *cp; ) {
+		c = *cp++;
+		if (!isspace(c))
+			base64_input_char(c);
+	}
+}
+
+void
+base64_dec_finish()
+{
+	if (b64_state)
+		b64_err_flag = 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/defs.h	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,31 @@
+/*
+ * This header file holds miscellaneous definitions for f-demime,
+ * including various limits.
+ */
+
+enum msg_state {
+	MSG_STATE_UNDEF,	/* before From line */
+	MSG_STATE_HEADER,	/* message or body part header */
+	MSG_STATE_BODY_PASS,	/* body pass-through state */
+	MSG_STATE_PTEXT_B64,	/* text/plain decoding from base64 */
+	MSG_STATE_PTEXT_QP,	/* text/plain decoding from quoted-printable */
+	MSG_STATE_BLOB_B64,	/* blob extraction from base64 */
+	MSG_STATE_B64_TO_QP,	/* base64 to quoted-printable conversion */
+};
+
+enum msg_hdr_state {
+	HDR_STATE_BEGIN,	/* beginning of entity header */
+	HDR_STATE_GOTSOME,	/* got some header line(s), regular */
+	HDR_STATE_CONT_TYPE,	/* after Content-Type: header */
+	HDR_STATE_CONT_TE,	/* after Content-Transfer-Encoding: header */
+	HDR_STATE_ERROR		/* errored state */
+};
+
+#define	LINE_BUF_SIZE	1024
+#define	HDR_BUF_SIZE	2048
+
+#define	MAX_MP_BOUNDARY	70	/* not counting the hyphens */
+#define	MAX_MP_NESTING	8
+
+#define	OUTPUT_LINE_MAX	320
+#define	QP_MAX_LINE	76
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/finish.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,74 @@
+/*
+ * This module implements the handling at the end of entity body input.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "defs.h"
+
+extern enum msg_state msg_state;
+extern char cont_type_buf[HDR_BUF_SIZE], cont_te_buf[HDR_BUF_SIZE];
+extern int got_cont_type, got_cont_te;
+extern int b64_err_flag, b64_nonempty, qpdec_err_flag;
+
+static void
+check_b64_err()
+{
+	if (b64_err_flag)
+		puts("X-Fdemime-Error: bad base64 data");
+}
+
+static void
+check_qpdec_err()
+{
+	if (qpdec_err_flag)
+		puts("X-Fdemime-Error: bad quoted-printable data");
+}
+
+void
+finish_msg_body()
+{
+	switch (msg_state) {
+	case MSG_STATE_HEADER:
+		if (got_cont_type)
+			fputs(cont_type_buf, stdout);
+		if (got_cont_te)
+			fputs(cont_te_buf, stdout);
+		return;
+	case MSG_STATE_PTEXT_B64:
+		base64_dec_finish();
+		check_b64_err();
+		ptext_conv_finish();
+		ptext_mark_transform("decode-base64");
+		putchar('\n');
+		ptext_emit_output();
+		return;
+	case MSG_STATE_PTEXT_QP:
+		check_qpdec_err();
+		ptext_conv_finish();
+		ptext_mark_transform("decode-qp");
+		putchar('\n');
+		ptext_emit_output();
+		return;
+	case MSG_STATE_BLOB_B64:
+		base64_dec_finish();
+		check_b64_err();
+		putchar('\n');
+		attach_out_finish();
+		if (b64_nonempty)
+			puts("[base64 blob stripped]");
+		putchar('\n');
+		return;
+	case MSG_STATE_B64_TO_QP:
+		base64_dec_finish();
+		check_b64_err();
+		b2q_conv_finish();
+		puts("X-Fdemime-Transform: base64-to-qp");
+		puts("Content-Transfer-Encoding: quoted-printable");
+		putchar('\n');
+		b2q_emit_output();
+		return;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/header.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,145 @@
+/*
+ * This module implements initial processing (pass-through and collection)
+ * of message and body part headers.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "defs.h"
+
+extern enum msg_hdr_state hdr_state;
+extern char cont_type_buf[HDR_BUF_SIZE], cont_te_buf[HDR_BUF_SIZE];
+extern int got_cont_type, got_cont_te;
+
+static void
+error(line, msg)
+	char *line, *msg;
+{
+	if (got_cont_type) {
+		fputs(cont_type_buf, stdout);
+		got_cont_type = 0;
+	}
+	if (got_cont_te) {
+		fputs(cont_te_buf, stdout);
+		got_cont_te = 0;
+	}
+	printf("X-Fdemime-Error: %s\n", msg);
+	puts(line);
+	hdr_state = HDR_STATE_ERROR;
+}
+
+static void
+error_ct_cont(hdr_name)
+	char *hdr_name;
+{
+	printf("X-Fdemime-Error: %s header is too long\n", hdr_name);
+}
+
+static void
+cont_line(line)
+	char *line;
+{
+	unsigned prev_len;
+
+	switch (hdr_state) {
+	case HDR_STATE_BEGIN:
+		error(line, "continuation line at the beginning of header");
+		return;
+	case HDR_STATE_GOTSOME:
+		puts(line);
+		return;
+	case HDR_STATE_CONT_TYPE:
+		prev_len = strlen(cont_type_buf);
+		if (prev_len + strlen(line) + 2 > HDR_BUF_SIZE) {
+			error_ct_cont("Content-Type");
+			fputs(cont_type_buf, stdout);
+			puts(line);
+			hdr_state = HDR_STATE_ERROR;
+			got_cont_type = 0;
+			return;
+		}
+		sprintf(cont_type_buf + prev_len, "%s\n", line);
+		return;
+	case HDR_STATE_CONT_TE:
+		prev_len = strlen(cont_te_buf);
+		if (prev_len + strlen(line) + 2 > HDR_BUF_SIZE) {
+			error_ct_cont("Content-Transfer-Encoding");
+			fputs(cont_te_buf, stdout);
+			puts(line);
+			hdr_state = HDR_STATE_ERROR;
+			got_cont_te = 0;
+			return;
+		}
+		sprintf(cont_te_buf + prev_len, "%s\n", line);
+		return;
+	default:
+		fprintf(stderr,
+			"f-demime internal error: bad state in cont_line()\n");
+		abort();
+	}
+}
+
+void
+header_input_line(line)
+	char *line;
+{
+	char *cp, savech;
+	enum msg_hdr_state newhdr;
+
+	if (!line[0]) {
+		process_header_end();
+		return;
+	}
+	if (hdr_state == HDR_STATE_ERROR) {
+		puts(line);
+		return;
+	}
+	if (line[0] == ' ' || line[0] == '\t') {
+		cont_line(line);
+		return;
+	}
+	cp = index(line, ':');
+	if (!cp) {
+		error(line, "header line has no colon");
+		return;
+	}
+	if (cp == line) {
+		error(line, "null header field name");
+		return;
+	}
+	while (cp[-1] == ' ' || cp[-1] == '\t')
+		cp--;
+	savech = *cp;
+	*cp = '\0';
+	if (!strcasecmp(line, "Content-Type"))
+		newhdr = HDR_STATE_CONT_TYPE;
+	else if (!strcasecmp(line, "Content-Transfer-Encoding"))
+		newhdr = HDR_STATE_CONT_TE;
+	else
+		newhdr = HDR_STATE_GOTSOME;
+	*cp = savech;
+	switch (newhdr) {
+	case HDR_STATE_CONT_TYPE:
+		if (got_cont_type) {
+			error(line, "duplicate Content-Type");
+			return;
+		}
+		sprintf(cont_type_buf, "%s\n", line);
+		got_cont_type = 1;
+		break;
+	case HDR_STATE_CONT_TE:
+		if (got_cont_te) {
+			error(line, "duplicate Content-Transfer-Encoding");
+			return;
+		}
+		sprintf(cont_te_buf, "%s\n", line);
+		got_cont_te = 1;
+		break;
+	default:
+		puts(line);
+	}
+	hdr_state = newhdr;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/header_end.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,288 @@
+/*
+ * This module implements final processing of message and body part headers,
+ * deciding what to do at the end of each header.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "defs.h"
+
+extern enum msg_state msg_state;
+extern enum msg_hdr_state hdr_state;
+extern unsigned mp_nest_level;
+extern char mp_boundaries[MAX_MP_NESTING][MAX_MP_BOUNDARY+1];
+extern int mp_is_digest[MAX_MP_NESTING];
+extern char cont_type_buf[HDR_BUF_SIZE], cont_te_buf[HDR_BUF_SIZE];
+extern int got_cont_type, got_cont_te;
+
+static int
+is_valid_tchar(ch)
+{
+	if (ch < '!' || ch > '~')
+		return(0);
+	switch (ch) {
+	case '(':
+	case ')':
+	case '<':
+	case '>':
+	case '@':
+	case ',':
+	case ';':
+	case ':':
+	case '\\':
+	case '"':
+	case '/':
+	case '[':
+	case ']':
+	case '?':
+	case '=':
+		return(0);
+	default:
+		return(1);
+	}
+}
+
+static int
+gettoken(cpp, tokbuf)
+	char **cpp, *tokbuf;
+{
+	register char *cp, *dp;
+	register int i;
+
+	/* skip initial white space and comments */
+	for (cp = *cpp; ; ) {
+		if (isspace(*cp)) {
+			cp++;
+			continue;
+		}
+		if (*cp != '(')
+			break;
+		for (i = 0; ; ) {
+			if (!*cp)
+				break;
+			if (*cp == '\\')
+				cp++;
+			else if (*cp == '(')
+				i++;
+			else if (*cp == ')')
+				i--;
+			if (*cp)
+				cp++;
+			if (!i)
+				break;
+		}
+	}
+	if (!*cp) {
+		*cpp = cp;
+		return(0);
+	}
+	if (*cp == '/' || *cp == ';' || *cp == '=') {
+		i = *cp++;
+		*cpp = cp;
+		return(i);
+	}
+	if (*cp == '"') {
+		cp++;
+		for (dp = tokbuf; *cp; ) {
+			if (*cp == '"') {
+				cp++;
+				break;
+			}
+			if (cp[0] == '\\' && cp[1])
+				cp++;
+			*dp++ = *cp++;
+		}
+		*dp = '\0';
+		*cpp = cp;
+		return(2);
+	}
+	if (!is_valid_tchar(*cp)) {
+		*cpp = cp;
+		return(-1);
+	}
+	for (dp = tokbuf; is_valid_tchar(*cp); )
+		*dp++ = *cp++;
+	*dp = '\0';
+	*cpp = cp;
+	return(1);
+}
+
+static int
+parse_content_type(type, subtype, charset, boundary)
+	char *type, *subtype, *charset, *boundary;
+{
+	char *ctstr = cont_type_buf;
+	char tokbuf[HDR_BUF_SIZE], attr[HDR_BUF_SIZE];
+	int rc;
+
+	if (gettoken(&ctstr, type) != 1)
+		return(-1);
+	if (gettoken(&ctstr, tokbuf) != '/')
+		return(-1);
+	if (gettoken(&ctstr, subtype) != 1)
+		return(-1);
+	charset[0] = '\0';
+	boundary[0] = '\0';
+	for (;;) {
+		rc = gettoken(&ctstr, tokbuf);
+		if (!rc)
+			return(0);
+		if (rc != ';')
+			return(-1);
+		if (gettoken(&ctstr, attr) != 1)
+			return(-1);
+		if (gettoken(&ctstr, tokbuf) != '=')
+			return(-1);
+		rc = gettoken(&ctstr, tokbuf);
+		if (rc != 1 && rc != 2)
+			return(-1);
+		if (!strcasecmp(attr, "charset"))
+			strcpy(charset, tokbuf);
+		else if (!strcasecmp(attr, "boundary"))
+			strcpy(boundary, tokbuf);
+	}
+}
+
+static int
+parse_content_te(ctetoken)
+	char *ctetoken;
+{
+	char *ctestr = cont_te_buf;
+	char tokbuf[HDR_BUF_SIZE];
+
+	if (gettoken(&ctestr, ctetoken) != 1)
+		return(-1);
+	if (gettoken(&ctestr, tokbuf) == 0)
+		return(0);
+	else
+		return(-1);
+}
+
+static void
+handle_multipart(cont_subtype, boundary_attr)
+	char *cont_subtype, *boundary_attr;
+{
+	if (!boundary_attr[0]) {
+		puts("X-Fdemime-Error: multipart without boundary attr");
+		putchar('\n');
+		msg_state = MSG_STATE_BODY_PASS;
+		return;
+	}
+	if (index(boundary_attr, '\n')) {
+	      puts("X-Fdemime-Error: multipart boundary attr contains newline");
+		putchar('\n');
+		msg_state = MSG_STATE_BODY_PASS;
+		return;
+	}
+	if (strlen(boundary_attr) > MAX_MP_BOUNDARY) {
+		puts("X-Fdemime-Error: multipart boundary attr is too long");
+		putchar('\n');
+		msg_state = MSG_STATE_BODY_PASS;
+		return;
+	}
+	if (mp_nest_level >= MAX_MP_NESTING) {
+		puts("X-Fdemime-Error: multipart nesting is too deep");
+		putchar('\n');
+		msg_state = MSG_STATE_BODY_PASS;
+		return;
+	}
+	putchar('\n');
+	strcpy(mp_boundaries[mp_nest_level], boundary_attr);
+	mp_is_digest[mp_nest_level] = !strcasecmp(cont_subtype, "digest");
+	mp_nest_level++;
+	msg_state = MSG_STATE_BODY_PASS;
+}
+
+void
+process_header_end()
+{
+	char cont_type[HDR_BUF_SIZE], cont_subtype[HDR_BUF_SIZE];
+	char charset_attr[HDR_BUF_SIZE], boundary_attr[HDR_BUF_SIZE];
+	char content_te[HDR_BUF_SIZE];
+	int in_digest, rc;
+
+	if (hdr_state == HDR_STATE_ERROR) {
+		if (got_cont_type)
+			fputs(cont_type_buf, stdout);
+		if (got_cont_te)
+			fputs(cont_te_buf, stdout);
+		putchar('\n');
+		msg_state = MSG_STATE_BODY_PASS;
+		return;
+	}
+	if (mp_nest_level)
+		in_digest = mp_is_digest[mp_nest_level-1];
+	else
+		in_digest = 0;
+	if (got_cont_type) {
+		fputs(cont_type_buf, stdout);
+		rc = parse_content_type(cont_type, cont_subtype, charset_attr,
+					boundary_attr);
+		if (rc < 0) {
+			puts("X-Fdemime-Error: unable to parse Content-Type");
+			if (got_cont_te)
+				fputs(cont_te_buf, stdout);
+			putchar('\n');
+			msg_state = MSG_STATE_BODY_PASS;
+			return;
+		}
+	} else {
+		if (in_digest) {
+			strcpy(cont_type, "message");
+			strcpy(cont_subtype, "rfc822");
+		} else {
+			strcpy(cont_type, "text");
+			strcpy(cont_subtype, "plain");
+		}
+		charset_attr[0] = '\0';
+		boundary_attr[0] = '\0';
+	}
+	if (!strcasecmp(cont_type, "multipart")) {
+		if (got_cont_te)
+			fputs(cont_te_buf, stdout);
+		handle_multipart(cont_subtype, boundary_attr);
+		return;
+	}
+	if (!strcasecmp(cont_type, "message")) {
+		if (got_cont_te)
+			fputs(cont_te_buf, stdout);
+		putchar('\n');
+		msg_state = MSG_STATE_BODY_PASS;
+		return;
+	}
+	if (got_cont_te) {
+		rc = parse_content_te(content_te);
+		if (rc < 0) {
+			fputs(cont_te_buf, stdout);
+			puts(
+		"X-Fdemime-Error: unable to parse Content-Transfer-Encoding");
+			putchar('\n');
+			msg_state = MSG_STATE_BODY_PASS;
+			return;
+		}
+	} else
+		content_te[0] = '\0';
+	if (!strcasecmp(content_te, "base64")) {
+		if (!strcasecmp(cont_type, "text")) {
+			if (!strcasecmp(cont_subtype, "plain"))
+				init_base64_text_plain(charset_attr);
+			else
+				init_base64_text_other();
+		} else
+			init_base64_nontext();
+		return;
+	}
+	if (!strcasecmp(content_te, "quoted-printable") &&
+	    !strcasecmp(cont_type, "text") &&
+	    !strcasecmp(cont_subtype, "plain")) {
+		init_qp_text_plain(charset_attr);
+		return;
+	}
+	if (got_cont_te)
+		fputs(cont_te_buf, stdout);
+	putchar('\n');
+	msg_state = MSG_STATE_BODY_PASS;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/initconv.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,124 @@
+/*
+ * This module contains functions that implement initiation of various
+ * conversions performed by f-demime.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include "defs.h"
+
+extern enum msg_state msg_state;
+extern char cont_te_buf[HDR_BUF_SIZE];
+extern int got_cont_type, got_cont_te;
+extern char *att_filename_base;
+extern int qpdec_err_flag;
+
+void (*dec_outf)();
+FILE *tempfile;
+int text_is_utf8;
+
+static int
+create_tempfile()
+{
+	char template[16];
+	int fd;
+
+	strcpy(template, "/tmp/fdemXXXXXX");
+	fd = mkstemp(template);
+	if (fd < 0) {
+		perror("mkstemp");
+		return(-1);
+	}
+	unlink(template);
+	tempfile = fdopen(fd, "r+w");
+	if (!tempfile) {
+		perror("fdopen");
+		close(fd);
+		return(-1);
+	}
+	return(0);
+}
+
+static int
+init_tempfile()
+{
+	int rc;
+
+	rc = create_tempfile();
+	if (!rc)
+		return(0);
+	if (got_cont_te)
+		fputs(cont_te_buf, stdout);
+	puts("X-Fdemime-Error: unable to create temp file for conversion");
+	putchar('\n');
+	msg_state = MSG_STATE_BODY_PASS;
+	return(-1);
+}
+
+static void
+implicit_text_plain()
+{
+	if (!got_cont_type)
+		puts("Content-Type: text/plain (f-demime implied)");
+}
+
+static void
+grok_charset_attr(csa)
+	char *csa;
+{
+	text_is_utf8 = !strcasecmp(csa, "UTF-8") || !strcmp(csa, "csUTF8");
+}
+
+void
+init_base64_text_plain(charset_attr)
+	char *charset_attr;
+{
+	implicit_text_plain();
+	if (init_tempfile() < 0)
+		return;
+	grok_charset_attr(charset_attr);
+	base64_dec_init();
+	ptext_conv_init();
+	msg_state = MSG_STATE_PTEXT_B64;
+}
+
+void
+init_qp_text_plain(charset_attr)
+	char *charset_attr;
+{
+	implicit_text_plain();
+	if (init_tempfile() < 0)
+		return;
+	grok_charset_attr(charset_attr);
+	qpdec_err_flag = 0;
+	ptext_conv_init();
+	msg_state = MSG_STATE_PTEXT_QP;
+}
+
+void
+init_base64_text_other()
+{
+	if (init_tempfile() < 0)
+		return;
+	base64_dec_init();
+	b2q_conv_init();
+	msg_state = MSG_STATE_B64_TO_QP;
+}
+
+void
+init_base64_nontext()
+{
+	fputs(cont_te_buf, stdout);
+	if (init_attach_out() < 0) {
+		puts("X-Fdemime-Error: unable to create save file");
+		putchar('\n');
+		msg_state = MSG_STATE_BODY_PASS;
+		return;
+	}
+	printf("X-Fdemime-Saved: %s\n", att_filename_base);
+	base64_dec_init();
+	msg_state = MSG_STATE_BLOB_B64;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/main.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,100 @@
+/*
+ * This module contains the main() function for f-demime.  The following
+ * functions are called to pass input to further processing:
+ *
+ * begin_new_message(): called after processing a "From " line
+ * message_input_line(): called for all message lines after the "From " line
+ * finish_msg_body(): called when hitting EOF or a new "From " line
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include "defs.h"
+
+char *att_filename_buf, *att_filename_base, *att_filename_tail;
+unsigned input_lineno;
+
+static void
+get_attachment_dir()
+{
+	static char varname[] = "FDEMIME_ATT_DIR";
+	char *env, *cp;
+	unsigned dir_name_len;
+	time_t curtime;
+	struct tm *tm;
+
+	env = getenv(varname);
+	if (!env) {
+		fprintf(stderr,
+			"error: required environment variable %s is not set\n",
+			varname);
+		exit(1);
+	}
+	if (!env[0]) {
+		fprintf(stderr,
+			"error: empty string value for %s is not allowed\n",
+			varname);
+		exit(1);
+	}
+	dir_name_len = strlen(env);
+	att_filename_buf = malloc(dir_name_len + 31);	/* Y10K extra margin */
+	if (!att_filename_buf) {
+		fprintf(stderr,
+		"error: unable to malloc buffer for attachment filenames\n");
+		exit(1);
+	}
+	strcpy(att_filename_buf, env);
+	cp = att_filename_buf + dir_name_len;
+	*cp++ = '/';
+	att_filename_base = cp;
+	time(&curtime);
+	tm = gmtime(&curtime);
+	sprintf(cp, "%u%02u%02ua", tm->tm_year + 1900, tm->tm_mon + 1,
+		tm->tm_mday);
+	att_filename_tail = index(cp, '\0');
+}
+
+main(argc, argv)
+	char **argv;
+{
+	char linebuf[LINE_BUF_SIZE], *cp;
+
+	if (argc > 3) {
+		fprintf(stderr, "usage: %s [infile [outfile]]\n", argv[0]);
+		exit(1);
+	}
+	get_attachment_dir();
+	if (argc > 1 && !freopen(argv[1], "r", stdin)) {
+		perror(argv[1]);
+		exit(1);
+	}
+	if (argc > 2 && !freopen(argv[2], "w", stdout)) {
+		perror(argv[2]);
+		exit(1);
+	}
+	for (input_lineno = 1; fgets(linebuf, sizeof linebuf, stdin);
+	     input_lineno++) {
+		cp = index(linebuf, '\n');
+		if (cp) {
+			*cp = '\0';
+			if (cp > linebuf && cp[-1] == '\r')
+				*--cp = '\0';
+		} else {
+			fprintf(stderr,
+		"f-demime warning: input line %u too long or unterminated\n",
+				input_lineno);
+		}
+		if (!strncmp(linebuf, "From ", 5)) {
+			finish_msg_body();
+			puts(linebuf);
+			begin_new_message();
+			continue;
+		}
+		message_input_line(linebuf);
+	}
+	finish_msg_body();
+	exit(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/msgstate.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,110 @@
+/*
+ * This module implements the message state machine, receiving input lines
+ * and message start/end markers from the input processing main loop.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "defs.h"
+
+enum msg_state msg_state;
+enum msg_hdr_state hdr_state;
+unsigned mp_nest_level;
+char mp_boundaries[MAX_MP_NESTING][MAX_MP_BOUNDARY+1];
+int mp_is_digest[MAX_MP_NESTING];
+char cont_type_buf[HDR_BUF_SIZE], cont_te_buf[HDR_BUF_SIZE];
+int got_cont_type, got_cont_te;
+
+void
+start_entity_hdr()
+{
+	msg_state = MSG_STATE_HEADER;
+	hdr_state = HDR_STATE_BEGIN;
+	got_cont_type = 0;
+	got_cont_te = 0;
+}
+
+void
+begin_new_message()
+{
+	mp_nest_level = 0;
+	start_entity_hdr();
+}
+
+static void
+emit_prefrom_warning()
+{
+	static int done;
+
+	if (done)
+		return;
+	fprintf(stderr,
+		"f-demime warning: data present before the first From\n");
+	done = 1;
+}
+
+static int
+check_mp_terminator(line)
+	char *line;
+{
+	unsigned lev, bndlen;
+	char *bnd, *cp;
+
+	if (line[0] != '-' || line[1] != '-')
+		return(0);
+	for (lev = 0; lev < mp_nest_level; lev++) {
+		bnd = mp_boundaries[lev];
+		bndlen = strlen(bnd);
+		if (strncmp(line+2, bnd, bndlen))
+			continue;
+		finish_msg_body();
+		puts(line);
+		cp = line + 2 + bndlen;
+		if (!*cp || isspace(*cp)) {
+			mp_nest_level = lev + 1;
+			start_entity_hdr();
+			return(1);
+		}
+		if (cp[0] != '-' || cp[1] != '-')
+			puts("X-Fdemime-Error: invalid delimiter line");
+		mp_nest_level = lev;
+		msg_state = MSG_STATE_BODY_PASS;
+		return(1);
+	}
+	return(0);
+}
+
+void
+message_input_line(line)
+	char *line;
+{
+	if (check_mp_terminator(line))
+		return;
+	switch (msg_state) {
+	case MSG_STATE_UNDEF:
+		emit_prefrom_warning();
+		puts(line);
+		return;
+	case MSG_STATE_HEADER:
+		header_input_line(line);
+		return;
+	case MSG_STATE_BODY_PASS:
+		puts(line);
+		return;
+	case MSG_STATE_PTEXT_B64:
+	case MSG_STATE_BLOB_B64:
+	case MSG_STATE_B64_TO_QP:
+		base64_input_line(line);
+		return;
+	case MSG_STATE_PTEXT_QP:
+		qpdec_input_line(line);
+		return;
+	default:
+		fprintf(stderr,
+		"f-demime internal error: bad state in message_input_line()\n");
+		abort();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/ptext_in.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,240 @@
+/*
+ * This module implements transformations that are specific to text/plain.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "defs.h"
+
+extern void (*dec_outf)();
+extern FILE *tempfile;
+extern int text_is_utf8;
+
+int ptext_has_backslash, ptext_has_linesplit;
+
+static enum {
+	FPS_GROUND,
+	FPS_CR,
+	FPS_UTF8
+} first_pass_state;
+static u_char utf8_buf[4];
+static unsigned utf8_nbytes, utf8_wptr, unicode;
+static unsigned out_line_len, trailing_newline;
+
+static void
+unit_output(str)
+	char *str;
+{
+	unsigned newlen;
+
+	newlen = strlen(str);
+	if (out_line_len + newlen >= OUTPUT_LINE_MAX)
+		putc('\\', tempfile);
+		putc('\n', tempfile);
+		out_line_len = 0;
+		ptext_has_linesplit = 1;
+	}
+	fputs(str, tempfile);
+	out_line_len += newlen;
+	trailing_newline = 0;
+}
+
+static void
+newline_out()
+{
+	putc('\n', tempfile);
+	out_line_len = 0;
+	trailing_newline++;
+	if (trailing_newline > 2)
+		trailing_newline = 2;
+}
+
+static void
+direct_output(ch)
+{
+	char buf[2];
+
+	buf[0] = ch;
+	buf[1] = '\0';
+	unit_output(buf);
+}
+
+static void
+simple_escape(ch)
+{
+	char buf[3];
+
+	buf[0] = '\\';
+	buf[1] = ch;
+	buf[2] = '\0';
+	unit_output(buf);
+}
+
+static void
+hex_escape(ch)
+{
+	char buf[5];
+
+	sprintf(buf, "\\x%02X", ch);
+	unit_output(buf);
+}
+
+static void
+regular_byte(ch)
+{
+	if (ch == '\\') {
+		ptext_has_backslash = 1;
+		simple_escape(ch);
+		return;
+	}
+	if (ch >= ' ' && ch <= '~') {
+		direct_output(ch);
+		return;
+	}
+	switch (ch) {
+	case 0x07:
+		simple_escape('a');
+		return;
+	case 0x08':
+		simple_escape('b');
+		return;
+	case 0x09:
+		direct_output(ch);
+		return;
+	case 0x0B:
+		simple_escape('v');
+		return;
+	case 0x0C:
+		simple_escape('f');
+		return;
+	case 0x0D:
+		simple_escape('r');
+		return;
+	case 0x1B:
+		simple_escape('e');
+		return;
+	}
+	hex_escape(ch);
+}
+
+static int
+utf8_collect()
+{
+	switch (utf8_nbytes) {
+	case 2:
+		unicode = ((utf8_buf[0] & 0x1F) << 6) | (utf8_buf[1] & 0x3F);
+		return(1);
+	case 3:
+		unicode = ((utf8_buf[0] & 0x0F) << 12) |
+			  ((utf8_buf[1] & 0x3F) << 6) | (utf8_buf[2] & 0x3F);
+		if (unicode & 0xF800)
+			return(1);
+		else
+			return(0);
+	case 4:
+		unicode = ((utf8_buf[0] & 0x07) << 18) |
+			  ((utf8_buf[1] & 0x3F) << 12) |
+			  ((utf8_buf[2] & 0x3F) << 6) | (utf8_buf[3] & 0x3F);
+		if (unicode & 0x1F0000)
+			return(1);
+		else
+			return(0);
+	default:
+		return(0);
+	}
+}
+
+static void
+unicode_out()
+{
+	char buf[9];
+
+	if (unicode >= 0x10000)
+		sprintf(buf, "\\U%06u", unicode);
+	else
+		sprintf(buf, "\\u%04u", unicode);
+	unit_output(buf);
+}
+
+static void
+flush_first_pass_state()
+{
+	unsigned n;
+
+	switch (first_pass_state) {
+	case FPS_CR:
+		regular_byte('\r');
+		break;
+	case FPS_UTF8:
+		for (n = 0; n < utf8_wptr; n++)
+			regular_byte(utf8_buf[n]);
+		break;
+	}
+	first_pass_state = FPS_GROUND;
+}
+
+static void
+first_pass(ch)
+{
+	if (first_pass_state == FPS_CR && ch == '\n') {
+		first_pass_state = FPS_GROUND;
+		newline_out();
+		return;
+	}
+	if (first_pass_state == FPS_UTF8 && ch >= 0x80 && ch <= 0xBF) {
+		utf8_buf[utf8_wptr++] = ch;
+		if (utf8_wptr < utf8_nbytes)
+			return;
+		if (utf8_collect()) {
+			first_pass_state = FPS_GROUND;
+			unicode_out();
+			return;
+		}
+	}
+	flush_first_pass_state();
+	switch (ch) {
+	case '\n':
+		newline_out();
+		return;
+	case '\r':
+		first_pass_state = FPS_CR;
+		return;
+	}
+	if (!text_is_utf8 || ch < 0xC2 || ch > 0xF7) {
+		regular_byte(ch);
+		return;
+	}
+	first_pass_state = FPS_UTF8;
+	utf8_buf[0] = ch;
+	utf8_wptr = 1;
+	if (ch < 0xE0)
+		utf8_nbytes = 2;
+	else if (ch < 0xF0)
+		utf8_nbytes = 3;
+	else
+		utf8_nbytes = 4;
+}
+
+void
+ptext_conv_init()
+{
+	dec_outf = first_pass;
+	ptext_has_backslash = 0;
+	ptext_has_linesplit = 0;
+	first_pass_state = FPS_GROUND;
+	out_line_len = 0;
+	trailing_newline = 1;
+}
+
+void
+ptext_conv_finish()
+{
+	flush_first_pass_state();
+	while (trailing_newline < 2) {
+		putc('\n', tempfile);
+		trailing_newline++;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/ptext_out.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,62 @@
+/*
+ * This module implements transformations that are specific to text/plain.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "defs.h"
+
+extern FILE *tempfile;
+extern int ptext_has_backslash, ptext_has_linesplit;
+extern unsigned mp_nest_level;
+extern char mp_boundaries[MAX_MP_NESTING][MAX_MP_BOUNDARY+1];
+
+void
+ptext_mark_transform(base_xform)
+	char *base_xform;
+{
+	printf("X-Fdemime-Transform: %s", base_xform);
+	if (ptext_has_backslash)
+		fputs(", double-backslash", stdout);
+	if (ptext_has_linesplit)
+		fputs(", line-split", stdout);
+	putchar('\n');
+}
+
+static int
+boundary_hit(line)
+	char *line;
+{
+	unsigned lev, bndlen;
+	char *bnd;
+
+	if (line[0] != '-' || line[1] != '-')
+		return(0);
+	for (lev = 0; lev < mp_nest_level; lev++) {
+		bnd = mp_boundaries[lev];
+		bndlen = strlen(bnd);
+		if (strncmp(line+2, bnd, bndlen))
+			continue;
+		return(1);
+	}
+	return(0);
+}
+
+void
+ptext_emit_output()
+{
+	char line[LINE_BUF_SIZE];
+
+	rewind(tempfile);
+	while (fgets(line, sizeof line, tempfile)) {
+		if (!strncmp(line, "From ", 5) || boundary_hit(line) ||
+		    !strcmp(line, ".\n")) {
+			putchar('\\');
+			putchar('&');
+		}
+		fputs(line, stdout);
+	}
+	fclose(tempfile);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/f-demime/qpdec.c	Sat May 06 06:14:03 2023 +0000
@@ -0,0 +1,76 @@
+/*
+ * This module implements quoted-printable decoding.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "defs.h"
+
+extern void (*dec_outf)();
+
+int qpdec_err_flag;
+
+static void
+strip_trailing_lwsp(line)
+	char *line;
+{
+	char *cp;
+
+	cp = index(line, '\0');
+	while (cp > line && isspace(cp[-1])
+		cp--;
+	*cp = '\0';
+}
+
+static int
+decode_hex_digit(c)
+{
+	if (isdigit(c))
+		return(c - '0');
+	else if (isupper(c))
+		return(c - 'A' + 10);
+	else
+		return(c - 'a' + 10);
+}
+
+static int
+decode_hex_byte(cp)
+	char *cp;
+{
+	int u, l;
+
+	u = decode_hex_digit(cp[0]);
+	l = decode_hex_digit(cp[1]);
+	return (u << 4) | l;
+}
+
+void
+qpdec_input_line(line)
+	char *line;
+{
+	char *cp;
+	int c;
+
+	strip_trailing_lwsp(line);
+	for (cp = line; *cp; ) {
+		c = *cp++ & 0xFF;
+		if (c != '=') {
+			dec_outf(c);
+			continue;
+		}
+		if (!*cp)
+			return;
+		if (isxdigit(cp[0]) && isxdigit(cp[1])) {
+			c = decode_hex_byte(cp);
+			cp += 2;
+			dec_outf(c);
+			continue;
+		}
+		qpdec_err_flag = 1;
+		dec_outf('=');
+	}
+	dec_outf('\n');
+}