1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Account, macro and variable handling.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 #ifndef HAVE_AMALGAMATION
45 * TODO in general it would be nice if it would be possible to define "macros"
46 * TODO etc. inside of other "macros"
49 #define MA_PRIME HSHSIZE
50 #define MA_HASH(S) (strhash(S) % MA_PRIME)
55 MA_TYPE_MASK
= MA_ACC
,
56 MA_UNDEF
= 1<<1 /* Unlink after lookup */
60 struct macro
*ma_next
;
62 struct mline
*ma_contents
;
63 size_t ma_maxlen
; /* Maximum line length */
64 enum ma_flags ma_flags
;
65 struct var
*ma_localopts
; /* `account' unroll list, for `localopts' */
71 char l_line
[VFIELD_SIZE(sizeof(size_t))];
81 struct lostack
*s_up
; /* Outer context */
82 struct macro
*s_mac
; /* Context (`account' or `define') */
83 struct var
*s_localopts
;
84 bool_t s_unroll
; /* Unroll? */
87 static struct macro
*_acc_curr
; /* Currently active account */
88 static struct lostack
*_localopts
; /* Currently executing macro unroll list */
90 /* TODO once we have a dynamically sized hashtable we could unite _macros and
91 * TODO _variables into a single hashtable, stripping down fun interface;
92 * TODO also, setting and clearing a variable can be easily joined */
93 static struct macro
*_macros
[MA_PRIME
]; /* TODO dynamically spaced */
94 static struct var
*_vars
[MA_PRIME
]; /* TODO dynamically spaced */
96 /* Special cased value string allocation */
97 static char * _vcopy(char const *str
);
98 static void _vfree(char *cp
);
100 /* Check for special housekeeping. */
101 static bool_t
_check_special_vars(char const *name
, bool_t enable
,
104 /* If a variable name begins with a lowercase-character and contains at
105 * least one '@', it is converted to all-lowercase. This is necessary
106 * for lookups of names based on email addresses.
108 * Following the standard, only the part following the last '@' should
109 * be lower-cased, but practice has established otherwise here */
110 static char const * _canonify(char const *vn
);
112 /* Locate a variable and return its variable node */
113 static struct var
* _lookup(char const *name
, ui_it h
, bool_t hisset
);
115 /* Line *cp* consists solely of WS and a } */
116 static bool_t
_is_closing_angle(char const *cp
);
118 /* Lookup for macros/accounts */
119 static struct macro
*_malook(char const *name
, struct macro
*data
,
122 /* Walk all lines of a macro and execute() them */
123 static int _maexec(struct macro
const *mp
, struct var
**unroll_store
);
125 /* User display helpers */
126 static int _list_macros(enum ma_flags mafl
);
129 static bool_t
_define1(char const *name
, enum ma_flags mafl
);
130 static void _undef1(char const *name
, enum ma_flags mafl
);
131 static void _freelines(struct mline
*lp
);
133 /* qsort(3) helper */
134 static int __var_list_all_cmp(void const *s1
, void const *s2
);
136 /* Update replay-log */
137 static void _localopts_add(struct lostack
*losp
, char const *name
,
139 static void _localopts_unroll(struct var
**vapp
);
142 _vcopy(char const *str
)
150 len
= strlen(str
) + 1;
152 memcpy(news
, str
, len
);
165 _check_special_vars(char const *name
, bool_t enable
, char **val
)
167 /* TODO _check_special_vars --> value cache
168 * TODO in general: some may not be unset etc. etc. All this shouldn't
169 * TODO be handled like this, but through a generic value-cache interface,
170 * TODO which may apply constaints *before* use; also see below */
175 if (strcmp(name
, "debug") == 0)
177 else if (strcmp(name
, "header") == 0)
178 flag
= OPT_N_FLAG
, enable
= !enable
;
179 else if (strcmp(name
, "skipemptybody") == 0)
181 else if (strcmp(name
, "verbose") == 0)
183 else if (strcmp(name
, "folder") == 0) {
184 rv
= (val
== NULL
|| var_folder_updated(*val
, &cp
));
185 if (rv
&& cp
!= NULL
) {
187 /* It's smalloc()ed, but ensure we don't leak */
196 else if (strcmp(name
, "line-editor-cursor-right") == 0 &&
197 (rv
= (val
!= NULL
&& *val
!= NULL
))) {
198 char const *x
= cp
= *val
;
201 /* Set with no value? *//*TODO invalid,but no way to "re-unset";see above
202 * TODO adjust tty.c when line-editor-cursor-right is properly handled in
206 c
= expand_shell_escape(&x
, FAL0
);
210 } while (*x
!= '\0');
226 _canonify(char const *vn
)
228 if (! upperchar(*vn
)) {
231 for (vp
= vn
; *vp
!= '\0' && *vp
!= '@'; ++vp
)
233 vn
= (*vp
== '@') ? i_strdup(vn
) : vn
;
239 _lookup(char const *name
, ui_it h
, bool_t hisset
)
241 struct var
**vap
, *lvp
, *vp
;
247 for (lvp
= NULL
, vp
= *vap
; vp
!= NULL
; lvp
= vp
, vp
= vp
->v_link
)
248 if (*vp
->v_name
== *name
&& strcmp(vp
->v_name
, name
) == 0) {
249 /* Relink as head, hope it "sorts on usage" over time */
251 lvp
->v_link
= vp
->v_link
;
263 _is_closing_angle(char const *cp
)
266 while (spacechar(*cp
))
270 while (spacechar(*cp
))
277 static struct macro
*
278 _malook(char const *name
, struct macro
*data
, enum ma_flags mafl
)
280 enum ma_flags save_mafl
;
282 struct macro
*lmp
, *mp
;
285 mafl
&= MA_TYPE_MASK
;
288 for (lmp
= NULL
, mp
= _macros
[h
]; mp
!= NULL
; lmp
= mp
, mp
= mp
->ma_next
) {
289 if ((mp
->ma_flags
& MA_TYPE_MASK
) == mafl
&&
290 strcmp(mp
->ma_name
, name
) == 0) {
291 if (save_mafl
& MA_UNDEF
) {
293 _macros
[h
] = mp
->ma_next
;
295 lmp
->ma_next
= mp
->ma_next
;
302 data
->ma_next
= _macros
[h
];
311 _maexec(struct macro
const *mp
, struct var
**unroll_store
)
315 struct mline
const *lp
;
316 char *buf
= ac_alloc(mp
->ma_maxlen
+ 1);
318 los
.s_up
= _localopts
;
319 los
.s_mac
= UNCONST(mp
);
320 los
.s_localopts
= NULL
;
324 for (lp
= mp
->ma_contents
; lp
; lp
= lp
->l_next
) {
325 var_unset_allow_undefined
= TRU1
;
326 memcpy(buf
, lp
->l_line
, lp
->l_length
+ 1);
327 rv
|= execute(buf
, 0, lp
->l_length
); /* XXX break if != 0 ? */
328 var_unset_allow_undefined
= FAL0
;
331 _localopts
= los
.s_up
;
332 if (unroll_store
== NULL
)
333 _localopts_unroll(&los
.s_localopts
);
335 *unroll_store
= los
.s_localopts
;
342 _list_macros(enum ma_flags mafl
)
351 if ((fp
= Ftemp(&cp
, "Ra", "w+", 0600, 1)) == NULL
) {
358 mafl
&= MA_TYPE_MASK
;
359 typestr
= (mafl
& MA_ACC
) ? "account" : "define";
361 for (ti
= mc
= 0; ti
< MA_PRIME
; ++ti
)
362 for (mq
= _macros
[ti
]; mq
; mq
= mq
->ma_next
)
363 if ((mq
->ma_flags
& MA_TYPE_MASK
) == mafl
) {
366 fprintf(fp
, "%s %s {\n", typestr
, mq
->ma_name
);
367 for (lp
= mq
->ma_contents
; lp
; lp
= lp
->l_next
)
368 fprintf(fp
, " %s\n", lp
->l_line
);
372 page_or_print(fp
, 0);
374 mc
= (ui_it
)ferror(fp
);
380 _define1(char const *name
, enum ma_flags mafl
)
384 struct mline
*lp
, *lst
= NULL
, *lnd
= NULL
;
385 char *linebuf
= NULL
, *cp
;
386 size_t linesize
= 0, maxlen
= 0;
389 mp
= scalloc(1, sizeof *mp
);
390 mp
->ma_name
= sstrdup(name
);
394 n
= readline_input(LNED_LF_ESC
, "", &linebuf
, &linesize
);
396 fprintf(stderr
, tr(75, "Unterminated %s definition: \"%s\".\n"),
397 (mafl
& MA_ACC
? "account" : "macro"), mp
->ma_name
);
402 if (_is_closing_angle(linebuf
))
406 for (cp
= linebuf
, i
= 0; i
< n
; ++cp
, ++i
)
407 if (! whitechar(*cp
))
412 while (whitechar(cp
[n
- 1]))
418 maxlen
= MAX(maxlen
, (size_t)n
);
421 lp
= scalloc(1, sizeof(*lp
) - VFIELD_SIZEOF(struct mline
, l_line
) + n
);
422 memcpy(lp
->l_line
, cp
, n
);
423 lp
->l_length
= (size_t)--n
;
430 mp
->ma_contents
= lst
;
431 mp
->ma_maxlen
= maxlen
;
433 if (_malook(mp
->ma_name
, mp
, mafl
) != NULL
) {
434 if (! (mafl
& MA_ACC
)) {
435 fprintf(stderr
, tr(76, "A macro named \"%s\" already exists.\n"),
437 lst
= mp
->ma_contents
;
440 _undef1(mp
->ma_name
, MA_ACC
);
441 _malook(mp
->ma_name
, mp
, MA_ACC
);
458 _undef1(char const *name
, enum ma_flags mafl
)
462 if ((mp
= _malook(name
, NULL
, mafl
| MA_UNDEF
)) != NULL
) {
463 _freelines(mp
->ma_contents
);
470 _freelines(struct mline
*lp
)
474 for (lq
= NULL
; lp
!= NULL
; ) {
485 __var_list_all_cmp(void const *s1
, void const *s2
)
487 return strcmp(*(char**)UNCONST(s1
), *(char**)UNCONST(s2
));
491 _localopts_add(struct lostack
*losp
, char const *name
, struct var
*ovap
)
496 /* Propagate unrolling up the stack, as necessary */
497 while (! losp
->s_unroll
&& (losp
= losp
->s_up
) != NULL
)
502 /* We have found a level that wants to unroll; check wether it does it yet */
503 for (vap
= losp
->s_localopts
; vap
!= NULL
; vap
= vap
->v_link
)
504 if (strcmp(vap
->v_name
, name
) == 0)
507 nl
= strlen(name
) + 1;
508 vl
= (ovap
!= NULL
) ? strlen(ovap
->v_value
) + 1 : 0;
509 vap
= smalloc(sizeof(*vap
) + nl
+ vl
);
510 vap
->v_link
= losp
->s_localopts
;
511 losp
->s_localopts
= vap
;
512 vap
->v_name
= (char*)(vap
+ 1);
513 memcpy(vap
->v_name
, name
, nl
);
517 vap
->v_value
= (char*)(vap
+ 1) + nl
;
518 memcpy(vap
->v_value
, ovap
->v_value
, vl
);
525 _localopts_unroll(struct var
**vapp
)
527 struct lostack
*save_los
;
533 save_los
= _localopts
;
535 while (vap
!= NULL
) {
538 var_assign(x
->v_name
, x
->v_value
);
541 _localopts
= save_los
;
545 var_assign(char const *name
, char const *val
)
553 bool_t tmp
= var_unset_allow_undefined
;
554 var_unset_allow_undefined
= TRU1
;
555 err
= var_unset(name
);
556 var_unset_allow_undefined
= tmp
;
560 name
= _canonify(name
);
562 vp
= _lookup(name
, h
, TRU1
);
564 /* Don't care what happens later on, store this in the unroll list */
565 if (_localopts
!= NULL
)
566 _localopts_add(_localopts
, name
, vp
);
569 vp
= (struct var
*)scalloc(1, sizeof *vp
);
570 vp
->v_name
= _vcopy(name
);
571 vp
->v_link
= _vars
[h
];
576 vp
->v_value
= _vcopy(val
);
578 /* Check if update allowed XXX wasteful on error! */
579 if ((err
= !_check_special_vars(name
, TRU1
, &vp
->v_value
))) {
580 char *cp
= vp
->v_value
;
591 var_unset(char const *name
)
597 name
= _canonify(name
);
599 vp
= _lookup(name
, h
, TRU1
);
602 if (!sourcing
&& !var_unset_allow_undefined
) {
603 fprintf(stderr
, tr(203, "\"%s\": undefined variable\n"), name
);
607 if (_localopts
!= NULL
)
608 _localopts_add(_localopts
, name
, vp
);
610 /* Always listhead after _lookup() */
611 _vars
[h
] = _vars
[h
]->v_link
;
616 if (!_check_special_vars(name
, FAL0
, NULL
))
625 var_lookup(char const *name
, bool_t look_environ
)
630 name
= _canonify(name
);
631 if ((vp
= _lookup(name
, 0, FAL0
)) != NULL
)
633 else if (! look_environ
)
635 else if ((rv
= getenv(name
)) != NULL
&& *rv
!= '\0')
640 static char const * const _tempo_okmap
[] = {
641 "add-file-recipients",
651 "attachment-ask-content-description",
652 "attachment-ask-content-disposition",
653 "attachment-ask-content-id",
654 "attachment-ask-content-type",
659 "batch-exit-on-error",
675 "forward-as-attachment",
684 "keep-content-length",
686 "line-editor-disable",
688 "message-id-disable",
690 "mime-allow-text-controls",
691 "mime-counter-evidence",
699 "print-alternatives",
701 "quote-as-attachment",
704 "reply-in-same-charset",
709 "sendcharsets-else-ttycharset",
715 "smime-force-encryption",
716 "smime-no-default-ca",
734 "datefield-markout-older",
750 "line-editor-cursor-right",
754 "mimetypes-load-control",
784 "smime-sign-include-certs",
787 "smtp-auth-password",
813 _var_oklook(enum okeys okey
)
815 char const *k
= _tempo_okmap
[okey
];
817 return var_lookup(k
, TRU1
);
821 _var_okset(enum okeys okey
, uintptr_t val
)
823 char const *k
= _tempo_okmap
[okey
];
825 return var_assign(k
, (val
== 0x1) ? "" : (char const*)val
);
829 _var_okclear(enum okeys okey
)
831 char const *k
= _tempo_okmap
[okey
];
837 _var_voklook(char const *vokey
)
839 return var_lookup(vokey
, TRU1
);
843 _var_vokset(char const *vokey
, uintptr_t val
)
845 return var_assign(vokey
, (val
== 0x1) ? "" : (char const*)val
);
849 _var_vokclear(char const *vokey
)
851 return var_unset(vokey
);
858 char *cp
, **vacp
, **cap
;
863 if ((fp
= Ftemp(&cp
, "Ra", "w+", 0600, 1)) == NULL
) {
870 for (no
= i
= 0; i
< MA_PRIME
; ++i
)
871 for (vp
= _vars
[i
]; vp
!= NULL
; vp
= vp
->v_link
)
873 vacp
= salloc(no
* sizeof(*vacp
));
874 for (cap
= vacp
, i
= 0; i
< MA_PRIME
; ++i
)
875 for (vp
= _vars
[i
]; vp
!= NULL
; vp
= vp
->v_link
)
879 qsort(vacp
, no
, sizeof *vacp
, &__var_list_all_cmp
);
881 i
= (ok_blook(bsdcompat
) || ok_blook(bsdset
));
882 fmt
= (i
!= 0) ? "%s\t%s\n" : "%s=\"%s\"\n";
884 for (cap
= vacp
; no
!= 0; ++cap
, --no
) {
885 cp
= vok_vlook(*cap
); /* XXX when lookup checks value/binary, change */
888 if (i
|| *cp
!= '\0')
889 fprintf(fp
, fmt
, *cap
, cp
);
891 fprintf(fp
, "%s\n", *cap
);
894 page_or_print(fp
, (size_t)(cap
- vacp
));
907 if (args
[0] == NULL
) {
908 errs
= tr(504, "Missing macro name to `define'");
911 if (args
[1] == NULL
|| strcmp(args
[1], "{") || args
[2] != NULL
) {
912 errs
= tr(505, "Syntax is: define <name> {");
915 rv
= ! _define1(args
[0], MA_NONE
);
919 fprintf(stderr
, "%s\n", errs
);
930 fprintf(stderr
, tr(506, "Missing macro name to `undef'\n"));
934 _undef1(*args
, MA_NONE
);
946 char const *errs
, *name
;
949 if (args
[0] == NULL
|| (args
[1] != NULL
&& args
[2] != NULL
)) {
950 errs
= tr(507, "Syntax is: call <%s>\n");
955 if ((mp
= _malook(*args
, NULL
, MA_NONE
)) == NULL
) {
956 errs
= tr(508, "Undefined macro called: \"%s\"\n");
961 rv
= _maexec(mp
, NULL
);
965 fprintf(stderr
, errs
, name
);
970 callhook(char const *name
, int nmail
)
976 var
= ac_alloc(len
= strlen(name
) + 13);
977 snprintf(var
, len
, "folder-hook-%s", name
);
978 if ((cp
= vok_vlook(var
)) == NULL
&& (cp
= ok_vlook(folder_hook
)) == NULL
) {
982 if ((mp
= _malook(cp
, NULL
, MA_NONE
)) == NULL
) {
983 fprintf(stderr
, tr(49, "Cannot call hook for folder \"%s\": "
984 "Macro \"%s\" does not exist.\n"), name
, cp
);
989 inhook
= nmail
? 3 : 1;
990 rv
= _maexec(mp
, NULL
);
1001 return _list_macros(MA_NONE
);
1009 int rv
= 1, i
, oqf
, nqf
;
1011 if (args
[0] == NULL
) {
1012 rv
= _list_macros(MA_ACC
);
1016 if (args
[1] && args
[1][0] == '{' && args
[1][1] == '\0') {
1017 if (args
[2] != NULL
) {
1018 fprintf(stderr
, tr(517, "Syntax is: account <name> {\n"));
1021 if (asccasecmp(args
[0], ACCOUNT_NULL
) == 0) {
1022 fprintf(stderr
, tr(521, "Error: `%s' is a reserved name.\n"),
1026 rv
= ! _define1(args
[0], MA_ACC
);
1031 fprintf(stderr
, tr(518, "Cannot change account from within a hook.\n"));
1035 save_mbox_for_possible_quitstuff();
1038 if (asccasecmp(args
[0], ACCOUNT_NULL
) != 0 &&
1039 (mp
= _malook(args
[0], NULL
, MA_ACC
)) == NULL
) {
1040 fprintf(stderr
, tr(519, "Account `%s' does not exist.\n"), args
[0]);
1044 oqf
= savequitflags();
1045 if (_acc_curr
!= NULL
)
1046 _localopts_unroll(&_acc_curr
->ma_localopts
);
1047 account_name
= (mp
!= NULL
) ? mp
->ma_name
: NULL
;
1050 if (mp
!= NULL
&& _maexec(mp
, &mp
->ma_localopts
) == CBAD
) {
1051 /* XXX account switch incomplete, unroll? */
1052 fprintf(stderr
, tr(520, "Switching to account `%s' failed.\n"), args
[0]);
1056 if (! starting
&& ! inhook
) {
1057 nqf
= savequitflags(); /* TODO obsolete (leave -> void -> new box!) */
1058 restorequitflags(oqf
);
1059 if ((i
= setfile("%", 0)) < 0)
1061 callhook(mailname
, 0);
1062 if (i
> 0 && !ok_blook(emptystart
))
1064 announce(ok_blook(bsdcompat
) || ok_blook(bsdannounce
));
1065 restorequitflags(nqf
);
1073 c_localopts(void *v
)
1078 if (_localopts
== NULL
) {
1079 fprintf(stderr
, tr(522,
1080 "Cannot use `localopts' but from within a `define' or `account'\n"));
1084 _localopts
->s_unroll
= (**c
== '0') ? FAL0
: TRU1
;
1090 /* vim:set fenc=utf-8:s-it-mode */