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