1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 1999-2021 Free Software Foundation, Inc.
3
4 GNU Mailutils is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 GNU Mailutils is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
16
17 /* Initialize MH applications. */
18
19 #include <mh.h>
20 #include <mailutils/url.h>
21 #include <mailutils/tls.h>
22 #include <pwd.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/time.h>
26 #include <stdarg.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <fnmatch.h>
31 #include <sys/ioctl.h>
32
33 void
mh_init(void)34 mh_init (void)
35 {
36 mu_stdstream_setup (MU_STDSTREAM_RESET_NONE);
37
38 /* Register all mailbox and mailer formats */
39 mu_register_all_formats ();
40
41 /* Read user's profile */
42 mh_read_profile ();
43 }
44
45 void
mh_init2(void)46 mh_init2 (void)
47 {
48 mh_current_folder ();
49 }
50
51 void
mh_err_memory(int fatal)52 mh_err_memory (int fatal)
53 {
54 mu_error (_("not enough memory"));
55 if (fatal)
56 abort ();
57 }
58
59 static mu_address_t
mh_local_mailbox(void)60 mh_local_mailbox (void)
61 {
62 static mu_address_t local_mailbox;
63 if (!local_mailbox)
64 {
65 const char *p = mh_global_profile_get ("Local-Mailbox", NULL);
66 if (!p)
67 p = mu_get_user_email (NULL);
68 mu_address_create (&local_mailbox, p);
69 }
70 return local_mailbox;
71 }
72
73 char const *
mh_get_my_user_name(void)74 mh_get_my_user_name (void)
75 {
76 mu_address_t addr = mh_local_mailbox ();
77 char const *s;
78 MU_ASSERT (mu_address_sget_local_part (addr, 1, &s));
79 return s;
80 }
81
82 char const *
mh_get_my_real_name(void)83 mh_get_my_real_name (void)
84 {
85 mu_address_t addr = mh_local_mailbox ();
86 char const *s;
87 if (mu_address_sget_personal (addr, 1, &s))
88 return NULL;
89 return s;
90 }
91
92 char const *
mh_my_email(void)93 mh_my_email (void)
94 {
95 mu_address_t addr = mh_local_mailbox ();
96 char const *s;
97 MU_ASSERT (mu_address_sget_printable (addr, &s));
98 return s;
99 }
100
101 char const *
mh_my_host(void)102 mh_my_host (void)
103 {
104 mu_address_t addr = mh_local_mailbox ();
105 char const *s;
106 MU_ASSERT (mu_address_sget_domain (addr, 1, &s));
107 return s;
108 }
109
110 enum part_match_mode
111 {
112 part_match_local,
113 part_match_domain
114 };
115
116 enum part_match_result
117 {
118 part_match_false,
119 part_match_true,
120 part_match_abort
121 };
122
123 static int match_char_class (char const **pexpr, char c, int icase);
124
125 static enum part_match_result
part_match(char const * expr,char const * name,enum part_match_mode mode)126 part_match (char const *expr, char const *name, enum part_match_mode mode)
127 {
128 int c;
129
130 while (*expr)
131 {
132 if (*name == 0 && *expr != '*')
133 return part_match_abort;
134 switch (*expr)
135 {
136 case '*':
137 while (*++expr == '*')
138 ;
139 if (*expr == 0)
140 return part_match_true;
141 while (*name)
142 {
143 int res = part_match (expr, name++, mode);
144 if (res != part_match_false)
145 return res;
146 }
147 return part_match_abort;
148
149 case '?':
150 expr++;
151 if (*name == 0)
152 return part_match_false;
153 name++;
154 break;
155
156 case '[':
157 if (!match_char_class (&expr, *name, mode == part_match_domain))
158 return part_match_false;
159 name++;
160 break;
161
162 case '\\':
163 if (expr[1])
164 {
165 c = *++expr; expr++;
166 if (*name != mu_wordsplit_c_unquote_char (c))
167 return part_match_false;
168 name++;
169 break;
170 }
171 /* fall through */
172
173 default:
174 if (mode == part_match_local)
175 {
176 if (*expr != *name)
177 return part_match_false;
178 if ('@' == *name)
179 mode = part_match_domain;
180 }
181 else
182 {
183 if (mu_tolower (*expr) != mu_tolower (*name))
184 return part_match_false;
185 }
186 expr++;
187 name++;
188 }
189 }
190
191 if (*name == 0)
192 return part_match_true;
193
194 if (mode == part_match_local && *name == '@')
195 return part_match_true;
196
197 return part_match_false;
198 }
199
200 static int
match_char_class(char const ** pexpr,char c,int icase)201 match_char_class (char const **pexpr, char c, int icase)
202 {
203 int res;
204 int rc;
205 char const *expr = *pexpr;
206
207 if (icase)
208 c = mu_toupper (c);
209
210 expr++;
211 if (*expr == '^')
212 {
213 res = 0;
214 expr++;
215 }
216 else
217 res = 1;
218
219 if (*expr == '-' || *expr == ']')
220 rc = c == *expr++;
221 else
222 rc = !res;
223
224 for (; *expr && *expr != ']'; expr++)
225 {
226 if (rc == res)
227 {
228 if (*expr == '\\' && expr[1] == ']')
229 expr++;
230 }
231 else if (expr[1] == '-')
232 {
233 if (*expr == '\\')
234 rc = *++expr == c;
235 else
236 {
237 if (icase)
238 rc = mu_toupper (*expr) <= c && c <= mu_toupper (expr[2]);
239 else
240 rc = *expr <= c && c <= expr[2];
241 expr += 2;
242 }
243 }
244 else if (*expr == '\\' && expr[1] == ']')
245 rc = *++expr == c;
246 else if (icase)
247 rc = mu_toupper(*expr) == c;
248 else
249 rc = *expr == c;
250 }
251 *pexpr = *expr ? expr + 1 : expr;
252 return rc == res;
253 }
254
255 static int
email_match(char const * pattern,char const * name)256 email_match (char const *pattern, char const *name)
257 {
258 return part_match (pattern, name, part_match_local) == part_match_true;
259 }
260
261 int
mh_is_my_name(const char * name)262 mh_is_my_name (const char *name)
263 {
264 static mu_address_t addr;
265 mu_address_t p;
266
267 if (!addr)
268 {
269 const char *nlist;
270 int rc;
271
272 rc = mu_address_create (&addr, mh_my_email ());
273 if (rc)
274 {
275 mu_diag_funcall (MU_DIAG_ERROR, "mu_address_create", mh_my_email (),
276 rc);
277 return 0;
278 }
279
280 nlist = mh_global_profile_get ("Alternate-Mailboxes", NULL);
281 if (nlist)
282 {
283 mu_address_t tmp;
284 struct mu_address hint;
285
286 hint.domain = NULL;
287 rc = mu_address_create_hint (&tmp, nlist, &hint,
288 MU_ADDR_HINT_DOMAIN);
289 if (rc == 0)
290 {
291 rc = mu_address_union (&addr, tmp);
292 if (rc)
293 mu_diag_funcall (MU_DIAG_ERROR, "mu_address_union", NULL, rc);
294 mu_address_destroy (&tmp);
295 }
296 else
297 {
298 mu_error (_("bad Alternate-Mailboxes: %s; please fix"),
299 mu_strerror (rc));
300 }
301 }
302 }
303
304 for (p = addr; p; p = p->next)
305 {
306 if (email_match (p->email, name))
307 return 1;
308 }
309 return 0;
310 }
311
312 static int
make_dir_hier(const char * p,mode_t perm)313 make_dir_hier (const char *p, mode_t perm)
314 {
315 int rc = 0;
316 char *dir = mu_strdup (p);
317 char *q = dir;
318
319 while (!rc && (q = strchr (q + 1, '/')))
320 {
321 *q = 0;
322 if (access (dir, X_OK))
323 {
324 if (errno != ENOENT)
325 {
326 mu_error (_("cannot create directory %s: error accessing name component %s: %s"),
327
328 p, dir, strerror (errno));
329 rc = 1;
330 }
331 else if ((rc = mkdir (dir, perm)))
332 mu_error (_("cannot create directory %s: error creating name component %s: %s"),
333 p, dir, mu_strerror (rc));
334 }
335 *q = '/';
336 }
337 free (dir);
338 return rc;
339 }
340
341 int
mh_makedir(const char * p)342 mh_makedir (const char *p)
343 {
344 int rc;
345 mode_t save_umask;
346 mode_t perm = 0711;
347 const char *pb = mh_global_profile_get ("Folder-Protect", NULL);
348 if (pb)
349 perm = strtoul (pb, NULL, 8);
350
351 save_umask = umask (0);
352
353 if ((rc = make_dir_hier (p, perm)) == 0)
354 {
355 rc = mkdir (p, perm);
356 if (rc)
357 mu_error (_("cannot create directory %s: %s"),
358 p, strerror (errno));
359 }
360
361 umask (save_umask);
362 return rc;
363 }
364
365 int
mh_check_folder(const char * pathname,int confirm)366 mh_check_folder (const char *pathname, int confirm)
367 {
368 const char *p;
369 struct stat st;
370
371 if ((p = strchr (pathname, ':')) != NULL)
372 p++;
373 else
374 p = pathname;
375
376 if (stat (p, &st))
377 {
378 if (errno == ENOENT)
379 {
380 /* TRANSLATORS: This is a question and will be followed
381 by question mark on output. */
382 if (!confirm || mh_getyn (_("Create folder \"%s\""), p))
383 return mh_makedir (p);
384 else
385 return 1;
386 }
387 else
388 {
389 mu_diag_funcall (MU_DIAG_ERROR, "stat", p, errno);
390 return 1;
391 }
392 }
393 return 0;
394 }
395
396 int
mh_interactive_mode_p(void)397 mh_interactive_mode_p (void)
398 {
399 static int interactive = -1;
400
401 if (interactive < 0)
402 interactive = isatty (fileno (stdin)) ? 1 : 0;
403 return interactive;
404 }
405
406 int
mh_vgetyn(const char * fmt,va_list ap)407 mh_vgetyn (const char *fmt, va_list ap)
408 {
409 char repl[64];
410
411 while (1)
412 {
413 char *p;
414 int len, rc;
415
416 vfprintf (stdout, fmt, ap);
417 fprintf (stdout, "? ");
418 p = fgets (repl, sizeof repl, stdin);
419 if (!p)
420 return 0;
421 len = strlen (p);
422 if (len > 0 && p[len-1] == '\n')
423 p[len--] = 0;
424
425 rc = mu_true_answer_p (p);
426
427 if (rc >= 0)
428 return rc;
429
430 /* TRANSLATORS: See msgids "nN" and "yY". */
431 fprintf (stdout, _("Please answer yes or no: "));
432 }
433 return 0; /* to pacify gcc */
434 }
435
436 int
mh_getyn(const char * fmt,...)437 mh_getyn (const char *fmt, ...)
438 {
439 va_list ap;
440 int rc;
441
442 if (!mh_interactive_mode_p ())
443 return 1;
444 va_start (ap, fmt);
445 rc = mh_vgetyn (fmt, ap);
446 va_end (ap);
447 return rc;
448 }
449
450 int
mh_getyn_interactive(const char * fmt,...)451 mh_getyn_interactive (const char *fmt, ...)
452 {
453 va_list ap;
454 int rc;
455
456 va_start (ap, fmt);
457 rc = mh_vgetyn (fmt, ap);
458 va_end (ap);
459 return rc;
460 }
461
462 mu_stream_t
mh_audit_open(char * name,mu_mailbox_t mbox)463 mh_audit_open (char *name, mu_mailbox_t mbox)
464 {
465 mu_stream_t str;
466 char date[64];
467 time_t t;
468 struct tm *tm;
469 mu_url_t url;
470 char *namep;
471 int rc;
472
473 namep = mu_tilde_expansion (name, MU_HIERARCHY_DELIMITER, NULL);
474 if (strchr (namep, MU_HIERARCHY_DELIMITER) == NULL)
475 {
476 char *p = mh_safe_make_file_name (mu_folder_directory (), namep);
477 free (namep);
478 namep = p;
479 }
480
481 rc = mu_file_stream_create (&str, namep, MU_STREAM_CREAT|MU_STREAM_APPEND);
482 if (rc)
483 {
484 mu_error (_("cannot open audit file %s: %s"), namep, strerror (rc));
485 free (namep);
486 return NULL;
487 }
488 free (namep);
489
490 time (&t);
491 tm = localtime (&t);
492 mu_strftime (date, sizeof date, "%a, %d %b %Y %H:%M:%S %Z", tm);
493 mu_mailbox_get_url (mbox, &url);
494
495 mu_stream_printf (str, "<<%s>> %s %s\n",
496 mu_program_name,
497 date,
498 mu_url_to_string (url));
499 return str;
500 }
501
502 void
mh_audit_close(mu_stream_t str)503 mh_audit_close (mu_stream_t str)
504 {
505 mu_stream_close (str);
506 }
507
508 int
mh_message_number(mu_message_t msg,size_t * pnum)509 mh_message_number (mu_message_t msg, size_t *pnum)
510 {
511 return mu_message_get_uid (msg, pnum);
512 }
513
514 mu_mailbox_t
mh_open_folder(const char * folder,int flags)515 mh_open_folder (const char *folder, int flags)
516 {
517 mu_mailbox_t mbox = NULL;
518 char *name;
519
520 name = mh_expand_name (NULL, folder, NAME_FOLDER);
521 if ((flags & MU_STREAM_CREAT) && mh_check_folder (name, 1))
522 exit (0);
523
524 if (mu_mailbox_create_default (&mbox, name))
525 {
526 mu_error (_("cannot create mailbox %s: %s"),
527 name, strerror (errno));
528 exit (1);
529 }
530
531 if (mu_mailbox_open (mbox, flags))
532 {
533 mu_error (_("cannot open mailbox %s: %s"), name, strerror (errno));
534 exit (1);
535 }
536
537 free (name);
538
539 return mbox;
540 }
541
542 char *
mh_get_dir(void)543 mh_get_dir (void)
544 {
545 const char *mhdir = mh_global_profile_get ("Path", "Mail");
546 char *mhcopy;
547
548 if (mhdir[0] != '/')
549 {
550 char *p = mu_get_homedir ();
551 mhcopy = mh_safe_make_file_name (p, mhdir);
552 free (p);
553 }
554 else
555 mhcopy = strdup (mhdir);
556 if (!mhcopy)
557 {
558 mu_error (_("not enough memory"));
559 abort ();
560 }
561 return mhcopy;
562 }
563
564 char *
mh_expand_name(const char * base,const char * name,int what)565 mh_expand_name (const char *base, const char *name, int what)
566 {
567 char *p = NULL;
568 char *namep = NULL;
569
570 namep = mu_tilde_expansion (name, MU_HIERARCHY_DELIMITER, NULL);
571 if (namep[0] == '+')
572 memmove (namep, namep + 1, strlen (namep)); /* copy null byte as well */
573 else if (strncmp (namep, "../", 3) == 0 || strncmp (namep, "./", 2) == 0)
574 {
575 char *cwd = mu_getcwd ();
576 char *tmp = mh_safe_make_file_name (cwd, namep);
577 free (cwd);
578 if (what == NAME_FILE)
579 return tmp;
580 free (namep);
581 namep = tmp;
582 }
583
584 if (what == NAME_FOLDER)
585 {
586 if (memcmp (namep, "mh:/", 4) == 0)
587 return namep;
588 else if (namep[0] == '/')
589 mu_asprintf (&p, "mh:%s", namep);
590 else
591 mu_asprintf (&p, "mh:%s/%s", base ? base : mu_folder_directory (),
592 namep);
593 }
594 else if (namep[0] != '/')
595 {
596 if (what == NAME_FILE)
597 {
598 char *cwd = mu_getcwd ();
599 p = mh_safe_make_file_name (cwd, namep);
600 free (cwd);
601 }
602 else
603 p = mh_safe_make_file_name (base ? base : mu_folder_directory (),
604 namep);
605 }
606 else
607 return namep;
608
609 free (namep);
610 return p;
611 }
612
613 int
mh_find_file(const char * name,char ** resolved_name)614 mh_find_file (const char *name, char **resolved_name)
615 {
616 char *s;
617 int rc;
618
619 if (name[0] == '/' ||
620 (name[0] == '.' && name[1] == '/') ||
621 (name[0] == '.' && name[1] == '.' && name[2] == '/'))
622 {
623 *resolved_name = mu_strdup (name);
624 if (access (name, R_OK) == 0)
625 return 0;
626 return errno;
627 }
628
629 if (name[0] == '~')
630 {
631 s = mu_tilde_expansion (name, MU_HIERARCHY_DELIMITER, NULL);
632 *resolved_name = s;
633 if (access (s, R_OK) == 0)
634 return 0;
635 return errno;
636 }
637
638 s = mh_expand_name (NULL, name, NAME_ANY);
639 if (access (s, R_OK) == 0)
640 {
641 *resolved_name = s;
642 return 0;
643 }
644 if (errno != ENOENT)
645 mu_diag_output (MU_DIAG_WARNING,
646 _("cannot access %s: %s"), s, mu_strerror (errno));
647 free (s);
648
649 s = mh_expand_name (mh_global_profile_get ("mhetcdir", MHLIBDIR), name,
650 NAME_ANY);
651 if (access (s, R_OK) == 0)
652 {
653 *resolved_name = s;
654 return 0;
655 }
656 if (errno != ENOENT)
657 mu_diag_output (MU_DIAG_WARNING,
658 _("cannot access %s: %s"), s, mu_strerror (errno));
659 free (s);
660
661 *resolved_name = mu_strdup (name);
662 if (access (name, R_OK) == 0)
663 return 0;
664 rc = errno;
665 if (rc != ENOENT)
666 mu_diag_output (MU_DIAG_WARNING,
667 _("cannot access %s: %s"), s, mu_strerror (rc));
668
669 return rc;
670 }
671
672 int
mh_spawnp(const char * prog,const char * file)673 mh_spawnp (const char *prog, const char *file)
674 {
675 struct mu_wordsplit ws;
676 size_t i;
677 int rc, status;
678 char **xargv;
679
680 ws.ws_comment = "#";
681 if (mu_wordsplit (prog, &ws, MU_WRDSF_DEFFLAGS | MU_WRDSF_COMMENT))
682 {
683 mu_error (_("cannot split line `%s': %s"), prog,
684 mu_wordsplit_strerror (&ws));
685 return 1;
686 }
687
688 xargv = calloc (ws.ws_wordc + 2, sizeof (*xargv));
689 if (!xargv)
690 {
691 mh_err_memory (0);
692 mu_wordsplit_free (&ws);
693 return 1;
694 }
695
696 for (i = 0; i < ws.ws_wordc; i++)
697 xargv[i] = ws.ws_wordv[i];
698 xargv[i++] = (char*) file;
699 xargv[i++] = NULL;
700
701 rc = mu_spawnvp (xargv[0], xargv, &status);
702
703 free (xargv);
704 mu_wordsplit_free (&ws);
705
706 return rc;
707 }
708
709 /* Copy data from FROM to TO, creating the latter if necessary.
710 FIXME: How about formats?
711 */
712 int
mh_file_copy(const char * from,const char * to)713 mh_file_copy (const char *from, const char *to)
714 {
715 mu_stream_t in, out, flt;
716 int rc;
717
718 if ((rc = mu_file_stream_create (&in, from, MU_STREAM_READ)))
719 {
720 mu_error (_("cannot open input file `%s': %s"),
721 from, mu_strerror (rc));
722 return 1;
723 }
724
725 if ((rc = mu_file_stream_create (&out, to, MU_STREAM_RDWR|MU_STREAM_CREAT)))
726 {
727 mu_error (_("cannot open output file `%s': %s"),
728 to, mu_strerror (rc));
729 mu_stream_destroy (&in);
730 return 1;
731 }
732
733 rc = mu_filter_create (&flt, in, "INLINE-COMMENT", MU_FILTER_DECODE,
734 MU_STREAM_READ);
735 mu_stream_unref (in);
736 if (rc)
737 {
738 mu_error (_("cannot open filter stream: %s"), mu_strerror (rc));
739 mu_stream_destroy (&out);
740 return 1;
741 }
742
743 rc = mu_stream_copy (out, flt, 0, NULL);
744
745 mu_stream_destroy (&flt);
746 mu_stream_destroy (&out);
747
748 if (rc)
749 mu_error (_("error copying file `%s' to `%s': %s"),
750 from, to, mu_strerror (rc));
751
752 return rc;
753 }
754
755 static mu_message_t
_file_to_message(const char * file_name)756 _file_to_message (const char *file_name)
757 {
758 struct stat st;
759 int rc;
760 mu_stream_t instream;
761
762 if (stat (file_name, &st) < 0)
763 {
764 mu_diag_funcall (MU_DIAG_ERROR, "stat", file_name, errno);
765 return NULL;
766 }
767
768 if ((rc = mu_file_stream_create (&instream, file_name, MU_STREAM_READ)))
769 {
770 mu_error (_("cannot create input stream (file %s): %s"),
771 file_name, mu_strerror (rc));
772 return NULL;
773 }
774
775 return mh_stream_to_message (instream);
776 }
777
778 mu_message_t
mh_file_to_message(const char * folder,const char * file_name)779 mh_file_to_message (const char *folder, const char *file_name)
780 {
781 mu_message_t msg;
782 char *tmp_name = NULL;
783
784 if (folder)
785 {
786 tmp_name = mh_expand_name (folder, file_name, NAME_ANY);
787 msg = _file_to_message (tmp_name);
788 free (tmp_name);
789 }
790 else
791 msg = _file_to_message (file_name);
792
793 return msg;
794 }
795
796 void
mh_install_help(char * mhdir)797 mh_install_help (char *mhdir)
798 {
799 static char *text = N_(
800 "Prior to using MH, it is necessary to have a file in your login\n"
801 "directory (%s) named .mh_profile which contains information\n"
802 "to direct certain MH operations. The only item which is required\n"
803 "is the path to use for all MH folder operations. The suggested MH\n"
804 "path for you is %s...\n");
805
806 printf (_(text), mu_get_homedir (), mhdir);
807 }
808
809 void
mh_real_install(char * name,int automode)810 mh_real_install (char *name, int automode)
811 {
812 char *home = mu_get_homedir ();
813 char *mhdir;
814 char *ctx;
815 int rc;
816 mu_stream_t profile;
817
818 mhdir = mh_safe_make_file_name (home, "Mail");
819
820 if (!automode)
821 {
822 /* TRANSLATORS: This is a question and will be followed
823 by question mark on output. */
824 if (mh_getyn_interactive (_("Do you need help")))
825 mh_install_help (mhdir);
826
827 /* TRANSLATORS: This is a question and will be followed
828 by question mark on output. */
829 if (!mh_getyn_interactive (_("Do you want the standard MH path \"%s\""), mhdir))
830 {
831 int local;
832 char *p, *buf = NULL;
833 size_t size = 0;
834
835 /* TRANSLATORS: This is a question and will be followed
836 by question mark on output. */
837 local = mh_getyn_interactive (_("Do you want a path below your login directory"));
838 if (local)
839 mu_printf (_("What is the path? "));
840 else
841 mu_printf (_("What is the full path? "));
842 mu_stream_flush (mu_strin);
843 if (mu_stream_getline (mu_strin, &buf, &size, NULL))
844 exit (1);
845 p = mu_str_stripws (buf);
846 if (p > buf)
847 memmove (buf, p, strlen (p) + 1);
848
849 free (mhdir);
850 if (local)
851 {
852 mhdir = mh_safe_make_file_name (home, p);
853 free (p);
854 }
855 else
856 mhdir = p;
857 }
858 }
859
860 if (mh_check_folder (mhdir, !automode))
861 exit (1);
862
863 rc = mu_file_stream_create (&profile, name,
864 MU_STREAM_WRITE | MU_STREAM_CREAT);
865 if (rc)
866 {
867 mu_error (_("cannot open file %s: %s"), name, mu_strerror (rc));
868 exit (1);
869 }
870 mu_stream_printf (profile, "Path: %s\n", mhdir);
871 mu_stream_destroy (&profile);
872
873 ctx = mh_safe_make_file_name (mhdir, MH_CONTEXT_FILE);
874 rc = mu_file_stream_create (&profile, ctx,
875 MU_STREAM_WRITE | MU_STREAM_CREAT);
876 if (rc)
877 {
878 mu_stream_printf (profile, "Current-Folder: inbox\n");
879 mu_stream_destroy (&profile);
880 }
881 free (ctx);
882 ctx = mh_safe_make_file_name (mhdir, "inbox");
883 if (mh_check_folder (ctx, !automode))
884 exit (1);
885 free (ctx);
886 free (mhdir);
887 }
888
889 void
mh_install(char * name,int automode)890 mh_install (char *name, int automode)
891 {
892 struct stat st;
893
894 if (stat (name, &st))
895 {
896 if (errno == ENOENT)
897 {
898 if (automode)
899 printf(_("I'm going to create the standard MH path for you.\n"));
900 mh_real_install (name, automode);
901 }
902 else
903 {
904 mu_diag_funcall (MU_DIAG_ERROR, "stat", name, errno);
905 exit (1);
906 }
907 }
908 else if ((st.st_mode & S_IFREG) || (st.st_mode & S_IFLNK))
909 {
910 mu_error(_("You already have an MH profile, use an editor to modify it"));
911 exit (1);
912 }
913 else
914 {
915 mu_error (_("You already have file %s which is not a regular file or a symbolic link."), name);
916 mu_error (_("Please remove it and try again"));
917 exit (1);
918 }
919 }
920
921 void
mh_annotate(mu_message_t msg,const char * field,const char * text,int date)922 mh_annotate (mu_message_t msg, const char *field, const char *text, int date)
923 {
924 mu_header_t hdr;
925 mu_attribute_t attr;
926
927 if (mu_message_get_header (msg, &hdr))
928 return;
929
930 if (date)
931 {
932 time_t t;
933 struct tm *tm;
934 char datebuf[80];
935 t = time (NULL);
936 tm = localtime (&t);
937 mu_strftime (datebuf, sizeof datebuf, "%a, %d %b %Y %H:%M:%S %Z", tm);
938
939 mu_header_prepend (hdr, field, datebuf);
940 }
941
942 if (text)
943 mu_header_prepend (hdr, field, text);
944 mu_message_get_attribute (msg, &attr);
945 mu_attribute_set_modified (attr);
946 }
947
948 char *
mh_create_message_id(int subpart)949 mh_create_message_id (int subpart)
950 {
951 char *p;
952 mu_rfc2822_msg_id (subpart, &p);
953 return p;
954 }
955
956 void
mh_set_reply_regex(const char * str)957 mh_set_reply_regex (const char *str)
958 {
959 char *err;
960 int rc = mu_unre_set_regex (str, 0, &err);
961 if (rc)
962 mu_error ("reply_regex: %s%s%s", mu_strerror (rc),
963 err ? ": " : "",
964 mu_prstr (err));
965 }
966
967 const char *
mh_charset(const char * dfl)968 mh_charset (const char *dfl)
969 {
970 const char *charset = mh_global_profile_get ("Charset", dfl);
971
972 if (!charset)
973 return NULL;
974 if (mu_c_strcasecmp (charset, "auto") == 0)
975 {
976 static char *saved_charset;
977
978 if (!saved_charset)
979 {
980 /* Try to deduce the charset from LC_ALL variable */
981 struct mu_lc_all lc_all;
982 if (mu_parse_lc_all (getenv ("LC_ALL"), &lc_all, MU_LC_CSET) == 0)
983 saved_charset = lc_all.charset; /* FIXME: Mild memory leak */
984 }
985 charset = saved_charset;
986 }
987 return charset;
988 }
989
990 int
mh_decode_2047(char const * text,char ** decoded_text)991 mh_decode_2047 (char const *text, char **decoded_text)
992 {
993 const char *charset = mh_charset (NULL);
994 if (!charset)
995 return 1;
996
997 return mu_rfc2047_decode (charset, text, decoded_text);
998 }
999
1000 void
mh_quote(const char * in,char ** out)1001 mh_quote (const char *in, char **out)
1002 {
1003 size_t len = strlen (in);
1004 if (len && in[0] == '"' && in[len - 1] == '"')
1005 {
1006 const char *p;
1007 char *q;
1008
1009 for (p = in + 1; p < in + len - 1; p++)
1010 if (*p == '\\' || *p == '"')
1011 len++;
1012
1013 *out = mu_alloc (len + 1);
1014 q = *out;
1015 p = in;
1016 *q++ = *p++;
1017 while (p[1])
1018 {
1019 if (*p == '\\' || *p == '"')
1020 *q++ = '\\';
1021 *q++ = *p++;
1022 }
1023 *q++ = *p++;
1024 *q = 0;
1025 }
1026 else
1027 *out = mu_strdup (in);
1028 }
1029
1030 void
mh_expand_aliases(mu_message_t msg,mu_address_t * addr_to,mu_address_t * addr_cc,mu_address_t * addr_bcc)1031 mh_expand_aliases (mu_message_t msg,
1032 mu_address_t *addr_to,
1033 mu_address_t *addr_cc,
1034 mu_address_t *addr_bcc)
1035 {
1036 mu_header_t hdr;
1037 size_t i, num;
1038 const char *buf;
1039
1040 mu_message_get_header (msg, &hdr);
1041 mu_header_get_field_count (hdr, &num);
1042 for (i = 1; i <= num; i++)
1043 {
1044 if (mu_header_sget_field_name (hdr, i, &buf) == 0)
1045 {
1046 if (mu_c_strcasecmp (buf, MU_HEADER_TO) == 0
1047 || mu_c_strcasecmp (buf, MU_HEADER_CC) == 0
1048 || mu_c_strcasecmp (buf, MU_HEADER_BCC) == 0)
1049 {
1050 char *value;
1051 mu_address_t addr = NULL;
1052 int incl;
1053
1054 mu_header_aget_field_value_unfold (hdr, i, &value);
1055
1056 mh_alias_expand (value, &addr, &incl);
1057 free (value);
1058 if (mu_c_strcasecmp (buf, MU_HEADER_TO) == 0)
1059 mu_address_union (addr_to, addr);
1060 else if (mu_c_strcasecmp (buf, MU_HEADER_CC) == 0)
1061 mu_address_union (addr_cc ? addr_cc : addr_to, addr);
1062 else if (mu_c_strcasecmp (buf, MU_HEADER_BCC) == 0)
1063 mu_address_union (addr_bcc ? addr_bcc : addr_to, addr);
1064 }
1065 }
1066 }
1067 }
1068
1069 int
mh_draft_message(const char * name,const char * msgspec,char ** pname)1070 mh_draft_message (const char *name, const char *msgspec, char **pname)
1071 {
1072 mu_url_t url;
1073 size_t uid;
1074 int rc;
1075 mu_mailbox_t mbox;
1076 const char *path;
1077
1078 mbox = mh_open_folder (name, MU_STREAM_RDWR);
1079 if (!mbox)
1080 return 1;
1081
1082 mu_mailbox_get_url (mbox, &url);
1083
1084 if (strcmp (msgspec, "new") == 0)
1085 {
1086 mu_property_t prop;
1087
1088 rc = mu_mailbox_uidnext (mbox, &uid);
1089 if (rc)
1090 {
1091 mu_error (_("cannot obtain sequence number for the new message: %s"),
1092 mu_strerror (rc));
1093 exit (1);
1094 }
1095 mu_mailbox_get_property (mbox, &prop);
1096 mu_property_set_value (prop, "cur", mu_umaxtostr (0, uid), 1);
1097 }
1098 else
1099 {
1100 char *argv[2];
1101 mu_msgset_t msgset;
1102
1103 argv[0] = (char*) msgspec;
1104 argv[1] = NULL;
1105 mh_msgset_parse (&msgset, mbox, 1, argv, "cur");
1106 if (!mh_msgset_single_message (msgset))
1107 mu_error (_("only one message at a time!"));
1108 else
1109 uid = mh_msgset_first (msgset, RET_UID);
1110 mu_msgset_free (msgset);
1111 }
1112
1113 mu_url_sget_path (url, &path);
1114 rc = mu_asprintf (pname, "%s/%lu", path, (unsigned long) uid);
1115 if (rc)
1116 {
1117 mu_diag_funcall (MU_DIAG_ERROR, "mu_asprintf", NULL, rc);
1118 exit (1);
1119 }
1120 mu_mailbox_close (mbox);
1121 mu_mailbox_destroy (&mbox);
1122 return rc;
1123 }
1124
1125 char *
mh_safe_make_file_name(const char * dir,const char * file)1126 mh_safe_make_file_name (const char *dir, const char *file)
1127 {
1128 char *name = mu_make_file_name (dir, file);
1129 if (!name)
1130 {
1131 mu_diag_funcall (MU_DIAG_ERROR, "mu_make_file_name", NULL, ENOMEM);
1132 abort ();
1133 }
1134 return name;
1135 }
1136
1137 int
mh_width(void)1138 mh_width (void)
1139 {
1140 struct winsize ws;
1141 ws.ws_col = ws.ws_row = 0;
1142 if ((ioctl(1, TIOCGWINSZ, (char *) &ws) < 0) || ws.ws_col == 0)
1143 return 80; /* FIXME: Should we exit()/abort() if col <= 0 ? */
1144 return ws.ws_col;
1145 }
1146
1147