Home | History | Annotate | Line # | Download | only in mail
      1 /*	$NetBSD: support.c,v 1.27 2023/09/08 20:46:45 shm Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1980, 1993
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. Neither the name of the University nor the names of its contributors
     16  *    may be used to endorse or promote products derived from this software
     17  *    without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     29  * SUCH DAMAGE.
     30  */
     31 
     32 #include <sys/cdefs.h>
     33 #ifndef lint
     34 #if 0
     35 static char sccsid[] = "@(#)aux.c	8.1 (Berkeley) 6/6/93";
     36 #else
     37 __RCSID("$NetBSD: support.c,v 1.27 2023/09/08 20:46:45 shm Exp $");
     38 #endif
     39 #endif /* not lint */
     40 
     41 #include <stdarg.h>
     42 #include <util.h>
     43 
     44 #include "rcv.h"
     45 #include "extern.h"
     46 #include "mime.h"
     47 #include "thread.h"
     48 
     49 /*
     50  * Mail -- a mail program
     51  *
     52  * Auxiliary functions.
     53  */
     54 
     55 
     56 /*
     57  * Return a pointer to a dynamic copy of the argument.
     58  */
     59 PUBLIC char *
     60 savestr(const char *str)
     61 {
     62 	char *new;
     63 	size_t size = strlen(str) + 1;
     64 
     65 	if ((new = salloc(size)) != NULL)
     66 		(void)memmove(new, str, size);
     67 	return new;
     68 }
     69 
     70 /*
     71  * Make a copy of new argument incorporating old one.
     72  */
     73 static char *
     74 save2str(char *str, char *old)
     75 {
     76 	char *new;
     77 	size_t newsize = strlen(str) + 1;
     78 	size_t oldsize = old ? strlen(old) + 1 : 0;
     79 
     80 	if ((new = salloc(newsize + oldsize)) != NULL) {
     81 		if (oldsize) {
     82 			(void)memmove(new, old, oldsize);
     83 			new[oldsize - 1] = ' ';
     84 		}
     85 		(void)memmove(new + oldsize, str, newsize);
     86 	}
     87 	return new;
     88 }
     89 
     90 /*
     91  * Like asprintf(), but with result salloc-ed rather than malloc-ed.
     92  */
     93 PUBLIC int
     94 sasprintf(char **ret, const char *format, ...)
     95 {
     96 	int rval;
     97 	char *p;
     98 	va_list args;
     99 
    100 	va_start(args, format);
    101 	rval = evasprintf(&p, format, args);
    102 	*ret = savestr(p);
    103 	free(p);
    104 	va_end(args);
    105 	return rval;
    106 }
    107 
    108 struct set_m_flag_args_s {
    109 	int and_bits;
    110 	int xor_bits;
    111 };
    112 static int
    113 set_m_flag_core(struct message *mp, void *v)
    114 {
    115 	struct set_m_flag_args_s *args;
    116 	args = v;
    117 	mp->m_flag &= args->and_bits;
    118 	mp->m_flag ^= args->xor_bits;
    119 	return 0;
    120 }
    121 
    122 PUBLIC struct message *
    123 set_m_flag(int msgnum, int and_bits, int xor_bits)
    124 {
    125 	struct message *mp;
    126 	struct set_m_flag_args_s args;
    127 	mp = get_message(msgnum);
    128 	args.and_bits = and_bits;
    129 	args.xor_bits = xor_bits;
    130 	(void)thread_recursion(mp, set_m_flag_core, &args);
    131 	return mp;
    132 }
    133 
    134 /*
    135  * Touch the named message by setting its MTOUCH flag.
    136  * Touched messages have the effect of not being sent
    137  * back to the system mailbox on exit.
    138  */
    139 PUBLIC void
    140 touch(struct message *mp)
    141 {
    142 
    143 	mp->m_flag |= MTOUCH;
    144 	if ((mp->m_flag & MREAD) == 0)
    145 		mp->m_flag |= MREAD|MSTATUS;
    146 }
    147 
    148 /*
    149  * Test to see if the passed file name is a directory.
    150  * Return true if it is.
    151  */
    152 PUBLIC int
    153 isdir(const char name[])
    154 {
    155 	struct stat sbuf;
    156 
    157 	if (stat(name, &sbuf) < 0)
    158 		return 0;
    159 	return S_ISDIR(sbuf.st_mode);
    160 }
    161 
    162 /*
    163  * Count the number of arguments in the given string raw list.
    164  */
    165 PUBLIC int
    166 argcount(char **argv)
    167 {
    168 	char **ap;
    169 
    170 	for (ap = argv; *ap++ != NULL; /*EMPTY*/)
    171 		continue;
    172 	return (int)(ap - argv - 1);
    173 }
    174 
    175 /*
    176  * Check whether the passed line is a header line of
    177  * the desired breed.  Return the field body, or NULL.
    178  */
    179 static char*
    180 ishfield(const char linebuf[], char *colon, const char field[])
    181 {
    182 	char *cp = colon;
    183 
    184 	*cp = 0;
    185 	if (strcasecmp(linebuf, field) != 0) {
    186 		*cp = ':';
    187 		return NULL;
    188 	}
    189 	*cp = ':';
    190 	for (cp++; is_WSP(*cp); cp++)
    191 		continue;
    192 	return cp;
    193 }
    194 
    195 /*
    196  * Return the next header field found in the given message.
    197  * Return >= 0 if something found, < 0 elsewise.
    198  * "colon" is set to point to the colon in the header.
    199  * Must deal with \ continuations & other such fraud.
    200  *
    201  * WARNING - do not call this outside hfield() or decoding will not
    202  *           get done.
    203  */
    204 static int
    205 gethfield(FILE *f, char linebuf[], int rem, char **colon)
    206 {
    207 	char line2[LINESIZE];
    208 	char *cp, *cp2;
    209 	int c;
    210 
    211 	for (;;) {
    212 		if (--rem < 0)
    213 			return -1;
    214 		if ((c = readline(f, linebuf, LINESIZE, 0)) <= 0)
    215 			return -1;
    216 		for (cp = linebuf;
    217 		     isprint((unsigned char)*cp) && *cp != ' ' && *cp != ':';
    218 		     cp++)
    219 			continue;
    220 		if (*cp != ':' || cp == linebuf)
    221 			continue;
    222 		/*
    223 		 * I guess we got a headline.
    224 		 * Handle wraparounding
    225 		 */
    226 		*colon = cp;
    227 		cp = linebuf + c;
    228 		for (;;) {
    229 			while (--cp >= linebuf && is_WSP(*cp))
    230 				continue;
    231 			cp++;
    232 			if (rem <= 0)
    233 				break;
    234 			(void)ungetc(c = getc(f), f);
    235 			if (!is_WSP(c))
    236 				break;
    237 			if ((c = readline(f, line2, LINESIZE, 0)) < 0)
    238 				break;
    239 			rem--;
    240 			cp2 = skip_WSP(line2);
    241 			c -= (int)(cp2 - line2);
    242 			if (cp + c >= linebuf + LINESIZE - 2)
    243 				break;
    244 			*cp++ = ' ';
    245 			(void)memmove(cp, cp2, (size_t)c);
    246 			cp += c;
    247 		}
    248 		*cp = 0;
    249 		return rem;
    250 	}
    251 	/* NOTREACHED */
    252 }
    253 
    254 /*
    255  * Return the desired header line from the passed message
    256  * pointer (or NULL if the desired header field is not available).
    257  */
    258 PUBLIC char *
    259 hfield(const char field[], const struct message *mp)
    260 {
    261 	FILE *ibuf;
    262 	char linebuf[LINESIZE];
    263 	int lc;
    264 	char *headerfield;
    265 	char *colon, *oldhfield = NULL;
    266 #ifdef MIME_SUPPORT
    267 	int decode;
    268 
    269 	decode = value(ENAME_MIME_DECODE_MSG) &&
    270 	    value(ENAME_MIME_DECODE_HDR);
    271 #endif
    272 
    273 	ibuf = setinput(mp);
    274 	if ((lc = (int)(mp->m_lines - 1)) < 0)
    275 		return NULL;
    276 	if (readline(ibuf, linebuf, LINESIZE, 0) < 0)
    277 		return NULL;
    278 	while (lc > 0) {
    279 		if ((lc = gethfield(ibuf, linebuf, lc, &colon)) < 0)
    280 			return oldhfield;
    281 #ifdef MIME_SUPPORT
    282 		if ((headerfield = ishfield(linebuf, colon, field)) != NULL) {
    283 			char linebuf2[LINESIZE];
    284 			if (decode)
    285 				headerfield = mime_decode_hfield(linebuf2,
    286 				    sizeof(linebuf2), linebuf, headerfield);
    287 			oldhfield = save2str(headerfield, oldhfield);
    288 		}
    289 #else
    290 		if ((headerfield = ishfield(linebuf, colon, field)) != NULL)
    291 			oldhfield = save2str(headerfield, oldhfield);
    292 #endif
    293 	}
    294 	return oldhfield;
    295 }
    296 
    297 /*
    298  * Copy a string, lowercasing it as we go.
    299  */
    300 PUBLIC void
    301 istrcpy(char *dest, const char *src)
    302 {
    303 
    304 	do {
    305 		*dest++ = tolower((unsigned char)*src);
    306 	} while (*src++ != 0);
    307 }
    308 
    309 /*
    310  * The following code deals with input stacking to do source
    311  * commands.  All but the current file pointer are saved on
    312  * the stack.
    313  */
    314 
    315 static	int	ssp;			/* Top of file stack */
    316 static struct sstack {
    317 	FILE	*s_file;		/* File we were in. */
    318 	int	s_cond;			/* Saved state of conditionals */
    319 #ifdef NEW_CONDITIONAL
    320 	struct cond_stack_s *s_cond_stack; /* Saved conditional stack */
    321 #endif
    322 	int	s_loading;		/* Loading .mailrc, etc. */
    323 } sstack[NOFILE];
    324 
    325 /*
    326  * Pushdown current input file and switch to a new one.
    327  * Set the global flag "sourcing" so that others will realize
    328  * that they are no longer reading from a tty (in all probability).
    329  */
    330 PUBLIC int
    331 source(void *v)
    332 {
    333 	char **arglist = v;
    334 	FILE *fi;
    335 	const char *cp;
    336 
    337 	if ((cp = expand(*arglist)) == NULL)
    338 		return 1;
    339 	if ((fi = Fopen(cp, "ref")) == NULL) {
    340 		warn("%s", cp);
    341 		return 1;
    342 	}
    343 	if (ssp >= NOFILE - 1) {
    344 		(void)printf("Too much \"sourcing\" going on.\n");
    345 		(void)Fclose(fi);
    346 		return 1;
    347 	}
    348 	sstack[ssp].s_file = input;
    349 	sstack[ssp].s_cond = cond;
    350 #ifdef NEW_CONDITIONAL
    351 	sstack[ssp].s_cond_stack = cond_stack;
    352 #endif
    353 	sstack[ssp].s_loading = loading;
    354 	ssp++;
    355 	loading = 0;
    356 	cond = CNONE;
    357 	input = fi;
    358 	sourcing++;
    359 	return 0;
    360 }
    361 
    362 /*
    363  * Pop the current input back to the previous level.
    364  * Update the "sourcing" flag as appropriate.
    365  */
    366 PUBLIC int
    367 unstack(void)
    368 {
    369 	if (ssp <= 0) {
    370 		(void)printf("\"Source\" stack over-pop.\n");
    371 		sourcing = 0;
    372 		return 1;
    373 	}
    374 	(void)Fclose(input);
    375 	if (cond != CNONE || cond_stack != NULL)
    376 		(void)printf("Unmatched \"if\"\n");
    377 	ssp--;
    378 	input = sstack[ssp].s_file;
    379 	cond = sstack[ssp].s_cond;
    380 #ifdef NEW_CONDITIONAL
    381 	cond_stack = sstack[ssp].s_cond_stack;
    382 #endif
    383 	loading = sstack[ssp].s_loading;
    384 	if (ssp == 0)
    385 		sourcing = loading;
    386 	return 0;
    387 }
    388 
    389 /*
    390  * Touch the indicated file.
    391  * This is nifty for the shell.
    392  */
    393 PUBLIC void
    394 alter(char *name)
    395 {
    396 	struct stat sb;
    397 	struct timeval tv[2];
    398 
    399 	if (stat(name, &sb))
    400 		return;
    401 	(void)gettimeofday(&tv[0], NULL);
    402 	tv[0].tv_sec++;
    403 	TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec);
    404 	(void)utimes(name, tv);
    405 }
    406 
    407 /*
    408  * Examine the passed line buffer and
    409  * return true if it is all blanks and tabs.
    410  */
    411 PUBLIC int
    412 blankline(char linebuf[])
    413 {
    414 	char *cp;
    415 
    416 	for (cp = linebuf; *cp; cp++)
    417 		if (!is_WSP(*cp))
    418 			return 0;
    419 	return 1;
    420 }
    421 
    422 /*
    423  * Start of a "comment".
    424  * Ignore it.
    425  */
    426 static char *
    427 skip_comment(char *cp)
    428 {
    429 	int nesting = 1;
    430 
    431 	for (/*EMPTY*/; nesting > 0 && *cp; cp++) {
    432 		switch (*cp) {
    433 		case '\\':
    434 			if (cp[1])
    435 				cp++;
    436 			break;
    437 		case '(':
    438 			nesting++;
    439 			break;
    440 		case ')':
    441 			nesting--;
    442 			break;
    443 		}
    444 	}
    445 	return cp;
    446 }
    447 
    448 /*
    449  * Skin an arpa net address according to the RFC 822 interpretation
    450  * of "host-phrase."
    451  */
    452 PUBLIC char *
    453 skin(char *name)
    454 {
    455 	int c;
    456 	char *cp, *cp2;
    457 	char *bufend;
    458 	int gotlt, lastsp;
    459 	char *nbuf, *ret;
    460 
    461 	if (name == NULL)
    462 		return NULL;
    463 	if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
    464 	    && strchr(name, ' ') == NULL)
    465 		return name;
    466 
    467 	nbuf = emalloc(strlen(name) + 1);
    468 	gotlt = 0;
    469 	lastsp = 0;
    470 	bufend = nbuf;
    471 	for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; /*EMPTY*/) {
    472 		switch (c) {
    473 		case '(':
    474 			cp = skip_comment(cp);
    475 			lastsp = 0;
    476 			break;
    477 
    478 		case '"':
    479 			/*
    480 			 * Start of a "quoted-string".
    481 			 * Copy it in its entirety.
    482 			 */
    483 			while ((c = *cp) != '\0') {
    484 				cp++;
    485 				if (c == '"')
    486 					break;
    487 				if (c != '\\')
    488 					*cp2++ = c;
    489 				else if ((c = *cp) != '\0') {
    490 					*cp2++ = c;
    491 					cp++;
    492 				}
    493 			}
    494 			lastsp = 0;
    495 			break;
    496 
    497 		case ' ':
    498 			if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
    499 				cp += 3, *cp2++ = '@';
    500 			else
    501 			if (cp[0] == '@' && cp[1] == ' ')
    502 				cp += 2, *cp2++ = '@';
    503 			else
    504 				lastsp = 1;
    505 			break;
    506 
    507 		case '<':
    508 			cp2 = bufend;
    509 			gotlt++;
    510 			lastsp = 0;
    511 			break;
    512 
    513 		case '>':
    514 			if (gotlt) {
    515 				gotlt = 0;
    516 				while ((c = *cp) && c != ',') {
    517 					cp++;
    518 					if (c == '(')
    519 						cp = skip_comment(cp);
    520 					else if (c == '"')
    521 						while ((c = *cp) != '\0') {
    522 							cp++;
    523 							if (c == '"')
    524 								break;
    525 							if (c == '\\' && *cp)
    526 								cp++;
    527 						}
    528 				}
    529 				lastsp = 0;
    530 				break;
    531 			}
    532 			/* FALLTHROUGH */
    533 
    534 		default:
    535 			if (lastsp) {
    536 				lastsp = 0;
    537 				*cp2++ = ' ';
    538 			}
    539 			*cp2++ = c;
    540 			if (c == ',' && *cp == ' ' && !gotlt) {
    541 				*cp2++ = ' ';
    542 				for (/*EMPTY*/; *cp == ' '; cp++)
    543 					continue;
    544 				lastsp = 0;
    545 				bufend = cp2;
    546 			}
    547 		}
    548 	}
    549 	*cp2 = 0;
    550 
    551 	ret = savestr(nbuf);
    552 	free(nbuf);
    553 
    554 	return ret;
    555 }
    556 
    557 /*
    558  * Fetch the sender's name from the passed message.
    559  * Reptype can be
    560  *	0 -- get sender's name for display purposes
    561  *	1 -- get sender's name for reply
    562  *	2 -- get sender's name for Reply
    563  */
    564 static char *
    565 name1(struct message *mp, int reptype)
    566 {
    567 	char namebuf[LINESIZE];
    568 	char linebuf[LINESIZE];
    569 	char *cp, *cp2;
    570 	FILE *ibuf;
    571 	int firstrun = 1;
    572 
    573 	if ((cp = hfield("from", mp)) != NULL)
    574 		return cp;
    575 	if (reptype == 0 && (cp = hfield("sender", mp)) != NULL)
    576 		return cp;
    577 	ibuf = setinput(mp);
    578 	namebuf[0] = '\0';
    579 	if (readline(ibuf, linebuf, LINESIZE, 0) < 0)
    580 		return savestr(namebuf);
    581  newname:
    582 	for (cp = linebuf; *cp && *cp != ' '; cp++)
    583 		continue;
    584 	cp = skip_WSP(cp);
    585 	for (cp2 = &namebuf[strlen(namebuf)];
    586 	     *cp && !is_WSP(*cp) && cp2 < namebuf + LINESIZE - 1;
    587 	     /*EMPTY*/)
    588 		*cp2++ = *cp++;
    589 	*cp2 = '\0';
    590 	if (readline(ibuf, linebuf, LINESIZE, 0) < 0)
    591 		return savestr(namebuf);
    592 	if ((cp = strchr(linebuf, 'F')) == NULL)
    593 		return savestr(namebuf);
    594 	if (strncmp(cp, "From", 4) != 0)
    595 		return savestr(namebuf);
    596 	while ((cp = strchr(cp, 'r')) != NULL) {
    597 		if (strncmp(cp, "remote", 6) == 0) {
    598 			if ((cp = strchr(cp, 'f')) == NULL)
    599 				break;
    600 			if (strncmp(cp, "from", 4) != 0)
    601 				break;
    602 			if ((cp = strchr(cp, ' ')) == NULL)
    603 				break;
    604 			cp++;
    605 			if (firstrun) {
    606 				cp2 = namebuf;
    607 				firstrun = 0;
    608 			} else
    609 				cp2 = strrchr(namebuf, '!') + 1;
    610 			while (*cp && cp2 < namebuf + LINESIZE - 1)
    611 				*cp2++ = *cp++;
    612 			if (cp2 < namebuf + LINESIZE - 1)
    613 				*cp2++ = '!';
    614 			*cp2 = '\0';
    615 			if (cp2 < namebuf + LINESIZE - 1)
    616 				goto newname;
    617 			else
    618 				break;
    619 		}
    620 		cp++;
    621 	}
    622 	return savestr(namebuf);
    623 }
    624 
    625 /*
    626  * Count the occurrences of c in str
    627  */
    628 static int
    629 charcount(char *str, int c)
    630 {
    631 	char *cp;
    632 	int i;
    633 
    634 	for (i = 0, cp = str; *cp; cp++)
    635 		if (*cp == c)
    636 			i++;
    637 	return i;
    638 }
    639 
    640 /*
    641  * Get sender's name from this message.  If the message has
    642  * a bunch of arpanet stuff in it, we may have to skin the name
    643  * before returning it.
    644  */
    645 PUBLIC char *
    646 nameof(struct message *mp, int reptype)
    647 {
    648 	char *cp, *cp2;
    649 
    650 	cp = skin(name1(mp, reptype));
    651 	if (reptype != 0 || charcount(cp, '!') < 2)
    652 		return cp;
    653 	cp2 = strrchr(cp, '!');
    654 	cp2--;
    655 	while (cp2 > cp && *cp2 != '!')
    656 		cp2--;
    657 	if (*cp2 == '!')
    658 		return cp2 + 1;
    659 	return cp;
    660 }
    661 
    662 /*
    663  * Copy s1 to s2, return pointer to null in s2.
    664  */
    665 PUBLIC char *
    666 copy(char *s1, char *s2)
    667 {
    668 
    669 	while ((*s2++ = *s1++) != '\0')
    670 		continue;
    671 	return s2 - 1;
    672 }
    673 
    674 /*
    675  * The core routine to check the ignore table for a field.
    676  * Note: realfield should be lower-cased!
    677  */
    678 PUBLIC int
    679 member(char *realfield, struct ignoretab *table)
    680 {
    681 	struct ignore *igp;
    682 
    683 	for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
    684 		if (*igp->i_field == *realfield &&
    685 		    equal(igp->i_field, realfield))
    686 			return 1;
    687 	return 0;
    688 }
    689 
    690 /*
    691  * See if the given header field is supposed to be ignored.
    692  */
    693 PUBLIC int
    694 isign(const char *field, struct ignoretab ignoretabs[2])
    695 {
    696 	char realfld[LINESIZE];
    697 
    698 	if (ignoretabs == ignoreall)
    699 		return 1;
    700 	/*
    701 	 * Lower-case the string, so that "Status" and "status"
    702 	 * will hash to the same place.
    703 	 */
    704 	istrcpy(realfld, field);
    705 	if (ignoretabs[1].i_count > 0)
    706 		return !member(realfld, ignoretabs + 1);
    707 	else
    708 		return member(realfld, ignoretabs);
    709 }
    710 
    711 PUBLIC void
    712 add_ignore(const char *name, struct ignoretab *tab)
    713 {
    714 	char field[LINESIZE];
    715 	int h;
    716 	struct ignore *igp;
    717 
    718 	istrcpy(field, name);
    719 	if (member(field, tab))
    720 		return;
    721 	h = hash(field);
    722 	igp = ecalloc(1, sizeof(struct ignore));
    723 	igp->i_field = ecalloc(strlen(field) + 1, sizeof(char));
    724 	(void)strcpy(igp->i_field, field);
    725 	igp->i_link = tab->i_head[h];
    726 	tab->i_head[h] = igp;
    727 	tab->i_count++;
    728 }
    729 
    730 /*
    731  * Write a file to stdout, skipping comment lines.
    732  */
    733 PUBLIC void
    734 cathelp(const char *fname)
    735 {
    736 	char *line;
    737 	FILE *f;
    738 	size_t len;
    739 
    740 	if ((f = Fopen(fname, "ref")) == NULL) {
    741 		warn("%s", fname);
    742 		return;
    743 	}
    744 	while ((line = fgetln(f, &len)) != NULL) {
    745 		if (*line == '#')
    746 			continue;
    747 		if (fwrite(line, 1, len, stdout) != len)
    748 			break;
    749 	}
    750 	(void)Fclose(f);
    751 	return;
    752 }
    753