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