1 /*
2  * Copyright (c) 2006-2011 Internet Initiative Japan Inc. All rights reserved.
3  *
4  * The terms and conditions of the accompanying program
5  * shall be provided separately by Internet Initiative Japan Inc.
6  * Any use, reproduction or distribution of the program are permitted
7  * provided that you agree to be bound to such terms and conditions.
8  *
9  * $Id: inetmailbox.c 1365 2011-10-16 08:08:36Z takahiko $
10  */
11 
12 #include "rcsid.h"
13 RCSID("$Id: inetmailbox.c 1365 2011-10-16 08:08:36Z takahiko $");
14 
15 #include <assert.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <strings.h>
19 #include <stdbool.h>
20 #include <sys/types.h>
21 
22 #include "ptrop.h"
23 #include "xskip.h"
24 #include "xparse.h"
25 #include "xbuffer.h"
26 #include "inetmailbox.h"
27 
28 struct InetMailbox {
29     char *localpart;
30     char *domain;
31     char buf[];
32 };
33 
34 /**
35  * create InetMailbox object
36  * @return initialized InetMailbox object, or NULL if memory allocation failed.
37  */
38 static InetMailbox *
InetMailbox_new(size_t buflen)39 InetMailbox_new(size_t buflen)
40 {
41     InetMailbox *self = (InetMailbox *) malloc(sizeof(InetMailbox) + buflen);
42     if (NULL == self) {
43         return NULL;
44     }   // end if
45     memset(self, 0, sizeof(InetMailbox));   // 0 で埋めるのは先頭部分だけで十分
46 
47     return self;
48 }   // end function: InetMailbox_new
49 
50 /**
51  * release InetMailbox object
52  * @param self InetMailbox object to release
53  */
54 void
InetMailbox_free(InetMailbox * self)55 InetMailbox_free(InetMailbox *self)
56 {
57     assert(NULL != self);
58     free(self);
59 }   // end function: InetMailbox_free
60 
61 const char *
InetMailbox_getLocalPart(const InetMailbox * self)62 InetMailbox_getLocalPart(const InetMailbox *self)
63 {
64     return self->localpart;
65 }   // end function: InetMailbox_getLocalPart
66 
67 const char *
InetMailbox_getDomain(const InetMailbox * self)68 InetMailbox_getDomain(const InetMailbox *self)
69 {
70     return self->domain;
71 }   // end function: InetMailbox_getDomain
72 
73 /*
74  * いわゆる "<>" かどうかを調べる.
75  */
76 bool
InetMailbox_isNullAddr(const InetMailbox * self)77 InetMailbox_isNullAddr(const InetMailbox *self)
78 {
79     return (NULL != self->localpart) && ('\0' == *(self->localpart))
80         && ('\0' == *(self->domain));
81 }   // end function: InetMailbox_isNullAddr
82 
83 /*
84  * @param errptr エラー情報を返す. メモリの確保に失敗した場合は NULL をセットする.
85  *               parse に失敗した場合は失敗した位置へのポインタを返す.
86  *
87  * addr-spec = local-part "@" domain
88  */
89 static InetMailbox *
InetMailbox_parse(const char * head,const char * tail,const char ** nextp,xparse_funcp xparse_localpart,bool require_localpart,xparse_funcp xparse_domain,bool require_domain,const char ** errptr)90 InetMailbox_parse(const char *head, const char *tail, const char **nextp,
91                   xparse_funcp xparse_localpart, bool require_localpart,
92                   xparse_funcp xparse_domain, bool require_domain, const char **errptr)
93 {
94     const char *p = head;
95 
96     XBuffer *xbuf = XBuffer_new(tail - head);
97     if (NULL == xbuf) {
98         SETDEREF(errptr, NULL);
99         goto cleanup;
100     }   // end if
101 
102     if (0 >= xparse_localpart(p, tail, &p, xbuf) && require_localpart) {
103         SETDEREF(errptr, p);
104         goto cleanup;
105     }   // end if
106 
107     if (0 != XBuffer_status(xbuf)) {
108         SETDEREF(errptr, NULL);
109         goto cleanup;
110     }   // end if
111 
112     size_t localpartlen = XBuffer_getSize(xbuf);
113     if (0 > XBuffer_appendChar(xbuf, '\0')) {   // local-part と domain の区切りの NULL 文字
114         SETDEREF(errptr, NULL);
115         goto cleanup;
116     }   // end if
117 
118     if (0 >= XSkip_char(p, tail, '@', &p)) {
119         SETDEREF(errptr, p);
120         goto cleanup;
121     }   // end if
122 
123     if (0 >= xparse_domain(p, tail, &p, xbuf) && require_domain) {
124         SETDEREF(errptr, p);
125         goto cleanup;
126     }   // end if
127 
128     if (0 != XBuffer_status(xbuf)) {
129         SETDEREF(errptr, NULL);
130         goto cleanup;
131     }   // end if
132 
133     size_t xbuflen = XBuffer_getSize(xbuf);
134     InetMailbox *self = InetMailbox_new(xbuflen + 1);   // 1 は NULL 文字の分
135     if (NULL == self) {
136         SETDEREF(errptr, NULL);
137         goto cleanup;
138     }   // end if
139 
140     memcpy(self->buf, XBuffer_getBytes(xbuf), xbuflen);
141     self->buf[xbuflen] = '\0';
142     self->localpart = self->buf;
143     self->domain = self->buf + localpartlen + 1;
144 
145     XBuffer_free(xbuf);
146     *nextp = p;
147     SETDEREF(errptr, NULL);
148     return self;
149 
150   cleanup:
151     if (NULL != xbuf) {
152         XBuffer_free(xbuf);
153     }   // end if
154     *nextp = head;
155     return NULL;
156 }   // end function: InetMailbox_parse
157 
158 /*
159  * [RFC2822]
160  * mailbox      = name-addr / addr-spec
161  * mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list
162  * name-addr    = [display-name] angle-addr
163  * angle-addr   = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
164  * display-name = phrase
165  * addr-spec    = local-part "@" domain
166  */
167 InetMailbox *
InetMailbox_build2822Mailbox(const char * head,const char * tail,const char ** nextp,const char ** errptr)168 InetMailbox_build2822Mailbox(const char *head, const char *tail, const char **nextp,
169                              const char **errptr)
170 {
171     bool guessNameaddr;         // mailbox = name-addr を想定している ('<' が見つかった) 場合に真
172 
173     // ABNF をまとめると
174     // mailbox = ([phrase] [CFWS] "<" addr-spec ">" [CFWS]) / addr-spec
175     // 判断基準は '<', '>' の存在だけ
176 
177     // display-name を捨てて addr-spec にたどり着くために
178     // name-addr にマッチするか調べる
179     const char *p = head;
180     XSkip_phrase(p, tail, &p);  // display-name の実体
181     XSkip_cfws(p, tail, &p);
182     if (0 < XSkip_char(p, tail, '<', &p)) {
183         // mailbox = name-addr
184         guessNameaddr = true;
185     } else {
186         // mailbox = addr-spec
187         p = head;
188         guessNameaddr = false;
189     }   // end if
190 
191     InetMailbox *self =
192         InetMailbox_parse(p, tail, &p, XParse_2822LocalPart, true, XParse_2822Domain, true, errptr);
193     if (NULL == self) {
194         goto cleanup;
195     }   // end if
196 
197     if (guessNameaddr) {
198         // mailbox = name-addr なのに '>' が存在しない
199         if (0 >= XSkip_char(p, tail, '>', &p)) {
200             SETDEREF(errptr, p);
201             goto cleanup;
202         }   // end if
203         XSkip_cfws(p, tail, &p);
204     }   // end if
205 
206     *nextp = p;
207     return self;
208 
209   cleanup:
210     if (NULL != self) {
211         InetMailbox_free(self);
212     }   // end if
213     *nextp = head;
214     return NULL;
215 }   // end function: InetMailbox_build2822Mailbox
216 
217 /*
218  * @attention source route は取り扱わない.
219  *
220  * [RFC2821]
221  * Mailbox = Local-part "@" Domain
222  * Local-part = Dot-string / Quoted-string
223  *       ; MAY be case-sensitive
224  */
225 InetMailbox *
InetMailbox_build2821Mailbox(const char * head,const char * tail,const char ** nextp,const char ** errptr)226 InetMailbox_build2821Mailbox(const char *head, const char *tail, const char **nextp,
227                              const char **errptr)
228 {
229     const char *p = head;
230     InetMailbox *self =
231         InetMailbox_parse(p, tail, &p, XParse_2821LocalPart, true, XParse_2821Domain, true, errptr);
232     if (NULL == self) {
233         *nextp = head;
234         return NULL;
235     }   // end if
236 
237     *nextp = p;
238     return self;
239 }   // end function: InetMailbox_build2821Mailbox
240 
241 /*
242  * @attention source route は取り扱わない.
243  *
244  * [RFC2821]
245  * Reverse-path = Path
246  * Forward-path = Path
247  * Path = "<" [ A-d-l ":" ] Mailbox ">"
248  * A-d-l = At-domain *( "," A-d-l )
249  *       ; Note that this form, the so-called "source route",
250  *       ; MUST BE accepted, SHOULD NOT be generated, and SHOULD be
251  *       ; ignored.
252  * At-domain = "@" domain
253  * Mailbox = Local-part "@" Domain
254  * Local-part = Dot-string / Quoted-string
255  *       ; MAY be case-sensitive
256  */
257 static InetMailbox *
InetMailbox_build2821PathImpl(const char * head,const char * tail,const char ** nextp,bool require_bracket,const char ** errptr)258 InetMailbox_build2821PathImpl(const char *head, const char *tail, const char **nextp,
259                               bool require_bracket, const char **errptr)
260 {
261     InetMailbox *self = NULL;
262     bool have_bracket = false;
263 
264     const char *p = head;
265     if (0 < XSkip_char(p, tail, '<', &p)) {
266         have_bracket = true;
267     } else {
268         if (require_bracket) {
269             SETDEREF(errptr, p);
270             goto cleanup;
271         }   // end if
272     }   // end if
273 
274     self =
275         InetMailbox_parse(p, tail, &p, XParse_2821LocalPart, true, XParse_2821Domain, true, errptr);
276     if (NULL == self) {
277         goto cleanup;
278     }   // end if
279 
280     if (have_bracket && 0 >= XSkip_char(p, tail, '>', &p)) {
281         // "<" で始まっているのに対応する ">" が見つからなかった場合
282         SETDEREF(errptr, p);
283         goto cleanup;
284     }   // end if
285 
286     *nextp = p;
287     return self;
288 
289   cleanup:
290     if (NULL != self) {
291         InetMailbox_free(self);
292     }   // end if
293     *nextp = head;
294     return NULL;
295 }   // end function: InetMailbox_build2821PathImpl
296 
297 InetMailbox *
InetMailbox_build2821Path(const char * head,const char * tail,const char ** nextp,const char ** errptr)298 InetMailbox_build2821Path(const char *head, const char *tail, const char **nextp,
299                           const char **errptr)
300 {
301     return InetMailbox_build2821PathImpl(head, tail, nextp, true, errptr);
302 }   // end function: InetMailbox_build2821Path
303 
304 /*
305  * sendmail の envelope from/rcpt に "<", ">" なしのメールアドレスを受け付ける実装に対応しつつ InetMailbox オブジェクトを構築する.
306  * "<>" は受け付けない.
307  */
308 InetMailbox *
InetMailbox_buildSendmailPath(const char * head,const char * tail,const char ** nextp,const char ** errptr)309 InetMailbox_buildSendmailPath(const char *head, const char *tail, const char **nextp,
310                               const char **errptr)
311 {
312     return InetMailbox_build2821PathImpl(head, tail, nextp, false, errptr);
313 }   // end function: InetMailbox_buildSendmailPath
314 
315 static InetMailbox *
InetMailbox_build2821ReversePathImpl(const char * head,const char * tail,const char ** nextp,bool require_bracket,const char ** errptr)316 InetMailbox_build2821ReversePathImpl(const char *head, const char *tail, const char **nextp,
317                                      bool require_bracket, const char **errptr)
318 {
319     if (0 < XSkip_string(head, tail, "<>", nextp)) {
320         // "<>" 用
321         SETDEREF(errptr, NULL);
322         return InetMailbox_build("", "");
323     }   // end if
324 
325     return InetMailbox_build2821PathImpl(head, tail, nextp, require_bracket, errptr);
326 }   // end function: InetMailbox_build2821ReversePathImpl
327 
328 /*
329  * @attention 厳密には Reverse-path には "<>" は含まれない
330  */
331 InetMailbox *
InetMailbox_build2821ReversePath(const char * head,const char * tail,const char ** nextp,const char ** errptr)332 InetMailbox_build2821ReversePath(const char *head, const char *tail, const char **nextp,
333                                  const char **errptr)
334 {
335     return InetMailbox_build2821ReversePathImpl(head, tail, nextp, true, errptr);
336 }   // end function: InetMailbox_build2821ReversePath
337 
338 /*
339  * sendmail の envelope from/rcpt に "<", ">" なしのメールアドレスを受け付ける実装に対応しつつ InetMailbox オブジェクトを構築する.
340  * "<>" を受け付ける.
341  */
342 InetMailbox *
InetMailbox_buildSendmailReversePath(const char * head,const char * tail,const char ** nextp,const char ** errptr)343 InetMailbox_buildSendmailReversePath(const char *head, const char *tail, const char **nextp,
344                                      const char **errptr)
345 {
346     return InetMailbox_build2821ReversePathImpl(head, tail, nextp, false, errptr);
347 }   // end function: InetMailbox_buildSendmailReversePath
348 
349 /*
350  * [RFC6376]
351  * sig-i-tag       = %x69 [FWS] "=" [FWS] [ Local-part ]
352  *                            "@" domain-name
353  */
354 InetMailbox *
InetMailbox_buildDkimIdentity(const char * head,const char * tail,const char ** nextp,const char ** errptr)355 InetMailbox_buildDkimIdentity(const char *head, const char *tail, const char **nextp,
356                               const char **errptr)
357 {
358     return InetMailbox_parse(head, tail, nextp, XParse_2821LocalPart, false, XParse_domainName,
359                              true, errptr);
360 }   // end function: InetMailbox_buildDkimIdentity
361 
362 
363 InetMailbox *
InetMailbox_buildWithLength(const char * localpart,size_t localpart_len,const char * domain,size_t domain_len)364 InetMailbox_buildWithLength(const char *localpart, size_t localpart_len, const char *domain,
365                             size_t domain_len)
366 {
367     assert(NULL != localpart);
368     assert(NULL != domain);
369 
370     InetMailbox *self = InetMailbox_new(localpart_len + domain_len + 2);
371     if (NULL == self) {
372         return NULL;
373     }   // end if
374 
375     memcpy(self->buf, localpart, localpart_len);
376     self->buf[localpart_len] = '\0';
377     memcpy(self->buf + localpart_len + 1, domain, domain_len);
378     self->buf[localpart_len + 1 + domain_len] = '\0';
379     self->localpart = self->buf;
380     self->domain = self->buf + localpart_len + 1;
381 
382     return self;
383 }   // end function: InetMailbox_build
384 
385 /**
386  * local-part と domain を指定して InetMailbox オブジェクトを構築する
387  * @param localpart local-part を指定する. NULL は許されない.
388  * @param domain domain を指定する. NULL は許されない.
389  * @return 構築した InetMailbox オブジェクト. 失敗した場合は NULL
390  */
391 InetMailbox *
InetMailbox_build(const char * localpart,const char * domain)392 InetMailbox_build(const char *localpart, const char *domain)
393 {
394     return InetMailbox_buildWithLength(localpart, strlen(localpart), domain, strlen(domain));
395 }   // end function: InetMailbox_build
396 
397 /**
398  * InetMailbox オブジェクトを複製する.
399  * @param mailbox 複製したい InetMailbox オブジェクト.
400  * @return 構築した InetMailbox オブジェクト. 失敗した場合は NULL.
401  */
402 InetMailbox *
InetMailbox_duplicate(const InetMailbox * mailbox)403 InetMailbox_duplicate(const InetMailbox *mailbox)
404 {
405     assert(NULL != mailbox);
406     return InetMailbox_build(mailbox->localpart, mailbox->domain);
407 }   // end function: InetMailbox_duplicate
408 
409 /*
410  * local-part + "@" + domain の長さを返す.
411  */
412 size_t
InetMailbox_getRawAddrLength(const InetMailbox * self)413 InetMailbox_getRawAddrLength(const InetMailbox *self)
414 {
415     assert(NULL != self);
416     return strlen(self->localpart) + strlen(self->domain) + 1;  // 1 は '@' の分
417 }   // end function: InetMailbox_getRawAddrLength
418 
419 /*
420  * @return 成功した場合は 0, エラーが発生した場合はエラーコード
421  */
422 int
InetMailbox_writeRawAddr(const InetMailbox * self,XBuffer * xbuf)423 InetMailbox_writeRawAddr(const InetMailbox *self, XBuffer *xbuf)
424 {
425     assert(NULL != self);
426     assert(NULL != xbuf);
427     XBuffer_appendString(xbuf, self->localpart);
428     XBuffer_appendChar(xbuf, '@');
429     XBuffer_appendString(xbuf, self->domain);
430     return XBuffer_status(xbuf);
431 }   // end function: InetMailbox_writeRawAddr
432 
433 /*
434  * localpart が dot-atom-text にマッチするかを調べる.
435  * マッチしない場合は, ヘッダに書き出す際には localpart を DQUOTE で括る必要がある.
436  */
437 bool
InetMailbox_isLocalPartQuoted(const InetMailbox * self)438 InetMailbox_isLocalPartQuoted(const InetMailbox *self)
439 {
440     assert(NULL != self);
441     assert(NULL != self->localpart);
442     const char *nextp = NULL;
443     const char *localparttail = STRTAIL(self->localpart);
444     XSkip_looseDotAtomText(self->localpart, localparttail, &nextp);
445     return nextp < localparttail;
446 }   // end function: InetMailbox_isLocalPartQuoted
447 
448 /*
449  * @attention "<>" は扱わない. "<>" も扱いたい場合は InetMailbox_writeMailbox() を使用のこと.
450  */
451 int
InetMailbox_writeAddrSpec(const InetMailbox * self,XBuffer * xbuf)452 InetMailbox_writeAddrSpec(const InetMailbox *self, XBuffer *xbuf)
453 // localpart に NULL, CR, LF は含まれないという前提
454 {
455     assert(NULL != self);
456     assert(NULL != xbuf);
457 
458     const char *localparttail = STRTAIL(self->localpart);
459     bool quoted = InetMailbox_isLocalPartQuoted(self);
460 
461     if (quoted) {
462         XBuffer_appendChar(xbuf, '"');
463     }   // end if
464 
465     for (const char *p = self->localpart; p < localparttail; ++p) {
466         switch (*p) {
467         case '\r':
468         case '\n':
469             // quoted-pair にもならない, そもそも含まれていてはならない
470             // abort();
471             break;
472 
473         case ' ':
474         case '"':
475         case '\\':
476         case '\t':
477             // text にはマッチするが qtext にはマッチしない文字を quote する
478             XBuffer_appendChar(xbuf, '\\');
479             break;
480 
481         default:
482             // do nothing
483             break;
484         }   // end switch
485         XBuffer_appendChar(xbuf, *p);
486     }   // end for
487 
488     if (quoted) {
489         XBuffer_appendChar(xbuf, '"');
490     }   // end if
491 
492     XBuffer_appendChar(xbuf, '@');
493     XBuffer_appendString(xbuf, self->domain);
494     return XBuffer_status(xbuf);
495 }   // end function: InetMailbox_writeAddrSpec
496 
497 int
InetMailbox_writeMailbox(const InetMailbox * self,XBuffer * xbuf)498 InetMailbox_writeMailbox(const InetMailbox *self, XBuffer *xbuf)
499 // localpart に NULL, CR, LF は含まれないという前提
500 {
501     if (InetMailbox_isNullAddr(self)) {
502         XBuffer_appendString(xbuf, "<>");
503         return XBuffer_status(xbuf);
504     } else {
505         return InetMailbox_writeAddrSpec(self, xbuf);
506     }   // end if
507 }   // end function: InetMailbox_writeMailbox
508