1/*	$NetBSD: quote_822_local.c,v 1.3 2022/10/08 16:12:45 christos Exp $	*/
2
3/*++
4/* NAME
5/*	quote_822_local 3
6/* SUMMARY
7/*	quote local part of mailbox
8/* SYNOPSIS
9/*	#include <quote_822_local.h>
10/*
11/*	VSTRING	*quote_822_local(dst, src)
12/*	VSTRING	*dst;
13/*	const char *src;
14/*
15/*	VSTRING	*quote_822_local_flags(dst, src, flags)
16/*	VSTRING	*dst;
17/*	const char *src;
18/*	int	flags;
19/*
20/*	VSTRING	*unquote_822_local(dst, src)
21/*	VSTRING	*dst;
22/*	const char *src;
23/* DESCRIPTION
24/*	quote_822_local() quotes the local part of a mailbox and
25/*	returns a result that can be used in message headers as
26/*	specified by RFC 822 (actually, an 8-bit clean version of
27/*	RFC 822). It implements an 8-bit clean version of RFC 822.
28/*
29/*	quote_822_local_flags() provides finer control.
30/*
31/*	unquote_822_local() transforms the local part of a mailbox
32/*	address to unquoted (internal) form.
33/*
34/*	Arguments:
35/* .IP dst
36/*	The result.
37/* .IP src
38/*	The input address.
39/* .IP flags
40/*	Bit-wise OR of zero or more of the following.
41/* .RS
42/* .IP QUOTE_FLAG_8BITCLEAN
43/*	In violation with RFCs, treat 8-bit text as ordinary text.
44/* .IP QUOTE_FLAG_EXPOSE_AT
45/*	In violation with RFCs, treat `@' as an ordinary character.
46/* .IP QUOTE_FLAG_APPEND
47/*	Append to the result buffer, instead of overwriting it.
48/* .IP QUOTE_FLAG_BARE_LOCALPART
49/*	The input is a localpart without @domain part.
50/* .RE
51/* STANDARDS
52/*	RFC 822 (ARPA Internet Text Messages)
53/* BUGS
54/*	The code assumes that the domain is RFC 822 clean.
55/* LICENSE
56/* .ad
57/* .fi
58/*	The Secure Mailer license must be distributed with this software.
59/* AUTHOR(S)
60/*	Wietse Venema
61/*	IBM T.J. Watson Research
62/*	P.O. Box 704
63/*	Yorktown Heights, NY 10598, USA
64/*
65/*	Wietse Venema
66/*	Google, Inc.
67/*	111 8th Avenue
68/*	New York, NY 10011, USA
69/*--*/
70
71/* System library. */
72
73#include <sys_defs.h>
74#include <string.h>
75#include <ctype.h>
76
77/* Utility library. */
78
79#include <vstring.h>
80
81/* Global library. */
82
83/* Application-specific. */
84
85#include "quote_822_local.h"
86
87/* Local stuff. */
88
89#define YES	1
90#define	NO	0
91
92/* is_822_dot_string - is this local-part an rfc 822 dot-string? */
93
94static int is_822_dot_string(const char *local_part, const char *end, int flags)
95{
96    const char *cp;
97    int     ch;
98
99    /*
100     * Detect any deviations from a sequence of atoms separated by dots. We
101     * could use lookup tables to speed up some of the work, but hey, how
102     * large can a local-part be anyway?
103     *
104     * RFC 822 expects 7-bit data. Rather than quoting every 8-bit character
105     * (and still passing it on as 8-bit data) we leave 8-bit data alone.
106     */
107    if (local_part == end || local_part[0] == 0 || local_part[0] == '.')
108	return (NO);
109    for (cp = local_part; cp < end && (ch = *(unsigned char *) cp) != 0; cp++) {
110	if (ch == '.' && (cp + 1) < end && cp[1] == '.')
111	    return (NO);
112	if (ch > 127 && !(flags & QUOTE_FLAG_8BITCLEAN))
113	    return (NO);
114	if (ch == ' ')
115	    return (NO);
116	if (ISCNTRL(ch))
117	    return (NO);
118	if (ch == '(' || ch == ')'
119	    || ch == '<' || ch == '>'
120	    || (ch == '@' && !(flags & QUOTE_FLAG_EXPOSE_AT)) || ch == ','
121	    || ch == ';' || ch == ':'
122	    || ch == '\\' || ch == '"'
123	    || ch == '[' || ch == ']')
124	    return (NO);
125    }
126    if (cp[-1] == '.')
127	return (NO);
128    return (YES);
129}
130
131/* make_822_quoted_string - make quoted-string from local-part */
132
133static VSTRING *make_822_quoted_string(VSTRING *dst, const char *local_part,
134				               const char *end, int flags)
135{
136    const char *cp;
137    int     ch;
138
139    /*
140     * Put quotes around the result, and prepend a backslash to characters
141     * that need quoting when they occur in a quoted-string.
142     */
143    VSTRING_ADDCH(dst, '"');
144    for (cp = local_part; cp < end && (ch = *(unsigned char *) cp) != 0; cp++) {
145	if ((ch > 127 && !(flags & QUOTE_FLAG_8BITCLEAN))
146	    || ch == '"' || ch == '\\' || ch == '\r')
147	    VSTRING_ADDCH(dst, '\\');
148	VSTRING_ADDCH(dst, ch);
149    }
150    VSTRING_ADDCH(dst, '"');
151    return (dst);
152}
153
154/* quote_822_local_flags - quote local part of mailbox according to rfc 822 */
155
156VSTRING *quote_822_local_flags(VSTRING *dst, const char *mbox, int flags)
157{
158    const char *start;			/* first byte of localpart */
159    const char *end;			/* first byte after localpart */
160    const char *colon;
161
162    /*
163     * According to RFC 822, a local-part is a dot-string or a quoted-string.
164     * We first see if the local-part is a dot-string. If it is not, we turn
165     * it into a quoted-string. Anything else would be too painful. But
166     * first, skip over any source route that precedes the local-part.
167     */
168    if (mbox[0] == '@' && (colon = strchr(mbox, ':')) != 0)
169	start = colon + 1;
170    else
171	start = mbox;
172    if ((flags & QUOTE_FLAG_BARE_LOCALPART) != 0
173	|| (end = strrchr(start, '@')) == 0)
174	end = start + strlen(start);
175    if ((flags & QUOTE_FLAG_APPEND) == 0)
176	VSTRING_RESET(dst);
177    if (is_822_dot_string(start, end, flags)) {
178	return (vstring_strcat(dst, mbox));
179    } else {
180	vstring_strncat(dst, mbox, start - mbox);
181	make_822_quoted_string(dst, start, end, flags & QUOTE_FLAG_8BITCLEAN);
182	return (vstring_strcat(dst, end));
183    }
184}
185
186/* unquote_822_local - unquote local part of mailbox according to rfc 822 */
187
188VSTRING *unquote_822_local(VSTRING *dst, const char *mbox)
189{
190    const char *start;			/* first byte of localpart */
191    const char *colon;
192    const char *cp;
193    int     in_quote = 0;
194    const char *bare_at_src;
195    int     bare_at_dst_pos = -1;
196
197    /* Don't unquote a routing prefix. Is this still possible? */
198    if (mbox[0] == '@' && (colon = strchr(mbox, ':')) != 0) {
199	start = colon + 1;
200	vstring_strncpy(dst, mbox, start - mbox);
201    } else {
202	start = mbox;
203	VSTRING_RESET(dst);
204    }
205    /* Locate the last unquoted '@'. */
206    for (cp = start; *cp; cp++) {
207	if (*cp == '"') {
208	    in_quote = !in_quote;
209	    continue;
210	} else if (*cp == '@') {
211	    if (!in_quote) {
212		bare_at_dst_pos = VSTRING_LEN(dst);
213		bare_at_src = cp;
214	    }
215	} else if (*cp == '\\') {
216	    if (cp[1] == 0)
217		continue;
218	    cp++;
219	}
220	VSTRING_ADDCH(dst, *cp);
221    }
222    /* Don't unquote text after the last unquoted '@'. */
223    if (bare_at_dst_pos >= 0) {
224	vstring_truncate(dst, bare_at_dst_pos);
225	vstring_strcat(dst, bare_at_src);
226    } else
227	VSTRING_TERMINATE(dst);
228    return (dst);
229}
230
231#ifdef TEST
232
233 /*
234  * Proof-of-concept test program. Read an unquoted address from stdin, and
235  * show the quoted and unquoted results. Specify <> to test behavior for an
236  * empty unquoted address.
237  */
238#include <ctype.h>
239#include <string.h>
240
241#include <msg.h>
242#include <name_mask.h>
243#include <stringops.h>
244#include <vstream.h>
245#include <vstring_vstream.h>
246
247#define STR	vstring_str
248
249int     main(int unused_argc, char **argv)
250{
251    VSTRING *in = vstring_alloc(100);
252    VSTRING *out = vstring_alloc(100);
253    char   *cmd;
254    char   *bp;
255    int     flags;
256
257    while (vstring_fgets_nonl(in, VSTREAM_IN)) {
258	bp = STR(in);
259	if ((cmd = mystrtok(&bp, CHARS_SPACE)) != 0) {
260	    while (ISSPACE(*bp))
261		bp++;
262	    if (*bp == 0) {
263		msg_warn("missing argument");
264		continue;
265	    }
266	    if (strcmp(bp, "<>") == 0)
267		bp = "";
268	    if (strcmp(cmd, "quote") == 0) {
269		quote_822_local(out, bp);
270		vstream_printf("'%s' quoted '%s'\n", bp, STR(out));
271	    } else if (strcmp(cmd, "quote_with_flags") == 0) {
272		if ((cmd = mystrtok(&bp, CHARS_SPACE)) == 0) {
273		    msg_warn("missing flags");
274		    continue;
275		}
276		while (ISSPACE(*bp))
277		    bp++;
278		flags = quote_flags_from_string(cmd);
279		quote_822_local_flags(out, bp, flags);
280		vstream_printf("'%s' quoted flags=%s '%s'\n",
281			       bp, quote_flags_to_string((VSTRING *) 0, flags), STR(out));
282	    } else if (strcmp(cmd, "unquote") == 0) {
283		unquote_822_local(out, bp);
284		vstream_printf("'%s' unquoted '%s'\n", bp, STR(out));
285	    } else {
286		msg_warn("unknown command: %s", cmd);
287	    }
288	    vstream_fflush(VSTREAM_OUT);
289	}
290    }
291    vstring_free(in);
292    vstring_free(out);
293    return (0);
294}
295
296#endif
297