1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2009-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 #include "mail.h"
18 #include "mailutils/kwd.h"
19 
20 #define MAILVAR_ALIAS  0x0001
21 #define MAILVAR_RDONLY 0x0002
22 #define MAILVAR_HIDDEN 0x0004
23 
24 #define MAILVAR_TYPEMASK(type) (1<<(8+(type)))
25 
26 enum mailvar_cmd
27   {
28     mailvar_cmd_set,
29     mailvar_cmd_unset
30   };
31 
32 struct mailvar_symbol
33 {
34   struct mailvar_variable var;
35   int flags;
36   char *descr;
37   int (*handler) (enum mailvar_cmd, struct mailvar_variable *);
38 };
39 
40 mu_list_t mailvar_list = NULL;
41 
42 static int set_decode_fallback (enum mailvar_cmd cmd,
43 				struct mailvar_variable *);
44 static int set_replyregex (enum mailvar_cmd cmd,
45 			   struct mailvar_variable *);
46 static int set_screen (enum mailvar_cmd cmd,
47 		       struct mailvar_variable *);
48 static int set_verbose (enum mailvar_cmd cmd,
49 			struct mailvar_variable *);
50 static int set_debug (enum mailvar_cmd cmd,
51 		      struct mailvar_variable *);
52 static int set_folder (enum mailvar_cmd cmd,
53 		       struct mailvar_variable *);
54 static int set_headline (enum mailvar_cmd,
55 			 struct mailvar_variable *);
56 static int set_outfilename (enum mailvar_cmd,
57 			    struct mailvar_variable *);
58 static int set_escape (enum mailvar_cmd,
59 		       struct mailvar_variable *);
60 
61 struct mailvar_symbol mailvar_tab[] =
62   {
63     /* FIXME: */
64     { { mailvar_name_allnet, }, MAILVAR_HIDDEN },
65 
66     /* For compatibility with other mailx implementations.
67        Never used, always true. */
68     { { mailvar_name_append, },
69       MAILVAR_TYPEMASK (mailvar_type_boolean) | MAILVAR_RDONLY,
70       /* TRANSLATORS: "mbox" is the name of a command. Don't translate it. */
71       N_("messages saved in mbox are appended to the end rather than prepended") },
72     { { mailvar_name_appenddeadletter, },
73       MAILVAR_TYPEMASK (mailvar_type_boolean),
74       /* TRANSLATORS: Don't translate "dead.letter". */
75       N_("append the contents of canceled letter to dead.letter file") },
76     { { mailvar_name_askbcc, },
77       MAILVAR_TYPEMASK (mailvar_type_boolean),
78       N_("prompt user for bcc before composing the message") },
79     { { mailvar_name_askcc, },
80       MAILVAR_TYPEMASK (mailvar_type_boolean),
81       N_("prompt user for cc before composing the message") },
82     { { mailvar_name_ask, },
83       MAILVAR_TYPEMASK (mailvar_type_boolean),
84       N_("prompt user for subject before composing the message") },
85     { { mailvar_name_asksub, }, MAILVAR_ALIAS, NULL },
86     { { mailvar_name_autoinc, },
87       MAILVAR_TYPEMASK (mailvar_type_boolean),
88       N_("automatically incorporate newly arrived messages")},
89     { { mailvar_name_autoprint, },
90       MAILVAR_TYPEMASK (mailvar_type_boolean),
91       /* TRANSLATORS: "delete" and "dp" are command names. */
92       N_("delete command behaves like dp") },
93     { { mailvar_name_bang, },
94       MAILVAR_TYPEMASK (mailvar_type_boolean),
95       N_("replace every occurrence of ! in arguments to the shell command"
96 	 " with the last executed command") },
97     { { mailvar_name_charset, },
98       MAILVAR_TYPEMASK (mailvar_type_string),
99       N_("output character set for decoded header fields") },
100     { { mailvar_name_cmd, },
101       MAILVAR_TYPEMASK (mailvar_type_string),
102       /* TRANSLATORS: "pipe" is the command name. */
103       N_("default shell command for pipe") },
104     { { mailvar_name_columns, },
105       MAILVAR_TYPEMASK (mailvar_type_number),
106       N_("number of columns on terminal screen") },
107     { { mailvar_name_crt, },
108       MAILVAR_TYPEMASK (mailvar_type_number) |
109       MAILVAR_TYPEMASK (mailvar_type_boolean),
110       N_("if numeric, sets the minimum number of output lines needed "
111 	 "to engage paging; if boolean, use the height of the terminal "
112 	 "screen to compute the threshold") },
113     { { mailvar_name_datefield, },
114       MAILVAR_TYPEMASK (mailvar_type_boolean),
115       N_("get date from the `Date:' header, instead of the envelope") },
116     { { mailvar_name_debug, },
117       MAILVAR_TYPEMASK (mailvar_type_string) |
118 	MAILVAR_TYPEMASK (mailvar_type_boolean),
119       N_("set Mailutils debug level"),
120       set_debug },
121     { { mailvar_name_decode_fallback, },
122       MAILVAR_TYPEMASK (mailvar_type_string),
123       N_("how to represent characters that cannot be rendered using the "
124 	 "current character set"),
125       set_decode_fallback },
126     { { mailvar_name_dot, },
127       MAILVAR_TYPEMASK (mailvar_type_boolean),
128       N_("input message is terminated with a dot alone on a line") },
129     { { mailvar_name_editheaders, },
130       MAILVAR_TYPEMASK (mailvar_type_boolean),
131       N_("allow editing message headers while composing") },
132     { { mailvar_name_emptystart, },
133       MAILVAR_TYPEMASK (mailvar_type_boolean),
134       N_("start interactive mode if the mailbox is empty") },
135     { { mailvar_name_escape, },
136       MAILVAR_TYPEMASK (mailvar_type_string),
137       N_("command escape character"),
138       set_escape },
139     { { mailvar_name_flipr, },
140       MAILVAR_TYPEMASK (mailvar_type_boolean),
141       N_("swap the meaning of reply and Reply commands") },
142     { { mailvar_name_folder, },
143       MAILVAR_TYPEMASK (mailvar_type_string),
144       N_("folder directory name"),
145       set_folder },
146     { { mailvar_name_fromfield, },
147       MAILVAR_TYPEMASK (mailvar_type_boolean),
148       N_("get sender address from the `From:' header, instead of "
149 	 "the envelope") },
150     { { mailvar_name_gnu_last_command, },
151       MAILVAR_TYPEMASK (mailvar_type_string) | MAILVAR_RDONLY,
152       N_("last executed command line") },
153     { { mailvar_name_header, },
154       MAILVAR_TYPEMASK (mailvar_type_boolean),
155       N_("run the `headers' command after entering interactive mode") },
156     { { mailvar_name_headline, },
157       MAILVAR_TYPEMASK (mailvar_type_string),
158       N_("format string to use for the header summary"),
159       set_headline },
160     { { mailvar_name_hold, },
161       MAILVAR_TYPEMASK (mailvar_type_boolean),
162       N_("hold the read or saved messages in the system mailbox") },
163     { { mailvar_name_ignore, },
164       MAILVAR_TYPEMASK (mailvar_type_boolean),
165       N_("ignore keyboard interrupts when composing messages") },
166     { { mailvar_name_ignoreeof, },
167       MAILVAR_TYPEMASK (mailvar_type_boolean),
168       N_("ignore EOF character") },
169     { { mailvar_name_indentprefix, },
170       MAILVAR_TYPEMASK (mailvar_type_string),
171       N_("string used by the ~m escape for indenting quoted messages") },
172     { { mailvar_name_inplacealiases, },
173       MAILVAR_TYPEMASK (mailvar_type_boolean),
174       N_("expand aliases in the address header field "
175 	 "before starting composing the message") },
176     /* For compatibility with other mailx implementations.
177        Never used, always true. */
178     { { mailvar_name_keep, },
179       MAILVAR_TYPEMASK (mailvar_type_boolean) | MAILVAR_RDONLY,
180       N_("keep the empty user's system mailbox,"
181 	 " instead of removing it") },
182     { { mailvar_name_keepsave, },
183       MAILVAR_TYPEMASK (mailvar_type_boolean),
184       N_("keep saved messages in system mailbox too") },
185     { { mailvar_name_mailx, },
186       MAILVAR_TYPEMASK (mailvar_type_boolean),
187       N_("enable mailx compatibility mode") },
188     { { mailvar_name_metamail, },
189       MAILVAR_TYPEMASK (mailvar_type_boolean),
190       N_("interpret the content of message parts; if set to a string "
191 	 "specifies the name of the external metamail command") },
192     { { mailvar_name_metoo, },
193       MAILVAR_TYPEMASK (mailvar_type_boolean),
194       N_("do not remove sender addresses from the recipient list") },
195     { { mailvar_name_mimenoask, },
196       MAILVAR_TYPEMASK (mailvar_type_string),
197       N_("a comma-separated list of MIME types for which "
198 	 "no confirmation is needed before running metamail interpreter") },
199     { { mailvar_name_mode, },
200       MAILVAR_TYPEMASK (mailvar_type_string) | MAILVAR_RDONLY,
201       N_("the name of current operation mode") },
202     { { mailvar_name_nullbody, },
203       MAILVAR_TYPEMASK (mailvar_type_boolean),
204       N_("accept messages with an empty body") },
205     { { mailvar_name_nullbodymsg, },
206       MAILVAR_TYPEMASK (mailvar_type_string),
207       N_("display this text when sending a message with empty body") },
208     { { mailvar_name_outfolder, },
209       MAILVAR_TYPEMASK (mailvar_type_string) |
210       MAILVAR_TYPEMASK (mailvar_type_boolean),
211       N_("If boolean, causes the files used to record outgoing messages to"
212 	 " be located in the directory specified by the folder variable"
213 	 " (unless the pathname is absolute).\n"
214 	 "If string, names the directory where to store these files.") },
215     { { mailvar_name_page, },
216       MAILVAR_TYPEMASK (mailvar_type_boolean),
217       N_("pipe command terminates each message with a formfeed") },
218     { { mailvar_name_prompt, },
219       MAILVAR_TYPEMASK (mailvar_type_string),
220       N_("command prompt sequence") },
221     { { mailvar_name_quit, },
222       MAILVAR_TYPEMASK (mailvar_type_boolean),
223       N_("keyboard interrupts terminate the program") },
224     { { mailvar_name_rc, },
225       MAILVAR_TYPEMASK (mailvar_type_boolean),
226       N_("read the system-wide configuration file upon startup") },
227     { { mailvar_name_readonly, },
228       MAILVAR_TYPEMASK (mailvar_type_boolean),
229       N_("mailboxes are opened in readonly mode") },
230     { { mailvar_name_record, },
231       MAILVAR_TYPEMASK (mailvar_type_string),
232       N_("save outgoing messages in this file") },
233     { { mailvar_name_recursivealiases, },
234       MAILVAR_TYPEMASK (mailvar_type_boolean),
235       N_("recursively expand aliases") },
236     { { mailvar_name_regex, },
237       MAILVAR_TYPEMASK (mailvar_type_boolean),
238       N_("use of regular expressions in search message specifications") },
239     { { mailvar_name_replyprefix, },
240       MAILVAR_TYPEMASK (mailvar_type_string),
241       N_("prefix for the subject line of a reply message") },
242     { { mailvar_name_replyregex, },
243       MAILVAR_TYPEMASK (mailvar_type_string),
244       N_("regexp for recognizing subject lines of reply messages"),
245       set_replyregex },
246     { { mailvar_name_return_address },
247       MAILVAR_TYPEMASK (mailvar_type_string),
248       N_("return address for outgoing messages") },
249     { { mailvar_name_save, },
250       MAILVAR_TYPEMASK (mailvar_type_boolean),
251       N_("store aborted messages in the user's dead.file") },
252     { { mailvar_name_screen, },
253       MAILVAR_TYPEMASK (mailvar_type_number),
254       N_("number of lines on terminal screen"),
255       set_screen },
256     { { mailvar_name_sendmail, },
257       MAILVAR_TYPEMASK (mailvar_type_string),
258       N_("URL of the mail transport agent") },
259     /* FIXME: Not yet used. */
260     { { mailvar_name_sendwait, },
261       MAILVAR_TYPEMASK (mailvar_type_boolean) | MAILVAR_HIDDEN, NULL },
262     { { mailvar_name_sign, },
263       MAILVAR_TYPEMASK (mailvar_type_string),
264       N_("signature for use with the ~a command") },
265     { { mailvar_name_Sign, },
266       MAILVAR_TYPEMASK (mailvar_type_string),
267       N_("name of the signature file for use with the ~A command") },
268     { { mailvar_name_showenvelope, },
269       MAILVAR_TYPEMASK (mailvar_type_boolean),
270       N_("`print' command includes the SMTP envelope in its output") },
271     { { mailvar_name_showto, },
272       MAILVAR_TYPEMASK (mailvar_type_boolean),
273       N_("if the message was sent by me, print its recipient address "
274 	 "in the header summary") },
275     { { mailvar_name_toplines, },
276       MAILVAR_TYPEMASK (mailvar_type_number),
277       N_("number of lines to be displayed by `top' or `Top'") },
278     { { mailvar_name_variable_pretty_print, },
279       MAILVAR_TYPEMASK (mailvar_type_boolean),
280       N_("print variables with short descriptions") },
281     { { mailvar_name_varpp, }, MAILVAR_ALIAS },
282     { { mailvar_name_variable_strict, },
283       MAILVAR_TYPEMASK (mailvar_type_boolean),
284       N_("perform strict checking when setting variables") },
285     { { mailvar_name_varstrict, }, MAILVAR_ALIAS },
286     { { mailvar_name_verbose, },
287       MAILVAR_TYPEMASK (mailvar_type_boolean),
288       N_("verbosely trace the process of message delivery"),
289       set_verbose },
290 
291     { { mailvar_name_useragent, },
292       MAILVAR_TYPEMASK (mailvar_type_boolean),
293       N_("add the `User-Agent' header to the outgoing messages") },
294     { { mailvar_name_xmailer, }, MAILVAR_ALIAS },
295 
296     { { mailvar_name_mime },
297       MAILVAR_TYPEMASK (mailvar_type_boolean),
298       N_("always compose MIME messages") },
299 
300     { { mailvar_name_fullnames },
301       MAILVAR_TYPEMASK (mailvar_type_boolean),
302       N_("when replying, preserve personal parts of recipient emails") },
303 
304     { { mailvar_name_outfilename },
305       MAILVAR_TYPEMASK (mailvar_type_string),
306       N_("how to create outgoing file name: local, domain, email"),
307       set_outfilename },
308 
309     { { mailvar_name_PID },
310       MAILVAR_TYPEMASK (mailvar_type_string) | MAILVAR_RDONLY,
311       N_("PID of this process") },
312 
313     /* These will be implemented later */
314     { { mailvar_name_onehop, }, MAILVAR_HIDDEN, NULL },
315 
316     { { mailvar_name_quiet, },
317       MAILVAR_TYPEMASK (mailvar_type_boolean) | MAILVAR_HIDDEN,
318       N_("suppress the printing of the version when first invoked") },
319 
320     { { NULL }, }
321   };
322 
323 static int mailvar_symbol_count =
324   sizeof (mailvar_tab) / sizeof (mailvar_tab[0]) - 1;
325 
326 struct mailvar_symbol *
find_mailvar_symbol(const char * var)327 find_mailvar_symbol (const char *var)
328 {
329   struct mailvar_symbol *ep;
330   for (ep = mailvar_tab; ep->var.name; ep++)
331     if (strcmp (ep->var.name, var) == 0)
332       {
333 	while ((ep->flags & MAILVAR_ALIAS) && ep > mailvar_tab)
334 	  ep--;
335 	return ep;
336       }
337   return NULL;
338 }
339 
340 static void
print_descr(mu_stream_t out,const char * s,int n,int doc_col,int rmargin,char * pfx)341 print_descr (mu_stream_t out, const char *s, int n,
342 	     int doc_col, int rmargin, char *pfx)
343 {
344   mu_stream_stat_buffer stat;
345 
346   if (!s)
347     return;
348 
349   mu_stream_set_stat (out, MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT), stat);
350   stat[MU_STREAM_STAT_OUT] = n;
351   do
352     {
353       const char *p;
354       const char *space = NULL;
355 
356       if (stat[MU_STREAM_STAT_OUT] && pfx)
357 	mu_stream_printf (out, "%s", pfx);
358 
359       while (stat[MU_STREAM_STAT_OUT] < doc_col)
360 	mu_stream_write (out, " ", 1, NULL);
361 
362       for (p = s; *p && p < s + (rmargin - doc_col); p++)
363 	if (*p == '\n')
364 	  {
365 	    space = p;
366 	    break;
367 	  }
368 	else if (mu_isspace (*p))
369 	  space = p;
370 
371       if (!space || (*space != '\n' && p < s + (rmargin - doc_col)))
372 	{
373 	  mu_stream_printf (out, "%s", s);
374 	  s += strlen (s);
375 	}
376       else
377 	{
378 	  for (; s < space; s++)
379 	    mu_stream_write (out, s, 1, NULL);
380 	  for (; *s && mu_isspace (*s); s++)
381 	    ;
382 	}
383       mu_stream_printf (out, "\n");
384       stat[MU_STREAM_STAT_OUT] = 1;
385     }
386   while (*s);
387   mu_stream_set_stat (out, 0, NULL);
388 }
389 
390 /* Functions for dealing with internal mailvar_list variables */
391 static int
mailvar_variable_comp(const void * a,const void * b)392 mailvar_variable_comp (const void *a, const void *b)
393 {
394   const struct mailvar_variable *v1 = a;
395   const struct mailvar_variable *v2 = b;
396 
397   return strcmp (v1->name, v2->name);
398 }
399 
400 /* Find mailvar_list entry VAR. If not found and CREATE is not NULL, then
401    create the (unset and untyped) variable */
402 struct mailvar_variable *
mailvar_find_variable(const char * name,int create)403 mailvar_find_variable (const char *name, int create)
404 {
405   struct mailvar_symbol *sym;
406   struct mailvar_variable *var;
407 
408   sym = find_mailvar_symbol (name);
409   if (sym)
410     var = &sym->var;
411   else
412     {
413       struct mailvar_variable entry, *p;
414 
415       entry.name = (char*)name;
416       /* Store it into the list */
417       if (mailvar_list == NULL)
418 	{
419 	  mu_list_create (&mailvar_list);
420 	  mu_list_set_comparator (mailvar_list, mailvar_variable_comp);
421 	}
422 
423       if (mu_list_locate (mailvar_list, &entry, (void**)&p))
424 	{
425 	  if (!create)
426 	    return 0;
427 	  else
428 	    {
429 	      p = mu_alloc (sizeof *p);
430 	      p->name = mu_strdup (name);
431 	      mu_list_prepend (mailvar_list, p);
432 	    }
433 	}
434       var = p;
435       var->set = 0;
436       var->type = mailvar_type_whatever;
437       var->value.number = 0;
438     }
439 
440   return var;
441 }
442 
443 
444 /* Retrieve the value of a specified variable of given type.
445    The value is stored in the location pointed to by PTR variable.
446    VARIABLE and TYPE specify the variable name and type. If the
447    variable is not found and WARN is not null, the warning message
448    is issued.
449 
450    Return value is 0 if the variable is found, 1 otherwise.
451    If PTR is not NULL, it must point to
452 
453    int           if TYPE is mailvar_type_number or mailvar_type_boolean
454    const char *  if TYPE is mailvar_type_string.
455 
456    Passing PTR=NULL may be used to check whether the variable is set
457    without retrieving its value. */
458 
459 int
mailvar_get(void * ptr,const char * variable,enum mailvar_type type,int warn)460 mailvar_get (void *ptr, const char *variable, enum mailvar_type type, int warn)
461 {
462   struct mailvar_variable *var = mailvar_find_variable (variable, 0);
463 
464   if (!var->set || (type != mailvar_type_whatever && var->type != type))
465     {
466       if (warn)
467 	mu_error (_("No value set for \"%s\""), variable);
468       return 1;
469     }
470   if (ptr)
471     switch (type)
472       {
473       case mailvar_type_string:
474 	*(char**)ptr = var->value.string;
475 	break;
476 
477       case mailvar_type_number:
478 	*(int*)ptr = var->value.number;
479 	break;
480 
481       case mailvar_type_boolean:
482 	*(int*)ptr = var->value.bool;
483 	break;
484 
485       default:
486 	break;
487       }
488 
489   return 0;
490 }
491 
492 int
mailvar_is_true(char const * name)493 mailvar_is_true (char const *name)
494 {
495   return mailvar_get (NULL, name, mailvar_type_boolean, 0) == 0;
496 }
497 
498 /* Initialize mailvar_list entry: clear set indicator and free any memory
499    associated with the data */
500 void
mailvar_variable_reset(struct mailvar_variable * var)501 mailvar_variable_reset (struct mailvar_variable *var)
502 {
503   if (!var->set)
504     return;
505 
506   switch (var->type)
507     {
508     case mailvar_type_string:
509       free (var->value.string);
510       var->value.string = NULL;
511       break;
512 
513     default:
514       break;
515     }
516   var->set = 0;
517 }
518 
519 /* Set environement
520    The  mailvar_set() function adds to the mailvar_list the VARIABLE
521    with the given VALUE, if VARIABLE does not already exist.
522    If it does exist in the mailvar_list, then its value is changed
523    to VALUE if MOPTF_OVERWRITE bit is set in FLAGS, otherwise the
524    value is not changed.
525 
526    Unless MOPTF_QUIET bit is set in FLAGS, the function performs
527    semantic check, using the builtin options table.
528 
529    If VALUE is null the VARIABLE is unset. */
530 int
mailvar_set(const char * variable,void * value,enum mailvar_type type,int flags)531 mailvar_set (const char *variable, void *value, enum mailvar_type type,
532 	     int flags)
533 {
534   struct mailvar_variable *var, newvar;
535   const struct mailvar_symbol *sym = find_mailvar_symbol (variable);
536   enum mailvar_cmd cmd =
537     (flags & MOPTF_UNSET) ? mailvar_cmd_unset : mailvar_cmd_set;
538 
539   if (!(flags & MOPTF_QUIET) && mailvar_is_true (mailvar_name_variable_strict))
540     {
541       if (!sym)
542 	mu_diag_output (MU_DIAG_WARNING, _("setting unknown variable %s"),
543 			variable);
544       else if (sym->flags & MAILVAR_RDONLY)
545 	{
546 	  mu_error (cmd == mailvar_cmd_set
547 		     ? _("Cannot set read-only variable %s")
548 		     : _("Cannot unset read-only variable %s"),
549 		    variable);
550 	  return 1;
551 	}
552       else if (!(sym->flags & MAILVAR_TYPEMASK (type))
553 	       && cmd == mailvar_cmd_set)
554 	{
555 	  mu_error (_("Wrong type for %s"), variable);
556 	  return 1;
557 	}
558     }
559 
560   var = mailvar_find_variable (variable, cmd == mailvar_cmd_set);
561 
562   if (!var || (var->set && !(flags & MOPTF_OVERWRITE)))
563     return 0;
564 
565   newvar.name = var->name;
566   newvar.type = var->type;
567   newvar.set = 0;
568   memset (&newvar.value, 0, sizeof (newvar.value));
569 
570   switch (cmd)
571     {
572     case mailvar_cmd_set:
573       if (value)
574 	{
575 	  switch (type)
576 	    {
577 	    case mailvar_type_number:
578 	      newvar.value.number = *(int*)value;
579 	      break;
580 
581 	    case mailvar_type_string:
582 	      {
583 		char *p = strdup (value);
584 		if (!p)
585 		  {
586 		    mu_error ("%s", _("Not enough memory"));
587 		    return 1;
588 		  }
589 		newvar.value.string = p;
590 	      }
591 	      break;
592 
593 	    case mailvar_type_boolean:
594 	      newvar.value.bool = *(int*)value;
595 	      break;
596 
597 	    default:
598 	      abort();
599 	    }
600 	  newvar.set = 1;
601 	}
602       newvar.type = type;
603       if (sym
604 	  && sym->handler
605 	  && sym->flags & MAILVAR_TYPEMASK (type)
606 	  && sym->handler (cmd, &newvar))
607 	{
608 	  mailvar_variable_reset (&newvar);
609 	  return 1;
610 	}
611       mailvar_variable_reset (var);
612       *var = newvar;
613       break;
614 
615     case mailvar_cmd_unset:
616       if (sym
617 	  && sym->handler
618 	  && sym->handler (cmd, var))
619 	return 1;
620       mailvar_variable_reset (var);
621     }
622 
623   return 0;
624 }
625 
626 static int
set_folder(enum mailvar_cmd cmd,struct mailvar_variable * var)627 set_folder (enum mailvar_cmd cmd, struct mailvar_variable *var)
628 {
629   int rc;
630 
631   if (var->value.string)
632     {
633       char *p = mu_tilde_expansion (var->value.string, MU_HIERARCHY_DELIMITER,
634 				    NULL);
635       if (!p)
636 	mu_alloc_die ();
637       if (var->set)
638 	free (var->value.string);
639       var->value.string = p;
640     }
641 
642   rc = mu_set_folder_directory (var->value.string);
643   if (rc)
644     mu_diag_funcall (MU_DIAG_ERROR, "mu_set_folder_directory",
645 		     var->value.string, rc);
646   return rc;
647 }
648 
649 
650 static int
set_headline(enum mailvar_cmd cmd,struct mailvar_variable * var)651 set_headline (enum mailvar_cmd cmd, struct mailvar_variable *var)
652 {
653   if (cmd == mailvar_cmd_unset)
654     return 1;
655 
656   mail_compile_headline (var->value.string);
657   return 0;
658 }
659 
660 static int
set_decode_fallback(enum mailvar_cmd cmd,struct mailvar_variable * var)661 set_decode_fallback (enum mailvar_cmd cmd, struct mailvar_variable *var)
662 {
663   char *value;
664   int rc;
665 
666   switch (cmd)
667     {
668     case mailvar_cmd_set:
669       value = var->value.string;
670       break;
671 
672     case mailvar_cmd_unset:
673       value = "none";
674     }
675 
676   rc = mu_set_default_fallback (value);
677   if (rc)
678     mu_error (_("Incorrect value for decode-fallback"));
679   return rc;
680 }
681 
682 static int
set_replyregex(enum mailvar_cmd cmd,struct mailvar_variable * var)683 set_replyregex (enum mailvar_cmd cmd, struct mailvar_variable *var)
684 {
685   int rc;
686   char *err;
687 
688   switch (cmd)
689     {
690     case mailvar_cmd_set:
691       if ((rc = mu_unre_set_regex (var->value.string, 0, &err)))
692 	{
693 	  if (err)
694 	    mu_error ("%s: %s", mu_strerror (rc), err);
695 	  else
696 	    mu_error ("%s", mu_strerror (rc));
697 	  return 1;
698 	}
699       break;
700 
701     case mailvar_cmd_unset:
702       return 1;
703     }
704 
705   return 0;
706 }
707 
708 static int
set_screen(enum mailvar_cmd cmd,struct mailvar_variable * var)709 set_screen (enum mailvar_cmd cmd, struct mailvar_variable *var)
710 {
711   if (cmd == mailvar_cmd_set)
712     page_invalidate (1);
713   return 0;
714 }
715 
716 #define DEFAULT_DEBUG_LEVEL  MU_DEBUG_LEVEL_UPTO (MU_DEBUG_TRACE7)
717 
718 static int
set_verbose(enum mailvar_cmd cmd,struct mailvar_variable * var)719 set_verbose (enum mailvar_cmd cmd, struct mailvar_variable *var)
720 {
721   switch (cmd)
722     {
723     case mailvar_cmd_set:
724       mu_debug_set_category_level (MU_DEBCAT_APP, DEFAULT_DEBUG_LEVEL);
725       break;
726 
727     case mailvar_cmd_unset:
728       mu_debug_set_category_level (MU_DEBCAT_APP, 0);
729     }
730   return 0;
731 }
732 
733 static int
set_debug(enum mailvar_cmd cmd,struct mailvar_variable * var)734 set_debug (enum mailvar_cmd cmd, struct mailvar_variable *var)
735 {
736   mu_debug_clear_all ();
737 
738   if (cmd == mailvar_cmd_set)
739     {
740       if (var->type == mailvar_type_boolean)
741 	{
742 	  if (var->set)
743 	    mu_debug_set_category_level (MU_DEBCAT_ALL, DEFAULT_DEBUG_LEVEL);
744 	  return 0;
745 	}
746       mu_debug_parse_spec (var->value.string);
747     }
748   return 0;
749 }
750 
751 static int
set_outfilename(enum mailvar_cmd cmd,struct mailvar_variable * var)752 set_outfilename (enum mailvar_cmd cmd, struct mailvar_variable *var)
753 {
754   static struct mu_kwd kwtab[] = {
755     { "local", outfilename_local },
756     { "email", outfilename_email },
757     { "domain", outfilename_domain },
758     { NULL }
759   };
760 
761   switch (cmd)
762     {
763     case mailvar_cmd_set:
764       if (mu_kwd_xlat_name (kwtab, var->value.string, &outfilename_mode))
765 	{
766 	  mu_error (_("unsupported value for %s"), var->name);
767 	  return 1;
768 	}
769       break;
770 
771     default:
772       return 1;
773     }
774   return 0;
775 }
776 
777 static int
set_escape(enum mailvar_cmd cmd,struct mailvar_variable * var)778 set_escape (enum mailvar_cmd cmd, struct mailvar_variable *var)
779 {
780   if (cmd == mailvar_cmd_set)
781     {
782       if (var->value.string[0] == 0)
783 	{
784 	  mu_error (_("escape character cannot be empty"));
785 	  return 1;
786 	}
787       else if (var->value.string[1] != 0)
788 	{
789 	  mu_error (_("escape value must be a single character"));
790 	  return 1;
791 	}
792     }
793   return 0;
794 }
795 
796 size_t
_mailvar_symbol_count(int set)797 _mailvar_symbol_count (int set)
798 {
799   if (!set)
800     return mailvar_symbol_count;
801   else
802     {
803       struct mailvar_symbol *s;
804       size_t count = 0;
805       for (s = mailvar_tab; s->var.name; s++)
806 	if (s->var.set)
807 	  count++;
808       return count;
809     }
810 }
811 
812 static int
mailvar_mapper(void ** itmv,size_t itmc,void * call_data)813 mailvar_mapper (void **itmv, size_t itmc, void *call_data)
814 {
815   return MU_LIST_MAP_OK;
816 }
817 
818 int
_mailvar_symbol_to_list(int set,mu_list_t list)819 _mailvar_symbol_to_list (int set, mu_list_t list)
820 {
821   struct mailvar_symbol *s;
822   for (s = mailvar_tab; s->var.name; s++)
823     if (!set || s->var.set)
824       mu_list_append (list, &s->var);
825   return 0;
826 }
827 
828 mu_list_t
mailvar_list_copy(int set)829 mailvar_list_copy (int set)
830 {
831   mu_list_t list = NULL;
832 
833   if (mailvar_list)
834     mu_list_map (mailvar_list, mailvar_mapper, NULL, 1, &list);
835   if (!list)
836     mu_list_create (&list);
837   _mailvar_symbol_to_list (set, list);
838   mu_list_sort (list, mailvar_variable_comp);
839   return list;
840 }
841 
842 
843 enum
844   {
845     MAILVAR_ITR_ALL = 0,         /* Return all variables */
846     MAILVAR_ITR_SET = 0x1,       /* Return only variables that have been set */
847     MAILVAR_ITR_WRITABLE = 0x2   /* Return only writable variables */
848   };
849 
850 struct mailvar_iterator
851 {
852   int flags;                /* MAILVAR_ITR_ bits */
853   const char *prefix;       /* Prefix to match */
854   int prefixlen;            /* Length of the prefix */
855   mu_list_t varlist;        /* List of collected variables */
856   mu_iterator_t varitr;     /* Iterator over varlist */
857 };
858 
859 /* Return next match from ITR */
860 const char *
mailvar_iterate_next(struct mailvar_iterator * itr)861 mailvar_iterate_next (struct mailvar_iterator *itr)
862 {
863   struct mailvar_variable *vp;
864 
865   while (!mu_iterator_is_done (itr->varitr))
866     {
867       size_t len;
868 
869       mu_iterator_current (itr->varitr, (void**) &vp);
870       mu_iterator_next (itr->varitr);
871 
872       if (itr->flags & MAILVAR_ITR_WRITABLE)
873 	{
874 	  const struct mailvar_symbol *sym = find_mailvar_symbol (vp->name);
875 	  if (sym && (sym->flags & MAILVAR_RDONLY))
876 	    continue;
877 	}
878 
879       if (strlen (vp->name) >= itr->prefixlen
880 	  && strncmp (vp->name, itr->prefix, itr->prefixlen) == 0)
881 	return strdup (vp->name);
882 
883       /* See if it's a negated boolean */
884       if (itr->prefixlen >= 2 && memcmp (itr->prefix, "no", 2) == 0
885 	  && (len = strlen (vp->name)) >= itr->prefixlen - 2
886 	  && memcmp (vp->name, itr->prefix + 2, itr->prefixlen - 2) == 0)
887 	{
888 	  char *p;
889 	  struct mailvar_symbol *sym = find_mailvar_symbol (vp->name);
890 	  if (sym && !(sym->flags & (MAILVAR_TYPEMASK (mailvar_type_boolean))))
891 	    continue;
892 	  p = malloc (len + 3);
893 	  if (p)
894 	    {
895 	      strcpy (p, "no");
896 	      strcpy (p + 2, vp->name);
897 	    }
898 	  return p;
899 	}
900     }
901   return NULL;
902 }
903 
904 /* Initialize iterator, return the name of the first match */
905 const char *
mailvar_iterate_first(int flags,const char * prefix,struct mailvar_iterator ** pitr)906 mailvar_iterate_first (int flags,
907 		       const char *prefix, struct mailvar_iterator **pitr)
908 {
909   struct mailvar_iterator *itr = mu_alloc (sizeof *itr);
910   itr->flags = flags;
911   itr->prefix = prefix;
912   itr->prefixlen = strlen (prefix);
913   itr->varlist = mailvar_list_copy (flags & MAILVAR_ITR_SET);
914   mu_list_get_iterator (itr->varlist, &itr->varitr);
915   mu_iterator_first (itr->varitr);
916   *pitr = itr;
917   return mailvar_iterate_next (itr);
918 }
919 
920 /* Release memory used by ITR */
921 void
mailvar_iterate_end(struct mailvar_iterator ** pitr)922 mailvar_iterate_end (struct mailvar_iterator **pitr)
923 {
924   if (pitr && *pitr)
925     {
926       struct mailvar_iterator *itr = *pitr;
927       mu_iterator_destroy (&itr->varitr);
928       mu_list_destroy (&itr->varlist);
929       free (itr);
930       *pitr = NULL;
931     }
932 }
933 
934 struct mailvar_print_closure
935 {
936   int prettyprint;
937   mu_stream_t out;
938   int width;
939 };
940 
941 static int
mailvar_printer(void * item,void * data)942 mailvar_printer (void *item, void *data)
943 {
944   struct mailvar_variable *vp = item;
945   struct mailvar_print_closure *clos = data;
946 
947   if (clos->prettyprint)
948     {
949       const struct mailvar_symbol *sym = find_mailvar_symbol (vp->name);
950 
951       if (sym)
952 	{
953 	  if (sym->flags & MAILVAR_HIDDEN)
954 	    return 0;
955 	  if (sym->flags & MAILVAR_RDONLY)
956 	    mu_stream_printf (clos->out, "# %s:\n", _("Read-only variable"));
957 	  print_descr (clos->out, gettext (sym->descr), 1, 3,
958 		       clos->width - 1, "# ");
959 	}
960     }
961   switch (vp->type)
962     {
963     case mailvar_type_number:
964       mu_stream_printf (clos->out, "%s=%d", vp->name, vp->value.number);
965       break;
966 
967     case mailvar_type_string:
968       mu_stream_printf (clos->out, "%s=\"%s\"", vp->name, vp->value.string);
969       break;
970 
971     case mailvar_type_boolean:
972       if (!vp->value.bool)
973 	mu_stream_printf (clos->out, "no");
974       mu_stream_printf (clos->out, "%s", vp->name);
975       break;
976 
977     case mailvar_type_whatever:
978       mu_stream_printf (clos->out, "%s %s", vp->name, _("oops?"));
979     }
980   mu_stream_printf (clos->out, "\n");
981   return 0;
982 }
983 
984 void
mailvar_print(int set)985 mailvar_print (int set)
986 {
987   mu_list_t varlist;
988   size_t count;
989   struct mailvar_print_closure clos;
990 
991   varlist = mailvar_list_copy (set);
992   mu_list_count (varlist, &count);
993   clos.out = open_pager (count);
994   clos.prettyprint = mailvar_is_true (mailvar_name_variable_pretty_print);
995   clos.width = util_screen_columns ();
996 
997   mu_list_foreach (varlist, mailvar_printer, &clos);
998   mu_list_destroy (&varlist);
999   mu_stream_unref (clos.out);
1000 }
1001 
1002 
1003 void
mailvar_variable_format(mu_stream_t stream,const struct mailvar_variable * var,const char * defval)1004 mailvar_variable_format (mu_stream_t stream,
1005 			 const struct mailvar_variable *var,
1006 			 const char *defval)
1007 {
1008   if (var)
1009     switch (var->type)
1010       {
1011       case mailvar_type_string:
1012 	mu_stream_printf (stream, "%s", var->value.string);
1013 	break;
1014 
1015       case mailvar_type_number:
1016 	mu_stream_printf (stream, "%d", var->value.number);
1017 	break;
1018 
1019       case mailvar_type_boolean:
1020 	mu_stream_printf (stream, "%s", var->set ? "yes" : "no");
1021 	break;
1022 
1023       default:
1024 	if (defval)
1025 	  mu_stream_printf (stream, "%s", defval);
1026 	break;
1027       }
1028 }
1029 
1030 
1031 static char *typestr[] =
1032   {
1033     N_("untyped"),
1034     N_("numeric"),
1035     N_("string"),
1036     N_("boolean")
1037   };
1038 
1039 static void
describe_symbol(mu_stream_t out,int width,const struct mailvar_symbol * sym)1040 describe_symbol (mu_stream_t out, int width, const struct mailvar_symbol *sym)
1041 {
1042   int i, t;
1043   const struct mailvar_symbol *ali;
1044   mu_stream_stat_buffer stat;
1045 
1046   mu_stream_set_stat (out, MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT),
1047 		      stat);
1048   mu_stream_printf (out, "%s", sym->var.name);
1049   for (ali = sym + 1; ali->var.name && ali->flags & MAILVAR_ALIAS; ali++)
1050     {
1051       size_t len = strlen (ali->var.name) + 2;
1052       if (stat[MU_STREAM_STAT_OUT] + len > width)
1053 	{
1054 	  stat[MU_STREAM_STAT_OUT] = 0;
1055 	  mu_stream_printf (out, "\n%s", ali->var.name);
1056 	}
1057       else
1058 	mu_stream_printf (out, ", %s", ali->var.name);
1059     }
1060   mu_stream_printf (out, "\n");
1061   mu_stream_set_stat (out, 0, NULL);
1062 
1063   mu_stream_printf (out, _("Type: "));
1064   for (i = 0, t = 0; i < sizeof (typestr) / sizeof (typestr[0]); i++)
1065     if (sym->flags & MAILVAR_TYPEMASK (i))
1066       {
1067 	if (t++)
1068 	  mu_stream_printf (out, " %s ", _("or"));
1069 	mu_stream_printf (out, "%s", gettext (typestr[i]));
1070       }
1071   if (!t)
1072     mu_stream_printf (out, "%s", gettext (typestr[0]));
1073   mu_stream_printf (out, "\n");
1074 
1075   mu_stream_printf (out, "%s", _("Current value: "));
1076   mailvar_variable_format (out, &sym->var, _("[not set]"));
1077 
1078   if (sym->flags & MAILVAR_RDONLY)
1079     mu_stream_printf (out, " [%s]", _("read-only"));
1080   mu_stream_printf (out, "\n");
1081 
1082   print_descr (out, gettext (sym->descr ? sym->descr : N_("Not documented")),
1083 	       1, 1, width - 1, NULL);
1084   mu_stream_printf (out, "\n");
1085 }
1086 
1087 int
mail_variable(int argc,char ** argv)1088 mail_variable (int argc, char **argv)
1089 {
1090   int pagelines = util_get_crt ();
1091   int width = util_screen_columns ();
1092   mu_stream_t out = open_pager (pagelines + 1);
1093 
1094   if (argc == 1)
1095     {
1096       struct mailvar_symbol *sym;
1097 
1098       for (sym = mailvar_tab; sym->var.name; sym++)
1099 	if (!(sym->flags & (MAILVAR_HIDDEN|MAILVAR_ALIAS)))
1100 	  describe_symbol (out, width, sym);
1101     }
1102   else
1103     {
1104       int i;
1105 
1106       for (i = 1; i < argc; i++)
1107 	{
1108 	  struct mailvar_symbol *sym = find_mailvar_symbol (argv[i]);
1109 	  if (!sym)
1110 	    mu_stream_printf (out, "%s: unknown\n", argv[i]);
1111 	  else
1112 	    describe_symbol (out, width, sym);
1113 	}
1114     }
1115   mu_stream_unref (out);
1116   return 0;
1117 }
1118 
1119 #ifdef WITH_READLINE
1120 static char *
mailvar_generator(int flags,const char * text,int state)1121 mailvar_generator (int flags, const char *text, int state)
1122 {
1123   static struct mailvar_iterator *itr;
1124   const char *p;
1125 
1126   if (!state)
1127     p = mailvar_iterate_first (flags, text, &itr);
1128   else
1129     p = mailvar_iterate_next (itr);
1130 
1131   if (!p)
1132     {
1133       mailvar_iterate_end (&itr);
1134       return NULL;
1135     }
1136   return strdup (p);
1137 }
1138 
1139 /* Completion generator for the "set" command */
1140 static char *
mailvar_set_generator(const char * text,int state)1141 mailvar_set_generator (const char *text, int state)
1142 {
1143   return mailvar_generator (MAILVAR_ITR_WRITABLE, text, state);
1144 }
1145 
1146 /* Completion generator for the "unset" command */
1147 static char *
mailvar_unset_generator(const char * text,int state)1148 mailvar_unset_generator (const char *text, int state)
1149 {
1150   return mailvar_generator (MAILVAR_ITR_SET | MAILVAR_ITR_WRITABLE,
1151 			    text, state);
1152 }
1153 
1154 /* Completion generator for the "variable" command */
1155 static char *
mailvar_variable_generator(const char * text,int state)1156 mailvar_variable_generator (const char *text, int state)
1157 {
1158   return mailvar_generator (MAILVAR_ITR_ALL, text, state);
1159 }
1160 
1161 /* Completion function for all three commands */
1162 char **
mailvar_set_compl(int argc,char ** argv,int point)1163 mailvar_set_compl (int argc, char **argv, int point)
1164 {
1165   /* Possible values for argv[0] are: set, unset, variable */
1166   char **matches =
1167     rl_completion_matches (point & COMPL_WS ? "" : argv[argc-1],
1168 			   argv[0][0] == 'u'
1169 			    ? mailvar_unset_generator
1170 			    : argv[0][0] == 's'
1171 			       ? mailvar_set_generator
1172 			       : mailvar_variable_generator);
1173   ml_attempted_completion_over ();
1174   if (matches && matches[1] == NULL) /* Exact match */
1175     {
1176       switch (argv[0][0])
1177 	{
1178 	case 'u':
1179 	  ml_set_completion_append_character (' ');
1180 	  break;
1181 
1182 	case 's':
1183 	  {
1184 	    struct mailvar_symbol *sym = find_mailvar_symbol (matches[0]);
1185 	    if (sym &&
1186 		(sym->flags & (MAILVAR_TYPEMASK (mailvar_type_string)
1187 			       | MAILVAR_TYPEMASK (mailvar_type_number))))
1188 	      ml_set_completion_append_character ('=');
1189 	    else
1190 	      ml_set_completion_append_character (' ');
1191 	  }
1192 	  break;
1193 
1194 	default:
1195 	  ml_set_completion_append_character (0);
1196 	}
1197     }
1198 
1199   return matches;
1200 }
1201 
1202 #endif
1203