]> Pileus Git - ~andy/fetchmail/blob - rcfile_y.y
Kill Kerberos IV and RPOP.
[~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 "i18n.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 <proto> PROTO AUTHTYPE
69 %token <sval>  STRING
70 %token <number> NUMBER
71 %token NO KEEP FLUSH LIMITFLUSH FETCHALL REWRITE FORCECR STRIPCR PASS8BITS 
72 %token DROPSTATUS DROPDELIVERED
73 %token DNS SERVICE PORT UIDL INTERVAL MIMEDECODE IDLE CHECKALIAS 
74 %token SSL SSLKEY SSLCERT SSLPROTO SSLCERTCK SSLCERTFILE SSLCERTPATH SSLCOMMONNAME SSLFINGERPRINT
75 %token PRINCIPAL ESMTPNAME ESMTPPASSWORD
76 %token TRACEPOLLS
77
78 %expect 2
79
80 %destructor { free ($$); } STRING
81
82 %%
83
84 rcfile          : /* empty */
85                 | statement_list
86                 ;
87
88 statement_list  : statement
89                 | statement_list statement
90                 ;
91
92 optmap          : MAP | /* EMPTY */;
93
94 /* future global options should also have the form SET <name> optmap <value> */
95 statement       : SET LOGFILE optmap STRING     {run.logfile = prependdir ($4, rcfiledir); free($4);}
96                 | SET IDFILE optmap STRING      {run.idfile = prependdir ($4, rcfiledir); free($4);}
97                 | SET PIDFILE optmap STRING     {run.pidfile = prependdir ($4, rcfiledir); free($4);}
98                 | SET DAEMON optmap NUMBER      {run.poll_interval = $4;}
99                 | SET POSTMASTER optmap STRING  {run.postmaster = $4;}
100                 | SET BOUNCEMAIL                {run.bouncemail = TRUE;}
101                 | SET NO BOUNCEMAIL             {run.bouncemail = FALSE;}
102                 | SET SPAMBOUNCE                {run.spambounce = TRUE;}
103                 | SET NO SPAMBOUNCE             {run.spambounce = FALSE;}
104                 | SET SOFTBOUNCE                {run.softbounce = TRUE;}
105                 | SET NO SOFTBOUNCE             {run.softbounce = FALSE;}
106                 | SET PROPERTIES optmap STRING  {run.properties = $4;}
107                 | SET SYSLOG                    {run.use_syslog = TRUE;}
108                 | SET NO SYSLOG                 {run.use_syslog = FALSE;}
109                 | SET INVISIBLE                 {run.invisible = TRUE;}
110                 | SET NO INVISIBLE              {run.invisible = FALSE;}
111                 | SET SHOWDOTS                  {run.showdots = FLAG_TRUE;}
112                 | SET NO SHOWDOTS               {run.showdots = FLAG_FALSE;}
113
114 /* 
115  * The way the next two productions are written depends on the fact that
116  * userspecs cannot be empty.  It's a kluge to deal with files that set
117  * up a load of defaults and then have poll statements following with no
118  * user options at all. 
119  */
120                 | define_server serverspecs             {record_current();}
121                 | define_server serverspecs userspecs
122
123 /* detect and complain about the most common user error */
124                 | define_server serverspecs userspecs serv_option
125                         {yyerror(GT_("server option after user options"));}
126                 ;
127
128 define_server   : POLL STRING           {reset_server($2, FALSE); free($2);}
129                 | SKIP STRING           {reset_server($2, TRUE);  free($2);}
130                 | DEFAULTS              {reset_server("defaults", FALSE);}
131                 ;
132
133 serverspecs     : /* EMPTY */
134                 | serverspecs serv_option
135                 ;
136
137 alias_list      : STRING                {save_str(&current.server.akalist,$1,0); free($1);}
138                 | alias_list STRING     {save_str(&current.server.akalist,$2,0); free($2);}
139                 ;
140
141 domain_list     : STRING                {save_str(&current.server.localdomains,$1,0); free($1);}
142                 | domain_list STRING    {save_str(&current.server.localdomains,$2,0); free($2);}
143                 ;
144
145 serv_option     : AKA alias_list
146                 | VIA STRING            {current.server.via = $2;}
147                 | LOCALDOMAINS domain_list
148                 | PROTOCOL PROTO        {current.server.protocol = $2;}
149                 | PROTOCOL KPOP         {
150                                             current.server.protocol = P_POP3;
151 #ifdef KERBEROS_V5
152                                             if (current.server.authenticate == A_PASSWORD)
153                                                 current.server.authenticate = A_KERBEROS_V5;
154                                             current.server.service = KPOP_PORT;
155 #else
156                                             yyerror(GT_("Kerberos not enabled."));
157 #endif
158                                         }
159                 | PRINCIPAL STRING      {current.server.principal = $2;}
160                 | ESMTPNAME STRING      {current.server.esmtp_name = $2;}
161                 | ESMTPPASSWORD STRING  {current.server.esmtp_password = $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 = $3;
198                                             current.server.envskip = $2;
199                                         }
200                 | ENVELOPE STRING
201                                         {
202                                             current.server.envelope = $2;
203                                             current.server.envskip = 0;
204                                         }
205
206                 | QVIRTUAL STRING       {current.server.qvirtual = $2;}
207                 | INTERFACE STRING      {
208 #ifdef CAN_MONITOR
209                                         interface_parse($2, &current.server);
210 #else
211                                         fprintf(stderr, GT_("fetchmail: interface option is only supported under Linux (without IPv6) and FreeBSD\n"));
212 #endif
213                                         free($2);
214                                         }
215                 | MONITOR STRING        {
216 #ifdef CAN_MONITOR
217                                         current.server.monitor = $2;
218 #else
219                                         fprintf(stderr, GT_("fetchmail: monitor option is only supported under Linux (without IPv6) and FreeBSD\n"));
220                                         free($2);
221 #endif
222                                         }
223                 | PLUGIN STRING         { current.server.plugin = $2; }
224                 | PLUGOUT STRING        { current.server.plugout = $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                 | BADHEADER ACCEPT      {current.server.badheader = BHACCEPT;}
231                 | BADHEADER REJECT_     {current.server.badheader = BHREJECT;}
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 = $2;}
246                 | USERNAME mapping_list HERE
247                 | USERNAME STRING THERE {current.remotename = $2;}
248                 ;
249
250 user0opts       : /* EMPTY */
251                 | user0opts user_option
252                 ;
253
254 user1opts       : user_option
255                 | user1opts user_option
256                 ;
257
258 mapping_list    : mapping               
259                 | mapping_list mapping
260                 ;
261
262 mapping         : STRING                {if (0 == strcmp($1, "*")) {
263                                               current.wildcard = TRUE;
264                                           } else {
265                                             save_str_pair(&current.localnames, $1, NULL);
266                                           }
267                                          free($1);}
268                 | STRING MAP STRING     {save_str_pair(&current.localnames, $1, $3); free($1); free($3);}
269                 ;
270
271 folder_list     : STRING                {save_str(&current.mailboxes,$1,0); free($1);}
272                 | folder_list STRING    {save_str(&current.mailboxes,$2,0); free($2);}
273                 ;
274
275 smtp_list       : STRING                {save_str(&current.smtphunt, $1,TRUE); free($1);}
276                 | smtp_list STRING      {save_str(&current.smtphunt, $2,TRUE); free($2);}
277                 ;
278
279 fetch_list      : STRING                {save_str(&current.domainlist, $1,TRUE); free($1);}
280                 | fetch_list STRING     {save_str(&current.domainlist, $2,TRUE); free($2);}
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 mapping_list HERE
298                 | TO mapping_list
299                 | IS mapping_list HERE
300                 | IS mapping_list
301
302                 | IS STRING THERE       {current.remotename  = $2;}
303                 | PASSWORD STRING       {current.password    = $2;}
304                 | FOLDER folder_list
305                 | SMTPHOST smtp_list
306                 | FETCHDOMAINS fetch_list
307                 | SMTPADDRESS STRING    {current.smtpaddress = $2;}
308                 | SMTPNAME STRING       {current.smtpname =    $2;}
309                 | SPAMRESPONSE num_list
310                 | MDA STRING            {current.mda         = $2;}
311                 | BSMTP STRING          {current.bsmtp       = prependdir ($2, rcfiledir); free($2);}
312                 | LMTP                  {current.listener    = LMTP_MODE;}
313                 | PRECONNECT STRING     {current.preconnect  = $2;}
314                 | POSTCONNECT STRING    {current.postconnect = $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); free($2);}
337                 | SSLCERT STRING        {current.sslcert = prependdir ($2, rcfiledir); free($2);}
338                 | SSLPROTO STRING       {current.sslproto = $2;}
339                 | SSLCERTCK             {current.sslcertck = FLAG_TRUE;}
340                 | SSLCERTFILE STRING    {current.sslcertfile = prependdir($2, rcfiledir); free($2);}
341                 | SSLCERTPATH STRING    {current.sslcertpath = prependdir($2, rcfiledir); free($2);}
342                 | SSLCOMMONNAME STRING  {current.sslcommonname = $2;}
343                 | SSLFINGERPRINT STRING {current.sslfingerprint = $2;}
344
345                 | NO KEEP               {current.keep        = FLAG_FALSE;}
346                 | NO FLUSH              {current.flush       = FLAG_FALSE;}
347                 | NO LIMITFLUSH         {current.limitflush  = FLAG_FALSE;}
348                 | NO FETCHALL           {current.fetchall    = FLAG_FALSE;}
349                 | NO REWRITE            {current.rewrite     = FLAG_FALSE;}
350                 | NO FORCECR            {current.forcecr     = FLAG_FALSE;}
351                 | NO STRIPCR            {current.stripcr     = FLAG_FALSE;}
352                 | NO PASS8BITS          {current.pass8bits   = FLAG_FALSE;}
353                 | NO DROPSTATUS         {current.dropstatus  = FLAG_FALSE;}
354                 | NO DROPDELIVERED      {current.dropdelivered = FLAG_FALSE;}
355                 | NO MIMEDECODE         {current.mimedecode  = FLAG_FALSE;}
356                 | NO IDLE               {current.idle        = FLAG_FALSE;}
357
358                 | NO SSL                {current.use_ssl     = FLAG_FALSE;}
359
360                 | LIMIT NUMBER          {current.limit       = NUM_VALUE_IN($2);}
361                 | WARNINGS NUMBER       {current.warnings    = NUM_VALUE_IN($2);}
362                 | FETCHLIMIT NUMBER     {current.fetchlimit  = NUM_VALUE_IN($2);}
363                 | FETCHSIZELIMIT NUMBER {current.fetchsizelimit = NUM_VALUE_IN($2);}
364                 | FASTUIDL NUMBER       {current.fastuidl    = NUM_VALUE_IN($2);}
365                 | BATCHLIMIT NUMBER     {current.batchlimit  = NUM_VALUE_IN($2);}
366                 | EXPUNGE NUMBER        {current.expunge     = NUM_VALUE_IN($2);}
367
368                 | PROPERTIES STRING     {current.properties  = $2;}
369                 ;
370 %%
371
372 /* lexer interface */
373 extern char *rcfile;
374 extern int prc_lineno;
375 extern char *yytext;
376 extern FILE *yyin;
377
378 static struct query *hosttail;  /* where to add new elements */
379
380 void yyerror (const char *s)
381 /* report a syntax error */
382 {
383     report_at_line(stderr, 0, rcfile, prc_lineno, GT_("%s at %s"), s, 
384                    (yytext && yytext[0]) ? yytext : GT_("end of input"));
385     prc_errflag++;
386 }
387
388 /** check that a configuration file is secure, returns PS_* status codes */
389 int prc_filecheck(const char *pathname,
390                   const flag securecheck /** shortcuts permission, filetype and uid tests if false */)
391 {
392     struct stat statbuf;
393
394     errno = 0;
395
396     /* special case useful for debugging purposes */
397     if (strcmp("/dev/null", pathname) == 0)
398         return(PS_SUCCESS);
399
400     /* pass through the special name for stdin */
401     if (strcmp("-", pathname) == 0)
402         return(PS_SUCCESS);
403
404     /* the run control file must have the same uid as the REAL uid of this 
405        process, it must have permissions no greater than 600, and it must not 
406        be a symbolic link.  We check these conditions here. */
407
408     if (stat(pathname, &statbuf) < 0) {
409         if (errno == ENOENT) 
410             return(PS_SUCCESS);
411         else {
412             report(stderr, "lstat: %s: %s\n", pathname, strerror(errno));
413             return(PS_IOERR);
414         }
415     }
416
417     if (!securecheck)   return PS_SUCCESS;
418
419     if (!S_ISREG(statbuf.st_mode))
420     {
421         fprintf(stderr, GT_("File %s must be a regular file.\n"), pathname);
422         return(PS_IOERR);
423     }
424
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------ (0700) permissions.\n"), 
431                 pathname);
432         return(PS_IOERR);
433     }
434
435     if (statbuf.st_uid != geteuid())
436     {
437         fprintf(stderr, GT_("File %s must be owned by you.\n"), pathname);
438         return(PS_IOERR);
439     }
440     return(PS_SUCCESS);
441 }
442
443 int prc_parse_file (const char *pathname, const flag securecheck)
444 /* digest the configuration into a linked list of host records */
445 {
446     prc_errflag = 0;
447     querylist = hosttail = (struct query *)NULL;
448
449     errno = 0;
450
451     /* Check that the file is secure */
452     if ( (prc_errflag = prc_filecheck(pathname, securecheck)) != 0 )
453         return(prc_errflag);
454
455     /*
456      * Croak if the configuration directory does not exist.
457      * This probably means an NFS mount failed and we can't
458      * see a configuration file that ought to be there.
459      * Question: is this a portable check? It's not clear
460      * that all implementations of lstat() will return ENOTDIR
461      * rather than plain ENOENT in this case...
462      */
463     if (errno == ENOTDIR)
464         return(PS_IOERR);
465     else if (errno == ENOENT)
466         return(PS_SUCCESS);
467
468     /* Open the configuration file and feed it to the lexer. */
469     if (strcmp(pathname, "-") == 0)
470         yyin = stdin;
471     else if ((yyin = fopen(pathname,"r")) == (FILE *)NULL) {
472         report(stderr, "open: %s: %s\n", pathname, strerror(errno));
473         return(PS_IOERR);
474     }
475
476     yyparse();          /* parse entire file */
477
478     fclose(yyin);       /* not checking this should be safe, file mode was r */
479
480     if (prc_errflag) 
481         return(PS_SYNTAX);
482     else
483         return(PS_SUCCESS);
484 }
485
486 static void reset_server(const char *name, int skip)
487 /* clear the entire global record and initialize it with a new name */
488 {
489     trailer = FALSE;
490     memset(&current,'\0',sizeof(current));
491     current.smtp_socket = -1;
492     current.server.pollname = xstrdup(name);
493     current.server.skip = skip;
494     current.server.principal = (char *)NULL;
495 }
496
497
498 static void user_reset(void)
499 /* clear the global current record (user parameters) used by the parser */
500 {
501     struct hostdata save;
502
503     /*
504      * Purpose of this code is to initialize the new server block, but
505      * preserve whatever server name was previously set.  Also
506      * preserve server options unless the command-line explicitly
507      * overrides them.
508      */
509     save = current.server;
510
511     memset(&current, '\0', sizeof(current));
512     current.smtp_socket = -1;
513
514     current.server = save;
515 }
516
517 /** append a host record to the host list */
518 struct query *hostalloc(struct query *init /** pointer to block containing
519                                                initial values */)
520 {
521     struct query *node;
522
523     /* allocate new node */
524     node = (struct query *) xmalloc(sizeof(struct query));
525
526     /* initialize it */
527     if (init)
528         memcpy(node, init, sizeof(struct query));
529     else
530     {
531         memset(node, '\0', sizeof(struct query));
532         node->smtp_socket = -1;
533     }
534
535     /* append to end of list */
536     if (hosttail != (struct query *) 0)
537         hosttail->next = node;  /* list contains at least one element */
538     else
539         querylist = node;       /* list is empty */
540     hosttail = node;
541
542     if (trailer)
543         node->server.lead_server = leadentry;
544     else
545     {
546         node->server.lead_server = NULL;
547         leadentry = &node->server;
548     }
549
550     return(node);
551 }
552
553 static void record_current(void)
554 /* register current parameters and append to the host list */
555 {
556     (void) hostalloc(&current);
557     trailer = TRUE;
558 }
559
560 char *prependdir (const char *file, const char *dir)
561 /* if a filename is relative to dir, convert it to an absolute path */
562 {
563     char *newfile;
564     if (!file[0] ||                     /* null path */
565         file[0] == '/' ||               /* absolute path */
566         strcmp(file, "-") == 0 ||       /* stdin/stdout */
567         !dir[0])                        /* we don't HAVE_GETCWD */
568         return xstrdup (file);
569     newfile = (char *)xmalloc (strlen (dir) + 1 + strlen (file) + 1);
570     if (dir[strlen(dir) - 1] != '/')
571         sprintf (newfile, "%s/%s", dir, file);
572     else
573         sprintf (newfile, "%s%s", dir, file);
574     return newfile;
575 }
576
577 /* rcfile_y.y ends here */