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