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