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