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