1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2  *@ Folder (mailbox) initialization, newmail announcement and related.
3  *
4  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5  * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6  * SPDX-License-Identifier: BSD-3-Clause
7  */
8 /*
9  * Copyright (c) 1980, 1993
10  *      The Regents of the University of California.  All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 #undef su_FILE
37 #define su_FILE folder
38 #define mx_SOURCE
39 
40 #ifndef mx_HAVE_AMALGAMATION
41 # include "mx/nail.h"
42 #endif
43 
44 #include <pwd.h>
45 
46 #include <su/cs.h>
47 #include <su/mem.h>
48 
49 #include "mx/cmd-shortcut.h"
50 #include "mx/dig-msg.h"
51 #include "mx/file-locks.h"
52 #include "mx/file-streams.h"
53 #include "mx/net-pop3.h"
54 #include "mx/net-socket.h"
55 #include "mx/sigs.h"
56 #include "mx/ui-str.h"
57 
58 /* TODO fake */
59 #include "su/code-in.h"
60 
61 /* Update mailname (if name != NIL) and displayname, return whether displayname
62  * was large enough to swallow mailname */
63 static boole  _update_mailname(char const *name);
64 #ifdef mx_HAVE_C90AMEND1 /* TODO unite __narrow_suffix() into one fun! */
65 su_SINLINE uz __narrow_suffix(char const *cp, uz cpl, uz maxl);
66 #endif
67 
68 /**/
69 static void a_folder_info(void);
70 
71 /* Set up the input pointers while copying the mail file into /tmp */
72 static void a_folder_mbox_setptr(FILE *ibuf, off_t offset, boole iseml);
73 
74 #ifdef mx_HAVE_C90AMEND1
75 su_SINLINE uz
__narrow_suffix(char const * cp,uz cpl,uz maxl)76 __narrow_suffix(char const *cp, uz cpl, uz maxl)
77 {
78    int err;
79    uz i, ok;
80    NYD_IN;
81 
82    for (err = ok = i = 0; cpl > maxl || err;) {
83       int ml = mblen(cp, cpl);
84       if (ml < 0) { /* XXX _narrow_suffix(): mblen() error; action? */
85          (void)mblen(NULL, 0);
86          err = 1;
87          ml = 1;
88       } else {
89          if (!err)
90             ok = i;
91          err = 0;
92          if (ml == 0)
93             break;
94       }
95       cp += ml;
96       i += ml;
97       cpl -= ml;
98    }
99    NYD_OU;
100    return ok;
101 }
102 #endif /* mx_HAVE_C90AMEND1 */
103 
104 static boole
_update_mailname(char const * name)105 _update_mailname(char const *name) /* TODO 2MUCH work, cache, prop of Obj! */
106 {
107    char const *foldp;
108    char *mailp, *dispp;
109    uz i, j, foldlen;
110    boole rv;
111    NYD_IN;
112 
113    /* Do not realpath(3) if it's only an update request */
114    if(name != NIL){
115 #ifdef mx_HAVE_REALPATH
116       char const *adjname;
117       enum protocol p;
118 
119       p = which_protocol(name, TRU1, TRU1, &adjname);
120 
121       if(p == n_PROTO_FILE || p == n_PROTO_MAILDIR || p == n_PROTO_EML){
122          name = adjname;
123          if(realpath(name, mailname) == NIL && su_err_no() != su_ERR_NOENT){
124             n_err(_("Cannot canonicalize %s\n"), n_shexp_quote_cp(name, FAL0));
125             goto jdocopy;
126          }
127       }else
128 jdocopy:
129 #endif
130          su_cs_pcopy_n(mailname, name, sizeof(mailname));
131    }
132 
133    mailp = mailname;
134    dispp = displayname;
135 
136    /* Don't display an absolute path but "+FOLDER" if under *folder* */
137    if(*(foldp = n_folder_query()) != '\0'){
138       foldlen = su_cs_len(foldp);
139       if(strncmp(foldp, mailp, foldlen))
140          foldlen = 0;
141    }else
142       foldlen = 0;
143 
144    /* We want to see the name of the folder .. on the screen */
145    i = su_cs_len(mailp);
146    if(i < sizeof(displayname) - 3 -1){
147       if(foldlen > 0){
148          *dispp++ = '+';
149          *dispp++ = '[';
150          su_mem_copy(dispp, mailp, foldlen);
151          dispp += foldlen;
152          mailp += foldlen;
153          *dispp++ = ']';
154          su_mem_copy(dispp, mailp, i -= foldlen);
155          dispp[i] = '\0';
156       }else
157          su_mem_copy(dispp, mailp, i +1);
158       rv = TRU1;
159    }else{
160       /* Avoid disrupting multibyte sequences (if possible) */
161 #ifndef mx_HAVE_C90AMEND1
162       j = sizeof(displayname) / 3 - 3;
163       i -= sizeof(displayname) - (1/* + */ + 3) - j;
164 #else
165       j = field_detect_clip(sizeof(displayname) / 3, mailp, i);
166       i = j + __narrow_suffix(mailp + j, i - j,
167          sizeof(displayname) - (1/* + */ + 3 + 1) - j);
168 #endif
169       snprintf(dispp, sizeof(displayname), "%s%.*s...%s",
170          (foldlen > 0 ? "[+]" : ""), (int)j, mailp, mailp + i);
171       rv = FAL0;
172    }
173 
174    n_PS_ROOT_BLOCK((ok_vset(mailbox_resolved, mailname),
175       ok_vset(mailbox_display, displayname)));
176    NYD_OU;
177    return rv;
178 }
179 
180 static void
a_folder_info(void)181 a_folder_info(void){
182    struct message *mp;
183    int u, n, d, s, hidden, moved;
184    NYD2_IN;
185 
186    if(mb.mb_type == MB_VOID){
187       fprintf(n_stdout, _("(Currently no active mailbox)"));
188       goto jleave;
189    }
190 
191    s = d = hidden = moved = 0;
192    for (mp = message, n = 0, u = 0; PCMP(mp, <, message + msgCount); ++mp) {
193       if (mp->m_flag & MNEW)
194          ++n;
195       if ((mp->m_flag & MREAD) == 0)
196          ++u;
197       if ((mp->m_flag & (MDELETED | MSAVED)) == (MDELETED | MSAVED))
198          ++moved;
199       if ((mp->m_flag & (MDELETED | MSAVED)) == MDELETED)
200          ++d;
201       if ((mp->m_flag & (MDELETED | MSAVED)) == MSAVED)
202          ++s;
203       if (mp->m_flag & MHIDDEN)
204          ++hidden;
205    }
206 
207    /* If displayname gets truncated the user effectively has no option to see
208     * the full pathname of the mailbox, so print it at least for '? fi' */
209    fprintf(n_stdout, "%s: ", n_shexp_quote_cp(
210       (_update_mailname(NULL) ? displayname : mailname), FAL0));
211    if (msgCount == 1)
212       fprintf(n_stdout, _("1 message"));
213    else
214       fprintf(n_stdout, _("%d messages"), msgCount);
215    if (n > 0)
216       fprintf(n_stdout, _(" %d new"), n);
217    if (u-n > 0)
218       fprintf(n_stdout, _(" %d unread"), u);
219    if (d > 0)
220       fprintf(n_stdout, _(" %d deleted"), d);
221    if (s > 0)
222       fprintf(n_stdout, _(" %d saved"), s);
223    if (moved > 0)
224       fprintf(n_stdout, _(" %d moved"), moved);
225    if (hidden > 0)
226       fprintf(n_stdout, _(" %d hidden"), hidden);
227    if (mb.mb_perm == 0)
228       fprintf(n_stdout, _(" [Read-only]"));
229 #ifdef mx_HAVE_IMAP
230    if (mb.mb_type == MB_CACHE)
231       fprintf(n_stdout, _(" [Disconnected]"));
232 #endif
233 
234 jleave:
235    putc('\n', n_stdout);
236    NYD2_OU;
237 }
238 
239 static void
a_folder_mbox_setptr(FILE * ibuf,off_t offset,boole iseml)240 a_folder_mbox_setptr(FILE *ibuf, off_t offset, boole iseml){
241    enum{
242       a_RFC4155 = 1u<<0,
243       a_HAD_BAD_FROM_ = 1u<<1,
244       a_HADONE = 1u<<2,
245       a_MAYBE = 1u<<3,
246       a_CREATE = 1u<<4,
247       a_INHEAD = 1u<<5,
248       a_COMMIT = 1u<<6,
249       a_ISEML = 1u<<16
250    };
251 
252    struct message self, commit;
253    char *linebuf, *cp;
254    boole from_;
255    u32 f;
256    uz filesize, linesize, cnt;
257    NYD_IN;
258 
259    filesize = mailsize - offset;
260 
261    su_mem_set(&self, 0, sizeof self);
262    self.m_flag = MUSED | MNEW | MNEWEST;
263 
264    offset = ftell(mb.mb_otf);
265    f = a_MAYBE | (iseml ? a_ISEML : (ok_blook(mbox_rfc4155) ? a_RFC4155 : 0));
266 
267    mx_fs_linepool_aquire(&linebuf, &linesize);
268    for(;;){
269       /* Ensure space for terminating LF, so do append it */
270       if(UNLIKELY(fgetline(&linebuf, &linesize, &filesize, &cnt, ibuf, TRU1
271             ) == NIL)){
272          /* TODO We are not prepared for error here */
273          if(f & a_HADONE){
274             if(f & a_CREATE){
275                commit.m_size += self.m_size;
276                commit.m_lines += self.m_lines;
277             }else
278                commit = self;
279             commit.m_xsize = commit.m_size;
280             commit.m_xlines = commit.m_lines;
281             commit.m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
282             message_append(&commit);
283          }
284          message_append_null();
285 
286          if(f & a_HAD_BAD_FROM_){
287             /*if(!(mb.mb_active & MB_BAD_FROM_))*/{
288                mb.mb_active |= MB_BAD_FROM_;
289                /* TODO mbox-rfc4155 does NOT fix From_ line! */
290                n_err(_("MBOX contains non-conforming From_ line(s)!\n"
291                   "  Message boundaries may have been misdetected!\n"
292                   "  Setting *mbox-rfc4155* and reopening _may_ "
293                      "improve the result.\n"
294                   "  Recreating the mailbox will perform MBOXO quoting: "
295                      "\"copy * SOME-FILE\".\n"
296                   "  (Then unset *mbox-rfc4155* again.)\n"));
297             }
298          }
299 
300          mx_fs_linepool_release(linebuf, linesize);
301          break;
302       }
303 
304       /* Normalize away line endings, we will place (readded) \n */
305       if(cnt >= 2 && linebuf[cnt - 2] == '\r')
306          linebuf[--cnt] = '\0';
307       linebuf[--cnt] = '\0';
308       /* We cannot use this ASSERTion since it will trigger for example when
309        * the Linux kernel crashes and the log files (which may contain NULs)
310        * are sent out via email!  (It has been active for quite some time..) */
311       /*ASSERT(linebuf[0] != '\0' || cnt == 0);*/
312 
313       /* TODO In v15 this should use a/the flat MIME parser in order to ignore
314        * TODO "From " when MIME boundaries are active -- whereas this opens
315        * TODO another can of worms, it very likely is better than messing up
316        * TODO MIME because of a "From " line!.
317        * TODO That is: Mailbox superclass, MBOX:Mailbox, virtual load() which
318        * TODO creates collection of MessageHull objects which are able to load
319        * TODO their content, and normalize content, correct structural errs */
320       if(UNLIKELY(cnt == 0)){
321          if(!(f & a_ISEML))
322             f |= a_MAYBE;
323          if(LIKELY(!(f & a_CREATE)))
324             f &= ~a_INHEAD;
325          else{
326             commit.m_size += self.m_size;
327             commit.m_lines += self.m_lines;
328             self = commit;
329             f &= ~(a_CREATE | a_INHEAD | a_COMMIT);
330          }
331          goto jputln;
332       }
333 
334       if(UNLIKELY((f & a_MAYBE) && ((from_ = ((f & a_ISEML) != 0)) ||
335                ((linebuf[0] == 'F') && (from_ = is_head(linebuf, cnt, TRU1)) &&
336                 (from_ == TRU1 || !(f & a_RFC4155)))))){
337          /* TODO char date[n_FROM_DATEBUF];
338           * TODO extract_date_from_from_(linebuf, cnt, date);
339           * TODO self.m_time = 10000; */
340          self.m_xsize = self.m_size;
341          self.m_xlines = self.m_lines;
342          self.m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
343          commit = self;
344          f |= a_CREATE | a_INHEAD;
345 
346          f &= ~a_MAYBE;
347          if(from_ == TRUM1){
348             f |= a_HAD_BAD_FROM_;
349             /* TODO MBADFROM_ causes the From_ line to be replaced entirely
350              * TODO when the message is newly written via e.g. `copy'.
351              * TODO Instead this From_ should be an object which can fix
352              * TODO the parts which are missing or are faulty, such that
353              * TODO good info can be kept; this is possible after the main
354              * TODO message header has been fully parsed.  For now we are stuck
355              * TODO and fail for example in a_header_extract_date_from_from_()
356              * TODO (which should not exist as such btw) */
357             self.m_flag = MUSED | MNEW | MNEWEST | MBADFROM_;
358          }else
359             self.m_flag = MUSED | MNEW | MNEWEST | (f & a_ISEML ? MNOFROM : 0);
360          self.m_size = 0;
361          self.m_lines = 0;
362          self.m_block = mailx_blockof(offset);
363          self.m_offset = mailx_offsetof(offset);
364          goto jputln;
365       }
366 
367       f &= ~a_MAYBE;
368       if(LIKELY(!(f & a_INHEAD)))
369          goto jputln;
370 
371       if(LIKELY((cp = su_mem_find(linebuf, ':', cnt)) != NULL)){
372          char *cps, *cpe, c;
373 
374          if(f & a_CREATE)
375             f |= a_COMMIT;
376 
377          for(cps = linebuf; su_cs_is_blank(*cps); ++cps)
378             ;
379          for(cpe = cp; cpe > cps && (--cpe, su_cs_is_blank(*cpe));)
380             ;
381          switch(P2UZ(cpe - cps)){
382          case 5:
383             if(!su_cs_cmp_case_n(cps, "status", 5))
384                for(;;){
385                   ++cp;
386                   if((c = *cp) == '\0')
387                      break;
388                   if(c == 'R')
389                      self.m_flag |= MREAD;
390                   else if(c == 'O')
391                      self.m_flag &= ~MNEW;
392                }
393             break;
394          case 7:
395             if(!su_cs_cmp_case_n(cps, "x-status", 7))
396                for(;;){
397                   ++cp;
398                   if((c = *cp) == '\0')
399                      break;
400                   if(c == 'F')
401                      self.m_flag |= MFLAGGED;
402                   else if(c == 'A')
403                      self.m_flag |= MANSWERED;
404                   else if(c == 'T')
405                      self.m_flag |= MDRAFTED;
406                }
407             break;
408          }
409       }else if(!su_cs_is_blank(linebuf[0])){
410          /* So either this is a false detection (nothing but From_ line
411           * yet), or no separating empty line in between header/body!
412           * In the latter case, add one! */
413          if(!(f & a_CREATE)){
414             if(putc('\n', mb.mb_otf) == EOF){
415                n_perr(_("/tmp"), 0);
416                exit(n_EXIT_ERR); /* TODO no! */
417             }
418             ++offset;
419             ++self.m_size;
420             ++self.m_lines;
421          }else{
422             commit.m_size += self.m_size;
423             commit.m_lines += self.m_lines;
424             self = commit;
425             f &= ~(a_CREATE | a_INHEAD | a_COMMIT);
426          }
427       }
428 
429 jputln:
430       if(f & a_COMMIT){
431          f &= ~(a_CREATE | a_COMMIT);
432          if(f & a_HADONE)
433             message_append(&commit);
434          f |= a_HADONE;
435          msgCount++;
436       }
437       linebuf[cnt++] = '\n';
438       ASSERT(linebuf[cnt] == '\0');
439       fwrite(linebuf, sizeof *linebuf, cnt, mb.mb_otf);
440 
441       if(ferror(mb.mb_otf)){
442          n_perr(_("/tmp"), 0);
443          exit(n_EXIT_ERR); /* TODO no! */
444       }
445       offset += cnt;
446       self.m_size += cnt;
447       ++self.m_lines;
448    }
449    NYD_OU;
450 }
451 
452 FL int
setfile(char const * name,enum fedit_mode fm)453 setfile(char const *name, enum fedit_mode fm) /* TODO oh my god */
454 {
455    /* TODO This all needs to be converted to URL:: and Mailbox:: */
456    static int shudclob;
457 
458    struct stat stb;
459    uz offset;
460    char const *who, *orig_name;
461    enum protocol proto;
462    int rv, omsgCount = 0;
463    FILE *ibuf = NULL, *lckfp = NULL;
464    boole isdevnull = FAL0;
465    NYD_IN;
466 
467    n_pstate &= ~n_PS_SETFILE_OPENED;
468 
469    /* C99 */{
470       enum fexp_mode fexpm;
471 
472       if((who = mx_shortcut_expand(name)) != NIL){
473          fexpm = FEXP_NSHELL;
474          name = who;
475       }else
476          fexpm = FEXP_SHORTCUT | FEXP_NSHELL;
477 
478       if(name[0] == '%'){
479          char const *cp;
480 
481          fm |= FEDIT_SYSBOX; /* TODO fexpand() needs to tell is-valid-user! */
482          if(*(who = &name[1]) == ':')
483             ++who;
484          if((cp = su_cs_rfind_c(who, '/')) != NULL)
485             who = &cp[1];
486          if(*who == '\0')
487             goto jlogname;
488       }else{
489 jlogname:
490          if(fm & FEDIT_ACCOUNT){
491             if((who = ok_vlook(account)) == NULL)
492                who = ACCOUNT_NULL;
493             who = savecatsep(_("account"), ' ', who);
494          }else
495             who = ok_vlook(LOGNAME);
496       }
497 
498       if(!(fm & FEDIT_SYSBOX)){
499          char const *cp;
500 
501          if((((cp = ok_vlook(inbox)) != NIL && *cp != '\0') ||
502                   (cp = ok_vlook(MAIL)) != NIL) &&
503                !su_cs_cmp(cp, name))
504             fm |= FEDIT_SYSBOX;
505       }
506 
507       if((name = fexpand(name, fexpm)) == NIL)
508          goto jem1;
509    }
510 
511    /* For at least substdate() users TODO -> eventloop tick */
512    time_current_update(&time_current, FAL0);
513 
514    switch((proto = which_protocol(orig_name = name, TRU1, TRU1, &name))){
515    case n_PROTO_EML:
516       if(fm & ~FEDIT_RDONLY){
517          n_err(_("Sorry, for now eml:// files cannot be used like this: %s\n"),
518             orig_name);
519          goto jem1;
520       }
521       fm |= FEDIT_RDONLY;
522       /* FALLTHRU */
523    case PROTO_FILE:
524       isdevnull = ((n_poption & n_PO_BATCH_FLAG) &&
525             !su_cs_cmp(name, n_path_devnull));
526 #ifdef mx_HAVE_REALPATH
527       do { /* TODO we need objects, goes away then */
528 # ifdef mx_HAVE_REALPATH_NULL
529          char *cp;
530 
531          if ((cp = realpath(name, NULL)) != NULL) {
532             name = savestr(cp);
533             (free)(cp);
534          }
535 # else
536          char cbuf[PATH_MAX];
537 
538          if (realpath(name, cbuf) != NULL)
539             name = savestr(cbuf);
540 # endif
541       } while (0);
542 #endif
543       rv = 1;
544       break;
545 #ifdef mx_HAVE_MAILDIR
546    case PROTO_MAILDIR:
547       shudclob = 1;
548       rv = maildir_setfile(who, name, fm);
549       goto jleave;
550 #endif
551 #ifdef mx_HAVE_POP3
552    case PROTO_POP3:
553       shudclob = 1;
554       rv = mx_pop3_setfile(who, orig_name, fm);
555       goto jleave;
556 #endif
557 #ifdef mx_HAVE_IMAP
558    case PROTO_IMAP:
559       shudclob = 1;
560       if((fm & FEDIT_NEWMAIL) && mb.mb_type == MB_CACHE)
561          rv = 1;
562       else
563          rv = imap_setfile(who, orig_name, fm);
564       goto jleave;
565 #endif
566    default:
567       n_err(_("Cannot handle protocol: %s\n"), orig_name);
568       goto jem1;
569    }
570 
571    if((ibuf = mx_fs_open_any(savecat("file://", name), "r", NIL)) == NIL){
572       int e = su_err_no();
573 
574       if ((fm & FEDIT_SYSBOX) && e == su_ERR_NOENT) {
575          if (!(fm & FEDIT_ACCOUNT) && su_cs_cmp(who, ok_vlook(LOGNAME)) &&
576                getpwnam(who) == NULL) {
577             n_err(_("%s is not a user of this system\n"),
578                n_shexp_quote_cp(who, FAL0));
579             goto jem2;
580          }
581          if (!(n_poption & n_PO_QUICKRUN_MASK) && ok_blook(bsdcompat))
582             n_err(_("No mail for %s at %s\n"),
583                who, n_shexp_quote_cp(name, FAL0));
584       }
585       if (fm & FEDIT_NEWMAIL)
586          goto jleave;
587 
588       if(mb.mb_digmsg != NIL)
589          mx_dig_msg_on_mailbox_close(&mb);
590       mb.mb_type = MB_VOID;
591 
592       if (ok_blook(emptystart)) {
593          if (!(n_poption & n_PO_QUICKRUN_MASK) && !ok_blook(bsdcompat))
594             n_perr(name, e);
595          /* We must avoid returning -1 and causing program exit */
596          rv = 1;
597          goto jleave;
598       }
599       n_perr(name, e);
600       goto jem1;
601    }
602 
603    if (fstat(fileno(ibuf), &stb) == -1) {
604       if (fm & FEDIT_NEWMAIL)
605          goto jleave;
606       n_perr(_("fstat"), 0);
607       goto jem1;
608    }
609 
610    if (S_ISREG(stb.st_mode) || isdevnull) {
611       /* EMPTY */
612    } else {
613       if (fm & FEDIT_NEWMAIL)
614          goto jleave;
615       su_err_set_no(S_ISDIR(stb.st_mode) ? su_ERR_ISDIR : su_ERR_INVAL);
616       n_perr(name, 0);
617       goto jem1;
618    }
619 
620    if (shudclob && !(fm & FEDIT_NEWMAIL) && !quit(FAL0))
621       goto jem2;
622 
623    hold_sigs();
624 
625 #ifdef mx_HAVE_NET
626    if(!(fm & FEDIT_NEWMAIL) && mb.mb_sock != NIL){
627       if(mb.mb_sock->s_fd >= 0)
628          mx_socket_close(mb.mb_sock); /* TODO VMAILFS->close() on open thing */
629       su_FREE(mb.mb_sock);
630       mb.mb_sock = NIL;
631    }
632 #endif
633 
634    /* TODO There is no intermediate VOID box we've switched to: name may
635     * TODO point to the same box that we just have written, so any updates
636     * TODO we won't see!  Reopen again in this case.  RACY! Goes with VOID! */
637    /* TODO In addition: in case of compressed/hook boxes we lock a tmp file! */
638    /* TODO We may uselessly open twice but quit() doesn't say whether we were
639     * TODO modified so we can't tell: Mailbox::is_modified() :-(( */
640    if (/*shudclob && !(fm & FEDIT_NEWMAIL) &&*/ !su_cs_cmp(name, mailname)) {
641       name = mailname;
642       mx_fs_close(ibuf);
643 
644       if((ibuf = mx_fs_open_any(name, "r", NIL)) == NIL ||
645             fstat(fileno(ibuf), &stb) == -1 ||
646             (!S_ISREG(stb.st_mode) && !isdevnull)) {
647          n_perr(name, 0);
648          rele_sigs();
649          goto jem2;
650       }
651    }
652 
653    /* Copy the messages into /tmp and set pointers */
654    if (!(fm & FEDIT_NEWMAIL)) {
655       if (isdevnull) {
656          mb.mb_type = MB_VOID;
657          mb.mb_perm = 0;
658       } else {
659          mb.mb_type = MB_FILE;
660          mb.mb_perm = (((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY) ||
661                access(name, W_OK) < 0) ? 0 : MB_DELE | MB_EDIT);
662          if (shudclob) {
663             if (mb.mb_itf) {
664                fclose(mb.mb_itf);
665                mb.mb_itf = NULL;
666             }
667             if (mb.mb_otf) {
668                fclose(mb.mb_otf);
669                mb.mb_otf = NULL;
670             }
671          }
672       }
673       shudclob = 1;
674       if (fm & FEDIT_SYSBOX)
675          n_pstate &= ~n_PS_EDIT;
676       else
677          n_pstate |= n_PS_EDIT;
678       initbox(name);
679       offset = 0;
680    } else {
681       fseek(mb.mb_otf, 0L, SEEK_END);
682       /* TODO Doing this without holding a lock is.. And not err checking.. */
683       fseek(ibuf, mailsize, SEEK_SET);
684       offset = mailsize;
685       omsgCount = msgCount;
686    }
687 
688    if (isdevnull)
689       lckfp = (FILE*)-1;
690    else if(!(n_pstate & n_PS_EDIT))
691       lckfp = mx_file_dotlock(name, fileno(ibuf), mx_FILE_LOCK_TYPE_READ,
692             offset,0, (fm & FEDIT_NEWMAIL ? 0 : UZ_MAX));
693    else if(mx_file_lock(fileno(ibuf), mx_FILE_LOCK_TYPE_READ, offset,0,
694          (fm & FEDIT_NEWMAIL ? 0 : UZ_MAX)))
695       lckfp = (FILE*)-1;
696 
697    if (lckfp == NULL) {
698       if (!(fm & FEDIT_NEWMAIL)) {
699 #ifdef mx_HAVE_UISTRINGS
700          char const * const emsg = (n_pstate & n_PS_EDIT)
701                ? N_("Unable to lock mailbox, aborting operation")
702                : N_("Unable to (dot) lock mailbox, aborting operation");
703 #endif
704          n_perr(V_(emsg), 0);
705       }
706       rele_sigs();
707       if (!(fm & FEDIT_NEWMAIL))
708          goto jem1;
709       goto jleave;
710    }
711 
712    mailsize = fsize(ibuf);
713 
714    /* TODO This is too simple minded?  We should regenerate an index file
715     * TODO to be able to truly tell whether *anything* has changed! */
716    if ((fm & FEDIT_NEWMAIL) && UCMP(z, mailsize, <=, offset)) {
717       rele_sigs();
718       goto jleave;
719    }
720    a_folder_mbox_setptr(ibuf, offset, (proto == n_PROTO_EML));
721    setmsize(msgCount);
722    if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted) {
723       mb.mb_threaded = 0;
724       c_sort((void*)-1);
725    }
726 
727    mx_fs_close(ibuf);
728    ibuf = NIL;
729    if(lckfp != NIL && lckfp != R(FILE*,-1)){
730       mx_fs_pipe_close(lckfp, FAL0);
731       /*lckfp = NIL;*/
732    }
733 
734    if (!(fm & FEDIT_NEWMAIL)) {
735       n_pstate &= ~n_PS_SAW_COMMAND;
736       n_pstate |= n_PS_SETFILE_OPENED;
737    }
738 
739    rele_sigs();
740 
741    rv = (msgCount == 0);
742    if(rv)
743       su_err_set_no(su_ERR_NODATA);
744 
745    if(n_poption & n_PO_EXISTONLY)
746       goto jleave;
747 
748    if(rv){
749       if(!(n_pstate & n_PS_EDIT) || (fm & FEDIT_NEWMAIL)){
750          if(!(fm & FEDIT_NEWMAIL)){
751             if (!ok_blook(emptystart))
752                n_err(_("No mail for %s at %s\n"),
753                   who, n_shexp_quote_cp(name, FAL0));
754          }
755          goto jleave;
756       }
757    }
758 
759    if(fm & FEDIT_NEWMAIL)
760       newmailinfo(omsgCount);
761 jleave:
762    if(ibuf != NIL){
763       mx_fs_close(ibuf);
764       if(lckfp != NIL && lckfp != R(FILE*,-1))
765          mx_fs_pipe_close(lckfp, FAL0);
766    }
767 
768    NYD_OU;
769    return rv;
770 
771 jem2:
772    if(mb.mb_digmsg != NIL)
773       mx_dig_msg_on_mailbox_close(&mb);
774    mb.mb_type = MB_VOID;
775 jem1:
776    su_err_set_no(su_ERR_NOTOBACCO);
777    rv = -1;
778    goto jleave;
779 }
780 
781 FL int
newmailinfo(int omsgCount)782 newmailinfo(int omsgCount)
783 {
784    int mdot, i;
785    NYD_IN;
786 
787    for (i = 0; i < omsgCount; ++i)
788       message[i].m_flag &= ~MNEWEST;
789 
790    if (msgCount > omsgCount) {
791       for (i = omsgCount; i < msgCount; ++i)
792          message[i].m_flag |= MNEWEST;
793       fprintf(n_stdout, _("New mail has arrived.\n"));
794       if ((i = msgCount - omsgCount) == 1)
795          fprintf(n_stdout, _("Loaded 1 new message.\n"));
796       else
797          fprintf(n_stdout, _("Loaded %d new messages.\n"), i);
798    } else
799       fprintf(n_stdout, _("Loaded %d messages.\n"), msgCount);
800 
801    temporary_folder_hook_check(TRU1);
802 
803    mdot = getmdot(1);
804 
805    if(ok_blook(header) && (i = omsgCount + 1) <= msgCount){
806 #ifdef mx_HAVE_IMAP
807       if(mb.mb_type == MB_IMAP)
808          imap_getheaders(i, msgCount); /* TODO not here */
809 #endif
810       for(omsgCount = 0; i <= msgCount; ++omsgCount, ++i)
811          n_msgvec[omsgCount] = i;
812       n_msgvec[omsgCount] = 0;
813       print_headers(n_msgvec, FAL0, FAL0);
814    }
815    NYD_OU;
816    return mdot;
817 }
818 
819 FL void
setmsize(int size)820 setmsize(int size)
821 {
822    NYD_IN;
823    if(n_msgvec != NULL)
824       n_free(n_msgvec);
825    n_msgvec = n_calloc(size +1, sizeof *n_msgvec);
826    NYD_OU;
827 }
828 
829 FL void
print_header_summary(char const * Larg)830 print_header_summary(char const *Larg)
831 {
832    uz i;
833    NYD_IN;
834 
835    getmdot(0);
836 #ifdef mx_HAVE_IMAP
837       if(mb.mb_type == MB_IMAP)
838          imap_getheaders(0, msgCount); /* TODO not here */
839 #endif
840    ASSERT(n_msgvec != NULL);
841 
842    if (Larg != NULL) {
843       /* Avoid any messages XXX add a make_mua_silent() and use it? */
844       if ((n_poption & (n_PO_V | n_PO_EXISTONLY)) == n_PO_EXISTONLY) {
845          n_stdout = freopen(n_path_devnull, "w", stdout);
846          n_stderr = freopen(n_path_devnull, "w", stderr);
847       }
848       i = (n_getmsglist(n_shexp_quote_cp(Larg, FAL0), n_msgvec, 0, NULL) <= 0);
849       if (n_poption & n_PO_EXISTONLY)
850          n_exit_status = (int)i;
851       else if(i == 0)
852          print_headers(n_msgvec, TRU1, FAL0); /* TODO should be iterator! */
853    } else {
854       i = 0;
855       if(!mb.mb_threaded){
856          for(; UCMP(z, i, <, msgCount); ++i)
857             n_msgvec[i] = i + 1;
858       }else{
859          struct message *mp;
860 
861          for(mp = threadroot; mp; ++i, mp = next_in_thread(mp))
862             n_msgvec[i] = (int)P2UZ(mp - message + 1);
863       }
864       print_headers(n_msgvec, FAL0, TRU1); /* TODO should be iterator! */
865    }
866    NYD_OU;
867 }
868 
869 FL void
n_folder_announce(enum n_announce_flags af)870 n_folder_announce(enum n_announce_flags af){
871    int vec[2], mdot;
872    NYD_IN;
873 
874    mdot = (mb.mb_type == MB_VOID) ? 1 : getmdot(0);
875    dot = &message[mdot - 1];
876 
877    if(af != n_ANNOUNCE_NONE && ok_blook(header) &&
878          ((af & n_ANNOUNCE_MAIN_CALL) ||
879           ((af & n_ANNOUNCE_CHANGE) && !ok_blook(posix))))
880       af |= n_ANNOUNCE_STATUS | n__ANNOUNCE_HEADER;
881 
882    if(af & n_ANNOUNCE_STATUS){
883       a_folder_info();
884       af |= n__ANNOUNCE_ANY;
885    }
886 
887    if(af & n__ANNOUNCE_HEADER){
888       if(!(af & n_ANNOUNCE_MAIN_CALL) && ok_blook(bsdannounce))
889          n_OBSOLETE(_("*bsdannounce* is now default behaviour"));
890       vec[0] = mdot;
891       vec[1] = 0;
892       print_header_group(vec); /* XXX errors? */
893       af |= n__ANNOUNCE_ANY;
894    }
895 
896    if(af & n__ANNOUNCE_ANY)
897       fflush(n_stdout);
898    NYD_OU;
899 }
900 
901 FL int
getmdot(int nmail)902 getmdot(int nmail)
903 {
904    struct message *mp;
905    char *cp;
906    int mdot;
907    enum mflag avoid = MHIDDEN | MDELETED;
908    NYD_IN;
909 
910    if (!nmail) {
911       if (ok_blook(autothread)) {
912          n_OBSOLETE(_("please use *autosort=thread* instead of *autothread*"));
913          c_thread(NULL);
914       } else if ((cp = ok_vlook(autosort)) != NULL) {
915          if (mb.mb_sorted != NULL)
916             n_free(mb.mb_sorted);
917          mb.mb_sorted = su_cs_dup(cp, 0);
918          c_sort(NULL);
919       }
920    }
921    if (mb.mb_type == MB_VOID) {
922       mdot = 1;
923       goto jleave;
924    }
925 
926    if (nmail)
927       for (mp = message; PCMP(mp, <, message + msgCount); ++mp)
928          if ((mp->m_flag & (MNEWEST | avoid)) == MNEWEST)
929             break;
930 
931    if (!nmail || PCMP(mp, >=, message + msgCount)) {
932       if (mb.mb_threaded) {
933          for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
934             if ((mp->m_flag & (MNEW | avoid)) == MNEW)
935                break;
936       } else {
937          for (mp = message; PCMP(mp, <, message + msgCount); ++mp)
938             if ((mp->m_flag & (MNEW | avoid)) == MNEW)
939                break;
940       }
941    }
942 
943    if ((mb.mb_threaded ? (mp == NULL) : PCMP(mp, >=, message + msgCount))) {
944       if (mb.mb_threaded) {
945          for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
946             if (mp->m_flag & MFLAGGED)
947                break;
948       } else {
949          for (mp = message; PCMP(mp, <, message + msgCount); ++mp)
950             if (mp->m_flag & MFLAGGED)
951                break;
952       }
953    }
954 
955    if ((mb.mb_threaded ? (mp == NULL) : PCMP(mp, >=, message + msgCount))) {
956       if (mb.mb_threaded) {
957          for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
958             if (!(mp->m_flag & (MREAD | avoid)))
959                break;
960       } else {
961          for (mp = message; PCMP(mp, <, message + msgCount); ++mp)
962             if (!(mp->m_flag & (MREAD | avoid)))
963                break;
964       }
965    }
966 
967    if (nmail &&
968          (mb.mb_threaded ? (mp != NULL) : PCMP(mp, <, message + msgCount)))
969       mdot = (int)P2UZ(mp - message + 1);
970    else if (ok_blook(showlast)) {
971       if (mb.mb_threaded) {
972          for (mp = this_in_thread(threadroot, -1); mp;
973                mp = prev_in_thread(mp))
974             if (!(mp->m_flag & avoid))
975                break;
976          mdot = (mp != NULL) ? (int)P2UZ(mp - message + 1) : msgCount;
977       } else {
978          for (mp = message + msgCount - 1; mp >= message; --mp)
979             if (!(mp->m_flag & avoid))
980                break;
981          mdot = (mp >= message) ? (int)P2UZ(mp - message + 1) : msgCount;
982       }
983    } else if (!nmail &&
984          (mb.mb_threaded ? (mp != NULL) : PCMP(mp, <, message + msgCount)))
985       mdot = (int)P2UZ(mp - message + 1);
986    else if (mb.mb_threaded) {
987       for (mp = threadroot; mp; mp = next_in_thread(mp))
988          if (!(mp->m_flag & avoid))
989             break;
990       mdot = (mp != NULL) ? (int)P2UZ(mp - message + 1) : 1;
991    } else {
992       for (mp = message; PCMP(mp, <, message + msgCount); ++mp)
993          if (!(mp->m_flag & avoid))
994             break;
995       mdot = PCMP(mp, <, message + msgCount)
996             ? (int)P2UZ(mp - message + 1) : 1;
997    }
998 jleave:
999    NYD_OU;
1000    return mdot;
1001 }
1002 
1003 FL void
initbox(char const * name)1004 initbox(char const *name)
1005 {
1006    struct mx_fs_tmp_ctx *fstcp;
1007    boole err;
1008    NYD_IN;
1009 
1010    if (mb.mb_type != MB_VOID)
1011       su_cs_pcopy_n(prevfile, mailname, PATH_MAX);
1012 
1013    /* TODO name always NE mailname (but goes away for objects anyway)
1014     * TODO Well, not true no more except that in parens */
1015    _update_mailname((name != mailname) ? name : NULL);
1016 
1017    err = FAL0;
1018    if((mb.mb_otf = mx_fs_tmp_open("tmpmbox", (mx_FS_O_WRONLY |
1019             mx_FS_O_HOLDSIGS), &fstcp)) == NIL){
1020       n_perr(_("initbox: temporary mail message file, writer"), 0);
1021       err = TRU1;
1022    }else if((mb.mb_itf = mx_fs_open(fstcp->fstc_filename, "&r")
1023          ) == NIL){
1024       n_perr(_("initbox: temporary mail message file, reader"), 0);
1025       err = TRU1;
1026    }
1027    mx_fs_tmp_release(fstcp);
1028    if(err)
1029       exit(n_EXIT_ERR);
1030 
1031    message_reset();
1032    mb.mb_active = MB_NONE;
1033    mb.mb_threaded = 0;
1034 #ifdef mx_HAVE_IMAP
1035    mb.mb_flags = MB_NOFLAGS;
1036 #endif
1037    if (mb.mb_sorted != NULL) {
1038       n_free(mb.mb_sorted);
1039       mb.mb_sorted = NULL;
1040    }
1041    dot = prevdot = threadroot = NULL;
1042    n_pstate &= ~n_PS_DID_PRINT_DOT;
1043    NYD_OU;
1044 }
1045 
1046 FL char const *
n_folder_query(void)1047 n_folder_query(void){
1048    struct n_string s_b, *s;
1049    enum protocol proto;
1050    char *cp;
1051    char const *rv, *adjcp;
1052    boole err;
1053    NYD_IN;
1054 
1055    s = n_string_creat_auto(&s_b);
1056 
1057    /* *folder* is linked with *folder_resolved*: we only use the latter */
1058    for(err = FAL0;;){
1059       if((rv = ok_vlook(folder_resolved)) != NIL)
1060          break;
1061 
1062       /* POSIX says:
1063        *    If directory does not start with a <slash> ('/'), the contents
1064        *    of HOME shall be prefixed to it.
1065        * And:
1066        *    If folder is unset or set to null, [.] filenames beginning with
1067        *    '+' shall refer to files in the current directory.
1068        * We may have the result already.
1069        * P.S.: that "or set to null" seems to be a POSIX bug, V10 mail and BSD
1070        * Mail since 1982 work differently, follow suit */
1071       rv = su_empty;
1072       err = FAL0;
1073 
1074       if((cp = ok_vlook(folder)) == NIL)
1075          goto jset;
1076 
1077       /* Expand the *folder*; skip %: prefix for simplicity of use */
1078       if(cp[0] == '%' && cp[1] == ':')
1079          cp += 2;
1080       if((err = (cp = fexpand(cp, FEXP_NSPECIAL | FEXP_NFOLDER | FEXP_NSHELL)
1081             ) == NIL) /*|| *cp == '\0'*/)
1082          goto jset;
1083       else{
1084          uz i;
1085 
1086          for(i = su_cs_len(cp);;){
1087             if(--i == 0)
1088                goto jset;
1089             if(cp[i] != '/'){
1090                cp[++i] = '\0';
1091                break;
1092             }
1093          }
1094       }
1095 
1096       switch((proto = which_protocol(cp, FAL0, FAL0, &adjcp))){
1097       case PROTO_POP3:
1098          n_err(_("*folder*: cannot use the POP3 protocol\n"));
1099          err = TRU1;
1100          goto jset;
1101       case PROTO_IMAP:
1102 #ifdef mx_HAVE_IMAP
1103          rv = cp;
1104          if(!su_cs_cmp(rv, protbase(rv)))
1105             rv = savecatsep(rv, '/', n_empty);
1106 #else
1107          n_err(_("*folder*: IMAP support not compiled in\n"));
1108          err = TRU1;
1109 #endif
1110          goto jset;
1111       default:
1112          /* Further expansion desired */
1113          break;
1114       }
1115 
1116       /* Prefix HOME as necessary */
1117       if(*adjcp != '/'){ /* XXX path_is_absolute() */
1118          uz l1, l2;
1119          char const *home;
1120 
1121          home = ok_vlook(HOME);
1122          l1 = su_cs_len(home);
1123          ASSERT(l1 > 0); /* (checked VIP variable) */
1124          l2 = su_cs_len(cp);
1125 
1126          s = n_string_reserve(s, l1 + 1 + l2 +1);
1127 
1128          if(cp != adjcp){
1129             uz i;
1130 
1131             s = n_string_push_buf(s, cp, i = P2UZ(adjcp - cp));
1132             cp += i;
1133             l2 -= i;
1134          }
1135 
1136          s = n_string_push_buf(s, home, l1);
1137          if(l2 > 0){
1138             s = n_string_push_c(s, '/');
1139             s = n_string_push_buf(s, cp, l2);
1140          }
1141          cp = n_string_cp(s);
1142          s = n_string_drop_ownership(s);
1143       }
1144 
1145       /* TODO Since visual mailname is resolved via realpath(3) if available
1146        * TODO to avoid that we loose track of our currently open folder in case
1147        * TODO we chdir away, but still checks the leading path portion against
1148        * TODO folder_query() to be able to abbreviate to the +FOLDER syntax if
1149        * TODO possible, we need to realpath(3) the folder, too */
1150 #ifndef mx_HAVE_REALPATH
1151       rv = cp;
1152 #else
1153       ASSERT(s->s_len == 0 && s->s_dat == NIL);
1154 # ifndef mx_HAVE_REALPATH_NULL
1155       s = n_string_reserve(s, PATH_MAX +1);
1156 # endif
1157 
1158       if((s->s_dat = realpath(cp, s->s_dat)) != NIL){
1159 # ifdef mx_HAVE_REALPATH_NULL
1160          n_string_cp(s = n_string_assign_cp(s, cp = s->s_dat));
1161          free(cp);
1162 # endif
1163          rv = s->s_dat;
1164       }else if(su_err_no() == su_ERR_NOENT)
1165          rv = cp;
1166       else{
1167          n_err(_("Cannot canonicalize *folder*: %s\n"),
1168             n_shexp_quote_cp(cp, FAL0));
1169          err = TRU1;
1170          rv = su_empty;
1171       }
1172       s = n_string_drop_ownership(s);
1173 #endif /* mx_HAVE_REALPATH */
1174 
1175       /* Always append a solidus to our result path upon success */
1176       if(!err){
1177          uz i;
1178 
1179          if(rv[(i = su_cs_len(rv)) - 1] != '/'){
1180             s = n_string_reserve(s, i + 1 +1);
1181             s = n_string_push_buf(s, rv, i);
1182             s = n_string_push_c(s, '/');
1183             rv = n_string_cp(s);
1184             s = n_string_drop_ownership(s);
1185          }
1186       }
1187 
1188 jset:
1189       n_PS_ROOT_BLOCK(ok_vset(folder_resolved, rv));
1190    }
1191 
1192    if(err){
1193       n_err(_("*folder* is not resolvable, using CWD\n"));
1194       ASSERT(rv != NIL && *rv == '\0');
1195    }
1196 
1197    NYD_OU;
1198    return rv;
1199 }
1200 
1201 FL int
n_folder_mbox_prepare_append(FILE * fout,struct stat * st_or_nil)1202 n_folder_mbox_prepare_append(FILE *fout, struct stat *st_or_nil){
1203    /* TODO n_folder_mbox_prepare_append -> Mailbox->append() */
1204    struct stat stb;
1205    char buf[2];
1206    int rv;
1207    boole needsep;
1208    NYD2_IN;
1209 
1210    if(fseek(fout, -2L, SEEK_END) == 0 && fread(buf, sizeof *buf, 2, fout) == 2)
1211       needsep = (buf[0] != '\n' || buf[1] != '\n');
1212    else{
1213       rv = su_err_no();
1214       if(st_or_nil == NIL){
1215          st_or_nil = &stb;
1216          if(fstat(fileno(fout), st_or_nil))
1217             goto jerrno;
1218       }
1219 
1220       if(st_or_nil->st_size >= 2)
1221          goto jleave;
1222       if(st_or_nil->st_size == 0){
1223          clearerr(fout);
1224          rv = su_ERR_NONE;
1225          goto jleave;
1226       }
1227 
1228       if(fseek(fout, -1L, SEEK_END))
1229          goto jerrno;
1230       if(fread(buf, sizeof *buf, 1, fout) != 1)
1231          goto jerrno;
1232       needsep = (buf[0] != '\n');
1233    }
1234 
1235    rv = su_ERR_NONE;
1236    if((needsep && (fseek(fout, 0L, SEEK_END) || putc('\n', fout) == EOF)) ||
1237          fflush(fout) == EOF)
1238 jerrno:
1239       rv = su_err_no();
1240 
1241 jleave:
1242    NYD2_OU;
1243    return rv;
1244 }
1245 
1246 #include "su/code-ou.h"
1247 /* s-it-mode */
1248