1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Implementation of net-pop3.h.
3 *@ TODO UIDL (as struct message.m_uid, *headline* %U), etc...
4 *@ TODO enum okay -> boole
5 *
6 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
7 * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
8 * SPDX-License-Identifier: BSD-4-Clause
9 */
10 /*
11 * Copyright (c) 2002
12 * Gunnar Ritter. All rights reserved.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 * 3. All advertising materials mentioning features or use of this software
23 * must display the following acknowledgement:
24 * This product includes software developed by Gunnar Ritter
25 * and his contributors.
26 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
27 * may be used to endorse or promote products derived from this software
28 * without specific prior written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
31 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
34 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * SUCH DAMAGE.
41 */
42 #undef su_FILE
43 #define su_FILE net_pop3
44 #define mx_SOURCE
45 #define mx_SOURCE_NET_POP3
46
47 #ifndef mx_HAVE_AMALGAMATION
48 # include "mx/nail.h"
49 #endif
50
51 su_EMPTY_FILE()
52 #ifdef mx_HAVE_POP3
53 #include <su/cs.h>
54 #include <su/icodec.h>
55 #include <su/mem.h>
56
57 #include "mx/cred-auth.h"
58 #include "mx/cred-md5.h"
59 #include "mx/file-streams.h"
60 #include "mx/net-socket.h"
61 #include "mx/sigs.h"
62
63 #ifdef mx_HAVE_GSSAPI
64 # include "mx/net-gssapi.h" /* $(MX_SRCDIR) */
65 #endif
66
67 #include "mx/net-pop3.h"
68 /* TODO fake */
69 #include "su/code-in.h"
70
71 struct a_pop3_ctx{
72 struct mx_socket *pc_sockp;
73 struct mx_cred_ctx pc_cred;
74 struct mx_url pc_url;
75 };
76
77 static struct str a_pop3_dat;
78 static char const *a_pop3_realdat;
79 static sigjmp_buf a_pop3_jmp;
80 static n_sighdl_t a_pop3_savealrm;
81 static s32 a_pop3_keepalive;
82 static int volatile a_pop3_lock;
83
84 /* Perform entire login handshake */
85 static enum okay a_pop3_login(struct mailbox *mp, struct a_pop3_ctx *pcp);
86
87 #ifdef mx_HAVE_MD5
88 /* APOP: get greeting credential or NIL... */
89 static char *a_pop3_lookup_apop_timestamp(char const *bp);
90
91 /* ...and authenticate */
92 static enum okay a_pop3_auth_apop(struct mailbox *mp,
93 struct a_pop3_ctx const *pcp, char const *ts);
94 #endif
95
96 /* Several (other) authentication methods */
97 static enum okay a_pop3_auth_plain(struct mailbox *mp,
98 struct a_pop3_ctx const *pcp);
99 static enum okay a_pop3_auth_oauthbearer(struct mailbox *mp,
100 struct a_pop3_ctx const *pcp);
101 static enum okay a_pop3_auth_external(struct mailbox *mp,
102 struct a_pop3_ctx const *pcp);
103
104 static void a_pop3_timer_off(void);
105 static enum okay a_pop3_answer(struct mailbox *mp);
106 static enum okay a_pop3_finish(struct mailbox *mp);
107 static void a_pop3_catch(int s);
108 static void a_pop3_maincatch(int s);
109 static enum okay a_pop3_noop1(struct mailbox *mp);
110 static void a_pop3alarm(int s);
111 static enum okay a_pop3_stat(struct mailbox *mp, off_t *size, int *cnt);
112 static enum okay a_pop3_list(struct mailbox *mp, int n, uz *size);
113 static void a_pop3_setptr(struct mailbox *mp, struct a_pop3_ctx const *pcp);
114 static enum okay a_pop3_get(struct mailbox *mp, struct message *m,
115 enum needspec need);
116 static enum okay a_pop3_exit(struct mailbox *mp);
117 static enum okay a_pop3_delete(struct mailbox *mp, int n);
118 static enum okay a_pop3_update(struct mailbox *mp);
119
120 #ifdef mx_HAVE_GSSAPI
121 # include <mx/net-gssapi.h>
122 #endif
123
124 /* Indirect POP3 I/O */
125 #define a_POP3_OUT(RV,X,Y,ACTIONSTOP) \
126 do{\
127 if(((RV) = a_pop3_finish(mp)) == STOP){\
128 ACTIONSTOP;\
129 }\
130 if(n_poption & n_PO_D_VV)\
131 n_err(">>> %s", X);\
132 mp->mb_active |= Y;\
133 if(((RV) = mx_socket_write(mp->mb_sock, X)) == STOP){\
134 ACTIONSTOP;\
135 }\
136 }while(0)
137
138 #define a_POP3_ANSWER(RV,ACTIONSTOP) \
139 do if(((RV) = a_pop3_answer(mp)) == STOP){\
140 ACTIONSTOP;\
141 }while(0)
142
143 static enum okay
a_pop3_login(struct mailbox * mp,struct a_pop3_ctx * pcp)144 a_pop3_login(struct mailbox *mp, struct a_pop3_ctx *pcp){
145 #ifdef mx_HAVE_MD5
146 char *ts;
147 #endif
148 enum okey_xlook_mode oxm;
149 enum okay rv;
150 NYD_IN;
151
152 oxm = (ok_vlook(v15_compat) != NIL) ? OXM_ALL : OXM_PLAIN | OXM_U_H_P;
153
154 /* Get the greeting, check whether APOP is advertised */
155 a_POP3_ANSWER(rv, goto jleave);
156 #ifdef mx_HAVE_MD5
157 ts = (pcp->pc_cred.cc_authtype == mx_CRED_AUTHTYPE_PLAIN)
158 ? a_pop3_lookup_apop_timestamp(a_pop3_realdat) : NIL;
159 #endif
160
161 /* If not yet secured, can we upgrade to TLS? */
162 #ifdef mx_HAVE_TLS
163 if(!(pcp->pc_url.url_flags & mx_URL_TLS_REQUIRED)){
164 if(xok_blook(pop3_use_starttls, &pcp->pc_url, oxm)){
165 a_POP3_OUT(rv, "STLS" NETNL, MB_COMD, goto jleave);
166 a_POP3_ANSWER(rv, goto jleave);
167 if(!n_tls_open(&pcp->pc_url, pcp->pc_sockp)){
168 rv = STOP;
169 goto jleave;
170 }
171 }else if(pcp->pc_cred.cc_needs_tls){
172 n_err(_("POP3 authentication %s needs TLS "
173 "(*pop3-use-starttls* set?)\n"),
174 pcp->pc_cred.cc_auth);
175 rv = STOP;
176 goto jleave;
177 }
178 }
179 #else
180 if(pcp->pc_cred.cc_needs_tls ||
181 xok_blook(pop3_use_starttls, &pcp->pc_url, oxm)){
182 n_err(_("No TLS support compiled in\n"));
183 rv = STOP;
184 goto jleave;
185 }
186 #endif
187
188 /* Use the APOP single roundtrip? */
189 #ifdef mx_HAVE_MD5
190 if(ts != NIL && !xok_blook(pop3_no_apop, &pcp->pc_url, oxm)){
191 if((rv = a_pop3_auth_apop(mp, pcp, ts)) != OKAY){
192 char const *ccp;
193
194 # ifdef mx_HAVE_TLS
195 if(pcp->pc_sockp->s_use_tls)
196 ccp = _("over a TLS encrypted connection");
197 else
198 # endif
199 ccp = _("(unfortunely without TLS!)");
200 n_err(_("POP3 APOP authentication failed!\n"
201 " Server announced support - please set *pop3-no-apop*,\n"
202 " it enforces plain authentication %s\n"), ccp);
203 }
204 goto jleave;
205 }
206 #endif
207
208 switch(pcp->pc_cred.cc_authtype){
209 case mx_CRED_AUTHTYPE_PLAIN:
210 rv = a_pop3_auth_plain(mp, pcp);
211 break;
212 case mx_CRED_AUTHTYPE_OAUTHBEARER:
213 rv = a_pop3_auth_oauthbearer(mp, pcp);
214 break;
215 case mx_CRED_AUTHTYPE_EXTERNAL:
216 case mx_CRED_AUTHTYPE_EXTERNANON:
217 rv = a_pop3_auth_external(mp, pcp);
218 break;
219 #ifdef mx_HAVE_GSSAPI
220 case mx_CRED_AUTHTYPE_GSSAPI:
221 if(n_poption & n_PO_D){
222 n_err(_(">>> We would perform GSS-API authentication now\n"));
223 rv = OKAY;
224 }else
225 rv = su_CONCAT(su_FILE,_gss)(mp->mb_sock, &pcp->pc_url, &pcp->pc_cred,
226 mp) ? OKAY : STOP;
227 break;
228 #endif
229 default:
230 rv = STOP;
231 break;
232 }
233
234 jleave:
235 NYD_OU;
236 return rv;
237 }
238
239 #ifdef mx_HAVE_MD5
240 static char *
a_pop3_lookup_apop_timestamp(char const * bp)241 a_pop3_lookup_apop_timestamp(char const *bp){
242 /* RFC 1939:
243 * A POP3 server which implements the APOP command will include
244 * a timestamp in its banner greeting. The syntax of the timestamp
245 * corresponds to the "msg-id" in [RFC822]
246 * RFC 822:
247 * msg-id = "<" addr-spec ">"
248 * addr-spec = local-part "@" domain */
249 char const *cp, *ep;
250 uz tl;
251 char *rp;
252 boole hadat;
253 NYD_IN;
254
255 hadat = FAL0;
256 rp = NIL;
257
258 if((cp = su_cs_find_c(bp, '<')) == NIL)
259 goto jleave;
260
261 /* xxx What about malformed APOP timestamp (<@>) here? */
262 for(ep = cp; *ep != '\0'; ++ep){
263 if(su_cs_is_space(*ep))
264 goto jleave;
265 else if(*ep == '@')
266 hadat = TRU1;
267 else if(*ep == '>'){
268 if(!hadat)
269 goto jleave;
270 break;
271 }
272 }
273 if(*ep != '>')
274 goto jleave;
275
276 tl = P2UZ(++ep - cp);
277 rp = n_autorec_alloc(tl +1);
278 su_mem_copy(rp, cp, tl);
279 rp[tl] = '\0';
280
281 jleave:
282 NYD_OU;
283 return rp;
284 }
285
286 static enum okay
a_pop3_auth_apop(struct mailbox * mp,struct a_pop3_ctx const * pcp,char const * ts)287 a_pop3_auth_apop(struct mailbox *mp, struct a_pop3_ctx const *pcp,
288 char const *ts){
289 unsigned char digest[mx_MD5_DIGEST_SIZE];
290 char hex[mx_MD5_TOHEX_SIZE], *cp;
291 mx_md5_t ctx;
292 uz i;
293 enum okay rv;
294 NYD_IN;
295
296 mx_md5_init(&ctx);
297 mx_md5_update(&ctx, S(uc*,UNCONST(char*,ts)), su_cs_len(ts));
298 mx_md5_update(&ctx, S(uc*,pcp->pc_cred.cc_pass.s), pcp->pc_cred.cc_pass.l);
299 mx_md5_final(digest, &ctx);
300 mx_md5_tohex(hex, digest);
301
302 rv = STOP;
303
304 i = pcp->pc_cred.cc_user.l;
305 cp = n_lofi_alloc(5 + i + 1 + mx_MD5_TOHEX_SIZE + sizeof(NETNL)-1 +1);
306
307 su_mem_copy(cp, "APOP ", 5);
308 su_mem_copy(&cp[5], pcp->pc_cred.cc_user.s, i);
309 i += 5;
310 cp[i++] = ' ';
311 su_mem_copy(&cp[i], hex, mx_MD5_TOHEX_SIZE);
312 i += mx_MD5_TOHEX_SIZE;
313 su_mem_copy(&cp[i], NETNL, sizeof(NETNL));
314 a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
315 a_POP3_ANSWER(rv, goto jleave);
316
317 rv = OKAY;
318 jleave:
319 n_lofi_free(cp);
320 NYD_OU;
321 return rv;
322 }
323 #endif /* mx_HAVE_MD5 */
324
325 static enum okay
a_pop3_auth_plain(struct mailbox * mp,struct a_pop3_ctx const * pcp)326 a_pop3_auth_plain(struct mailbox *mp, struct a_pop3_ctx const *pcp){
327 char *cp;
328 enum okay rv;
329 NYD_IN;
330
331 cp = n_lofi_alloc(MAX(pcp->pc_cred.cc_user.l, pcp->pc_cred.cc_pass.l) +
332 5 + sizeof(NETNL)-1 +1);
333
334 rv = STOP;
335
336 su_mem_copy(cp, "USER ", 5);
337 su_mem_copy(&cp[5], pcp->pc_cred.cc_user.s, pcp->pc_cred.cc_user.l);
338 su_mem_copy(&cp[5 + pcp->pc_cred.cc_user.l], NETNL, sizeof(NETNL));
339 a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
340 a_POP3_ANSWER(rv, goto jleave);
341
342 su_mem_copy(cp, "PASS ", 5);
343 su_mem_copy(&cp[5], pcp->pc_cred.cc_pass.s, pcp->pc_cred.cc_pass.l);
344 su_mem_copy(&cp[5 + pcp->pc_cred.cc_pass.l], NETNL, sizeof(NETNL));
345 a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
346 a_POP3_ANSWER(rv, goto jleave);
347
348 rv = OKAY;
349 jleave:
350 n_lofi_free(cp);
351 NYD_OU;
352 return rv;
353 }
354
355 static enum okay
a_pop3_auth_oauthbearer(struct mailbox * mp,struct a_pop3_ctx const * pcp)356 a_pop3_auth_oauthbearer(struct mailbox *mp, struct a_pop3_ctx const *pcp){
357 struct str b64;
358 int i;
359 uz cnt;
360 char *cp;
361 enum okay rv;
362 NYD_IN;
363
364 rv = STOP;
365 cp = NIL;
366
367 /* Calculate required storage */
368 cnt = pcp->pc_cred.cc_user.l;
369 #define a_MAX \
370 (2 + sizeof("AUTH XOAUTH2 " "user=\001auth=Bearer \001\001" NETNL))
371
372 if(pcp->pc_cred.cc_pass.l >= UZ_MAX - a_MAX ||
373 cnt >= UZ_MAX - a_MAX - pcp->pc_cred.cc_pass.l){
374 jerr_cred:
375 n_err(_("Credentials overflow buffer sizes\n"));
376 goto jleave;
377 }
378 cnt += pcp->pc_cred.cc_pass.l;
379
380 cnt += a_MAX;
381 #undef a_MAX
382 if((cnt = b64_encode_calc_size(cnt)) == UZ_MAX)
383 goto jerr_cred;
384
385 cp = n_lofi_alloc(cnt +1);
386
387 /* Then create login query */
388 i = snprintf(cp, cnt +1, "user=%s\001auth=Bearer %s\001\001",
389 pcp->pc_cred.cc_user.s, pcp->pc_cred.cc_pass.s);
390 if(b64_encode_buf(&b64, cp, i, B64_SALLOC) == NIL)
391 goto jleave;
392
393 cnt = sizeof("AUTH XOAUTH2 ") -1;
394 su_mem_copy(cp, "AUTH XOAUTH2 ", cnt);
395 su_mem_copy(&cp[cnt], b64.s, b64.l);
396 su_mem_copy(&cp[cnt += b64.l], NETNL, sizeof(NETNL));
397
398 a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
399 a_POP3_ANSWER(rv, goto jleave);
400
401 rv = OKAY;
402 jleave:
403 if(cp != NIL)
404 n_lofi_free(cp);
405 NYD_OU;
406 return rv;
407 }
408
409 static enum okay
a_pop3_auth_external(struct mailbox * mp,struct a_pop3_ctx const * pcp)410 a_pop3_auth_external(struct mailbox *mp, struct a_pop3_ctx const *pcp){
411 struct str s;
412 char *cp;
413 uz cnt;
414 enum okay rv;
415 NYD_IN;
416
417 rv = STOP;
418
419 /* Calculate required storage */
420 #define a_MAX \
421 (sizeof("AUTH EXTERNAL ") -1 + sizeof(NETNL) -1 +1)
422
423 cnt = 0;
424 if(pcp->pc_cred.cc_authtype != mx_CRED_AUTHTYPE_EXTERNANON){
425 cnt = pcp->pc_cred.cc_user.l;
426 cnt = b64_encode_calc_size(cnt);
427 }
428 if(cnt >= UZ_MAX - a_MAX){
429 n_err(_("Credentials overflow buffer sizes\n"));
430 goto j_leave;
431 }
432 cnt += a_MAX;
433 #undef a_MAX
434
435 cp = n_lofi_alloc(cnt +1);
436
437 su_mem_copy(cp, NETLINE("AUTH EXTERNAL"),
438 sizeof(NETLINE("AUTH EXTERNAL")));
439 a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
440 a_POP3_ANSWER(rv, goto jleave);
441
442 cnt = 0;
443 if(pcp->pc_cred.cc_authtype != mx_CRED_AUTHTYPE_EXTERNANON){
444 s.s = cp;
445 b64_encode_buf(&s, pcp->pc_cred.cc_user.s, pcp->pc_cred.cc_user.l,
446 B64_BUF);
447 cnt = s.l;
448 }
449 su_mem_copy(&cp[cnt], NETNL, sizeof(NETNL));
450 a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
451 a_POP3_ANSWER(rv, goto jleave);
452
453 rv = OKAY;
454 jleave:
455 n_lofi_free(cp);
456 j_leave:
457 NYD_OU;
458 return rv;
459 }
460
461 static void
a_pop3_timer_off(void)462 a_pop3_timer_off(void){
463 NYD_IN;
464 if(a_pop3_keepalive > 0){
465 n_pstate &= ~n_PS_SIGALARM;
466 alarm(0);
467 safe_signal(SIGALRM, a_pop3_savealrm);
468 }
469 NYD_OU;
470 }
471
472 static enum okay
a_pop3_answer(struct mailbox * mp)473 a_pop3_answer(struct mailbox *mp){
474 int i;
475 uz blen;
476 enum okay rv;
477 NYD_IN;
478
479 rv = STOP;
480 jretry:
481 if((i = mx_socket_getline(&a_pop3_dat.s, &a_pop3_dat.l, &blen, mp->mb_sock)
482 ) > 0){
483 if((mp->mb_active & (MB_COMD | MB_MULT)) == MB_MULT)
484 goto jmultiline;
485
486 while(blen > 0 && (a_pop3_dat.s[blen - 1] == NETNL[0] ||
487 a_pop3_dat.s[blen - 1] == NETNL[1]))
488 a_pop3_dat.s[--blen] = '\0';
489
490 if(n_poption & n_PO_D_VV)
491 n_err(">>> SERVER: %s\n", a_pop3_dat.s);
492
493 switch(*a_pop3_dat.s){
494 case '+':
495 if(blen == 1)
496 a_pop3_realdat = su_empty;
497 else{
498 for(a_pop3_realdat = a_pop3_dat.s;
499 *a_pop3_realdat != '\0' && !su_cs_is_space(*a_pop3_realdat);
500 ++a_pop3_realdat)
501 ;
502 while(*a_pop3_realdat != '\0' && su_cs_is_space(*a_pop3_realdat))
503 ++a_pop3_realdat;
504 }
505 rv = OKAY;
506 mp->mb_active &= ~MB_COMD;
507 break;
508 case '-':
509 rv = STOP;
510 mp->mb_active = MB_NONE;
511 n_err(_("POP3 error: %s\n"), a_pop3_dat.s);
512 break;
513 default:
514 /* If the answer starts neither with '+' nor with '-', it must be part
515 * of a multiline response. Get lines until a single dot appears */
516 jmultiline:
517 while(a_pop3_dat.s[0] != '.' || a_pop3_dat.s[1] != NETNL[0] ||
518 a_pop3_dat.s[2] != NETNL[1] || a_pop3_dat.s[3] != '\0'){
519 i = mx_socket_getline(&a_pop3_dat.s, &a_pop3_dat.l, NIL,
520 mp->mb_sock);
521 if(i <= 0)
522 goto jeof;
523 }
524 mp->mb_active &= ~MB_MULT;
525 if(mp->mb_active != MB_NONE)
526 goto jretry;
527 }
528 }else{
529 jeof:
530 rv = STOP;
531 mp->mb_active = MB_NONE;
532 }
533
534 NYD_OU;
535 return rv;
536 }
537
538 static enum okay
a_pop3_finish(struct mailbox * mp)539 a_pop3_finish(struct mailbox *mp){
540 NYD_IN;
541 while(mp->mb_sock->s_fd > 0 && mp->mb_active != MB_NONE)
542 a_pop3_answer(mp);
543 NYD_OU;
544 return OKAY; /* XXX ? */
545 }
546
547 static void
a_pop3_catch(int s)548 a_pop3_catch(int s){
549 switch(s){
550 case SIGINT:
551 /*n_err_sighdl(_("Interrupt during POP3 operation\n"));*/
552 interrupts = 2; /* Force "Interrupt" message shall we onintr(0) */
553 siglongjmp(a_pop3_jmp, 1);
554 case SIGPIPE:
555 n_err_sighdl(_("Received SIGPIPE during POP3 operation\n"));
556 break;
557 }
558 }
559
560 static void
a_pop3_maincatch(int s)561 a_pop3_maincatch(int s){
562 UNUSED(s);
563 if(interrupts == 0)
564 n_err_sighdl(_("\n(Interrupt -- one more to abort operation)\n"));
565 else{
566 interrupts = 1;
567 siglongjmp(a_pop3_jmp, 1);
568 }
569 }
570
571 static enum okay
a_pop3_noop1(struct mailbox * mp)572 a_pop3_noop1(struct mailbox *mp){
573 enum okay rv;
574 NYD_IN;
575
576 a_POP3_OUT(rv, "NOOP" NETNL, MB_COMD, goto jleave);
577 a_POP3_ANSWER(rv, goto jleave);
578 jleave:
579 NYD_OU;
580 return rv;
581 }
582
583 static void
a_pop3alarm(int s)584 a_pop3alarm(int s){
585 n_sighdl_t volatile saveint, savepipe;
586 UNUSED(s);
587
588 if(a_pop3_lock++ == 0){
589 mx_sigs_all_holdx();
590 if((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
591 safe_signal(SIGINT, &a_pop3_maincatch);
592 savepipe = safe_signal(SIGPIPE, SIG_IGN);
593 if(sigsetjmp(a_pop3_jmp, 1)){
594 interrupts = 0;
595 safe_signal(SIGINT, saveint);
596 safe_signal(SIGPIPE, savepipe);
597 goto jbrk;
598 }
599 if(savepipe != SIG_IGN)
600 safe_signal(SIGPIPE, &a_pop3_catch);
601 mx_sigs_all_rele();
602
603 if(a_pop3_noop1(&mb) != OKAY){
604 safe_signal(SIGINT, saveint);
605 safe_signal(SIGPIPE, savepipe);
606 goto jleave;
607 }
608 safe_signal(SIGINT, saveint);
609 safe_signal(SIGPIPE, savepipe);
610 }
611 jbrk:
612 n_pstate |= n_PS_SIGALARM;
613 alarm(a_pop3_keepalive);
614 jleave:
615 --a_pop3_lock;
616 }
617
618 static enum okay
a_pop3_stat(struct mailbox * mp,off_t * size,int * cnt)619 a_pop3_stat(struct mailbox *mp, off_t *size, int *cnt){
620 char const *cp;
621 enum okay rv;
622 NYD_IN;
623
624 a_POP3_OUT(rv, "STAT" NETNL, MB_COMD, goto jleave);
625 a_POP3_ANSWER(rv, goto jleave);
626
627 rv = STOP;
628 cp = a_pop3_realdat;
629
630 if(*cp != '\0'){
631 uz i;
632
633 if(su_idec_uz_cp(&i, cp, 10, &cp) & su_IDEC_STATE_EMASK)
634 goto jerr;
635 if(i > INT_MAX)
636 goto jerr;
637 *cnt = S(int,i);
638
639 while(*cp != '\0' && !su_cs_is_space(*cp))
640 ++cp;
641 while(*cp != '\0' && su_cs_is_space(*cp))
642 ++cp;
643
644 if(*cp == '\0')
645 goto jerr;
646 if(su_idec_uz_cp(&i, cp, 10, NIL) & su_IDEC_STATE_EMASK)
647 goto jerr;
648 *size = S(off_t,i);
649 rv = OKAY;
650 }
651
652 if(rv == STOP)
653 jerr:
654 n_err(_("Invalid POP3 STAT response: %s\n"), a_pop3_dat.s);
655 jleave:
656 NYD_OU;
657 return rv;
658 }
659
660 static enum okay
a_pop3_list(struct mailbox * mp,int n,uz * size)661 a_pop3_list(struct mailbox *mp, int n, uz *size){
662 char o[LINESIZE];
663 char const *cp;
664 enum okay rv;
665 NYD_IN;
666
667 snprintf(o, sizeof o, "LIST %d" NETNL, n);
668 a_POP3_OUT(rv, o, MB_COMD, goto jleave);
669 a_POP3_ANSWER(rv, goto jleave);
670
671 cp = a_pop3_realdat;
672 while(*cp != '\0' && !su_cs_is_space(*cp))
673 ++cp;
674 while(*cp != '\0' && su_cs_is_space(*cp))
675 ++cp;
676 if(*cp != '\0')
677 su_idec_uz_cp(size, cp, 10, NIL);
678
679 jleave:
680 NYD_OU;
681 return rv;
682 }
683
684 static void
a_pop3_setptr(struct mailbox * mp,struct a_pop3_ctx const * pcp)685 a_pop3_setptr(struct mailbox *mp, struct a_pop3_ctx const *pcp){
686 uz i;
687 enum needspec ns;
688 NYD_IN;
689
690 message = n_calloc(msgCount + 1, sizeof *message);
691 message[msgCount].m_size = 0;
692 message[msgCount].m_lines = 0;
693 dot = message; /* (Just do it: avoid crash -- shall i now do ointr(0).. */
694
695 for(i = 0; UCMP(z, i, <, msgCount); ++i){
696 struct message *m;
697
698 m = &message[i];
699 m->m_flag = MUSED | MNEW | MNOFROM | MNEWEST;
700 m->m_block = 0;
701 m->m_offset = 0;
702 m->m_size = m->m_xsize = 0;
703 }
704
705 for(i = 0; UCMP(z, i, <, msgCount); ++i)
706 if(!a_pop3_list(mp, i + 1, &message[i].m_xsize))
707 goto jleave;
708
709 /* Force the load of all messages right now */
710 ns = xok_blook(pop3_bulk_load, &pcp->pc_url, OXM_ALL)
711 ? NEED_BODY : NEED_HEADER;
712 for(i = 0; UCMP(z, i, <, msgCount); ++i)
713 if(!a_pop3_get(mp, message + i, ns))
714 goto jleave;
715
716 n_autorec_relax_create();
717 for(i = 0; UCMP(z, i, <, msgCount); ++i){
718 char const *cp;
719 struct message *m;
720
721 m = &message[i];
722
723 if((cp = hfield1("status", m)) != NIL)
724 while(*cp != '\0'){
725 if(*cp == 'R')
726 m->m_flag |= MREAD;
727 else if(*cp == 'O')
728 m->m_flag &= ~MNEW;
729 ++cp;
730 }
731
732 substdate(m);
733 n_autorec_relax_unroll();
734 }
735 n_autorec_relax_gut();
736
737 setdot(message);
738 jleave:
739 NYD_OU;
740 }
741
742 static enum okay
a_pop3_get(struct mailbox * mp,struct message * m,enum needspec volatile need)743 a_pop3_get(struct mailbox *mp, struct message *m, enum needspec volatile need){
744 char o[LINESIZE], *line, *lp;
745 n_sighdl_t volatile saveint, savepipe;
746 uz linesize, linelen, size;
747 int number, lines;
748 int volatile emptyline;
749 off_t offset;
750 enum okay volatile rv;
751 NYD_IN;
752
753 mx_fs_linepool_aquire(&line, &linesize);
754 saveint = savepipe = SIG_IGN;
755 number = S(int,P2UZ(m - message + 1));
756 emptyline = 0;
757 rv = STOP;
758
759 if(mp->mb_sock == NIL || mp->mb_sock->s_fd < 0){
760 n_err(_("POP3 connection already closed\n"));
761 ++a_pop3_lock;
762 goto jleave;
763 }
764
765 if(a_pop3_lock++ == 0){
766 mx_sigs_all_holdx();
767 if((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
768 safe_signal(SIGINT, &a_pop3_maincatch);
769 savepipe = safe_signal(SIGPIPE, SIG_IGN);
770 if(sigsetjmp(a_pop3_jmp, 1))
771 goto jleave;
772 if(savepipe != SIG_IGN)
773 safe_signal(SIGPIPE, &a_pop3_catch);
774 mx_sigs_all_rele();
775 }
776
777 fseek(mp->mb_otf, 0L, SEEK_END);
778 offset = ftell(mp->mb_otf);
779 jretry:
780 switch(need){
781 case NEED_HEADER:
782 snprintf(o, sizeof o, "TOP %d 0" NETNL, number);
783 break;
784 case NEED_BODY:
785 snprintf(o, sizeof o, "RETR %d" NETNL, number);
786 break;
787 case NEED_UNSPEC:
788 n_panic("net-pop3.c bug\n");
789 }
790 a_POP3_OUT(rv, o, MB_COMD | MB_MULT, goto jleave);
791
792 if(a_pop3_answer(mp) == STOP){
793 if(need == NEED_HEADER){
794 /* The TOP POP3 command is optional, so retry with entire message */
795 need = NEED_BODY;
796 goto jretry;
797 }
798 goto jleave;
799 }
800
801 size = 0;
802 lines = 0;
803 while(mx_socket_getline(&line, &linesize, &linelen, mp->mb_sock) > 0){
804 while(linelen > 0 && (line[linelen - 1] == NETNL[0] ||
805 line[linelen - 1] == NETNL[1]))
806 line[--linelen] = '\0';
807
808 if(n_poption & n_PO_D_VVV)
809 n_err(">>> SERVER: %s\n", line);
810
811 if(line[0] == '.'){
812 if(*(lp = &line[1]) == '\0'){
813 mp->mb_active &= ~MB_MULT;
814 break;
815 }
816 --linelen;
817 }else
818 lp = line;
819
820 /* TODO >>
821 * Need to mask 'From ' lines. This cannot be done properly
822 * since some servers pass them as 'From ' and others as
823 * '>From '. Although one could identify the first kind of
824 * server in principle, it is not possible to identify the
825 * second as '>From ' may also come from a server of the
826 * first type as actual data. So do what is absolutely
827 * necessary only - mask 'From '.
828 *
829 * If the line is the first line of the message header, it
830 * is likely a real 'From ' line. In this case, it is just
831 * ignored since it violates all standards.
832 * TODO i have *never* seen the latter?!?!?
833 * TODO <<
834 */
835 /* TODO Since we simply copy over data without doing any transfer
836 * TODO encoding reclassification/adjustment we *have* to perform
837 * TODO RFC 4155 compliant From_ quoting here TODO REALLY NOT! */
838 if(emptyline && is_head(lp, linelen, FAL0)){
839 putc('>', mp->mb_otf);
840 ++size;
841 }
842 if(!(emptyline = (linelen == 0)))
843 fwrite(lp, 1, linelen, mp->mb_otf);
844 putc('\n', mp->mb_otf);
845 size += ++linelen;
846 ++lines;
847 }
848
849 if(!emptyline){
850 /* TODO This is very ugly; but some POP3 daemons don't end a
851 * TODO message with NETNL NETNL, and we need \n\n for mbox format.
852 * TODO That is to say we do it wrong here in order to get it right
853 * TODO when send.c stuff or with MBOX handling, even though THIS
854 * TODO line is solely a property of the MBOX database format! */
855 putc('\n', mp->mb_otf);
856 ++size;
857 ++lines;
858 }
859
860 fflush(mp->mb_otf);
861
862 m->m_size = size;
863 m->m_lines = lines;
864 m->m_block = mailx_blockof(offset);
865 m->m_offset = mailx_offsetof(offset);
866
867 switch(need){
868 case NEED_HEADER:
869 m->m_content_info |= CI_HAVE_HEADER;
870 break;
871 case NEED_BODY:
872 m->m_content_info |= CI_HAVE_HEADER | CI_HAVE_BODY;
873 m->m_xlines = m->m_lines;
874 m->m_xsize = m->m_size;
875 break;
876 case NEED_UNSPEC:
877 break;
878 }
879
880 rv = OKAY;
881 jleave:
882 mx_fs_linepool_release(line, linesize);
883 if(saveint != SIG_IGN)
884 safe_signal(SIGINT, saveint);
885 if(savepipe != SIG_IGN)
886 safe_signal(SIGPIPE, savepipe);
887 --a_pop3_lock;
888 NYD_OU;
889 if(interrupts)
890 n_raise(SIGINT);
891 return rv;
892 }
893
894 static enum okay
a_pop3_exit(struct mailbox * mp)895 a_pop3_exit(struct mailbox *mp){
896 enum okay rv;
897 NYD_IN;
898
899 a_POP3_OUT(rv, "QUIT" NETNL, MB_COMD, goto jleave);
900 a_POP3_ANSWER(rv, goto jleave);
901 jleave:
902 NYD_OU;
903 return rv;
904 }
905
906 static enum okay
a_pop3_delete(struct mailbox * mp,int n)907 a_pop3_delete(struct mailbox *mp, int n){
908 char o[LINESIZE];
909 enum okay rv;
910 NYD_IN;
911
912 snprintf(o, sizeof o, "DELE %d" NETNL, n);
913 a_POP3_OUT(rv, o, MB_COMD, goto jleave);
914 a_POP3_ANSWER(rv, goto jleave);
915 jleave:
916 NYD_OU;
917 return rv;
918 }
919
920 static enum okay
a_pop3_update(struct mailbox * mp)921 a_pop3_update(struct mailbox *mp){
922 struct message *m;
923 int dodel, c, gotcha, held;
924 NYD_IN;
925
926 if(!(n_pstate & n_PS_EDIT)){
927 holdbits();
928 c = 0;
929 for(m = message; PCMP(m, <, &message[msgCount]); ++m)
930 if(m->m_flag & MBOX)
931 ++c;
932 if(c > 0)
933 makembox();
934 }
935
936 gotcha = held = 0;
937 for(m = message; PCMP(m, <, message + msgCount); ++m){
938 if(n_pstate & n_PS_EDIT)
939 dodel = m->m_flag & MDELETED;
940 else
941 dodel = !((m->m_flag & MPRESERVE) || !(m->m_flag & MTOUCH));
942 if(dodel){
943 a_pop3_delete(mp, P2UZ(m - message + 1));
944 ++gotcha;
945 }else
946 ++held;
947 }
948
949 /* C99 */{
950 char const *dnq;
951
952 dnq = n_shexp_quote_cp(displayname, FAL0);
953
954 if(gotcha && (n_pstate & n_PS_EDIT)){
955 fprintf(n_stdout, _("%s "), dnq);
956 fprintf(n_stdout, (ok_blook(bsdcompat) || ok_blook(bsdmsgs))
957 ? _("complete\n") : _("updated\n"));
958 }else if(held && !(n_pstate & n_PS_EDIT)){
959 if(held == 1)
960 fprintf(n_stdout, _("Held 1 message in %s\n"), dnq);
961 else
962 fprintf(n_stdout, _("Held %d messages in %s\n"), held, dnq);
963 }
964 }
965 fflush(n_stdout);
966
967 NYD_OU;
968 return OKAY;
969 }
970
971 #ifdef mx_HAVE_GSSAPI
972 # include <mx/net-gssapi.h>
973 #endif
974
975 #undef a_POP3_OUT
976 #undef a_POP3_ANSWER
977
978 enum okay
mx_pop3_noop(void)979 mx_pop3_noop(void){
980 n_sighdl_t volatile saveint, savepipe;
981 enum okay volatile rv;
982 NYD_IN;
983
984 rv = STOP;
985 a_pop3_lock = 1;
986
987 mx_sigs_all_holdx();
988 if((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
989 safe_signal(SIGINT, &a_pop3_maincatch);
990 savepipe = safe_signal(SIGPIPE, SIG_IGN);
991 if(sigsetjmp(a_pop3_jmp, 1) == 0){
992 if(savepipe != SIG_IGN)
993 safe_signal(SIGPIPE, &a_pop3_catch);
994 mx_sigs_all_rele();
995 rv = a_pop3_noop1(&mb);
996 }
997 safe_signal(SIGINT, saveint);
998 safe_signal(SIGPIPE, savepipe);
999
1000 a_pop3_lock = 0;
1001 NYD_OU;
1002 return rv;
1003 }
1004
1005 int
mx_pop3_setfile(char const * who,char const * server,enum fedit_mode fm)1006 mx_pop3_setfile(char const *who, char const *server, enum fedit_mode fm){
1007 struct a_pop3_ctx pc;
1008 n_sighdl_t saveint, savepipe;
1009 char const *cp;
1010 int volatile rv;
1011 NYD_IN;
1012
1013 rv = 1;
1014 if(fm & FEDIT_NEWMAIL)
1015 goto jleave;
1016 rv = -1;
1017
1018 if(!mx_url_parse(&pc.pc_url, CPROTO_POP3, server))
1019 goto jleave;
1020 if(ok_vlook(v15_compat) == NIL && pc.pc_url.url_pass.s != NIL){
1021 n_err(_("POP3: new-style URL used without *v15-compat* being set: %s\n"),
1022 n_shexp_quote_cp(server, FAL0));
1023 goto jleave;
1024 }
1025
1026 if(!((ok_vlook(v15_compat) != NIL)
1027 ? mx_cred_auth_lookup(&pc.pc_cred, &pc.pc_url)
1028 : mx_cred_auth_lookup_old(&pc.pc_cred, CPROTO_POP3,
1029 ((pc.pc_url.url_flags & mx_URL_HAD_USER)
1030 ? pc.pc_url.url_eu_h_p.s
1031 : pc.pc_url.url_u_h_p.s))))
1032 goto jleave;
1033
1034 if(!quit(FAL0))
1035 goto jleave;
1036
1037 pc.pc_sockp = su_TALLOC(struct mx_socket, 1);
1038 if(!mx_socket_open(pc.pc_sockp, &pc.pc_url)){
1039 su_FREE(pc.pc_sockp);
1040 goto jleave;
1041 }
1042
1043 rv = 1;
1044
1045 if(fm & FEDIT_SYSBOX)
1046 n_pstate &= ~n_PS_EDIT;
1047 else
1048 n_pstate |= n_PS_EDIT;
1049
1050 if(mb.mb_sock != NIL){
1051 if(mb.mb_sock->s_fd >= 0)
1052 mx_socket_close(mb.mb_sock);
1053 su_FREE(mb.mb_sock);
1054 mb.mb_sock = NIL;
1055 }
1056
1057 if(mb.mb_itf != NIL){
1058 fclose(mb.mb_itf);
1059 mb.mb_itf = NIL;
1060 }
1061 if(mb.mb_otf != NIL){
1062 fclose(mb.mb_otf);
1063 mb.mb_otf = NIL;
1064 }
1065
1066 initbox(pc.pc_url.url_p_u_h_p);
1067 mb.mb_type = MB_VOID;
1068 a_pop3_lock = 1;
1069
1070 saveint = safe_signal(SIGINT, SIG_IGN);
1071 savepipe = safe_signal(SIGPIPE, SIG_IGN);
1072 if(sigsetjmp(a_pop3_jmp, 1)){
1073 mb.mb_sock = NIL;
1074 mx_socket_close(pc.pc_sockp);
1075 su_FREE(pc.pc_sockp);
1076 n_err(_("POP3 connection closed\n"));
1077 safe_signal(SIGINT, saveint);
1078 safe_signal(SIGPIPE, savepipe);
1079
1080 a_pop3_lock = 0;
1081 rv = -1;
1082 if(interrupts > 0)
1083 n_raise(SIGINT);
1084 goto jleave;
1085 }
1086 if(saveint != SIG_IGN)
1087 safe_signal(SIGINT, &a_pop3_catch);
1088 if(savepipe != SIG_IGN)
1089 safe_signal(SIGPIPE, &a_pop3_catch);
1090
1091 if((cp = xok_vlook(pop3_keepalive, &pc.pc_url, OXM_ALL)) != NIL){
1092 su_idec_s32_cp(&a_pop3_keepalive, cp, 10, NIL);
1093 if(a_pop3_keepalive > 0){ /* Is a "positive number" */
1094 n_pstate |= n_PS_SIGALARM;
1095 a_pop3_savealrm = safe_signal(SIGALRM, a_pop3alarm);
1096 alarm(a_pop3_keepalive);
1097 }
1098 }
1099
1100 pc.pc_sockp->s_desc = (pc.pc_url.url_flags & mx_URL_TLS_REQUIRED)
1101 ? "POP3S" : "POP3";
1102 pc.pc_sockp->s_onclose = &a_pop3_timer_off;
1103 mb.mb_sock = pc.pc_sockp;
1104
1105 if(a_pop3_login(&mb, &pc) != OKAY ||
1106 a_pop3_stat(&mb, &mailsize, &msgCount) != OKAY){
1107 mb.mb_sock = NIL;
1108 mx_socket_close(pc.pc_sockp);
1109 su_FREE(pc.pc_sockp);
1110 a_pop3_timer_off();
1111
1112 safe_signal(SIGINT, saveint);
1113 safe_signal(SIGPIPE, savepipe);
1114 a_pop3_lock = 0;
1115 goto jleave;
1116 }
1117
1118 setmsize(msgCount);
1119 mb.mb_type = MB_POP3;
1120 mb.mb_perm = ((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY))
1121 ? 0 : MB_DELE;
1122 a_pop3_setptr(&mb, &pc);
1123
1124 /*if (!(fm & FEDIT_NEWMAIL)) */{
1125 n_pstate &= ~n_PS_SAW_COMMAND;
1126 n_pstate |= n_PS_SETFILE_OPENED;
1127 }
1128
1129 safe_signal(SIGINT, saveint);
1130 safe_signal(SIGPIPE, savepipe);
1131 a_pop3_lock = 0;
1132
1133 if((n_poption & (n_PO_EXISTONLY | n_PO_HEADERLIST)) == n_PO_EXISTONLY){
1134 rv = (msgCount == 0);
1135 goto jleave;
1136 }
1137
1138 if(!(n_pstate & n_PS_EDIT) && msgCount == 0){
1139 if(!ok_blook(emptystart))
1140 n_err(_("No mail for %s at %s\n"), who, pc.pc_url.url_p_eu_h_p);
1141 goto jleave;
1142 }
1143
1144 rv = 0;
1145 jleave:
1146 NYD_OU;
1147 return rv;
1148 }
1149
1150 enum okay
mx_pop3_header(struct message * m)1151 mx_pop3_header(struct message *m){
1152 enum okay rv;
1153 NYD_IN;
1154
1155 /* TODO no URL here, no OXM possible; (however it is used in setfile()..) */
1156 rv = a_pop3_get(&mb, m,
1157 (ok_blook(pop3_bulk_load) ? NEED_BODY : NEED_HEADER));
1158 NYD_OU;
1159 return rv;
1160 }
1161
1162 enum okay
mx_pop3_body(struct message * m)1163 mx_pop3_body(struct message *m){
1164 enum okay rv;
1165 NYD_IN;
1166
1167 rv = a_pop3_get(&mb, m, NEED_BODY);
1168 NYD_OU;
1169 return rv;
1170 }
1171
1172 boole
mx_pop3_quit(boole hold_sigs_on)1173 mx_pop3_quit(boole hold_sigs_on){
1174 n_sighdl_t volatile saveint, savepipe;
1175 boole rv;
1176 NYD_IN;
1177
1178 if(hold_sigs_on)
1179 rele_sigs();
1180
1181 rv = FAL0;
1182
1183 if(mb.mb_sock == NIL || mb.mb_sock->s_fd < 0){
1184 n_err(_("POP3 connection already closed\n"));
1185 rv = TRU1;
1186 goto jleave;
1187 }
1188
1189 a_pop3_lock = 1;
1190 saveint = safe_signal(SIGINT, SIG_IGN);
1191 savepipe = safe_signal(SIGPIPE, SIG_IGN);
1192 if(sigsetjmp(a_pop3_jmp, 1)){
1193 safe_signal(SIGINT, saveint);
1194 safe_signal(SIGPIPE, savepipe);
1195 a_pop3_lock = 0;
1196 interrupts = 0;
1197 goto jleave;
1198 }
1199 if(saveint != SIG_IGN)
1200 safe_signal(SIGINT, &a_pop3_catch);
1201 if(savepipe != SIG_IGN)
1202 safe_signal(SIGPIPE, &a_pop3_catch);
1203
1204 a_pop3_update(&mb);
1205 a_pop3_exit(&mb);
1206 mx_socket_close(mb.mb_sock);
1207 su_FREE(mb.mb_sock);
1208 mb.mb_sock = NIL;
1209
1210 safe_signal(SIGINT, saveint);
1211 safe_signal(SIGPIPE, savepipe);
1212 a_pop3_lock = 0;
1213
1214 rv = TRU1;
1215 jleave:
1216 if(hold_sigs_on)
1217 hold_sigs();
1218 NYD_OU;
1219 return rv;
1220 }
1221
1222 #include "su/code-ou.h"
1223 #endif /* mx_HAVE_POP3 */
1224 #undef mx_SOURCE_NET_POP3
1225 #undef su_FILE
1226 /* s-it-mode */
1227