]> Pileus Git - ~andy/fetchmail/blob - rcfile_y.y
Treat permanent delivery errors as temporary (configurable).
[~andy/fetchmail] / rcfile_y.y
1 %{
2 /*
3  * rcfile_y.y -- Run control file parser for fetchmail
4  *
5  * For license terms, see the file COPYING in this directory.
6  */
7
8 #include "config.h"
9 #include <stdio.h>
10 #include <sys/types.h>
11 #include <sys/file.h>
12 #if defined(HAVE_SYS_WAIT_H)
13 #include <sys/wait.h>
14 #endif
15 #include <sys/stat.h>
16 #include <errno.h>
17 #if defined(STDC_HEADERS)
18 #include <stdlib.h>
19 #endif
20 #if defined(HAVE_UNISTD_H)
21 #include <unistd.h>
22 #endif
23 #include <string.h>
24
25 #if defined(__CYGWIN__)
26 #include <sys/cygwin.h>
27 #endif /* __CYGWIN__ */
28
29 #include "fetchmail.h"
30 #include "i18n.h"
31   
32 /* parser reads these */
33 char *rcfile;                   /* path name of rc file */
34 struct query cmd_opts;          /* where to put command-line info */
35
36 /* parser sets these */
37 struct query *querylist;        /* head of server list (globally visible) */
38
39 int yydebug;                    /* in case we didn't generate with -- debug */
40
41 static struct query current;    /* current server record */
42 static int prc_errflag;
43 static struct hostdata *leadentry;
44 static flag trailer;
45
46 static void record_current(void);
47 static void user_reset(void);
48 static void reset_server(const char *name, int skip);
49
50 /* these should be of size PATH_MAX */
51 char currentwd[1024] = "", rcfiledir[1024] = "";
52
53 /* using Bison, this arranges that yydebug messages will show actual tokens */
54 extern char * yytext;
55 #define YYPRINT(fp, type, val)  fprintf(fp, " = \"%s\"", yytext)
56 %}
57
58 %union {
59   int proto;
60   int number;
61   char *sval;
62 }
63
64 %token DEFAULTS POLL SKIP VIA AKA LOCALDOMAINS PROTOCOL
65 %token AUTHENTICATE TIMEOUT KPOP SDPS ENVELOPE QVIRTUAL
66 %token USERNAME PASSWORD FOLDER SMTPHOST FETCHDOMAINS MDA BSMTP LMTP
67 %token SMTPADDRESS SMTPNAME SPAMRESPONSE PRECONNECT POSTCONNECT LIMIT WARNINGS
68 %token INTERFACE MONITOR PLUGIN PLUGOUT
69 %token IS HERE THERE TO MAP WILDCARD
70 %token BATCHLIMIT FETCHLIMIT FETCHSIZELIMIT FASTUIDL EXPUNGE PROPERTIES
71 %token SET LOGFILE DAEMON SYSLOG IDFILE PIDFILE INVISIBLE POSTMASTER BOUNCEMAIL
72 %token SPAMBOUNCE SOFTBOUNCE SHOWDOTS
73 %token <proto> PROTO AUTHTYPE
74 %token <sval>  STRING
75 %token <number> NUMBER
76 %token NO KEEP FLUSH LIMITFLUSH FETCHALL REWRITE FORCECR STRIPCR PASS8BITS 
77 %token DROPSTATUS DROPDELIVERED
78 %token DNS SERVICE PORT UIDL INTERVAL MIMEDECODE IDLE CHECKALIAS 
79 %token SSL SSLKEY SSLCERT SSLPROTO SSLCERTCK SSLCERTPATH SSLCOMMONNAME SSLFINGERPRINT
80 %token PRINCIPAL ESMTPNAME ESMTPPASSWORD
81 %token TRACEPOLLS
82
83 %%
84
85 rcfile          : /* empty */
86                 | statement_list
87                 ;
88
89 statement_list  : statement
90                 | statement_list statement
91                 ;
92
93 optmap          : MAP | /* EMPTY */;
94
95 /* future global options should also have the form SET <name> optmap <value> */
96 statement       : SET LOGFILE optmap STRING     {run.logfile = prependdir ($4, rcfiledir);}
97                 | SET IDFILE optmap STRING      {run.idfile = prependdir ($4, rcfiledir);}
98                 | SET PIDFILE optmap STRING     {run.pidfile = prependdir ($4, rcfiledir);}
99                 | SET DAEMON optmap NUMBER      {run.poll_interval = $4;}
100                 | SET POSTMASTER optmap STRING  {run.postmaster = xstrdup($4);}
101                 | SET BOUNCEMAIL                {run.bouncemail = TRUE;}
102                 | SET NO BOUNCEMAIL             {run.bouncemail = FALSE;}
103                 | SET SPAMBOUNCE                {run.spambounce = TRUE;}
104                 | SET NO SPAMBOUNCE             {run.spambounce = FALSE;}
105                 | SET SOFTBOUNCE                {run.softbounce = TRUE;}
106                 | SET NO SOFTBOUNCE             {run.softbounce = FALSE;}
107                 | SET PROPERTIES optmap STRING  {run.properties =xstrdup($4);}
108                 | SET SYSLOG                    {run.use_syslog = TRUE;}
109                 | SET NO SYSLOG                 {run.use_syslog = FALSE;}
110                 | SET INVISIBLE                 {run.invisible = TRUE;}
111                 | SET NO INVISIBLE              {run.invisible = FALSE;}
112                 | SET SHOWDOTS                  {run.showdots = FLAG_TRUE;}
113                 | SET NO SHOWDOTS               {run.showdots = FLAG_FALSE;}
114
115 /* 
116  * The way the next two productions are written depends on the fact that
117  * userspecs cannot be empty.  It's a kluge to deal with files that set
118  * up a load of defaults and then have poll statements following with no
119  * user options at all. 
120  */
121                 | define_server serverspecs             {record_current();}
122                 | define_server serverspecs userspecs
123
124 /* detect and complain about the most common user error */
125                 | define_server serverspecs userspecs serv_option
126                         {yyerror(GT_("server option after user options"));}
127                 ;
128
129 define_server   : POLL STRING           {reset_server($2, FALSE);}
130                 | SKIP STRING           {reset_server($2, TRUE);}
131                 | DEFAULTS              {reset_server("defaults", FALSE);}
132                 ;
133
134 serverspecs     : /* EMPTY */
135                 | serverspecs serv_option
136                 ;
137
138 alias_list      : STRING                {save_str(&current.server.akalist,$1,0);}
139                 | alias_list STRING     {save_str(&current.server.akalist,$2,0);}
140                 ;
141
142 domain_list     : STRING                {save_str(&current.server.localdomains,$1,0);}
143                 | domain_list STRING    {save_str(&current.server.localdomains,$2,0);}
144                 ;
145
146 serv_option     : AKA alias_list
147                 | VIA STRING            {current.server.via = xstrdup($2);}
148                 | LOCALDOMAINS domain_list
149                 | PROTOCOL PROTO        {current.server.protocol = $2;}
150                 | PROTOCOL KPOP         {
151                                             current.server.protocol = P_POP3;
152
153                                             if (current.server.authenticate == A_PASSWORD)
154 #ifdef KERBEROS_V5
155                                                 current.server.authenticate = A_KERBEROS_V5;
156 #else
157                                                 current.server.authenticate = A_KERBEROS_V4;
158 #endif /* KERBEROS_V5 */
159                                             current.server.service = KPOP_PORT;
160                                         }
161                 | PRINCIPAL STRING      {current.server.principal = xstrdup($2);}
162                 | ESMTPNAME STRING      {current.server.esmtp_name = xstrdup($2);}
163                 | ESMTPPASSWORD STRING  {current.server.esmtp_password = xstrdup($2);}
164                 | PROTOCOL SDPS         {
165 #ifdef SDPS_ENABLE
166                                             current.server.protocol = P_POP3;
167                                             current.server.sdps = TRUE;
168 #else
169                                             yyerror(GT_("SDPS not enabled."));
170 #endif /* SDPS_ENABLE */
171                                         }
172                 | UIDL                  {current.server.uidl = FLAG_TRUE;}
173                 | NO UIDL               {current.server.uidl  = FLAG_FALSE;}
174                 | CHECKALIAS            {current.server.checkalias = FLAG_TRUE;}
175                 | NO CHECKALIAS         {current.server.checkalias  = FLAG_FALSE;}
176                 | SERVICE STRING        {
177                                         current.server.service = $2;
178                                         }
179                 | SERVICE NUMBER        {
180                                         int port = $2;
181                                         char buf[10];
182                                         snprintf(buf, sizeof buf, "%d", port);
183                                         current.server.service = xstrdup(buf);
184                 }
185                 | PORT NUMBER           {
186                                         int port = $2;
187                                         char buf[10];
188                                         snprintf(buf, sizeof buf, "%d", port);
189                                         current.server.service = xstrdup(buf);
190                 }
191                 | INTERVAL NUMBER
192                         {current.server.interval = $2;}
193                 | AUTHENTICATE AUTHTYPE
194                         {current.server.authenticate = $2;}
195                 | TIMEOUT NUMBER
196                         {current.server.timeout = $2;}
197                 | ENVELOPE NUMBER STRING 
198                                         {
199                                             current.server.envelope = 
200                                                 xstrdup($3);
201                                             current.server.envskip = $2;
202                                         }
203                 | ENVELOPE STRING
204                                         {
205                                             current.server.envelope = 
206                                                 xstrdup($2);
207                                             current.server.envskip = 0;
208                                         }
209
210                 | QVIRTUAL STRING       {current.server.qvirtual=xstrdup($2);}
211                 | INTERFACE STRING      {
212 #ifdef CAN_MONITOR
213                                         interface_parse($2, &current.server);
214 #else
215                                         fprintf(stderr, GT_("fetchmail: interface option is only supported under Linux (without IPv6) and FreeBSD\n"));
216 #endif
217                                         }
218                 | MONITOR STRING        {
219 #ifdef CAN_MONITOR
220                                         current.server.monitor = xstrdup($2);
221 #else
222                                         fprintf(stderr, GT_("fetchmail: monitor option is only supported under Linux (without IPv6) and FreeBSD\n"));
223 #endif
224                                         }
225                 | PLUGIN STRING         { current.server.plugin = xstrdup($2); }
226                 | PLUGOUT STRING        { current.server.plugout = xstrdup($2); }
227                 | DNS                   {current.server.dns = FLAG_TRUE;}
228                 | NO DNS                {current.server.dns = FLAG_FALSE;}
229                 | NO ENVELOPE           {current.server.envelope = STRING_DISABLED;}
230                 | TRACEPOLLS            {current.server.tracepolls = FLAG_TRUE;}
231                 | NO TRACEPOLLS         {current.server.tracepolls = FLAG_FALSE;}
232                 ;
233
234 userspecs       : user1opts             {record_current(); user_reset();}
235                 | explicits
236                 ;
237
238 explicits       : explicitdef           {record_current(); user_reset();}
239                 | explicits explicitdef {record_current(); user_reset();}
240                 ;
241
242 explicitdef     : userdef user0opts
243                 ;
244
245 userdef         : USERNAME STRING       {current.remotename = xstrdup($2);}
246                 | USERNAME mapping_list HERE
247                 | USERNAME STRING THERE {current.remotename = xstrdup($2);}
248                 ;
249
250 user0opts       : /* EMPTY */
251                 | user0opts user_option
252                 ;
253
254 user1opts       : user_option
255                 | user1opts user_option
256                 ;
257
258 localnames      : WILDCARD              {current.wildcard =  TRUE;}
259                 | mapping_list          {current.wildcard =  FALSE;}
260                 | mapping_list WILDCARD {current.wildcard =  TRUE;}
261                 ;
262
263 mapping_list    : mapping               
264                 | mapping_list mapping
265                 ;
266
267 mapping         : STRING        
268                                 {save_str_pair(&current.localnames, $1, NULL);}
269                 | STRING MAP STRING
270                                 {save_str_pair(&current.localnames, $1, $3);}
271                 ;
272
273 folder_list     : STRING                {save_str(&current.mailboxes,$1,0);}
274                 | folder_list STRING    {save_str(&current.mailboxes,$2,0);}
275                 ;
276
277 smtp_list       : STRING                {save_str(&current.smtphunt, $1,TRUE);}
278                 | smtp_list STRING      {save_str(&current.smtphunt, $2,TRUE);}
279                 ;
280
281 fetch_list      : STRING                {save_str(&current.domainlist, $1,TRUE);}
282                 | fetch_list STRING     {save_str(&current.domainlist, $2,TRUE);}
283                 ;
284
285 num_list        : NUMBER
286                         {
287                             struct idlist *id;
288                             id=save_str(&current.antispam,STRING_DUMMY,0);
289                             id->val.status.num = $1;
290                         }
291                 | num_list NUMBER
292                         {
293                             struct idlist *id;
294                             id=save_str(&current.antispam,STRING_DUMMY,0);
295                             id->val.status.num = $2;
296                         }
297                 ;
298
299 user_option     : TO localnames HERE
300                 | TO localnames
301                 | IS localnames HERE
302                 | IS localnames
303
304                 | IS STRING THERE       {current.remotename  = xstrdup($2);}
305                 | PASSWORD STRING       {current.password    = xstrdup($2);}
306                 | FOLDER folder_list
307                 | SMTPHOST smtp_list
308                 | FETCHDOMAINS fetch_list
309                 | SMTPADDRESS STRING    {current.smtpaddress = xstrdup($2);}
310                 | SMTPNAME STRING       {current.smtpname = xstrdup($2);}
311                 | SPAMRESPONSE num_list
312                 | MDA STRING            {current.mda         = xstrdup($2);}
313                 | BSMTP STRING          {current.bsmtp       = prependdir ($2, rcfiledir);}
314                 | LMTP                  {current.listener    = LMTP_MODE;}
315                 | PRECONNECT STRING     {current.preconnect  = xstrdup($2);}
316                 | POSTCONNECT STRING    {current.postconnect = xstrdup($2);}
317
318                 | KEEP                  {current.keep        = FLAG_TRUE;}
319                 | FLUSH                 {current.flush       = FLAG_TRUE;}
320                 | LIMITFLUSH            {current.limitflush  = FLAG_TRUE;}
321                 | FETCHALL              {current.fetchall    = FLAG_TRUE;}
322                 | REWRITE               {current.rewrite     = FLAG_TRUE;}
323                 | FORCECR               {current.forcecr     = FLAG_TRUE;}
324                 | STRIPCR               {current.stripcr     = FLAG_TRUE;}
325                 | PASS8BITS             {current.pass8bits   = FLAG_TRUE;}
326                 | DROPSTATUS            {current.dropstatus  = FLAG_TRUE;}
327                 | DROPDELIVERED         {current.dropdelivered = FLAG_TRUE;}
328                 | MIMEDECODE            {current.mimedecode  = FLAG_TRUE;}
329                 | IDLE                  {current.idle        = FLAG_TRUE;}
330
331                 | SSL                   {
332 #ifdef SSL_ENABLE
333                     current.use_ssl = FLAG_TRUE;
334 #else
335                     yyerror(GT_("SSL is not enabled"));
336 #endif 
337                 }
338                 | SSLKEY STRING         {current.sslkey = prependdir ($2, rcfiledir);}
339                 | SSLCERT STRING        {current.sslcert = prependdir ($2, rcfiledir);}
340                 | SSLPROTO STRING       {current.sslproto = xstrdup($2);}
341                 | SSLCERTCK             {current.sslcertck = FLAG_TRUE;}
342                 | SSLCERTPATH STRING    {current.sslcertpath = prependdir($2, rcfiledir);}
343                 | SSLCOMMONNAME STRING  {current.sslcommonname = xstrdup($2);}
344                 | SSLFINGERPRINT STRING {current.sslfingerprint = xstrdup($2);}
345
346                 | NO KEEP               {current.keep        = FLAG_FALSE;}
347                 | NO FLUSH              {current.flush       = FLAG_FALSE;}
348                 | NO LIMITFLUSH         {current.limitflush  = FLAG_FALSE;}
349                 | NO FETCHALL           {current.fetchall    = FLAG_FALSE;}
350                 | NO REWRITE            {current.rewrite     = FLAG_FALSE;}
351                 | NO FORCECR            {current.forcecr     = FLAG_FALSE;}
352                 | NO STRIPCR            {current.stripcr     = FLAG_FALSE;}
353                 | NO PASS8BITS          {current.pass8bits   = FLAG_FALSE;}
354                 | NO DROPSTATUS         {current.dropstatus  = FLAG_FALSE;}
355                 | NO DROPDELIVERED      {current.dropdelivered = FLAG_FALSE;}
356                 | NO MIMEDECODE         {current.mimedecode  = FLAG_FALSE;}
357                 | NO IDLE               {current.idle        = FLAG_FALSE;}
358
359                 | NO SSL                {current.use_ssl     = FLAG_FALSE;}
360
361                 | LIMIT NUMBER          {current.limit       = NUM_VALUE_IN($2);}
362                 | WARNINGS NUMBER       {current.warnings    = NUM_VALUE_IN($2);}
363                 | FETCHLIMIT NUMBER     {current.fetchlimit  = NUM_VALUE_IN($2);}
364                 | FETCHSIZELIMIT NUMBER {current.fetchsizelimit = NUM_VALUE_IN($2);}
365                 | FASTUIDL NUMBER       {current.fastuidl    = NUM_VALUE_IN($2);}
366                 | BATCHLIMIT NUMBER     {current.batchlimit  = NUM_VALUE_IN($2);}
367                 | EXPUNGE NUMBER        {current.expunge     = NUM_VALUE_IN($2);}
368
369                 | PROPERTIES STRING     {current.properties  = xstrdup($2);}
370                 ;
371 %%
372
373 /* lexer interface */
374 extern char *rcfile;
375 extern int prc_lineno;
376 extern char *yytext;
377 extern FILE *yyin;
378
379 static struct query *hosttail;  /* where to add new elements */
380
381 void yyerror (const char *s)
382 /* report a syntax error */
383 {
384     report_at_line(stderr, 0, rcfile, prc_lineno, GT_("%s at %s"), s, 
385                    (yytext && yytext[0]) ? yytext : GT_("end of input"));
386     prc_errflag++;
387 }
388
389 /** check that a configuration file is secure, returns PS_* status codes */
390 int prc_filecheck(const char *pathname,
391                   const flag securecheck /** shortcuts permission, filetype and uid tests if false */)
392 {
393 #ifndef __EMX__
394     struct stat statbuf;
395
396     errno = 0;
397
398     /* special case useful for debugging purposes */
399     if (strcmp("/dev/null", pathname) == 0)
400         return(PS_SUCCESS);
401
402     /* pass through the special name for stdin */
403     if (strcmp("-", pathname) == 0)
404         return(PS_SUCCESS);
405
406     /* the run control file must have the same uid as the REAL uid of this 
407        process, it must have permissions no greater than 600, and it must not 
408        be a symbolic link.  We check these conditions here. */
409
410     if (stat(pathname, &statbuf) < 0) {
411         if (errno == ENOENT) 
412             return(PS_SUCCESS);
413         else {
414             report(stderr, "lstat: %s: %s\n", pathname, strerror(errno));
415             return(PS_IOERR);
416         }
417     }
418
419     if (!securecheck)   return PS_SUCCESS;
420
421     if (!S_ISREG(statbuf.st_mode))
422     {
423         fprintf(stderr, GT_("File %s must be a regular file.\n"), pathname);
424         return(PS_IOERR);
425     }
426
427 #ifndef __BEOS__
428 #ifdef __CYGWIN__
429     if (cygwin_internal(CW_CHECK_NTSEC, pathname))
430 #endif /* __CYGWIN__ */
431     if (statbuf.st_mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH))
432     {
433         fprintf(stderr, GT_("File %s must have no more than -rwx------ (0700) permissions.\n"), 
434                 pathname);
435         return(PS_IOERR);
436     }
437 #endif /* __BEOS__ */
438
439 #ifdef HAVE_GETEUID
440     if (statbuf.st_uid != geteuid())
441 #else
442     if (statbuf.st_uid != getuid())
443 #endif /* HAVE_GETEUID */
444     {
445         fprintf(stderr, GT_("File %s must be owned by you.\n"), pathname);
446         return(PS_IOERR);
447     }
448 #endif
449     return(PS_SUCCESS);
450 }
451
452 int prc_parse_file (const char *pathname, const flag securecheck)
453 /* digest the configuration into a linked list of host records */
454 {
455     prc_errflag = 0;
456     querylist = hosttail = (struct query *)NULL;
457
458     errno = 0;
459
460     /* Check that the file is secure */
461     if ( (prc_errflag = prc_filecheck(pathname, securecheck)) != 0 )
462         return(prc_errflag);
463
464     /*
465      * Croak if the configuration directory does not exist.
466      * This probably means an NFS mount failed and we can't
467      * see a configuration file that ought to be there.
468      * Question: is this a portable check? It's not clear
469      * that all implementations of lstat() will return ENOTDIR
470      * rather than plain ENOENT in this case...
471      */
472     if (errno == ENOTDIR)
473         return(PS_IOERR);
474     else if (errno == ENOENT)
475         return(PS_SUCCESS);
476
477     /* Open the configuration file and feed it to the lexer. */
478     if (strcmp(pathname, "-") == 0)
479         yyin = stdin;
480     else if ((yyin = fopen(pathname,"r")) == (FILE *)NULL) {
481         report(stderr, "open: %s: %s\n", pathname, strerror(errno));
482         return(PS_IOERR);
483     }
484
485     yyparse();          /* parse entire file */
486
487     fclose(yyin);       /* not checking this should be safe, file mode was r */
488
489     if (prc_errflag) 
490         return(PS_SYNTAX);
491     else
492         return(PS_SUCCESS);
493 }
494
495 static void reset_server(const char *name, int skip)
496 /* clear the entire global record and initialize it with a new name */
497 {
498     trailer = FALSE;
499     memset(&current,'\0',sizeof(current));
500     current.smtp_socket = -1;
501     current.server.pollname = xstrdup(name);
502     current.server.skip = skip;
503     current.server.principal = (char *)NULL;
504 }
505
506
507 static void user_reset(void)
508 /* clear the global current record (user parameters) used by the parser */
509 {
510     struct hostdata save;
511
512     /*
513      * Purpose of this code is to initialize the new server block, but
514      * preserve whatever server name was previously set.  Also
515      * preserve server options unless the command-line explicitly
516      * overrides them.
517      */
518     save = current.server;
519
520     memset(&current, '\0', sizeof(current));
521     current.smtp_socket = -1;
522
523     current.server = save;
524 }
525
526 /** append a host record to the host list */
527 struct query *hostalloc(struct query *init /** pointer to block containing
528                                                initial values */)
529 {
530     struct query *node;
531
532     /* allocate new node */
533     node = (struct query *) xmalloc(sizeof(struct query));
534
535     /* initialize it */
536     if (init)
537         memcpy(node, init, sizeof(struct query));
538     else
539     {
540         memset(node, '\0', sizeof(struct query));
541         node->smtp_socket = -1;
542     }
543
544     /* append to end of list */
545     if (hosttail != (struct query *) 0)
546         hosttail->next = node;  /* list contains at least one element */
547     else
548         querylist = node;       /* list is empty */
549     hosttail = node;
550
551     if (trailer)
552         node->server.lead_server = leadentry;
553     else
554     {
555         node->server.lead_server = NULL;
556         leadentry = &node->server;
557     }
558
559     return(node);
560 }
561
562 static void record_current(void)
563 /* register current parameters and append to the host list */
564 {
565     (void) hostalloc(&current);
566     trailer = TRUE;
567 }
568
569 char *prependdir (const char *file, const char *dir)
570 /* if a filename is relative to dir, convert it to an absolute path */
571 {
572     char *newfile;
573     if (!file[0] ||                     /* null path */
574         file[0] == '/' ||               /* absolute path */
575         strcmp(file, "-") == 0 ||       /* stdin/stdout */
576         !dir[0])                        /* we don't HAVE_GETCWD */
577         return xstrdup (file);
578     newfile = (char *)xmalloc (strlen (dir) + 1 + strlen (file) + 1);
579     if (dir[strlen(dir) - 1] != '/')
580         sprintf (newfile, "%s/%s", dir, file);
581     else
582         sprintf (newfile, "%s%s", dir, file);
583     return newfile;
584 }
585
586 /* rcfile_y.y ends here */