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