1 /* $Id$ */
2 
3 /*
4  * Copyright (c) 2006 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /* Declarations */
20 
21 %{
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 
25 #include <ctype.h>
26 #include <grp.h>
27 #include <pwd.h>
28 #include <string.h>
29 #include <syslog.h>
30 
31 #include "fdm.h"
32 #include "deliver.h"
33 #include "fetch.h"
34 #include "match.h"
35 
36 struct strb	*parse_tags;
37 struct macros	 parse_macros;
38 struct macro	*parse_last;	/* last command-line argument macro */
39 
40 u_int		 parse_ruleidx;
41 u_int		 parse_actionidx;
42 
43 ARRAY_DECL(, struct rule *) parse_rulestack;
44 struct rule	*parse_rule;
45 
46 struct files	 parse_filestack;
47 struct file	*parse_file;
48 
49 int		 yyparse(void);
50 
51 int
parse_conf(const char * path,struct strings * macros)52 parse_conf(const char *path, struct strings *macros)
53 {
54 	struct macro	*macro;
55 	FILE		*f;
56 	u_int		 i;
57 
58 	if ((f = fopen(path, "r")) == NULL)
59 		return (-1);
60 
61 	ARRAY_INIT(&parse_rulestack);
62 	parse_rule = NULL;
63 
64 	ARRAY_INIT(&parse_filestack);
65 	parse_file = xmalloc(sizeof *parse_file);
66 
67 	parse_file->f = f;
68 	parse_file->line = 0;
69 	parse_file->path = path;
70 
71 	strb_create(&parse_tags);
72 	default_tags(&parse_tags, NULL);
73 	add_tag(&parse_tags, "home", "%s", conf.user_home);
74 
75 	TAILQ_INIT(&parse_macros);
76 	parse_last = NULL;
77 	for (i = 0; i < ARRAY_LENGTH(macros); i++) {
78 		parse_last = extract_macro(ARRAY_ITEM(macros, i));
79 		TAILQ_INSERT_TAIL(&parse_macros, parse_last, entry);
80 	}
81 
82 	parse_file->line++;
83 	yyparse();
84 
85 	if (!ARRAY_EMPTY(&parse_rulestack))
86 		yyerror("missing }");
87 	ARRAY_FREE(&parse_rulestack);
88 
89 	ARRAY_FREE(&parse_filestack);
90 	xfree(parse_file);
91 
92 	while (!TAILQ_EMPTY(&parse_macros)) {
93 		macro = TAILQ_FIRST(&parse_macros);
94 		TAILQ_REMOVE(&parse_macros, macro, entry);
95 
96 		if (macro->type == MACRO_STRING)
97 			xfree(macro->value.str);
98 		xfree(macro);
99 	}
100 
101 	strb_destroy(&parse_tags);
102 
103 	fclose(f);
104 	return (0);
105 }
106 
107 __dead printflike1 void
yyerror(const char * fmt,...)108 yyerror(const char *fmt, ...)
109 {
110 	va_list	ap;
111 	char   *s;
112 
113 	xasprintf(&s,
114 	    "%s: %s at line %d", parse_file->path, fmt, parse_file->line);
115 
116 	va_start(ap, fmt);
117 	log_vwrite(LOG_CRIT, s, ap);
118 	va_end(ap);
119 
120 	exit(1);
121 }
122 %}
123 
124 %token TOKACCOUNT
125 %token TOKACCOUNTS
126 %token TOKACTION
127 %token TOKACTIONS
128 %token TOKADDHEADER
129 %token TOKADDTOCACHE
130 %token TOKAGE
131 %token TOKALL
132 %token TOKALLOWMANY
133 %token TOKAND
134 %token TOKANYNAME
135 %token TOKANYSIZE
136 %token TOKANYTYPE
137 %token TOKAPPEND
138 %token TOKATTACHMENT
139 %token TOKBODY
140 %token TOKBYTES
141 %token TOKCACHE
142 %token TOKCASE
143 %token TOKCMDUSER
144 %token TOKCOMPRESS
145 %token TOKCONTINUE
146 %token TOKCOUNT
147 %token TOKDAYS
148 %token TOKDEFUSER
149 %token TOKDELTOOBIG
150 %token TOKDISABLED
151 %token TOKDOMAIN
152 %token TOKDOTLOCK
153 %token TOKDROP
154 %token TOKEQ
155 %token TOKEXEC
156 %token TOKEXPIRE
157 %token TOKFCNTL
158 %token TOKFILEGROUP
159 %token TOKFILEUMASK
160 %token TOKFLOCK
161 %token TOKFOLDER
162 %token TOKFOLDERS
163 %token TOKFROM
164 %token TOKGIGABYTES
165 %token TOKGROUP
166 %token TOKGROUPS
167 %token TOKHEADER
168 %token TOKHEADERS
169 %token TOKHOURS
170 %token TOKIGNOREERRORS
171 %token TOKIMAP
172 %token TOKIMAPS
173 %token TOKIMPLACT
174 %token TOKIN
175 %token TOKINCACHE
176 %token TOKINSECURE
177 %token TOKINVALID
178 %token TOKKEEP
179 %token TOKKEY
180 %token TOKKILOBYTES
181 %token TOKLOCKFILE
182 %token TOKLOCKTIMEOUT
183 %token TOKLOCKTYPES
184 %token TOKLOCKWAIT
185 %token TOKLOOKUPORDER
186 %token TOKMAILDIR
187 %token TOKMAILDIRS
188 %token TOKMATCH
189 %token TOKMATCHED
190 %token TOKMAXSIZE
191 %token TOKMBOX
192 %token TOKMBOXES
193 %token TOKMEGABYTES
194 %token TOKMINUTES
195 %token TOKMONTHS
196 %token TOKNE
197 %token TOKNEWONLY
198 %token TOKNNTP
199 %token TOKNNTPS
200 %token TOKNOAPOP
201 %token TOKNOCRAMMD5
202 %token TOKNOCREATE
203 %token TOKNOLOGIN
204 %token TOKNONE
205 %token TOKNORECEIVED
206 %token TOKNOT
207 %token TOKNOUIDL
208 %token TOKNOVERIFY
209 %token TOKOLDONLY
210 %token TOKOR
211 %token TOKPARALLELACCOUNTS
212 %token TOKPASS
213 %token TOKPASSWD
214 %token TOKPIPE
215 %token TOKPOP3
216 %token TOKPOP3S
217 %token TOKPORT
218 %token TOKPROXY
219 %token TOKPURGEAFTER
220 %token TOKQUEUEHIGH
221 %token TOKQUEUELOW
222 %token TOKREMOVEFROMCACHE
223 %token TOKREMOVEHEADER
224 %token TOKREMOVEHEADERS
225 %token TOKRETURNS
226 %token TOKREWRITE
227 %token TOKSECONDS
228 %token TOKSERVER
229 %token TOKSET
230 %token TOKSIZE
231 %token TOKSMTP
232 %token TOKSTARTTLS
233 %token TOKSTDIN
234 %token TOKSTDOUT
235 %token TOKSTRING
236 %token TOKSTRIPCHARACTERS
237 %token TOKTAG
238 %token TOKTAGGED
239 %token TOKTIMEOUT
240 %token TOKTO
241 %token TOKTOTALSIZE
242 %token TOKUNMATCHED
243 %token TOKUSER
244 %token TOKUSERS
245 %token TOKVALUE
246 %token TOKVERIFYCERTS
247 %token TOKWEEKS
248 %token TOKWRITE
249 %token TOKYEARS
250 
251 %union
252 {
253 	long long		 number;
254 	char			*string;
255 	int			 flag;
256 	u_int			 locks;
257 	struct {
258 		struct fetch	*fetch;
259 		void		*data;
260 	} fetch;
261 	struct {
262 		char		*host;
263 		char		*port;
264 	} server;
265 	enum area		 area;
266 	enum exprop		 exprop;
267 	struct actitem		*actitem;
268 	struct actlist		*actlist;
269 	struct expr		*expr;
270 	struct expritem		*expritem;
271 	struct strings		*strings;
272 	struct replstrs		*replstrs;
273 	enum fetch_only		 only;
274 	struct {
275 		char		*path;
276 		enum fetch_only	 only;
277 	} poponly;
278 	struct {
279 		int		 flags;
280 		char		*str;
281 	} re;
282 	gid_t			 localgid;
283 	enum cmp		 cmp;
284 	struct rule		*rule;
285 	struct {
286 		char		*user;
287 		int		 user_netrc;
288 		char		*pass;
289 		int		 pass_netrc;
290 	} userpass;
291 	userfunction		 ufn;
292 	struct userfunctions	*ufns;
293 }
294 
295 %token NONE
296 %token <number> NUMBER
297 %token <string> STRING STRMACRO NUMMACRO
298 %token <string> STRCOMMAND NUMCOMMAND
299 
300 %type  <actitem> actitem
301 %type  <actlist> actlist
302 %type  <area> area
303 %type  <cmp> cmp ltgt eqne
304 %type  <expr> expr exprlist
305 %type  <expritem> expritem
306 %type  <exprop> exprop
307 %type  <fetch> fetchtype
308 %type  <flag> cont not disabled keep execpipe writeappend compress verify
309 %type  <flag> apop poptype imaptype nntptype nocrammd5 nologin uidl starttls
310 %type  <flag> insecure
311 %type  <localgid> localgid
312 %type  <locks> lock locklist
313 %type  <number> size time numv retrc expire
314 %type  <only> only imaponly
315 %type  <poponly> poponly
316 %type  <replstrs> replstrslist actions rmheaders accounts users
317 %type  <re> casere retre
318 %type  <rule> perform
319 %type  <server> server
320 %type  <string> port to from xstrv strv replstrv replpathv val optval folder1
321 %type  <string> user
322 %type  <strings> stringslist pathslist maildirs mboxes groups folders folderlist
323 %type  <userpass> userpass userpassreqd userpassnetrc
324 %type  <ufn> ufn
325 %type  <ufns> ufnlist
326 
327 %%
328 
329 /* Rules */
330 
331 cmds: /* empty */
332     | cmds account
333     | cmds defaction
334     | cmds defmacro
335     | cmds rule
336     | cmds set
337     | cmds close
338     | cmds cache
339     | cmds NONE
340 
341 /* Plural/singular combinations. */
342 actionp: TOKACTION
343        | TOKACTIONS
344 userp: TOKUSER
345      | TOKUSERS
346 accountp: TOKACCOUNT
347 	| TOKACCOUNTS
348 groupp: TOKGROUP
349       | TOKGROUPS
350 folderp: TOKFOLDER
351        | TOKFOLDERS
352 maildirp: TOKMAILDIR
353 	| TOKMAILDIRS
354 mboxp: TOKMBOX
355      | TOKMBOXES
356 rmheaderp: TOKREMOVEHEADER
357 	 | TOKREMOVEHEADERS
358 
359 val: TOKVALUE strv
360      {
361 	     $$ = $2;
362      }
363    | strv
364      {
365 	     $$ = $1;
366      }
367 
368 optval: TOKVALUE strv
369 	{
370 		$$ = $2;
371 	}
372       | /* empty */
373 	{
374 		$$ = NULL;
375 	}
376 
377 xstrv: STRCOMMAND
378        {
379 	       $$ = run_command($1, parse_file->path);
380 	       xfree($1);
381        }
382      | STRING
383        {
384 	       $$ = $1;
385        }
386      | STRMACRO
387        {
388 	       struct macro	*macro;
389 
390 	       if (strlen($1) > MAXNAMESIZE)
391 		       yyerror("macro name too long: %s", $1);
392 
393 	       if ((macro = find_macro($1)) == NULL)
394 		       yyerror("undefined macro: %s", $1);
395 	       if (macro->type != MACRO_STRING)
396 		       yyerror("string macro expected: %s", $1);
397 
398 	       $$ = xstrdup(macro->value.str);
399 
400 	       xfree($1);
401        }
402 
403 strv: xstrv
404       {
405 	      $$ = $1;
406       }
407     | strv '+' xstrv
408       {
409 	      size_t	size;
410 
411 	      size = strlen($1) + strlen($3) + 1;
412 	      $$ = xrealloc($1, 1, size);
413 	      strlcat($$, $3, size);
414 	      xfree($3);
415       }
416 
417 numv: NUMCOMMAND
418       {
419 	      const char	*errstr;
420 	      char		*s;
421 
422 	      s = run_command($1, parse_file->path);
423 
424 	      $$ = strtonum(s, 0, LLONG_MAX, &errstr);
425 	      if (errstr != NULL)
426 		      yyerror("number is %s", errstr);
427 
428 	      xfree(s);
429 
430 	      xfree($1);
431       }
432     | NUMBER
433       {
434 	      $$ = $1;
435       }
436     | NUMMACRO
437       {
438 	      struct macro	*macro;
439 
440 	      if (strlen($1) > MAXNAMESIZE)
441 		      yyerror("macro name too long: %s", $1);
442 
443 	      if ((macro = find_macro($1)) == NULL)
444 		      yyerror("undefined macro: %s", $1);
445 	      if (macro->type != MACRO_NUMBER)
446 		      yyerror("number macro expected: %s", $1);
447 
448 	      $$ = macro->value.num;
449 
450 	      xfree($1);
451       }
452 
453 replstrv: strv
454 	  {
455 		  struct replstr	rs;
456 
457 		  rs.str = $1;
458 		  $$ = replacestr(&rs, parse_tags, NULL, NULL);
459 		  xfree($1);
460 	  }
461 
462 replpathv: strv
463 	   {
464 		  struct replpath	rp;
465 
466 		  rp.str = $1;
467 		  $$ = replacepath(&rp, parse_tags, NULL, NULL, conf.user_home);
468 		  xfree($1);
469 	   }
470 
471 size: numv
472       {
473 	      $$ = $1;
474       }
475     | numv TOKBYTES
476       {
477 	      $$ = $1;
478       }
479     | numv TOKKILOBYTES
480       {
481 	      if ($1 > LLONG_MAX / 1024)
482 		      yyerror("size is too big");
483 	      $$ = $1 * 1024;
484       }
485     | numv TOKMEGABYTES
486       {
487 	      if ($1 > LLONG_MAX / (1024 * 1024))
488 		      yyerror("size is too big");
489 	      $$ = $1 * (1024 * 1024);
490       }
491     | numv TOKGIGABYTES
492       {
493 	      if ($1 > LLONG_MAX / (1024 * 1024 * 1024))
494 		      yyerror("size is too big");
495 	      $$ = $1 * (1024 * 1024 * 1024);
496       }
497 
498 time: numv
499       {
500 	      $$ = $1;
501       }
502     | numv TOKHOURS
503       {
504 	      if ($1 > LLONG_MAX / TIME_HOUR)
505 		      yyerror("time is too long");
506 	      $$ = $1 * TIME_HOUR;
507       }
508     | numv TOKMINUTES
509       {
510 	      if ($1 > LLONG_MAX / TIME_MINUTE)
511 		      yyerror("time is too long");
512 	      $$ = $1 * TIME_MINUTE;
513       }
514     | numv TOKSECONDS
515       {
516 	      $$ = $1;
517       }
518     | numv TOKDAYS
519       {
520 	      if ($1 > LLONG_MAX / TIME_DAY)
521 		      yyerror("time is too long");
522 	      $$ = $1 * TIME_DAY;
523       }
524     | numv TOKWEEKS
525       {
526 	      if ($1 > LLONG_MAX / TIME_WEEK)
527 		      yyerror("time is too long");
528 	      $$ = $1 * TIME_WEEK;
529       }
530     | numv TOKMONTHS
531       {
532 	      if ($1 > LLONG_MAX / TIME_MONTH)
533 		      yyerror("time is too long");
534 	      $$ = $1 * TIME_MONTH;
535       }
536     | numv TOKYEARS
537       {
538 	      if ($1 > LLONG_MAX / TIME_YEAR)
539 		      yyerror("time is too long");
540 	      $$ = $1 * TIME_YEAR;
541       }
542 
543 expire: TOKEXPIRE time
544 	{
545 #if UINT64_MAX < LLONG_MAX
546 		if ($2 > UINT64_MAX)
547 			yyerror("time too long");
548 #endif
549 
550 		$$ = $2;
551 	}
552       | /* empty */
553 	{
554 		$$ = -1;
555 	}
556 
557 cache: TOKCACHE replpathv expire
558        {
559 	       struct cache	*cache;
560 
561 	       TAILQ_FOREACH(cache, &conf.caches, entry) {
562 		       if (strcmp(cache->path, $2) == 0)
563 			       yyerror("duplicate cache path");
564 	       }
565 
566 	       cache = xcalloc(1, sizeof *cache);
567 	       cache->path = $2;
568 	       cache->expire = $3;
569 
570 	       TAILQ_INSERT_TAIL(&conf.caches, cache, entry);
571 
572 	       log_debug2("added cache \"%s\": expire %lld", cache->path, $3);
573        }
574 
575 set: TOKSET TOKMAXSIZE size
576      {
577 	     if ($3 == 0)
578 		     yyerror("zero maximum size");
579 	     if ($3 > MAXMAILSIZE)
580 		     yyerror("maximum size too large: %lld", $3);
581 	     conf.max_size = $3;
582      }
583    | TOKSET TOKLOCKTYPES locklist
584      {
585 	     if ($3 & LOCK_FCNTL && $3 & LOCK_FLOCK)
586 		     yyerror("fcntl and flock locking cannot be used together");
587 	     conf.lock_types = $3;
588      }
589    | TOKSET TOKLOCKFILE replpathv
590      {
591 	     if (conf.lock_file != NULL)
592 		     xfree(conf.lock_file);
593 	     conf.lock_file = $3;
594      }
595    | TOKSET TOKLOCKWAIT
596      {
597 	     conf.lock_wait = 1;
598      }
599    | TOKSET TOKLOCKTIMEOUT time
600      {
601 	     conf.lock_timeout = $3;
602      }
603    | TOKSET TOKDELTOOBIG
604      {
605 	     conf.del_big = 1;
606      }
607    | TOKSET TOKIGNOREERRORS
608      {
609 	     conf.ignore_errors = 1;
610      }
611    | TOKSET TOKALLOWMANY
612      {
613 	     conf.allow_many = 1;
614      }
615    | TOKSET TOKDEFUSER strv
616      {
617 	     if (conf.def_user == NULL)
618 		     conf.def_user = $3;
619      }
620    | TOKSET TOKCMDUSER strv
621      {
622 	     if (conf.cmd_user == NULL)
623 		     conf.cmd_user = $3;
624      }
625    | TOKSET TOKSTRIPCHARACTERS strv
626      {
627 	     xfree(conf.strip_chars);
628 	     conf.strip_chars = $3;
629      }
630    | TOKSET TOKTIMEOUT time
631      {
632 	     if ($3 == 0)
633 		     yyerror("zero timeout");
634 	     if ($3 > INT_MAX / 1000)
635 		     yyerror("timeout too long: %lld", $3);
636 	     conf.timeout = $3 * 1000;
637      }
638    | TOKSET TOKQUEUEHIGH numv
639      {
640 	     if ($3 == 0)
641 		     yyerror("zero queue-high");
642 	     if ($3 > MAXQUEUEVALUE)
643 		     yyerror("queue-high too big: %lld", $3);
644 	     if (conf.queue_low != -1 && $3 <= conf.queue_low)
645 		     yyerror("queue-high must be larger than queue-low");
646 	     conf.queue_high = $3;
647      }
648    | TOKSET TOKQUEUELOW numv
649      {
650 	     if ($3 > MAXQUEUEVALUE)
651 		     yyerror("queue-low too big: %lld", $3);
652 	     if (conf.queue_high == -1)
653 		     yyerror("queue-high not specified");
654 	     if ($3 >= conf.queue_high)
655 		     yyerror("queue-low must be smaller than queue-high");
656 	     conf.queue_low = $3;
657      }
658    | TOKSET TOKPARALLELACCOUNTS numv
659      {
660 	     if ($3 > INT_MAX)
661 		     yyerror("parallel-accounts too big: %lld", $3);
662 	     if ($3 == 0)
663 		     yyerror("parallel-accounts cannot be zero");
664 	     conf.max_accts = $3;
665      }
666    | TOKSET TOKPROXY replstrv
667      {
668 	     if (conf.proxy != NULL) {
669 		     xfree(conf.proxy->server.host);
670 		     xfree(conf.proxy->server.port);
671 		     if (conf.proxy->user != NULL)
672 			     xfree(conf.proxy->user);
673 		     if (conf.proxy->pass != NULL)
674 			     xfree(conf.proxy->pass);
675 	     }
676 	     if ((conf.proxy = getproxy($3)) == NULL)
677 		     yyerror("invalid proxy");
678 	     xfree($3);
679      }
680    | TOKSET TOKVERIFYCERTS
681      {
682 	     conf.verify_certs = 1;
683      }
684    | TOKSET TOKIMPLACT TOKKEEP
685      {
686 	     conf.impl_act = DECISION_KEEP;
687      }
688    | TOKSET TOKIMPLACT TOKDROP
689      {
690 	     conf.impl_act = DECISION_DROP;
691      }
692    | TOKSET TOKPURGEAFTER numv
693      {
694 	     if ($3 == 0)
695 		     yyerror("invalid purge-after value: 0");
696 	     if ($3 > UINT_MAX)
697 		     yyerror("purge-after value too large: %lld", $3);
698 
699 	     conf.purge_after = $3;
700      }
701    | TOKSET TOKPURGEAFTER TOKNONE
702      {
703 	     conf.purge_after = 0;
704      }
705    | TOKSET TOKNORECEIVED
706      {
707 	     conf.no_received = 1;
708      }
709    | TOKSET TOKNOCREATE
710      {
711 	     conf.no_create = 1;
712      }
713    | TOKSET TOKFILEGROUP TOKUSER
714      {
715 	     conf.file_group = -1;
716      }
717    | TOKSET TOKFILEGROUP localgid
718      {
719 	     conf.file_group = $3;
720      }
721    | TOKSET TOKFILEUMASK TOKUSER
722      {
723 	     conf.file_umask = umask(0);
724 	     umask(conf.file_umask);
725      }
726    | TOKSET TOKLOOKUPORDER ufnlist
727      {
728 	     ARRAY_FREEALL(conf.user_order);
729 	     conf.user_order = $3;
730      }
731    | TOKSET TOKFILEUMASK numv
732      {
733 	     char	s[8];
734 	     u_int	n;
735 
736 	     /*
737 	      * We can't differentiate umasks in octal from normal numbers
738 	      * (requiring a leading zero a la C would be nice, but it would
739 	      * potentially break existing configs), so we need to fiddle to
740 	      * convert.
741 	      */
742 	     memset(s, 0, sizeof s);
743 	     xsnprintf(s, sizeof s, "%03lld", $3);
744 	     if (s[3] != '\0' || s[0] < '0' || s[0] > '7' ||
745 		 s[1] < 0 || s[1] > '7' || s[2] < '0' || s[2] > '7')
746 		     yyerror("invalid umask: %s", s);
747 	     if (sscanf(s, "%o", &n) != 1)
748 		     yyerror("invalid umask: %s", s);
749 	     conf.file_umask = n;
750      }
751 
752 defmacro: STRMACRO '=' strv
753 	  {
754 		  struct macro	*macro;
755 
756 		  if (strlen($1) > MAXNAMESIZE)
757 			  yyerror("macro name too long: %s", $1);
758 
759 		  macro = xmalloc(sizeof *macro);
760 		  strlcpy(macro->name, $1, sizeof macro->name);
761 		  macro->type = MACRO_STRING;
762 		  macro->value.str = $3;
763 
764 		  if (parse_last == NULL)
765 			  TAILQ_INSERT_HEAD(&parse_macros, macro, entry);
766 		  else {
767 			  TAILQ_INSERT_AFTER(
768 			      &parse_macros, parse_last, macro, entry);
769 		  }
770 
771 		  log_debug3("added macro \"%s\": \"%s\"", macro->name,
772 		      macro->value.str);
773 		  xfree($1);
774 	  }
775 	| NUMMACRO '=' numv
776 	  {
777 		  struct macro	*macro;
778 
779 		  if (strlen($1) > MAXNAMESIZE)
780 			  yyerror("macro name too long: %s", $1);
781 
782 		  macro = xmalloc(sizeof *macro);
783 		  strlcpy(macro->name, $1, sizeof macro->name);
784 		  macro->type = MACRO_NUMBER;
785 		  macro->value.num = $3;
786 
787 		  if (parse_last == NULL)
788 			  TAILQ_INSERT_HEAD(&parse_macros, macro, entry);
789 		  else {
790 			  TAILQ_INSERT_AFTER(
791 			      &parse_macros, parse_last, macro, entry);
792 		  }
793 
794 		  log_debug3("added macro \"%s\": %lld", macro->name,
795 		      macro->value.num);
796 		  xfree($1);
797 	  }
798 
799 replstrslist: replstrslist strv
800 	      {
801 		      if (*$2 == '\0')
802 			      yyerror("empty string in list");
803 
804 		      $$ = $1;
805 		      ARRAY_EXPAND($$, 1);
806 		      ARRAY_LAST($$).str = $2;
807 	      }
808 	    | strv
809 	      {
810 		      if (*$1 == '\0')
811 			     yyerror("empty string in list");
812 
813 		      $$ = xmalloc(sizeof *$$);
814 		      ARRAY_INIT($$);
815 		      ARRAY_EXPAND($$, 1);
816 		      ARRAY_LAST($$).str = $1;
817 	      }
818 
819 stringslist: stringslist replstrv
820 	     {
821 		     if (*$2 == '\0')
822 			     yyerror("empty string in list");
823 
824 		     $$ = $1;
825 		     ARRAY_ADD($$, $2);
826 	     }
827 	   | replstrv
828 	     {
829 		     if (*$1 == '\0')
830 			     yyerror("empty string in list");
831 
832 		     $$ = xmalloc(sizeof *$$);
833 		     ARRAY_INIT($$);
834 		     ARRAY_ADD($$, $1);
835 	     }
836 
837 pathslist: pathslist replpathv
838 	   {
839 		   if (*$2 == '\0')
840 			   yyerror("invalid path");
841 
842 		   $$ = $1;
843 		   ARRAY_ADD($$, $2);
844 	   }
845 	 | replpathv
846 	   {
847 		   if (*$1 == '\0')
848 			   yyerror("invalid path");
849 
850 		   $$ = xmalloc(sizeof *$$);
851 		   ARRAY_INIT($$);
852 		   ARRAY_ADD($$, $1);
853 	   }
854 
855 ufn: TOKPASSWD
856      {
857 	     $$ = &passwd_lookup;
858      }
859 
860 ufnlist: ufnlist ufn
861 	 {
862 		 $$ = $1;
863 		 ARRAY_ADD($$, $2);
864 	 }
865        | ufn
866 	 {
867 		 $$ = xmalloc(sizeof *$$);
868 		 ARRAY_INIT($$);
869 		 ARRAY_ADD($$, $1);
870 	 }
871 
872 rmheaders: rmheaderp strv
873 	   {
874 		   if (*$2 == '\0')
875 			   yyerror("invalid header");
876 
877 		   $$ = xmalloc(sizeof *$$);
878 		   ARRAY_INIT($$);
879 		   ARRAY_EXPAND($$, 1);
880 		   ARRAY_LAST($$).str = $2;
881 	   }
882 	 | rmheaderp '{' replstrslist '}'
883 	   {
884 		   $$ = $3;
885 	   }
886 
887 maildirs: maildirp replpathv
888 	  {
889 		  if (*$2 == '\0')
890 			  yyerror("invalid path");
891 
892 		  $$ = xmalloc(sizeof *$$);
893 		  ARRAY_INIT($$);
894 		  ARRAY_ADD($$, $2);
895 	  }
896 	| maildirp '{' pathslist '}'
897 	  {
898 		  $$ = $3;
899 	  }
900 
901 mboxes: mboxp replpathv
902 	{
903 		if (*$2 == '\0')
904 			yyerror("invalid path");
905 
906 		$$ = xmalloc(sizeof *$$);
907 		ARRAY_INIT($$);
908 		ARRAY_ADD($$, $2);
909 	}
910       | mboxp '{' pathslist '}'
911 	{
912 		$$ = $3;
913 	}
914 
915 folders: folderp replstrv
916 	 {
917 		 if (*$2 == '\0')
918 			 yyerror("invalid folder");
919 
920 		 $$ = xmalloc(sizeof *$$);
921 		 ARRAY_INIT($$);
922 		 ARRAY_ADD($$, $2);
923 	 }
924        | folderp '{' stringslist '}'
925 	 {
926 		 $$ = $3;
927 	 }
928 
929 lock: TOKFCNTL
930       {
931 	      $$ = LOCK_FCNTL;
932       }
933     | TOKFLOCK
934       {
935 	      $$ = LOCK_FLOCK;
936       }
937     | TOKDOTLOCK
938       {
939 	      $$ = LOCK_DOTLOCK;
940       }
941 
942 locklist: locklist lock
943 	  {
944 		  $$ = $1 | $2;
945 	  }
946 	| lock
947 	  {
948 		  $$ = $1;
949 	  }
950 	| TOKNONE
951 	  {
952 		  $$ = 0;
953 	  }
954 
955 localgid: replstrv
956 	  {
957 		  struct group	*gr;
958 
959 		  if (*$1 == '\0')
960 			  yyerror("invalid group");
961 
962 		  gr = getgrnam($1);
963 		  if (gr == NULL)
964 			  yyerror("unknown group: %s", $1);
965 		  $$ = gr->gr_gid;
966 		  endgrent();
967 
968 		  xfree($1);
969 	  }
970 	| numv
971 	  {
972 		  struct group	*gr;
973 
974 		  if ($1 > GID_MAX)
975 			  yyerror("invalid gid: %llu", $1);
976 		  gr = getgrgid($1);
977 		  if (gr == NULL)
978 			  yyerror("unknown gid: %llu", $1);
979 		  $$ = gr->gr_gid;
980 		  endgrent();
981 	  }
982 
983 user: /* empty */
984       {
985 	      $$ = NULL;
986       }
987     | TOKUSER strv
988       {
989 	      $$ = $2;
990       }
991 
992 users: /* empty */
993        {
994 	       $$ = NULL;
995        }
996      | userp strv
997        {
998 	       $$ = xmalloc(sizeof *$$);
999 	       ARRAY_INIT($$);
1000 	       ARRAY_EXPAND($$, 1);
1001 	       ARRAY_LAST($$).str = $2;
1002        }
1003      | userp '{' replstrslist '}'
1004        {
1005 	       $$ = $3;
1006        }
1007 
1008 casere: TOKCASE replstrv
1009 	{
1010 		/* match case */
1011 		$$.flags = 0;
1012 		$$.str = $2;
1013 	}
1014       | replstrv
1015 	{
1016 		/* ignore case */
1017 		$$.flags = RE_IGNCASE;
1018 		$$.str = $1;
1019 	}
1020 
1021 not: TOKNOT
1022       {
1023 	      $$ = 1;
1024       }
1025     | /* empty */
1026       {
1027 	      $$ = 0;
1028       }
1029 
1030 keep: TOKKEEP
1031       {
1032 	      $$ = 1;
1033       }
1034     | /* empty */
1035       {
1036 	      $$ = 0;
1037       }
1038 
1039 disabled: TOKDISABLED
1040 	  {
1041 		  $$ = 1;
1042 	  }
1043 	| /* empty */
1044 	  {
1045 		  $$ = 0;
1046 	  }
1047 
1048 port: TOKPORT replstrv
1049       {
1050 	      if (*$2 == '\0')
1051 		      yyerror("invalid port");
1052 
1053 	      $$ = $2;
1054       }
1055     | TOKPORT numv
1056       {
1057 	      if ($2 == 0 || $2 > 65535)
1058 		      yyerror("invalid port");
1059 
1060 	      xasprintf(&$$, "%lld", $2);
1061       }
1062 
1063 server: TOKSERVER replstrv port
1064 {
1065 		if (*$2 == '\0')
1066 			yyerror("invalid host");
1067 
1068 		$$.host = $2;
1069 		$$.port = $3;
1070 	}
1071       | TOKSERVER replstrv
1072 	{
1073 		if (*$2 == '\0')
1074 			yyerror("invalid host");
1075 
1076 		$$.host = $2;
1077 		$$.port = NULL;
1078 	}
1079 
1080 to: /* empty */
1081     {
1082 	    $$ = NULL;
1083     }
1084   | TOKTO strv
1085     {
1086 	    $$ = $2;
1087     }
1088 
1089 from: /* empty */
1090       {
1091 	      $$ = NULL;
1092       }
1093     | TOKFROM strv
1094       {
1095 	      $$ = $2;
1096       }
1097 
1098 compress: TOKCOMPRESS
1099 	  {
1100 		  $$ = 1;
1101 	  }
1102 	| /* empty */
1103 	  {
1104 		  $$ = 0;
1105 	  }
1106 
1107 actitem: execpipe strv
1108 	 {
1109 		 struct deliver_pipe_data	*data;
1110 
1111 		 if (*$2 == '\0')
1112 			 yyerror("invalid command");
1113 
1114 		 $$ = xcalloc(1, sizeof *$$);
1115 		 $$->deliver = &deliver_pipe;
1116 
1117 		 data = xcalloc(1, sizeof *data);
1118 		 $$->data = data;
1119 
1120 		 data->pipe = $1;
1121 		 data->cmd.str = $2;
1122 	 }
1123        | TOKREWRITE strv
1124 	 {
1125 		 struct deliver_rewrite_data	*data;
1126 
1127 		 if (*$2 == '\0')
1128 			 yyerror("invalid command");
1129 
1130 		 $$ = xcalloc(1, sizeof *$$);
1131 		 $$->deliver = &deliver_rewrite;
1132 
1133 		 data = xcalloc(1, sizeof *data);
1134 		 $$->data = data;
1135 
1136 		 data->cmd.str = $2;
1137 	 }
1138        | writeappend strv
1139 	 {
1140 		 struct deliver_write_data	*data;
1141 
1142 		 if (*$2 == '\0')
1143 			 yyerror("invalid path");
1144 
1145 		 $$ = xcalloc(1, sizeof *$$);
1146 		 $$->deliver = &deliver_write;
1147 
1148 		 data = xcalloc(1, sizeof *data);
1149 		 $$->data = data;
1150 
1151 		 data->append = $1;
1152 		 data->path.str = $2;
1153 	 }
1154        | TOKMAILDIR strv
1155 	 {
1156 		 struct deliver_maildir_data	*data;
1157 
1158 		 if (*$2 == '\0')
1159 			 yyerror("invalid path");
1160 
1161 		 $$ = xcalloc(1, sizeof *$$);
1162 		 $$->deliver = &deliver_maildir;
1163 
1164 		 data = xcalloc(1, sizeof *data);
1165 		 $$->data = data;
1166 
1167 		 data->path.str = $2;
1168 	 }
1169        | rmheaders
1170 	 {
1171 		 struct deliver_remove_header_data *data;
1172 
1173 		 $$ = xcalloc(1, sizeof *$$);
1174 		 $$->deliver = &deliver_remove_header;
1175 
1176 		 data = xcalloc(1, sizeof *data);
1177 		 $$->data = data;
1178 
1179 		 data->hdrs = $1;
1180 	 }
1181        | TOKADDHEADER strv val
1182 	 {
1183 		 struct deliver_add_header_data	*data;
1184 
1185 		 if (*$2 == '\0')
1186 			 yyerror("invalid header");
1187 
1188 		 $$ = xcalloc(1, sizeof *$$);
1189 		 $$->deliver = &deliver_add_header;
1190 
1191 		 data = xcalloc(1, sizeof *data);
1192 		 $$->data = data;
1193 
1194 		 data->hdr.str = $2;
1195 		 data->value.str = $3;
1196 	 }
1197        | TOKMBOX strv compress
1198 	 {
1199 		 struct deliver_mbox_data	*data;
1200 
1201 		 if (*$2 == '\0')
1202 			 yyerror("invalid path");
1203 
1204 		 $$ = xcalloc(1, sizeof *$$);
1205 		 $$->deliver = &deliver_mbox;
1206 
1207 		 data = xcalloc(1, sizeof *data);
1208 		 $$->data = data;
1209 
1210 		 data->path.str = $2;
1211 		 data->compress = $3;
1212 	 }
1213        | imaptype server userpassnetrc folder1 verify nocrammd5 nologin
1214 	 starttls insecure
1215 	 {
1216 		 struct deliver_imap_data	*data;
1217 
1218 		 if ($1 && $8)
1219 			 yyerror("use either imaps or set starttls");
1220 
1221 		 $$ = xcalloc(1, sizeof *$$);
1222 		 $$->deliver = &deliver_imap;
1223 
1224 		 data = xcalloc(1, sizeof *data);
1225 		 $$->data = data;
1226 
1227 		 if ($3.user_netrc && $3.pass_netrc)
1228 			 find_netrc($2.host, &data->user, &data->pass);
1229 		 else {
1230 			 if ($3.user_netrc)
1231 				 find_netrc($2.host, &data->user, NULL);
1232 			 else
1233 				 data->user = $3.user;
1234 			 if ($3.pass_netrc)
1235 				 find_netrc($2.host, NULL, &data->pass);
1236 			 else
1237 				 data->pass = $3.pass;
1238 		 }
1239 
1240 		 data->folder.str = $4;
1241 		 data->server.ssl = $1;
1242 		 data->server.verify = $5;
1243 		 data->server.host = $2.host;
1244 		 if ($2.port != NULL)
1245 			 data->server.port = $2.port;
1246 		 else if ($1)
1247 			 data->server.port = xstrdup("imaps");
1248 		 else
1249 			 data->server.port = xstrdup("imap");
1250 		 data->server.ai = NULL;
1251 		 data->nocrammd5 = $6;
1252 		 data->nologin = $7;
1253 		 data->starttls = $8;
1254 		 data->server.insecure = $9;
1255 	 }
1256        | TOKSMTP server from to
1257 	 {
1258 		 struct deliver_smtp_data	*data;
1259 
1260 		 $$ = xcalloc(1, sizeof *$$);
1261 		 $$->deliver = &deliver_smtp;
1262 
1263 		 data = xcalloc(1, sizeof *data);
1264 		 $$->data = data;
1265 
1266 		 data->server.host = $2.host;
1267 		 if ($2.port != NULL)
1268 			 data->server.port = $2.port;
1269 		 else
1270 			 data->server.port = xstrdup("smtp");
1271 		 data->server.ai = NULL;
1272 		 data->from.str = $3;
1273 		 data->to.str = $4;
1274 	 }
1275        | TOKSTDOUT
1276 	 {
1277 		 $$ = xcalloc(1, sizeof *$$);
1278 		 $$->deliver = &deliver_stdout;
1279 	 }
1280        | TOKTAG strv optval
1281 	 {
1282 		 struct deliver_tag_data	*data;
1283 
1284 		 if (*$2 == '\0')
1285 			 yyerror("invalid tag");
1286 
1287 		 $$ = xcalloc(1, sizeof *$$);
1288 		 $$->deliver = &deliver_tag;
1289 
1290 		 data = xcalloc(1, sizeof *data);
1291 		 $$->data = data;
1292 
1293 		 data->key.str = $2;
1294 		 data->value.str = $3;
1295 	 }
1296        | TOKADDTOCACHE replpathv TOKKEY strv
1297 	 {
1298 		 struct deliver_add_to_cache_data	*data;
1299 
1300 		 if (*$2 == '\0')
1301 			 yyerror("invalid path");
1302 		 if (*$4 == '\0')
1303 			 yyerror("invalid key");
1304 
1305 		 $$ = xcalloc(1, sizeof *$$);
1306 		 $$->deliver = &deliver_add_to_cache;
1307 
1308 		 data = xcalloc(1, sizeof *data);
1309 		 $$->data = data;
1310 
1311 		 data->key.str = $4;
1312 		 data->path = $2;
1313 	 }
1314        | TOKREMOVEFROMCACHE replpathv TOKKEY strv
1315 	 {
1316 		 struct deliver_remove_from_cache_data	*data;
1317 
1318 		 if (*$2 == '\0')
1319 			 yyerror("invalid path");
1320 		 if (*$4 == '\0')
1321 			 yyerror("invalid key");
1322 
1323 		 $$ = xcalloc(1, sizeof *$$);
1324 		 $$->deliver = &deliver_remove_from_cache;
1325 
1326 		 data = xcalloc(1, sizeof *data);
1327 		 $$->data = data;
1328 
1329 		 data->key.str = $4;
1330 		 data->path = $2;
1331 	 }
1332        | actions
1333 	 {
1334 		 struct deliver_action_data	*data;
1335 
1336 		 /*
1337 		  * This is a special-case, handled when the list of delivery
1338 		  * targets is resolved rather than by calling a deliver
1339 		  * function, so the deliver pointer is NULL.
1340 		  */
1341 		 $$ = xcalloc(1, sizeof *$$);
1342 		 $$->deliver = NULL;
1343 
1344 		 data = xcalloc(1, sizeof *data);
1345 		 $$->data = data;
1346 
1347 		 data->actions = $1;
1348 	 }
1349        | TOKDROP
1350 	 {
1351 		 $$ = xcalloc(1, sizeof *$$);
1352 		 $$->deliver = &deliver_drop;
1353 	 }
1354        | TOKKEEP
1355 	 {
1356 		 $$ = xcalloc(1, sizeof *$$);
1357 		 $$->deliver = &deliver_keep;
1358 	 }
1359 
1360 actlist: actlist actitem
1361 	 {
1362 		 $$ = $1;
1363 
1364 		 TAILQ_INSERT_TAIL($$, $2, entry);
1365 		 $2->idx = parse_actionidx++;
1366 	 }
1367        | actitem
1368 	 {
1369 		 $$ = xmalloc(sizeof *$$);
1370 		 TAILQ_INIT($$);
1371 
1372 		 TAILQ_INSERT_HEAD($$, $1, entry);
1373 		 $1->idx = 0;
1374 
1375 		 parse_actionidx = 1;
1376 	 }
1377 
1378 defaction: TOKACTION replstrv users actitem
1379 	   {
1380 		   struct action	*t;
1381 
1382 		   if (strlen($2) >= MAXNAMESIZE)
1383 			   yyerror("action name too long: %s", $2);
1384 		   if (*$2 == '\0')
1385 			   yyerror("invalid action name");
1386 		   if (find_action($2) != NULL)
1387 			   yyerror("duplicate action: %s", $2);
1388 
1389 		   t = xmalloc(sizeof *t);
1390 		   strlcpy(t->name, $2, sizeof t->name);
1391 
1392 		   t->list = xmalloc(sizeof *t->list);
1393 		   TAILQ_INIT(t->list);
1394 		   TAILQ_INSERT_HEAD(t->list, $4, entry);
1395 		   $4->idx = 0;
1396 
1397 		   t->users = $3;
1398 		   TAILQ_INSERT_TAIL(&conf.actions, t, entry);
1399 
1400 		   print_action(t);
1401 
1402 		   xfree($2);
1403 	   }
1404 	 | TOKACTION replstrv users '{' actlist '}'
1405 	   {
1406 		   struct action	*t;
1407 
1408 		   if (strlen($2) >= MAXNAMESIZE)
1409 			   yyerror("action name too long: %s", $2);
1410 		   if (*$2 == '\0')
1411 			   yyerror("invalid action name");
1412 		   if (find_action($2) != NULL)
1413 			   yyerror("duplicate action: %s", $2);
1414 
1415 		   t = xmalloc(sizeof *t);
1416 		   strlcpy(t->name, $2, sizeof t->name);
1417 
1418 		   t->list = $5;
1419 
1420 		   t->users = $3;
1421 		   TAILQ_INSERT_TAIL(&conf.actions, t, entry);
1422 
1423 		   print_action(t);
1424 
1425 		   xfree($2);
1426 	   }
1427 
1428 accounts: accountp strv
1429 	  {
1430 		  if (*$2 == '\0')
1431 			  yyerror("invalid account name");
1432 		  if (!have_accounts($2))
1433 			  yyerror("no matching accounts: %s", $2);
1434 
1435 		  $$ = xmalloc(sizeof *$$);
1436 		  ARRAY_INIT($$);
1437 		  ARRAY_EXPAND($$, 1);
1438 		  ARRAY_LAST($$).str = $2;
1439 	  }
1440 	| accountp '{' replstrslist '}'
1441 	  {
1442 		  $$ = $3;
1443 	  }
1444 
1445 actions: actionp strv
1446 	 {
1447 		 if (*$2 == '\0')
1448 			 yyerror("invalid action name");
1449 
1450 		 $$ = xmalloc(sizeof *$$);
1451 		 ARRAY_INIT($$);
1452 		 ARRAY_EXPAND($$, 1);
1453 		 ARRAY_LAST($$).str = $2;
1454 	 }
1455        | actionp '{' replstrslist '}'
1456 	 {
1457 		 $$ = $3;
1458 	 }
1459 
1460 cont: /* empty */
1461       {
1462 	      $$ = 0;
1463       }
1464     | TOKCONTINUE
1465       {
1466 	      $$ = 1;
1467       }
1468 
1469 area: /* empty */
1470       {
1471 	      $$ = AREA_ANY;
1472       }
1473     | TOKIN TOKALL
1474       {
1475 	      $$ = AREA_ANY;
1476       }
1477     | TOKIN TOKHEADERS
1478       {
1479 	      $$ = AREA_HEADERS;
1480       }
1481     | TOKIN TOKBODY
1482       {
1483 	      $$ = AREA_BODY;
1484       }
1485 
1486 retrc: numv
1487        {
1488 	       if ($1 < 0 || $1 > 255)
1489 		       yyerror("invalid return code");
1490 
1491 	       $$ = $1;
1492        }
1493      | /* empty */
1494        {
1495 	       $$ = -1;
1496        }
1497 
1498 retre: casere
1499        {
1500 	       $$ = $1;
1501        }
1502      | /* empty */
1503        {
1504 	       $$.str = NULL;
1505        }
1506 
1507 ltgt: '<'
1508      {
1509 	     $$ = CMP_LT;
1510      }
1511    | '>'
1512      {
1513 	     $$ = CMP_GT;
1514      }
1515 
1516 eqne: TOKEQ
1517       {
1518 	      $$ = CMP_EQ;
1519       }
1520     | TOKNE
1521       {
1522 	      $$ = CMP_NE;
1523       }
1524 
1525 cmp: ltgt
1526      {
1527 	     $$ = $1;
1528      }
1529    | eqne
1530      {
1531 	     $$ = $1;
1532      }
1533 
1534 execpipe: TOKEXEC
1535 	  {
1536 		  $$ = 0;
1537 	  }
1538 	| TOKPIPE
1539 	  {
1540 		  $$ = 1;
1541 	  }
1542 
1543 writeappend: TOKWRITE
1544 	     {
1545 		     $$ = 0;
1546 	     }
1547 	   | TOKAPPEND
1548 	     {
1549 		     $$ = 1;
1550 	     }
1551 
1552 exprop: TOKAND
1553 	{
1554 		$$ = OP_AND;
1555 	}
1556       | TOKOR
1557 	{
1558 		$$ = OP_OR;
1559 	}
1560 
1561 expritem: not TOKALL
1562 	  {
1563 		  $$ = xcalloc(1, sizeof *$$);
1564 		  $$->match = &match_all;
1565 		  $$->inverted = $1;
1566 	  }
1567 	| not casere area
1568 	  {
1569 		  struct match_regexp_data	*data;
1570 		  char				*cause;
1571 
1572 		  $$ = xcalloc(1, sizeof *$$);
1573 		  $$->match = &match_regexp;
1574 		  $$->inverted = $1;
1575 
1576 		  data = xcalloc(1, sizeof *data);
1577 		  $$->data = data;
1578 
1579 		  data->area = $3;
1580 
1581 		  if (re_compile(&data->re, $2.str, $2.flags, &cause) != 0)
1582 			  yyerror("%s", cause);
1583 		  xfree($2.str);
1584 	  }
1585 	| not accounts
1586 	  {
1587 		  struct match_account_data	*data;
1588 
1589 		  $$ = xcalloc(1, sizeof *$$);
1590 		  $$->match = &match_account;
1591 		  $$->inverted = $1;
1592 
1593 		  data = xcalloc(1, sizeof *data);
1594 		  $$->data = data;
1595 
1596 		  data->accounts = $2;
1597 	  }
1598 	| not execpipe strv user TOKRETURNS '(' retrc ',' retre ')'
1599 	  {
1600 		  struct match_command_data	*data;
1601 		  char				*cause;
1602 
1603 		  if (*$3 == '\0' || ($3[0] == '|' && $3[1] == '\0'))
1604 			  yyerror("invalid command");
1605 		  if ($7 == -1 && $9.str == NULL)
1606 			  yyerror("return code or regexp must be specified");
1607 
1608 		  $$ = xcalloc(1, sizeof *$$);
1609 		  $$->match = &match_command;
1610 		  $$->inverted = $1;
1611 
1612 		  data = xcalloc(1, sizeof *data);
1613 		  $$->data = data;
1614 
1615 		  data->user.str = $4;
1616 		  data->pipe = $2;
1617 		  data->cmd.str = $3;
1618 
1619 		  data->ret = $7;
1620 
1621 		  if ($9.str != NULL) {
1622 			  if (re_compile(
1623 			      &data->re, $9.str, $9.flags, &cause) != 0)
1624 				  yyerror("%s", cause);
1625 			  xfree($9.str);
1626 		  }
1627 
1628 	  }
1629 	| not TOKTAGGED strv
1630 	  {
1631 		  struct match_tagged_data	*data;
1632 
1633 		  if (*$3 == '\0')
1634 			  yyerror("invalid tag");
1635 
1636 		  $$ = xcalloc(1, sizeof *$$);
1637 
1638 		  $$->match = &match_tagged;
1639 		  $$->inverted = $1;
1640 
1641 		  data = xcalloc(1, sizeof *data);
1642 		  $$->data = data;
1643 
1644 		  data->tag.str = $3;
1645 	  }
1646 	| not TOKSIZE ltgt size
1647 	  {
1648 		  struct match_size_data	*data;
1649 
1650 #if SIZE_MAX < LLONG_MAX
1651 		  if ($4 > SIZE_MAX)
1652 			  yyerror("size too large");
1653 #endif
1654 
1655 		  $$ = xcalloc(1, sizeof *$$);
1656 
1657 		  $$->match = &match_size;
1658 		  $$->inverted = $1;
1659 
1660 		  data = xcalloc(1, sizeof *data);
1661 		  $$->data = data;
1662 
1663 		  data->size = $4;
1664 		  data->cmp = $3;
1665 	  }
1666 	| not TOKSTRING strv TOKTO casere
1667 	  {
1668 		  struct match_string_data	*data;
1669 		  char				*cause;
1670 
1671 		  if (*$3 == '\0')
1672 			  yyerror("invalid string");
1673 
1674 		  $$ = xcalloc(1, sizeof *$$);
1675 
1676 		  $$->match = &match_string;
1677 		  $$->inverted = $1;
1678 
1679 		  data = xcalloc(1, sizeof *data);
1680 		  $$->data = data;
1681 
1682 		  data->str.str = $3;
1683 		  if (re_compile(
1684 		      &data->re, $5.str, $5.flags|RE_NOSUBST, &cause) != 0)
1685 			  yyerror("%s", cause);
1686 		  xfree($5.str);
1687 	  }
1688 	| not TOKINCACHE replpathv TOKKEY strv
1689 	  {
1690 		  struct match_in_cache_data	*data;
1691 
1692 		  if (*$3 == '\0')
1693 			  yyerror("invalid path");
1694 		  if (*$5 == '\0')
1695 			  yyerror("invalid key");
1696 
1697 		  $$ = xcalloc(1, sizeof *$$);
1698 
1699 		  $$->match = &match_in_cache;
1700 		  $$->inverted = $1;
1701 
1702 		  data = xcalloc(1, sizeof *data);
1703 		  $$->data = data;
1704 
1705 		  data->key.str = $5;
1706 		  data->path = $3;
1707 	  }
1708 	| not TOKMATCHED
1709 	  {
1710 		  $$ = xcalloc(1, sizeof *$$);
1711 
1712 		  $$->match = &match_matched;
1713 		  $$->inverted = $1;
1714 	  }
1715 	| not TOKUNMATCHED
1716 	  {
1717 		  $$ = xcalloc(1, sizeof *$$);
1718 
1719 		  $$->match = &match_unmatched;
1720 		  $$->inverted = $1;
1721 	  }
1722 	| not TOKAGE ltgt time
1723 	  {
1724 		  struct match_age_data	*data;
1725 
1726 		  if ($4 == 0)
1727 			  yyerror("invalid time");
1728 
1729 		  $$ = xcalloc(1, sizeof *$$);
1730 
1731 		  $$->match = &match_age;
1732 		  $$->inverted = $1;
1733 
1734 		  data = xcalloc(1, sizeof *data);
1735 		  $$->data = data;
1736 
1737 		  data->time = $4;
1738 		  data->cmp = $3;
1739 	  }
1740 	| not TOKAGE TOKINVALID
1741 	  {
1742 		  struct match_age_data	*data;
1743 
1744 		  $$ = xcalloc(1, sizeof *$$);
1745 
1746 		  $$->match = &match_age;
1747 		  $$->inverted = $1;
1748 
1749 		  data = xcalloc(1, sizeof *data);
1750 		  $$->data = data;
1751 
1752 		  data->time = -1;
1753 	  }
1754 	| not TOKATTACHMENT TOKCOUNT cmp numv
1755 	  {
1756 		  struct match_attachment_data	*data;
1757 
1758 		  $$ = xcalloc(1, sizeof *$$);
1759 
1760 		  $$->match = &match_attachment;
1761 		  $$->inverted = $1;
1762 
1763 		  data = xcalloc(1, sizeof *data);
1764 		  $$->data = data;
1765 
1766 		  data->op = ATTACHOP_COUNT;
1767 		  data->cmp = $4;
1768 		  data->value.num = $5;
1769 	  }
1770 	| not TOKATTACHMENT TOKTOTALSIZE ltgt size
1771 	  {
1772 		  struct match_attachment_data	*data;
1773 
1774 #if SIZE_MAX < LLONG_MAX
1775 		  if ($5 > SIZE_MAX)
1776 			  yyerror("size too large");
1777 #endif
1778 
1779 		  $$ = xcalloc(1, sizeof *$$);
1780 
1781 		  $$->match = &match_attachment;
1782 		  $$->inverted = $1;
1783 
1784 		  data = xcalloc(1, sizeof *data);
1785 		  $$->data = data;
1786 
1787 		  data->op = ATTACHOP_TOTALSIZE;
1788 		  data->cmp = $4;
1789 		  data->value.size = $5;
1790 	  }
1791 	| not TOKATTACHMENT TOKANYSIZE ltgt size
1792 	  {
1793 		  struct match_attachment_data	*data;
1794 
1795 #if SIZE_MAX < LLONG_MAX
1796 		  if ($5 > SIZE_MAX)
1797 			  yyerror("size too large");
1798 #endif
1799 
1800 		  $$ = xcalloc(1, sizeof *$$);
1801 
1802 		  $$->match = &match_attachment;
1803 		  $$->inverted = $1;
1804 
1805 		  data = xcalloc(1, sizeof *data);
1806 		  $$->data = data;
1807 
1808 		  data->op = ATTACHOP_ANYSIZE;
1809 		  data->cmp = $4;
1810 		  data->value.size = $5;
1811 	  }
1812 	| not TOKATTACHMENT TOKANYTYPE strv
1813 	  {
1814 		  struct match_attachment_data	*data;
1815 
1816 		  if (*$4 == '\0')
1817 			  yyerror("invalid string");
1818 
1819 		  $$ = xcalloc(1, sizeof *$$);
1820 
1821 		  $$->match = &match_attachment;
1822 		  $$->inverted = $1;
1823 
1824 		  data = xcalloc(1, sizeof *data);
1825 		  $$->data = data;
1826 
1827 		  data->op = ATTACHOP_ANYTYPE;
1828 		  data->value.str.str = $4;
1829 	  }
1830 	| not TOKATTACHMENT TOKANYNAME strv
1831 	  {
1832 		  struct match_attachment_data	*data;
1833 
1834 		  if (*$4 == '\0')
1835 			  yyerror("invalid string");
1836 
1837 		  $$ = xcalloc(1, sizeof *$$);
1838 
1839 		  $$->match = &match_attachment;
1840 		  $$->inverted = $1;
1841 
1842 		  data = xcalloc(1, sizeof *data);
1843 		  $$->data = data;
1844 
1845 		  data->op = ATTACHOP_ANYNAME;
1846 		  data->value.str.str = $4;
1847 	  }
1848 
1849 exprlist: exprlist exprop expritem
1850 	  {
1851 		  $$ = $1;
1852 
1853 		  $3->op = $2;
1854 		  TAILQ_INSERT_TAIL($$, $3, entry);
1855 	  }
1856 	| exprop expritem
1857 	  {
1858 		  $$ = xmalloc(sizeof *$$);
1859 		  TAILQ_INIT($$);
1860 
1861 		  $2->op = $1;
1862 		  TAILQ_INSERT_HEAD($$, $2, entry);
1863 	  }
1864 
1865 expr: expritem
1866       {
1867 	      $$ = xmalloc(sizeof *$$);
1868 	      TAILQ_INIT($$);
1869 
1870 	      TAILQ_INSERT_HEAD($$, $1, entry);
1871       }
1872     | expritem exprlist
1873       {
1874 	      $$ = $2;
1875 
1876 	      TAILQ_INSERT_HEAD($$, $1, entry);
1877       }
1878 
1879 perform: users actionp actitem cont
1880 	 {
1881 		 struct action	*t;
1882 
1883 		 $$ = xcalloc(1, sizeof *$$);
1884 		 $$->idx = parse_ruleidx++;
1885 		 $$->actions = NULL;
1886 		 TAILQ_INIT(&$$->rules);
1887 		 $$->stop = !$4;
1888 		 $$->users = $1;
1889 
1890 		 t = $$->lambda = xcalloc(1, sizeof *$$->lambda);
1891 		 xsnprintf(t->name, sizeof t->name, "<rule %u>", $$->idx);
1892 		 t->users = NULL;
1893 		 t->list = xmalloc(sizeof *t->list);
1894 		 TAILQ_INIT(t->list);
1895 		 TAILQ_INSERT_HEAD(t->list, $3, entry);
1896 		 $3->idx = 0;
1897 
1898 		 if (parse_rule == NULL)
1899 			 TAILQ_INSERT_TAIL(&conf.rules, $$, entry);
1900 		 else
1901 			 TAILQ_INSERT_TAIL(&parse_rule->rules, $$, entry);
1902 	 }
1903        | users actionp '{' actlist '}' cont
1904 	 {
1905 		 struct action	*t;
1906 
1907 		 $$ = xcalloc(1, sizeof *$$);
1908 		 $$->idx = parse_ruleidx++;
1909 		 $$->actions = NULL;
1910 		 TAILQ_INIT(&$$->rules);
1911 		 $$->stop = !$6;
1912 		 $$->users = $1;
1913 
1914 		 t = $$->lambda = xcalloc(1, sizeof *$$->lambda);
1915 		 xsnprintf(t->name, sizeof t->name, "<rule %u>", $$->idx);
1916 		 t->users = NULL;
1917 		 t->list = $4;
1918 
1919 		 if (parse_rule == NULL)
1920 			 TAILQ_INSERT_TAIL(&conf.rules, $$, entry);
1921 		 else
1922 			 TAILQ_INSERT_TAIL(&parse_rule->rules, $$, entry);
1923 	 }
1924        | users actions cont
1925 	 {
1926 		 $$ = xcalloc(1, sizeof *$$);
1927 		 $$->idx = parse_ruleidx++;
1928 		 $$->lambda = NULL;
1929 		 $$->actions = $2;
1930 		 TAILQ_INIT(&$$->rules);
1931 		 $$->stop = !$3;
1932 		 $$->users = $1;
1933 
1934 		 if (parse_rule == NULL)
1935 			 TAILQ_INSERT_TAIL(&conf.rules, $$, entry);
1936 		 else
1937 			 TAILQ_INSERT_TAIL(&parse_rule->rules, $$, entry);
1938 	 }
1939        | '{'
1940 	 {
1941 		 $$ = xcalloc(1, sizeof *$$);
1942 		 $$->idx = parse_ruleidx++;
1943 		 $$->lambda = NULL;
1944 		 $$->actions = NULL;
1945 		 TAILQ_INIT(&$$->rules);
1946 		 $$->stop = 0;
1947 		 $$->users = NULL;
1948 
1949 		 if (parse_rule == NULL)
1950 			 TAILQ_INSERT_TAIL(&conf.rules, $$, entry);
1951 		 else
1952 			 TAILQ_INSERT_TAIL(&parse_rule->rules, $$, entry);
1953 
1954 		 ARRAY_ADD(&parse_rulestack, parse_rule);
1955 		 parse_rule = $$;
1956 	 }
1957 
1958 close: '}'
1959        {
1960 	       if (parse_rule == NULL)
1961 		       yyerror("missing {");
1962 
1963 	       parse_rule = ARRAY_LAST(&parse_rulestack);
1964 	       ARRAY_TRUNC(&parse_rulestack, 1);
1965        }
1966 
1967 rule: TOKMATCH expr perform
1968       {
1969 	      $3->expr = $2;
1970 	      print_rule($3);
1971       }
1972 
1973 folderlist: /* empty */
1974 	    {
1975 		    $$ = xmalloc(sizeof *$$);
1976 		    ARRAY_INIT($$);
1977 		    ARRAY_ADD($$, xstrdup("INBOX"));
1978 	    }
1979 	  | folders
1980 	    {
1981 		    $$ = $1;
1982 	    }
1983 
1984 folder1: /* empty */
1985 	 {
1986 		 $$ = xstrdup("INBOX");
1987 	 }
1988        | folderp strv
1989 	 {
1990 		 $$ = $2;
1991 	 }
1992 
1993 
1994 groups: groupp replstrv
1995 	{
1996 		if (*$2 == '\0')
1997 			yyerror("invalid group");
1998 
1999 		$$ = xmalloc(sizeof *$$);
2000 		ARRAY_INIT($$);
2001 		ARRAY_ADD($$, $2);
2002 	}
2003       | groupp '{' stringslist '}'
2004 	{
2005 		$$ = $3;
2006 	}
2007 
2008 nocrammd5: TOKNOCRAMMD5
2009 	   {
2010 		   $$ = 1;
2011 	   }
2012 	 | /* empty */
2013 	   {
2014 		   $$ = 0;
2015 	   }
2016 
2017 nologin: TOKNOLOGIN
2018 	 {
2019 		 $$ = 1;
2020 	 }
2021        | /* empty */
2022 	 {
2023 		 $$ = 0;
2024 	 }
2025 
2026 starttls: TOKSTARTTLS
2027 	{
2028 		$$ = 1;
2029 	}
2030       | /* empty */
2031 	{
2032 		$$ = 0;
2033 	}
2034 
2035 
2036 uidl: TOKNOUIDL
2037 	{
2038 		$$ = 0;
2039 	}
2040       | /* empty */
2041 	{
2042 		$$ = 1;
2043 	}
2044 
2045 insecure: TOKINSECURE
2046 	{
2047 		$$ = 1;
2048 	}
2049       | /* empty */
2050 	{
2051 		$$ = 0;
2052 	}
2053 
2054 verify: TOKNOVERIFY
2055 	{
2056 		$$ = 0;
2057 	}
2058       | /* empty */
2059 	{
2060 		$$ = 1;
2061 	}
2062 
2063 apop: TOKNOAPOP
2064       {
2065 	      $$ = 0;
2066       }
2067     | /* empty */
2068       {
2069 	      $$ = 1;
2070       }
2071 
2072 only: TOKNEWONLY
2073       {
2074 	      $$ = FETCH_ONLY_NEW;
2075       }
2076     | TOKOLDONLY
2077       {
2078 	      $$ = FETCH_ONLY_OLD;
2079       }
2080 
2081 poptype: TOKPOP3
2082 	 {
2083 		 $$ = 0;
2084 	 }
2085        | TOKPOP3S
2086 	 {
2087 		 $$ = 1;
2088 	 }
2089 
2090 imaptype: TOKIMAP
2091 	  {
2092 		  $$ = 0;
2093 	  }
2094 	| TOKIMAPS
2095 	  {
2096 		  $$ = 1;
2097 	  }
2098 
2099 nntptype: TOKNNTP
2100 	  {
2101 		  $$ = 0;
2102 	  }
2103 	| TOKNNTPS
2104 	  {
2105 		  $$ = 1;
2106 	  }
2107 
2108 userpassnetrc: TOKUSER replstrv TOKPASS replstrv
2109 	       {
2110 		       if (*$2 == '\0')
2111 			       yyerror("invalid user");
2112 		       if (*$4 == '\0')
2113 			       yyerror("invalid pass");
2114 
2115 		       $$.user = $2;
2116 		       $$.user_netrc = 0;
2117 		       $$.pass = $4;
2118 		       $$.pass_netrc = 0;
2119 	       }
2120 	     | /* empty */
2121 	       {
2122 		       $$.user = NULL;
2123 		       $$.user_netrc = 1;
2124 		       $$.pass = NULL;
2125 		       $$.pass_netrc = 1;
2126 	       }
2127 	     | TOKUSER replstrv
2128 	       {
2129 		       if (*$2 == '\0')
2130 			       yyerror("invalid user");
2131 
2132 		       $$.user = $2;
2133 		       $$.user_netrc = 0;
2134 		       $$.pass = NULL;
2135 		       $$.pass_netrc = 1;
2136 	       }
2137 	     | TOKPASS replstrv
2138 	       {
2139 		       if (*$2 == '\0')
2140 			       yyerror("invalid pass");
2141 
2142 		       $$.user = NULL;
2143 		       $$.user_netrc = 1;
2144 		       $$.pass = $2;
2145 		       $$.pass_netrc = 0;
2146 	       }
2147 
2148 userpassreqd: TOKUSER replstrv TOKPASS replstrv
2149 	      {
2150 		      if (*$2 == '\0')
2151 			      yyerror("invalid user");
2152 		      if (*$4 == '\0')
2153 			      yyerror("invalid pass");
2154 
2155 		      $$.user = $2;
2156 		      $$.user_netrc = 0;
2157 		      $$.pass = $4;
2158 		      $$.pass_netrc = 0;
2159 	      }
2160 
2161 userpass: userpassreqd
2162 	  {
2163 		  $$.user = $1.user;
2164 		  $$.user_netrc = $1.user_netrc;
2165 		  $$.pass = $1.pass;
2166 		  $$.pass_netrc = $1.pass_netrc;
2167 	  }
2168 	| /* empty */
2169 	  {
2170 		  $$.user = NULL;
2171 		  $$.user_netrc = 0;
2172 		  $$.pass = NULL;
2173 		  $$.pass_netrc = 0;
2174 	  }
2175 
2176 poponly: only TOKCACHE replpathv
2177 	 {
2178 		 $$.path = $3;
2179 		 $$.only = $1;
2180 	 }
2181        | /* empty */
2182 	 {
2183 		 $$.path = NULL;
2184 		 $$.only = FETCH_ONLY_ALL;
2185 	 }
2186 
2187 imaponly: only
2188 	  {
2189 		  $$ = $1;
2190 	  }
2191 	| /* empty */
2192 	  {
2193 		  $$ = FETCH_ONLY_ALL;
2194 	  }
2195 
2196 fetchtype: poptype server userpassnetrc poponly apop verify uidl starttls
2197 	   insecure
2198 	   {
2199 		   struct fetch_pop3_data	*data;
2200 
2201 		   if ($1 && $8)
2202 			   yyerror("use either pop3s or set starttls");
2203 
2204 		   $$.fetch = &fetch_pop3;
2205 		   data = xcalloc(1, sizeof *data);
2206 		   $$.data = data;
2207 
2208 		   if ($3.user_netrc && $3.pass_netrc)
2209 			  find_netrc($2.host, &data->user, &data->pass);
2210 		   else {
2211 			   if ($3.user_netrc)
2212 				   find_netrc($2.host, &data->user, NULL);
2213 			   else
2214 				   data->user = $3.user;
2215 			   if ($3.pass_netrc)
2216 				   find_netrc($2.host, NULL, &data->pass);
2217 			   else
2218 				   data->pass = $3.pass;
2219 		   }
2220 
2221 		   data->server.ssl = $1;
2222 		   data->server.verify = $6;
2223 		   data->server.host = $2.host;
2224 		   if ($2.port != NULL)
2225 			   data->server.port = $2.port;
2226 		   else if ($1)
2227 			   data->server.port = xstrdup("pop3s");
2228 		   else
2229 			   data->server.port = xstrdup("pop3");
2230 		   data->server.ai = NULL;
2231 		   data->apop = $5;
2232 		   data->uidl = $7;
2233 		   data->starttls = $8;
2234 		   data->server.insecure = $9;
2235 
2236 		   data->path = $4.path;
2237 		   data->only = $4.only;
2238 	   }
2239 	 | TOKPOP3 TOKPIPE replstrv userpassreqd poponly apop
2240 	   {
2241 		   struct fetch_pop3_data	*data;
2242 
2243 		   $$.fetch = &fetch_pop3pipe;
2244 		   data = xcalloc(1, sizeof *data);
2245 		   $$.data = data;
2246 		   data->user = $4.user;
2247 		   data->pass = $4.pass;
2248 		   data->pipecmd = $3;
2249 		   if (data->pipecmd == NULL || *data->pipecmd == '\0')
2250 			   yyerror("invalid pipe command");
2251 		   data->apop = $6;
2252 		   data->path = $5.path;
2253 		   data->only = $5.only;
2254 	   }
2255 	 | imaptype server userpassnetrc folderlist imaponly verify nocrammd5
2256 	   nologin starttls insecure
2257 	   {
2258 		   struct fetch_imap_data	*data;
2259 
2260 		   if ($1 && $9)
2261 			   yyerror("use either imaps or set starttls");
2262 
2263 		   $$.fetch = &fetch_imap;
2264 		   data = xcalloc(1, sizeof *data);
2265 		   $$.data = data;
2266 
2267 		   if ($3.user_netrc && $3.pass_netrc)
2268 			   find_netrc($2.host, &data->user, &data->pass);
2269 		   else {
2270 			   if ($3.user_netrc)
2271 				   find_netrc($2.host, &data->user, NULL);
2272 			   else
2273 				   data->user = $3.user;
2274 			   if ($3.pass_netrc)
2275 				   find_netrc($2.host, NULL, &data->pass);
2276 			   else
2277 				   data->pass = $3.pass;
2278 		   }
2279 
2280 		   data->folders = $4;
2281 		   data->server.ssl = $1;
2282 		   data->server.verify = $6;
2283 		   data->server.host = $2.host;
2284 		   if ($2.port != NULL)
2285 			   data->server.port = $2.port;
2286 		   else if ($1)
2287 			   data->server.port = xstrdup("imaps");
2288 		   else
2289 			   data->server.port = xstrdup("imap");
2290 		   data->server.ai = NULL;
2291 		   data->only = $5;
2292 		   data->nocrammd5 = $7;
2293 		   data->nologin = $8;
2294 		   data->starttls = $9;
2295 		   data->server.insecure = $10;
2296 	   }
2297 	 | TOKIMAP TOKPIPE replstrv userpass folderlist imaponly
2298 	   {
2299 		   struct fetch_imap_data	*data;
2300 
2301 		   $$.fetch = &fetch_imappipe;
2302 		   data = xcalloc(1, sizeof *data);
2303 		   $$.data = data;
2304 		   data->user = $4.user;
2305 		   data->pass = $4.pass;
2306 		   data->folders = $5;
2307 		   data->pipecmd = $3;
2308 		   if (data->pipecmd == NULL || *data->pipecmd == '\0')
2309 			   yyerror("invalid pipe command");
2310 		   data->only = $6;
2311 	   }
2312 	 | TOKSTDIN
2313 	   {
2314 		   $$.fetch = &fetch_stdin;
2315 	   }
2316 	 | maildirs
2317 	   {
2318 		   struct fetch_maildir_data	*data;
2319 
2320 		   $$.fetch = &fetch_maildir;
2321 		   data = xcalloc(1, sizeof *data);
2322 		   $$.data = data;
2323 		   data->maildirs = $1;
2324 	   }
2325 	 | mboxes
2326 	   {
2327 		   struct fetch_mbox_data	*data;
2328 
2329 		   $$.fetch = &fetch_mbox;
2330 		   data = xcalloc(1, sizeof *data);
2331 		   $$.data = data;
2332 		   data->mboxes = $1;
2333 	   }
2334 	 | nntptype server userpassnetrc groups TOKCACHE replpathv verify
2335 	   insecure
2336 	   {
2337 		   struct fetch_nntp_data	*data;
2338 		   char				*cause;
2339 
2340 		   if (*$6 == '\0')
2341 			   yyerror("invalid cache");
2342 
2343 		   $$.fetch = &fetch_nntp;
2344 		   data = xcalloc(1, sizeof *data);
2345 		   $$.data = data;
2346 
2347 		   if ($3.user_netrc && $3.pass_netrc) {
2348 			   if (find_netrc1($2.host,
2349 			       &data->user, &data->pass, &cause) != 0) {
2350 				   log_debug2("%s", cause);
2351 				   xfree(cause);
2352 				   data->user = NULL;
2353 				   data->pass = NULL;
2354 			   }
2355 
2356 		   } else {
2357 			   if ($3.user_netrc)
2358 				   find_netrc($2.host, &data->user, NULL);
2359 			   else
2360 				   data->user = $3.user;
2361 			   if ($3.pass_netrc)
2362 				   find_netrc($2.host, NULL, &data->pass);
2363 			   else
2364 				   data->pass = $3.pass;
2365 		   }
2366 
2367 		   data->names = $4;
2368 		   data->path = $6;
2369 		   if (data->path == NULL || *data->path == '\0')
2370 			   yyerror("invalid cache");
2371 
2372 		   data->server.ssl = $1;
2373 		   data->server.verify = $7;
2374 		   data->server.insecure = $8;
2375 		   data->server.host = $2.host;
2376 		   if ($2.port != NULL)
2377 			   data->server.port = $2.port;
2378 		   else if ($1)
2379 			   data->server.port = xstrdup("nntps");
2380 		   else
2381 			   data->server.port = xstrdup("nntp");
2382 		   data->server.ai = NULL;
2383 	   }
2384 
2385 account: TOKACCOUNT replstrv disabled users fetchtype keep
2386 	 {
2387 		 struct account		*a;
2388 		 char			*su, desc[DESCBUFSIZE];
2389 
2390 		 if (strlen($2) >= MAXNAMESIZE)
2391 			 yyerror("account name too long: %s", $2);
2392 		 if (*$2 == '\0')
2393 			 yyerror("invalid account name");
2394 		 if (find_account($2) != NULL)
2395 			 yyerror("duplicate account: %s", $2);
2396 
2397 		 a = xcalloc(1, sizeof *a);
2398 		 strlcpy(a->name, $2, sizeof a->name);
2399 		 a->keep = $6;
2400 		 a->disabled = $3;
2401 		 a->users = $4;
2402 		 a->fetch = $5.fetch;
2403 		 a->data = $5.data;
2404 		 TAILQ_INSERT_TAIL(&conf.accounts, a, entry);
2405 
2406 		 if (a->users != NULL)
2407 			 su = fmt_replstrs(" users=", a->users);
2408 		 else
2409 			 su = xstrdup("");
2410 		 a->fetch->desc(a, desc, sizeof desc);
2411 		 log_debug2("added account \"%s\":%s fetch=%s", a->name, su,
2412 		     desc);
2413 		 xfree(su);
2414 
2415 		 xfree($2);
2416 	 }
2417 
2418 %%
2419 
2420 /* Programs */
2421