1 /* $OpenBSD: util.c,v 1.158 2024/05/13 06:48:26 jsg Exp $ */
2
3 /*
4 * Copyright (c) 2000,2001 Markus Friedl. All rights reserved.
5 * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
6 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
7 * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
8 *
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 */
21
22 #include <sys/stat.h>
23
24 #include <netinet/in.h>
25
26 #include <arpa/inet.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #include <fts.h>
30 #include <libgen.h>
31 #include <resolv.h>
32 #include <stdarg.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <syslog.h>
36 #include <unistd.h>
37
38 #include "smtpd.h"
39 #include "log.h"
40
41 const char *log_in6addr(const struct in6_addr *);
42 const char *log_sockaddr(struct sockaddr *);
43 static int parse_mailname_file(char *, size_t);
44
45 int tracing = 0;
46 int foreground_log = 0;
47
48 void *
xmalloc(size_t size)49 xmalloc(size_t size)
50 {
51 void *r;
52
53 if ((r = malloc(size)) == NULL)
54 fatal("malloc");
55
56 return (r);
57 }
58
59 void *
xcalloc(size_t nmemb,size_t size)60 xcalloc(size_t nmemb, size_t size)
61 {
62 void *r;
63
64 if ((r = calloc(nmemb, size)) == NULL)
65 fatal("calloc");
66
67 return (r);
68 }
69
70 char *
xstrdup(const char * str)71 xstrdup(const char *str)
72 {
73 char *r;
74
75 if ((r = strdup(str)) == NULL)
76 fatal("strdup");
77
78 return (r);
79 }
80
81 void *
xmemdup(const void * ptr,size_t size)82 xmemdup(const void *ptr, size_t size)
83 {
84 void *r;
85
86 if ((r = malloc(size)) == NULL)
87 fatal("malloc");
88
89 memmove(r, ptr, size);
90
91 return (r);
92 }
93
94 int
xasprintf(char ** ret,const char * format,...)95 xasprintf(char **ret, const char *format, ...)
96 {
97 int r;
98 va_list ap;
99
100 va_start(ap, format);
101 r = vasprintf(ret, format, ap);
102 va_end(ap);
103 if (r == -1)
104 fatal("vasprintf");
105
106 return (r);
107 }
108
109
110 #if !defined(NO_IO)
111 int
io_xprintf(struct io * io,const char * fmt,...)112 io_xprintf(struct io *io, const char *fmt, ...)
113 {
114 va_list ap;
115 int len;
116
117 va_start(ap, fmt);
118 len = io_vprintf(io, fmt, ap);
119 va_end(ap);
120 if (len == -1)
121 fatal("io_xprintf(%p, %s, ...)", io, fmt);
122
123 return len;
124 }
125
126 int
io_xprint(struct io * io,const char * str)127 io_xprint(struct io *io, const char *str)
128 {
129 int len;
130
131 len = io_print(io, str);
132 if (len == -1)
133 fatal("io_xprint(%p, %s, ...)", io, str);
134
135 return len;
136 }
137 #endif
138
139 char *
strip(char * s)140 strip(char *s)
141 {
142 size_t l;
143
144 while (isspace((unsigned char)*s))
145 s++;
146
147 for (l = strlen(s); l; l--) {
148 if (!isspace((unsigned char)s[l-1]))
149 break;
150 s[l-1] = '\0';
151 }
152
153 return (s);
154 }
155
156 int
bsnprintf(char * str,size_t size,const char * format,...)157 bsnprintf(char *str, size_t size, const char *format, ...)
158 {
159 int ret;
160 va_list ap;
161
162 va_start(ap, format);
163 ret = vsnprintf(str, size, format, ap);
164 va_end(ap);
165 if (ret < 0 || (size_t)ret >= size)
166 return 0;
167
168 return 1;
169 }
170
171
172 int
ckdir(const char * path,mode_t mode,uid_t owner,gid_t group,int create)173 ckdir(const char *path, mode_t mode, uid_t owner, gid_t group, int create)
174 {
175 char mode_str[12];
176 int ret;
177 struct stat sb;
178
179 if (stat(path, &sb) == -1) {
180 if (errno != ENOENT || create == 0) {
181 log_warn("stat: %s", path);
182 return (0);
183 }
184
185 /* chmod is deferred to avoid umask effect */
186 if (mkdir(path, 0) == -1) {
187 log_warn("mkdir: %s", path);
188 return (0);
189 }
190
191 if (chown(path, owner, group) == -1) {
192 log_warn("chown: %s", path);
193 return (0);
194 }
195
196 if (chmod(path, mode) == -1) {
197 log_warn("chmod: %s", path);
198 return (0);
199 }
200
201 if (stat(path, &sb) == -1) {
202 log_warn("stat: %s", path);
203 return (0);
204 }
205 }
206
207 ret = 1;
208
209 /* check if it's a directory */
210 if (!S_ISDIR(sb.st_mode)) {
211 ret = 0;
212 log_warnx("%s is not a directory", path);
213 }
214
215 /* check that it is owned by owner/group */
216 if (sb.st_uid != owner) {
217 ret = 0;
218 log_warnx("%s is not owned by uid %d", path, owner);
219 }
220 if (sb.st_gid != group) {
221 ret = 0;
222 log_warnx("%s is not owned by gid %d", path, group);
223 }
224
225 /* check permission */
226 if ((sb.st_mode & 07777) != mode) {
227 ret = 0;
228 strmode(mode, mode_str);
229 mode_str[10] = '\0';
230 log_warnx("%s must be %s (%o)", path, mode_str + 1, mode);
231 }
232
233 return ret;
234 }
235
236 int
rmtree(char * path,int keepdir)237 rmtree(char *path, int keepdir)
238 {
239 char *path_argv[2];
240 FTS *fts;
241 FTSENT *e;
242 int ret, depth;
243
244 path_argv[0] = path;
245 path_argv[1] = NULL;
246 ret = 0;
247 depth = 0;
248
249 fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
250 if (fts == NULL) {
251 log_warn("fts_open: %s", path);
252 return (-1);
253 }
254
255 while ((e = fts_read(fts)) != NULL) {
256 switch (e->fts_info) {
257 case FTS_D:
258 depth++;
259 break;
260 case FTS_DP:
261 case FTS_DNR:
262 depth--;
263 if (keepdir && depth == 0)
264 continue;
265 if (rmdir(e->fts_path) == -1) {
266 log_warn("rmdir: %s", e->fts_path);
267 ret = -1;
268 }
269 break;
270
271 case FTS_F:
272 if (unlink(e->fts_path) == -1) {
273 log_warn("unlink: %s", e->fts_path);
274 ret = -1;
275 }
276 }
277 }
278
279 fts_close(fts);
280
281 return (ret);
282 }
283
284 int
mvpurge(char * from,char * to)285 mvpurge(char *from, char *to)
286 {
287 size_t n;
288 int retry;
289 const char *sep;
290 char buf[PATH_MAX];
291
292 if ((n = strlen(to)) == 0)
293 fatalx("to is empty");
294
295 sep = (to[n - 1] == '/') ? "" : "/";
296 retry = 0;
297
298 again:
299 (void)snprintf(buf, sizeof buf, "%s%s%u", to, sep, arc4random());
300 if (rename(from, buf) == -1) {
301 /* ENOTDIR has actually 2 meanings, and incorrect input
302 * could lead to an infinite loop. Consider that after
303 * 20 tries something is hopelessly wrong.
304 */
305 if (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR) {
306 if ((retry++) >= 20)
307 return (-1);
308 goto again;
309 }
310 return -1;
311 }
312
313 return 0;
314 }
315
316
317 int
mktmpfile(void)318 mktmpfile(void)
319 {
320 char path[PATH_MAX];
321 int fd;
322
323 if (!bsnprintf(path, sizeof(path), "%s/smtpd.XXXXXXXXXX",
324 PATH_TEMPORARY)) {
325 log_warn("snprintf");
326 fatal("exiting");
327 }
328
329 if ((fd = mkstemp(path)) == -1) {
330 log_warn("cannot create temporary file %s", path);
331 fatal("exiting");
332 }
333 unlink(path);
334 return (fd);
335 }
336
337
338 /* Close file, signifying temporary error condition (if any) to the caller. */
339 int
safe_fclose(FILE * fp)340 safe_fclose(FILE *fp)
341 {
342 if (ferror(fp)) {
343 fclose(fp);
344 return 0;
345 }
346 if (fflush(fp)) {
347 fclose(fp);
348 if (errno == ENOSPC)
349 return 0;
350 fatal("safe_fclose: fflush");
351 }
352 if (fsync(fileno(fp)))
353 fatal("safe_fclose: fsync");
354 if (fclose(fp))
355 fatal("safe_fclose: fclose");
356
357 return 1;
358 }
359
360 int
hostname_match(const char * hostname,const char * pattern)361 hostname_match(const char *hostname, const char *pattern)
362 {
363 while (*pattern != '\0' && *hostname != '\0') {
364 if (*pattern == '*') {
365 while (*pattern == '*')
366 pattern++;
367 while (*hostname != '\0' &&
368 tolower((unsigned char)*hostname) !=
369 tolower((unsigned char)*pattern))
370 hostname++;
371 continue;
372 }
373
374 if (tolower((unsigned char)*pattern) !=
375 tolower((unsigned char)*hostname))
376 return 0;
377 pattern++;
378 hostname++;
379 }
380
381 return (*hostname == '\0' && *pattern == '\0');
382 }
383
384 int
mailaddr_match(const struct mailaddr * maddr1,const struct mailaddr * maddr2)385 mailaddr_match(const struct mailaddr *maddr1, const struct mailaddr *maddr2)
386 {
387 struct mailaddr m1 = *maddr1;
388 struct mailaddr m2 = *maddr2;
389 char *p;
390
391 /* catchall */
392 if (m2.user[0] == '\0' && m2.domain[0] == '\0')
393 return 1;
394
395 if (m2.domain[0] && !hostname_match(m1.domain, m2.domain))
396 return 0;
397
398 if (m2.user[0]) {
399 /* if address from table has a tag, we must respect it */
400 if (strchr(m2.user, *env->sc_subaddressing_delim) == NULL) {
401 /* otherwise, strip tag from session address if any */
402 p = strchr(m1.user, *env->sc_subaddressing_delim);
403 if (p)
404 *p = '\0';
405 }
406 if (strcasecmp(m1.user, m2.user))
407 return 0;
408 }
409 return 1;
410 }
411
412 int
valid_localpart(const char * s)413 valid_localpart(const char *s)
414 {
415 #define IS_ATEXT(c) (isalnum((unsigned char)(c)) || strchr(MAILADDR_ALLOWED, (c)))
416 nextatom:
417 if (!IS_ATEXT(*s) || *s == '\0')
418 return 0;
419 while (*(++s) != '\0') {
420 if (*s == '.')
421 break;
422 if (IS_ATEXT(*s))
423 continue;
424 return 0;
425 }
426 if (*s == '.') {
427 s++;
428 goto nextatom;
429 }
430 return 1;
431 }
432
433 int
valid_domainpart(const char * s)434 valid_domainpart(const char *s)
435 {
436 struct in_addr ina;
437 struct in6_addr ina6;
438 char *c, domain[SMTPD_MAXDOMAINPARTSIZE];
439 const char *p;
440 size_t dlen;
441
442 if (*s == '[') {
443 if (strncasecmp("[IPv6:", s, 6) == 0)
444 p = s + 6;
445 else
446 p = s + 1;
447
448 if (strlcpy(domain, p, sizeof domain) >= sizeof domain)
449 return 0;
450
451 c = strchr(domain, ']');
452 if (!c || c[1] != '\0')
453 return 0;
454
455 *c = '\0';
456
457 if (inet_pton(AF_INET6, domain, &ina6) == 1)
458 return 1;
459 if (inet_pton(AF_INET, domain, &ina) == 1)
460 return 1;
461
462 return 0;
463 }
464
465 if (*s == '\0')
466 return 0;
467
468 dlen = strlen(s);
469 if (dlen >= sizeof domain)
470 return 0;
471
472 if (s[dlen - 1] == '.')
473 return 0;
474
475 return res_hnok(s);
476 }
477
478 #define LABELCHR(c) ((c) == '-' || (c) == '_' || isalpha((unsigned char)(c)) || isdigit((unsigned char)(c)))
479 #define LABELMAX 63
480 #define DNAMEMAX 253
481
482 int
valid_domainname(const char * str)483 valid_domainname(const char *str)
484 {
485 const char *label, *s;
486
487 /*
488 * Expect a sequence of dot-separated labels, possibly with a trailing
489 * dot. The empty string is rejected, as well a single dot.
490 */
491 for (s = str; *s; s++) {
492
493 /* Start of a new label. */
494 label = s;
495 while (LABELCHR(*s))
496 s++;
497
498 /* Must have at least one char and at most LABELMAX. */
499 if (s == label || s - label > LABELMAX)
500 return 0;
501
502 /* If last label, stop here. */
503 if (*s == '\0')
504 break;
505
506 /* Expect a dot as label separator or last char. */
507 if (*s != '.')
508 return 0;
509 }
510
511 /* Must have at leat one label and no more than DNAMEMAX chars. */
512 if (s == str || s - str > DNAMEMAX)
513 return 0;
514
515 return 1;
516 }
517
518 int
valid_smtp_response(const char * s)519 valid_smtp_response(const char *s)
520 {
521 if (strlen(s) < 5)
522 return 0;
523
524 if ((s[0] < '2' || s[0] > '5') ||
525 (s[1] < '0' || s[1] > '9') ||
526 (s[2] < '0' || s[2] > '9') ||
527 (s[3] != ' '))
528 return 0;
529
530 return 1;
531 }
532
533 int
valid_xtext(const char * s)534 valid_xtext(const char *s)
535 {
536 for (; *s != '\0'; ++s) {
537 if (*s < '!' || *s > '~' || *s == '=')
538 return 0;
539
540 if (*s != '+')
541 continue;
542
543 s++;
544 if (!isdigit((unsigned char)*s) &&
545 !(*s >= 'A' && *s <= 'F'))
546 return 0;
547
548 s++;
549 if (!isdigit((unsigned char)*s) &&
550 !(*s >= 'A' && *s <= 'F'))
551 return 0;
552 }
553
554 return 1;
555 }
556
557 int
secure_file(int fd,char * path,char * userdir,uid_t uid,int mayread)558 secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread)
559 {
560 char buf[PATH_MAX];
561 char homedir[PATH_MAX];
562 struct stat st;
563 char *cp;
564
565 if (realpath(path, buf) == NULL)
566 return 0;
567
568 if (realpath(userdir, homedir) == NULL)
569 homedir[0] = '\0';
570
571 /* Check the open file to avoid races. */
572 if (fstat(fd, &st) == -1 ||
573 !S_ISREG(st.st_mode) ||
574 st.st_uid != uid ||
575 (st.st_mode & (mayread ? 022 : 066)) != 0)
576 return 0;
577
578 /* For each component of the canonical path, walking upwards. */
579 for (;;) {
580 if ((cp = dirname(buf)) == NULL)
581 return 0;
582 (void)strlcpy(buf, cp, sizeof(buf));
583
584 if (stat(buf, &st) == -1 ||
585 (st.st_uid != 0 && st.st_uid != uid) ||
586 (st.st_mode & 022) != 0)
587 return 0;
588
589 /* We can stop checking after reaching homedir level. */
590 if (strcmp(homedir, buf) == 0)
591 break;
592
593 /*
594 * dirname should always complete with a "/" path,
595 * but we can be paranoid and check for "." too
596 */
597 if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
598 break;
599 }
600
601 return 1;
602 }
603
604 void
addargs(arglist * args,char * fmt,...)605 addargs(arglist *args, char *fmt, ...)
606 {
607 va_list ap;
608 char *cp;
609 uint nalloc;
610 int r;
611 char **tmp;
612
613 va_start(ap, fmt);
614 r = vasprintf(&cp, fmt, ap);
615 va_end(ap);
616 if (r == -1)
617 fatal("addargs: argument too long");
618
619 nalloc = args->nalloc;
620 if (args->list == NULL) {
621 nalloc = 32;
622 args->num = 0;
623 } else if (args->num+2 >= nalloc)
624 nalloc *= 2;
625
626 tmp = reallocarray(args->list, nalloc, sizeof(char *));
627 if (tmp == NULL)
628 fatal("addargs: reallocarray");
629 args->list = tmp;
630 args->nalloc = nalloc;
631 args->list[args->num++] = cp;
632 args->list[args->num] = NULL;
633 }
634
635 int
lowercase(char * buf,const char * s,size_t len)636 lowercase(char *buf, const char *s, size_t len)
637 {
638 if (len == 0)
639 return 0;
640
641 if (strlcpy(buf, s, len) >= len)
642 return 0;
643
644 while (*buf != '\0') {
645 *buf = tolower((unsigned char)*buf);
646 buf++;
647 }
648
649 return 1;
650 }
651
652 int
uppercase(char * buf,const char * s,size_t len)653 uppercase(char *buf, const char *s, size_t len)
654 {
655 if (len == 0)
656 return 0;
657
658 if (strlcpy(buf, s, len) >= len)
659 return 0;
660
661 while (*buf != '\0') {
662 *buf = toupper((unsigned char)*buf);
663 buf++;
664 }
665
666 return 1;
667 }
668
669 void
xlowercase(char * buf,const char * s,size_t len)670 xlowercase(char *buf, const char *s, size_t len)
671 {
672 if (len == 0)
673 fatalx("lowercase: len == 0");
674
675 if (!lowercase(buf, s, len))
676 fatalx("lowercase: truncation");
677 }
678
679 uint64_t
generate_uid(void)680 generate_uid(void)
681 {
682 static uint32_t id;
683 static uint8_t inited;
684 uint64_t uid;
685
686 if (!inited) {
687 id = arc4random();
688 inited = 1;
689 }
690 while ((uid = ((uint64_t)(id++) << 32 | arc4random())) == 0)
691 ;
692
693 return (uid);
694 }
695
696 int
session_socket_error(int fd)697 session_socket_error(int fd)
698 {
699 int error;
700 socklen_t len;
701
702 len = sizeof(error);
703 if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
704 fatal("session_socket_error: getsockopt");
705
706 return (error);
707 }
708
709 const char *
parse_smtp_response(char * line,size_t len,char ** msg,int * cont)710 parse_smtp_response(char *line, size_t len, char **msg, int *cont)
711 {
712 if (len >= LINE_MAX)
713 return "line too long";
714
715 if (len > 3) {
716 if (msg)
717 *msg = line + 4;
718 if (cont)
719 *cont = (line[3] == '-');
720 } else if (len == 3) {
721 if (msg)
722 *msg = line + 3;
723 if (cont)
724 *cont = 0;
725 } else
726 return "line too short";
727
728 /* validate reply code */
729 if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) ||
730 !isdigit((unsigned char)line[2]))
731 return "reply code out of range";
732
733 return NULL;
734 }
735
736 static int
parse_mailname_file(char * hostname,size_t len)737 parse_mailname_file(char *hostname, size_t len)
738 {
739 FILE *fp;
740 char *buf = NULL;
741 size_t bufsz = 0;
742 ssize_t buflen;
743
744 if ((fp = fopen(MAILNAME_FILE, "r")) == NULL)
745 return 1;
746
747 buflen = getline(&buf, &bufsz, fp);
748 fclose(fp);
749 if (buflen == -1) {
750 free(buf);
751 return 1;
752 }
753
754 if (buf[buflen - 1] == '\n')
755 buf[buflen - 1] = '\0';
756
757 bufsz = strlcpy(hostname, buf, len);
758 free(buf);
759 if (bufsz >= len) {
760 fprintf(stderr, MAILNAME_FILE " entry too long");
761 return 1;
762 }
763
764 return 0;
765 }
766
767 int
getmailname(char * hostname,size_t len)768 getmailname(char *hostname, size_t len)
769 {
770 struct addrinfo hints, *res = NULL;
771 int error;
772
773 /* Try MAILNAME_FILE first */
774 if (parse_mailname_file(hostname, len) == 0)
775 return 0;
776
777 /* Next, gethostname(3) */
778 if (gethostname(hostname, len) == -1) {
779 fprintf(stderr, "getmailname: gethostname() failed\n");
780 return -1;
781 }
782
783 if (strchr(hostname, '.') != NULL)
784 return 0;
785
786 /* Canonicalize if domain part is missing */
787 memset(&hints, 0, sizeof hints);
788 hints.ai_family = PF_UNSPEC;
789 hints.ai_flags = AI_CANONNAME;
790 error = getaddrinfo(hostname, NULL, &hints, &res);
791 if (error)
792 return 0; /* Continue with non-canon hostname */
793
794 if (strlcpy(hostname, res->ai_canonname, len) >= len) {
795 fprintf(stderr, "hostname too long");
796 freeaddrinfo(res);
797 return -1;
798 }
799
800 freeaddrinfo(res);
801 return 0;
802 }
803
804 int
base64_encode(unsigned char const * src,size_t srclen,char * dest,size_t destsize)805 base64_encode(unsigned char const *src, size_t srclen,
806 char *dest, size_t destsize)
807 {
808 return __b64_ntop(src, srclen, dest, destsize);
809 }
810
811 int
base64_decode(char const * src,unsigned char * dest,size_t destsize)812 base64_decode(char const *src, unsigned char *dest, size_t destsize)
813 {
814 return __b64_pton(src, dest, destsize);
815 }
816
817 int
base64_encode_rfc3548(unsigned char const * src,size_t srclen,char * dest,size_t destsize)818 base64_encode_rfc3548(unsigned char const *src, size_t srclen,
819 char *dest, size_t destsize)
820 {
821 size_t i;
822 int ret;
823
824 if ((ret = base64_encode(src, srclen, dest, destsize)) == -1)
825 return -1;
826
827 for (i = 0; i < destsize; ++i) {
828 if (dest[i] == '/')
829 dest[i] = '_';
830 else if (dest[i] == '+')
831 dest[i] = '-';
832 }
833
834 return ret;
835 }
836
837 void
log_trace0(const char * emsg,...)838 log_trace0(const char *emsg, ...)
839 {
840 va_list ap;
841
842 va_start(ap, emsg);
843 vlog(LOG_DEBUG, emsg, ap);
844 va_end(ap);
845 }
846
847 void
log_trace_verbose(int v)848 log_trace_verbose(int v)
849 {
850 tracing = v;
851
852 /* Set debug logging in log.c */
853 log_setverbose(v & TRACE_DEBUG ? 2 : foreground_log);
854 }
855
856 int
parse_table_line(FILE * fp,char ** line,size_t * linesize,int * type,char ** key,char ** val,int * malformed)857 parse_table_line(FILE *fp, char **line, size_t *linesize,
858 int *type, char **key, char **val, int *malformed)
859 {
860 char *keyp, *valp;
861 ssize_t linelen;
862
863 *key = NULL;
864 *val = NULL;
865 *malformed = 0;
866
867 if ((linelen = getline(line, linesize, fp)) == -1)
868 return (-1);
869
870 keyp = *line;
871 while (isspace((unsigned char)*keyp)) {
872 ++keyp;
873 --linelen;
874 }
875 if (*keyp == '\0')
876 return 0;
877 while (linelen > 0 && isspace((unsigned char)keyp[linelen - 1]))
878 keyp[--linelen] = '\0';
879 if (*keyp == '#') {
880 if (*type == T_NONE) {
881 keyp++;
882 while (isspace((unsigned char)*keyp))
883 ++keyp;
884 if (!strcmp(keyp, "@list"))
885 *type = T_LIST;
886 }
887 return 0;
888 }
889
890 if (*keyp == '[') {
891 if ((valp = strchr(keyp, ']')) == NULL) {
892 *malformed = 1;
893 return (0);
894 }
895 valp++;
896 } else
897 valp = keyp + strcspn(keyp, " \t:");
898
899 if (*type == T_NONE)
900 *type = (*valp == '\0') ? T_LIST : T_HASH;
901
902 if (*type == T_LIST) {
903 *key = keyp;
904 return (0);
905 }
906
907 /* T_HASH */
908 if (*valp != '\0') {
909 *valp++ = '\0';
910 valp += strspn(valp, " \t");
911 }
912 if (*valp == '\0')
913 *malformed = 1;
914
915 *key = keyp;
916 *val = valp;
917 return (0);
918 }
919