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