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