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