Line data Source code
1 : /* mbox-util.c - Mail address helper functions
2 : * Copyright (C) 1998-2010 Free Software Foundation, Inc.
3 : * Copyright (C) 1998-2015 Werner Koch
4 : *
5 : * This file is part of GnuPG.
6 : *
7 : * This file is free software; you can redistribute it and/or modify
8 : * it under the terms of the GNU Lesser General Public License as
9 : * published by the Free Software Foundation; either version 2.1 of
10 : * the License, or (at your option) any later version.
11 : *
12 : * This file is distributed in the hope that it will be useful,
13 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : * GNU Lesser General Public License for more details.
16 : *
17 : * You should have received a copy of the GNU Lesser General Public License
18 : * along with this program; if not, see <https://www.gnu.org/licenses/>.
19 : */
20 :
21 : /* NB: This code has been taken from GnuPG. Please keep it in sync
22 : * with GnuPG. */
23 :
24 : #if HAVE_CONFIG_H
25 : # include <config.h>
26 : #endif
27 :
28 : #include <stdio.h>
29 : #include <stdlib.h>
30 : #include <string.h>
31 : #include <unistd.h>
32 : #include <errno.h>
33 :
34 : #include "mbox-util.h"
35 :
36 : /* Lowercase all ASCII characters in STRING. */
37 : static char *
38 1254 : ascii_strlwr (char *string)
39 : {
40 : char *p;
41 :
42 23742 : for (p = string; *p; p++ )
43 22488 : if (!(*p & ~0x7f) && *p >= 'A' && *p <= 'Z')
44 0 : *p |= 0x20;
45 :
46 1254 : return string;
47 : }
48 :
49 :
50 : static int
51 1172 : string_count_chr (const char *string, int c)
52 : {
53 : int count;
54 :
55 21712 : for (count=0; *string; string++ )
56 20540 : if ( *string == c )
57 1172 : count++;
58 1172 : return count;
59 : }
60 :
61 : static int
62 83 : mem_count_chr (const void *buffer, int c, size_t length)
63 : {
64 83 : const char *s = buffer;
65 : int count;
66 :
67 2034 : for (count=0; length; length--, s++)
68 1951 : if (*s == c)
69 82 : count++;
70 83 : return count;
71 : }
72 :
73 :
74 : /* This is a case-sensitive version of our memistr. I wonder why no
75 : standard function memstr exists but I better do not use the name
76 : memstr to avoid future conflicts. */
77 : static const char *
78 82 : my_memstr (const void *buffer, size_t buflen, const char *sub)
79 : {
80 82 : const unsigned char *buf = buffer;
81 82 : const unsigned char *t = (const unsigned char *)buf;
82 82 : const unsigned char *s = (const unsigned char *)sub;
83 82 : size_t n = buflen;
84 :
85 2030 : for ( ; n ; t++, n-- )
86 : {
87 1948 : if (*t == *s)
88 : {
89 146 : for (buf = t++, buflen = n--, s++; n && *t ==*s; t++, s++, n--)
90 : ;
91 146 : if (!*s)
92 0 : return (const char*)buf;
93 146 : t = (const unsigned char *)buf;
94 146 : s = (const unsigned char *)sub ;
95 146 : n = buflen;
96 : }
97 : }
98 82 : return NULL;
99 : }
100 :
101 :
102 :
103 : static int
104 1172 : string_has_ctrl_or_space (const char *string)
105 : {
106 21712 : for (; *string; string++ )
107 20540 : if (!(*string & 0x80) && *string <= 0x20)
108 0 : return 1;
109 1172 : return 0;
110 : }
111 :
112 :
113 : /* Return true if STRING has two consecutive '.' after an '@'
114 : sign. */
115 : static int
116 1172 : has_dotdot_after_at (const char *string)
117 : {
118 1172 : string = strchr (string, '@');
119 1172 : if (!string)
120 0 : return 0; /* No at-sign. */
121 1172 : string++;
122 1172 : return !!strstr (string, "..");
123 : }
124 :
125 :
126 : /* Check whether BUFFER has characters not valid in an RFC-822
127 : address. LENGTH gives the length of BUFFER.
128 :
129 : To cope with OpenPGP we ignore non-ascii characters so that for
130 : example umlauts are legal in an email address. An OpenPGP user ID
131 : must be utf-8 encoded but there is no strict requirement for
132 : RFC-822. Thus to avoid IDNA encoding we put the address verbatim
133 : as utf-8 into the user ID under the assumption that mail programs
134 : handle IDNA at a lower level and take OpenPGP user IDs as utf-8.
135 : Note that we can't do an utf-8 encoding checking here because in
136 : keygen.c this function is called with the native encoding and
137 : native to utf-8 encoding is only done later. */
138 : static int
139 360 : has_invalid_email_chars (const void *buffer, size_t length)
140 : {
141 360 : const unsigned char *s = buffer;
142 360 : int at_seen=0;
143 360 : const char *valid_chars=
144 : "01234567890_-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
145 :
146 3718 : for ( ; length && *s; length--, s++ )
147 : {
148 3635 : if ((*s & 0x80))
149 0 : continue; /* We only care about ASCII. */
150 3635 : if (*s == '@')
151 82 : at_seen=1;
152 3836 : else if (!at_seen && !(strchr (valid_chars, *s)
153 283 : || strchr ("!#$%&'*+/=?^`{|}~", *s)))
154 277 : return 1;
155 3276 : else if (at_seen && !strchr (valid_chars, *s))
156 0 : return 1;
157 : }
158 83 : return 0;
159 : }
160 :
161 :
162 : /* Same as is_valid_mailbox (see below) but operates on non-nul
163 : terminated buffer. */
164 : static int
165 360 : is_valid_mailbox_mem (const void *name_arg, size_t namelen)
166 : {
167 360 : const char *name = name_arg;
168 :
169 442 : return !( !name
170 360 : || !namelen
171 360 : || has_invalid_email_chars (name, namelen)
172 83 : || mem_count_chr (name, '@', namelen) != 1
173 82 : || *name == '@'
174 82 : || name[namelen-1] == '@'
175 82 : || name[namelen-1] == '.'
176 82 : || my_memstr (name, namelen, ".."));
177 : }
178 :
179 :
180 : /* Check whether NAME represents a valid mailbox according to
181 : RFC822. Returns true if so. */
182 : int
183 360 : _gpgme_is_valid_mailbox (const char *name)
184 : {
185 360 : return name? is_valid_mailbox_mem (name, strlen (name)) : 0;
186 : }
187 :
188 :
189 : /* Return the mailbox (local-part@domain) form a standard user id.
190 : All plain ASCII characters in the result are converted to
191 : lowercase. Caller must free the result. Returns NULL if no valid
192 : mailbox was found (or we are out of memory). */
193 : char *
194 1532 : _gpgme_mailbox_from_userid (const char *userid)
195 : {
196 : const char *s, *s_end;
197 : size_t len;
198 1532 : char *result = NULL;
199 :
200 1532 : s = strchr (userid, '<');
201 1532 : if (s)
202 : {
203 : /* Seems to be a standard user id. */
204 1172 : s++;
205 1172 : s_end = strchr (s, '>');
206 1172 : if (s_end && s_end > s)
207 : {
208 1172 : len = s_end - s;
209 1172 : result = malloc (len + 1);
210 1172 : if (!result)
211 0 : return NULL; /* Ooops - out of core. */
212 1172 : strncpy (result, s, len);
213 1172 : result[len] = 0;
214 : /* Apply some basic checks on the address. We do not use
215 : is_valid_mailbox because those checks are too strict. */
216 2344 : if (string_count_chr (result, '@') != 1 /* Need exactly one '@. */
217 1172 : || *result == '@' /* local-part missing. */
218 1172 : || result[len-1] == '@' /* domain missing. */
219 1172 : || result[len-1] == '.' /* ends with a dot. */
220 1172 : || string_has_ctrl_or_space (result)
221 1172 : || has_dotdot_after_at (result))
222 : {
223 0 : free (result);
224 0 : result = NULL;
225 0 : errno = EINVAL;
226 : }
227 : }
228 : else
229 0 : errno = EINVAL;
230 : }
231 360 : else if (_gpgme_is_valid_mailbox (userid))
232 : {
233 : /* The entire user id is a mailbox. Return that one. Note that
234 : this fallback method has some restrictions on the valid
235 : syntax of the mailbox. However, those who want weird
236 : addresses should know about it and use the regular <...>
237 : syntax. */
238 82 : result = strdup (userid);
239 : }
240 : else
241 278 : errno = EINVAL;
242 :
243 1532 : return result? ascii_strlwr (result): NULL;
244 : }
245 :
246 :
247 : /* /\* Check whether UID is a valid standard user id of the form */
248 : /* "Heinrich Heine <heinrichh@duesseldorf.de>" */
249 : /* and return true if this is the case. *\/ */
250 : /* int */
251 : /* is_valid_user_id (const char *uid) */
252 : /* { */
253 : /* if (!uid || !*uid) */
254 : /* return 0; */
255 :
256 : /* return 1; */
257 : /* } */
258 :
259 :
260 : /*
261 : * Exported public API
262 : */
263 :
264 :
265 : /* Return the mail address ("addr-spec" as per RFC-5322) from a string
266 : * which is assumed to be an user id ("address" in RFC-5322). All
267 : * plain ASCII characters (those with bit 7 cleared) in the result
268 : * are converted to lowercase. Caller must free the result using
269 : * gpgme_free. Returns NULL if no valid address was found (in which
270 : * case ERRNO is set to EINVAL) or for other errors. */
271 : char *
272 0 : gpgme_addrspec_from_uid (const char *uid)
273 : {
274 0 : return _gpgme_mailbox_from_userid (uid);
275 : }
|