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