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