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