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