xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/quote_822_local.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
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 
is_822_dot_string(const char * local_part,const char * end,int flags)94 static 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 
make_822_quoted_string(VSTRING * dst,const char * local_part,const char * end,int flags)133 static 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 
quote_822_local_flags(VSTRING * dst,const char * mbox,int flags)156 VSTRING *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 
unquote_822_local(VSTRING * dst,const char * mbox)188 VSTRING *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 
main(int unused_argc,char ** argv)249 int     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