1 /*
2  * Error message and syslog output.
3  *
4  *  Copyright (c) 2014-2017 by Farsight Security, Inc.
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */
18 
19 #include <axa/axa.h>
20 
21 #include <syslog.h>
22 #include <errno.h>
23 #include <paths.h>
24 #include <sysexits.h>
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #ifdef __linux
29 #include <bsd/string.h>			/* for strlcpy() */
30 #endif
31 #include <fcntl.h>
32 #include <sys/stat.h>
33 #include <sys/resource.h>
34 
35 
36 
37 static struct {
38 	int	priority;		/* syslog(3) facility|level */
39 	bool	set;
40 	bool	on;
41 	bool	out_stdout;		/* send messages to stdout */
42 	bool	out_stderr;		/* send messages to stderr */
43 } ss[3];				/* AXA_SYSLOG_{TRACE,ERROR,ACCT} */
44 
45 static bool syslog_set;
46 static bool syslog_open;
47 
48 char axa_prog_name[256];
49 
50 
51 /* Crash immediately on malloc failures. */
52 void *
axa_malloc(size_t s)53 axa_malloc(size_t s)
54 {
55 	void *p;
56 
57 	p = malloc(s);
58 	AXA_ASSERT(p != NULL);
59 	return (p);
60 }
61 
62 void *
axa_zalloc(size_t s)63 axa_zalloc(size_t s)
64 {
65 	void *p;
66 
67 	p = calloc(1, s);
68 	AXA_ASSERT(p != NULL);
69 	return (p);
70 }
71 
72 char *
axa_strdup(const char * s)73 axa_strdup(const char *s)
74 {
75 	char *p;
76 
77 	p = strdup(s);
78 	AXA_ASSERT(p != NULL);
79 	return (p);
80 }
81 
82 char *
axa_strndup(const char * s,size_t len)83 axa_strndup(const char *s, size_t len)
84 {
85 	char *p;
86 
87 	p = strndup(s, len);
88 	AXA_ASSERT(p != NULL);
89 	return (p);
90 }
91 
92 void
axa_vasprintf(char ** bufp,const char * p,va_list args)93 axa_vasprintf(char **bufp, const char *p, va_list args)
94 {
95 	int i;
96 
97 	i = vasprintf(bufp, p,  args);
98 	AXA_ASSERT(i >= 0);
99 }
100 
101 void AXA_PF(2,3)
axa_asprintf(char ** bufp,const char * p,...)102 axa_asprintf(char **bufp, const char *p, ...)
103 {
104 	va_list args;
105 
106 	va_start(args, p);
107 	axa_vasprintf(bufp, p,  args);
108 	va_end(args);
109 }
110 
111 /* Try to enable core files. */
112 void
axa_set_core(void)113 axa_set_core(void)
114 {
115 	struct rlimit rl;
116 
117 	if (0 > getrlimit(RLIMIT_CORE, &rl)) {
118 		axa_error_msg("getrlimit(RLIMIT_CORE): %s\n",
119 			strerror(errno));
120 		return;
121 	}
122 	if (rl.rlim_cur != 0)
123 		return;
124 	if (rl.rlim_max < 10*1024) {
125 		axa_error_msg("getrlimit(RLIMIT_CORE) max = %ld\n",
126 			(long)rl.rlim_max);
127 	}
128 	rl.rlim_cur = RLIM_INFINITY;
129 	if (0 > setrlimit(RLIMIT_CORE, &rl)) {
130 		axa_error_msg("setrlimit(RLIMIT_CORE %ld %ld): %s\n",
131 			(long)rl.rlim_cur, (long)rl.rlim_max, strerror(errno));
132 		return;
133 	}
134 }
135 
136 void
axa_set_me(const char * me)137 axa_set_me(const char *me)
138 {
139 	const char *p;
140 
141 	p = strrchr(me, '/');
142 	if (p != NULL)
143 		me = p+1;
144 	strlcpy(axa_prog_name, me, sizeof(axa_prog_name));
145 	if (syslog_open)
146 		axa_syslog_init();
147 }
148 
149 static int
parse_syslog_level(const char * level)150 parse_syslog_level(const char *level)
151 {
152 	static struct {
153 		const char *str;
154 		int level;
155 	} level_tbl[] = {
156 		{"LOG_EMERG",	LOG_EMERG},
157 		{"LOG_ALERT",	LOG_ALERT},
158 		{"LOG_CRIT",	LOG_CRIT},
159 		{"LOG_ERR",	LOG_ERR},
160 		{"LOG_WARNING",	LOG_WARNING},
161 		{"LOG_NOTICE",	LOG_NOTICE},
162 		{"LOG_INFO",	LOG_INFO},
163 		{"LOG_DEBUG",	LOG_DEBUG},
164 	};
165 	int i;
166 
167 	for (i = 0; i < AXA_DIM(level_tbl); ++i) {
168 		if (strcasecmp(level, level_tbl[i].str) == 0)
169 			return (level_tbl[i].level);
170 	}
171 	return (-1);
172 }
173 
174 static int
parse_syslog_facility(const char * facility)175 parse_syslog_facility(const char *facility)
176 {
177 	static struct {
178 	    const char *str;
179 	    int facility;
180 	} facility_tbl[] = {
181 		{"LOG_AUTH",	LOG_AUTH},
182 #ifdef LOG_AUTHPRIV
183 		{"LOG_AUTHPRIV",LOG_AUTHPRIV},
184 #endif
185 		{"LOG_CRON",	LOG_CRON},
186 		{"LOG_DAEMON",	LOG_DAEMON},
187 #ifdef LOG_FTP
188 		{"LOG_FTP",	LOG_FTP},
189 #endif
190 		{"LOG_KERN",	LOG_KERN},
191 		{"LOG_LPR",	LOG_LPR},
192 		{"LOG_MAIL",	LOG_MAIL},
193 		{"LOG_NEWS",	LOG_NEWS},
194 		{"LOG_USER",	LOG_USER},
195 		{"LOG_UUCP",	LOG_UUCP},
196 		{"LOG_LOCAL0",	LOG_LOCAL0},
197 		{"LOG_LOCAL1",	LOG_LOCAL1},
198 		{"LOG_LOCAL2",	LOG_LOCAL2},
199 		{"LOG_LOCAL3",	LOG_LOCAL3},
200 		{"LOG_LOCAL4",	LOG_LOCAL4},
201 		{"LOG_LOCAL5",	LOG_LOCAL5},
202 		{"LOG_LOCAL6",	LOG_LOCAL6},
203 		{"LOG_LOCAL7",	LOG_LOCAL7},
204 	};
205 	int i;
206 
207 	for (i = 0; i < AXA_DIM(facility_tbl); ++i) {
208 		if (strcasecmp(facility, facility_tbl[i].str) == 0)
209 			return (facility_tbl[i].facility);
210 	}
211 	return (-1);
212 }
213 
214 /*
215  * Parse
216  *	{trace|error|acct},{off|FACILITY.LEVEL}[,{none,stderr,stdout}]
217  */
218 bool
axa_parse_log_opt(axa_emsg_t * emsg,const char * arg)219 axa_parse_log_opt(axa_emsg_t *emsg, const char *arg)
220 {
221 	char type_buf[32], syslog_buf[32], syslog1_buf[32];
222 	const char *arg1, *syslog2_str;
223 	int facility, level;
224 	axa_syslog_type_t type;
225 	bool on, out_stdout, out_stderr;
226 
227 	arg1 = arg;
228 	axa_get_token(type_buf, sizeof(type_buf), &arg1, ",");
229 	if (strcasecmp(type_buf, "trace") == 0) {
230 		type = AXA_SYSLOG_TRACE;
231 	} else if (strcasecmp(type_buf, "error") == 0) {
232 		type = AXA_SYSLOG_ERROR;
233 	} else if (strcasecmp(type_buf, "acct") == 0) {
234 		type = AXA_SYSLOG_ACCT;
235 	} else {
236 		axa_pemsg(emsg, "\"%s\" in \"-L %s\""
237 			  " is neither \"trace\", \"error\", nor \"acct\"",
238 			  type_buf, arg);
239 		return (false);
240 	}
241 
242 	axa_get_token(syslog_buf, sizeof(syslog_buf), &arg1, ",");
243 	if (strcasecmp(syslog_buf, "off") == 0) {
244 		on = false;
245 		facility = 0;
246 		level = 0;
247 	} else {
248 		syslog2_str = syslog_buf;
249 		axa_get_token(syslog1_buf, sizeof(syslog1_buf),
250 			      &syslog2_str, ".");
251 
252 		facility = parse_syslog_facility(syslog1_buf);
253 		level = parse_syslog_level(syslog2_str);
254 		if (facility < 0 && level < 0) {
255 			/* Recognize both LEVEL.FACILITY and FACILITY.LEVEL */
256 			facility = parse_syslog_facility(syslog2_str);
257 			level = parse_syslog_level(syslog1_buf);
258 		}
259 		if (facility < 0) {
260 			axa_pemsg(emsg,
261 				  "unrecognized syslog facility in \"%s\"",
262 				  arg);
263 			return (false);
264 		}
265 		if (level < 0) {
266 			axa_pemsg(emsg, "unrecognized syslog level in \"%s\"",
267 				      arg);
268 			return (false);
269 		}
270 		on = true;
271 	}
272 
273 	if (arg1[0] == '\0' || AXA_CLITCMP(arg1, "stderr")) {
274 		out_stdout = false;
275 		out_stderr = true;
276 	} else if (AXA_CLITCMP(arg1, "off") || AXA_CLITCMP(arg1, "none")) {
277 		out_stdout = false;
278 		out_stderr = false;
279 	} else if (AXA_CLITCMP(arg1, "stdout")) {
280 		out_stdout = true;
281 		out_stderr = false;
282 	} else {
283 		axa_pemsg(emsg, "\"%s\" in \"-L %s\" is neither"
284 			  " NONE, STDERR, nor STDOUT",
285 			  arg1, arg);
286 		return (false);
287 	}
288 
289 	ss[type].on = on;
290 	ss[type].priority = facility | level;
291 	ss[type].out_stdout = out_stdout;
292 	ss[type].out_stderr = out_stderr;
293 
294 	if (ss[type].set)
295 		axa_error_msg("warning: \"-L %s,...\" already set", type_buf);
296 	ss[type].set = true;
297 
298 	return (true);
299 }
300 
301 /*
302  * Initialize AXA default logging.
303  *	axa_parse_log_opt() can override these values.
304  */
305 static void
set_syslog(void)306 set_syslog(void)
307 {
308 	axa_emsg_t emsg;
309 
310 	if (syslog_set)
311 		return;
312 
313 	if (!ss[AXA_SYSLOG_TRACE].set) {
314 		AXA_ASSERT(axa_parse_log_opt(&emsg,
315 					     "trace,LOG_DEBUG.LOG_DAEMON"));
316 		ss[AXA_SYSLOG_TRACE].set = false;
317 	}
318 	if (!ss[AXA_SYSLOG_ERROR].set) {
319 		/* transposed facility and level to check axa_parse_log_opt() */
320 		AXA_ASSERT(axa_parse_log_opt(&emsg,
321 					     "error,LOG_DAEMON.LOG_ERR"));
322 		ss[AXA_SYSLOG_ERROR].set = false;
323 	}
324 	if (!ss[AXA_SYSLOG_ACCT].set) {
325 		AXA_ASSERT(axa_parse_log_opt(&emsg,
326 					     "acct,LOG_NOTICE.LOG_AUTH,none"));
327 		ss[AXA_SYSLOG_ACCT].set = false;
328 	}
329 	syslog_set = true;
330 }
331 
332 void
axa_syslog_init(void)333 axa_syslog_init(void)
334 {
335 	set_syslog();
336 	if (axa_prog_name[0] != '\0') {
337 		if (syslog_open)
338 			closelog();
339 		openlog(axa_prog_name, LOG_PID, LOG_DAEMON);
340 		syslog_open = true;
341 	}
342 }
343 
344 static void
clean_stdfd(int stdfd)345 clean_stdfd(int stdfd)
346 {
347 	struct stat sb;
348 	int fd;
349 
350 	if (0 > fstat(stdfd, &sb) && errno == EBADF) {
351 		fd = open(_PATH_DEVNULL, 0, O_RDWR | O_CLOEXEC);
352 		if (fd < 0)		/* ignore errors we can't help */
353 			return;
354 		if (fd != stdfd) {
355 			dup2(fd, stdfd);
356 			close(fd);
357 		}
358 	}
359 }
360 
361 /*
362  * Add text to an error or other message buffer.
363  *	If we run out of room, add "...". */
364 void AXA_PF(3,4)
axa_buf_print(char ** bufp,size_t * buf_lenp,const char * p,...)365 axa_buf_print(char **bufp, size_t *buf_lenp, const char *p, ...)
366 {
367 	size_t buf_len, len;
368 	va_list args;
369 
370 	buf_len = *buf_lenp;
371 	if (buf_len < sizeof("...")) {
372 		if (buf_len != 0) {
373 			strlcpy(*bufp, "...", buf_len);
374 			*bufp += buf_len-1;
375 			*buf_lenp = 1;
376 		}
377 		return;
378 	}
379 
380 	va_start(args, p);
381 	len = vsnprintf(*bufp, *buf_lenp, p,  args);
382 	va_end(args);
383 	if (len+sizeof("...") > buf_len) {
384 		strcpy(*bufp+buf_len-sizeof("..."), "...");
385 		*bufp += buf_len-1;
386 		*buf_lenp = 1;
387 	} else {
388 		*buf_lenp -= len;
389 		*bufp += len;
390 	}
391 }
392 
393 
394 /* Prevent surprises from uses of stdio FDs by ensuring that the FDs are open */
395 void
axa_clean_stdio(void)396 axa_clean_stdio(void)
397 {
398 	clean_stdfd(STDIN_FILENO);
399 	clean_stdfd(STDOUT_FILENO);
400 	clean_stdfd(STDERR_FILENO);
401 }
402 
403 void
axa_vlog_msg(axa_syslog_type_t type,bool fatal,const char * p,va_list args)404 axa_vlog_msg(axa_syslog_type_t type, bool fatal, const char *p, va_list args)
405 {
406 	char buf[512], *bufp;
407 	size_t buf_len, n;
408 	FILE *stdio;
409 #	define FMSG "; fatal error"
410 
411 	/*
412 	 * This function cannot use axa_vasprintf() and other axa_*()
413 	 * functions that would themselves call this function.
414 	 */
415 
416 	bufp = buf;
417 	buf_len = sizeof(buf);
418 	if (fatal)
419 		buf_len -= sizeof(FMSG)-1;
420 
421 	n = vsnprintf(bufp, buf_len, p, args);
422 
423 	if (n >= buf_len)
424 		n = buf_len-1;
425 	if (n != 0 && buf[n-1] == '\n')
426 		buf[--n] = '\0';
427 	if (n == 0) {
428 		strlcat(bufp, "(empty error message)", buf_len);
429 		n = sizeof("(empty error message)")-1;
430 	}
431 	if (n >= buf_len)
432 		strcpy(&buf[buf_len-sizeof("...")], "...");
433 	if (fatal)
434 		strlcat(buf, FMSG, sizeof(buf));
435 
436 	/* keep stderr and stdout straight despite syslog output
437 	 * to stdout or stderr */
438 	fflush(stdout);
439 	fflush(stderr);
440 
441 	set_syslog();
442 
443 	if (ss[type].out_stderr)
444 		stdio = stderr;
445 	else if (ss[type].out_stdout)
446 		stdio = stdout;
447 	else
448 		stdio = NULL;
449 	if (stdio != NULL)
450 		fprintf(stdio, "%s\n", buf);
451 
452 	if (ss[type].on)
453 		syslog(ss[type].priority, "%s", buf);
454 
455 	/* Error messges also go to the trace stream. */
456 	if (type == AXA_SYSLOG_ERROR && ss[AXA_SYSLOG_TRACE].on
457 	    && ss[AXA_SYSLOG_TRACE].priority != ss[AXA_SYSLOG_ERROR].priority)
458 		syslog(ss[AXA_SYSLOG_TRACE].priority, "%s", buf);
459 
460 	fflush(stdout);
461 	fflush(stderr);
462 }
463 
464 /*
465  * Generate an error message string in a buffer, if we have a buffer.
466  * Log or print the message if there is no buffer
467  */
468 void
axa_vpemsg(axa_emsg_t * emsg,const char * p,va_list args)469 axa_vpemsg(axa_emsg_t *emsg, const char *p, va_list args)
470 {
471 	if (emsg == NULL) {
472 		axa_vlog_msg(AXA_SYSLOG_ERROR, false, p, args);
473 	} else {
474 		vsnprintf(emsg->c, sizeof(axa_emsg_t), p, args);
475 	}
476 }
477 
478 void AXA_PF(2,3)
axa_pemsg(axa_emsg_t * emsg,const char * p,...)479 axa_pemsg(axa_emsg_t *emsg, const char *p, ...)
480 {
481 	va_list args;
482 
483 	va_start(args, p);
484 	axa_vpemsg(emsg, p, args);
485 	va_end(args);
486 }
487 
488 void
axa_verror_msg(const char * p,va_list args)489 axa_verror_msg(const char *p, va_list args)
490 {
491 	axa_vlog_msg(AXA_SYSLOG_ERROR, false, p, args);
492 }
493 
494 void AXA_PF(1,2)
axa_error_msg(const char * p,...)495 axa_error_msg(const char *p, ...)
496 {
497 	va_list args;
498 
499 	va_start(args, p);
500 	axa_vlog_msg(AXA_SYSLOG_ERROR, false, p, args);
501 	va_end(args);
502 }
503 
504 void
axa_io_error(const char * op,const char * src,ssize_t len)505 axa_io_error(const char *op, const char *src, ssize_t len)
506 {
507 	if (len >= 0) {
508 		axa_error_msg("%s(%s)=%zd", op, src, len);
509 	} else {
510 		axa_error_msg("%s(%s): %s", op, src, strerror(errno));
511 	}
512 }
513 
514 void
axa_vtrace_msg(const char * p,va_list args)515 axa_vtrace_msg(const char *p, va_list args)
516 {
517 	axa_vlog_msg(AXA_SYSLOG_TRACE, false, p, args);
518 }
519 
520 void AXA_PF(1,2)
axa_trace_msg(const char * p,...)521 axa_trace_msg(const char *p, ...)
522 {
523 	va_list args;
524 
525 	va_start(args, p);
526 	axa_vlog_msg(AXA_SYSLOG_TRACE, false, p, args);
527 	va_end(args);
528 }
529 
530 void AXA_NORETURN
axa_vfatal_msg(int ex_code,const char * p,va_list args)531 axa_vfatal_msg(int ex_code, const char *p, va_list args)
532 {
533 	axa_vlog_msg(AXA_SYSLOG_ERROR, true, p, args);
534 
535 	if (ex_code == 0 || ex_code == EX_SOFTWARE)
536 		abort();
537 	exit(ex_code);
538 }
539 
540 /*
541  * Things are so sick that we must bail out.
542  */
543 void AXA_PF(2,3) AXA_NORETURN
axa_fatal_msg(int ex_code,const char * p,...)544 axa_fatal_msg(int ex_code, const char *p, ...)
545 {
546 	va_list args;
547 
548 	va_start(args, p);
549 	axa_vfatal_msg(ex_code, p, args);
550 }
551 
552 /*
553  * Get a logical line from a stdio stream, with leading and trailing
554  * whitespace trimmed and "\\\n" deleted as a continuation.
555  *	The file name and line number must be provided for error messages.
556  *	The line number is updated.
557  *	The buffer can be NULL.
558  *	If the buffer is NULL or it is not big enough, it is freed and a new
559  *	    buffer is allocated.
560  *	The must be freed after the last use of this function.
561  *	Except at error or EOF, the start of the next line is returned,
562  *	    which might not be at the start of the buffer.
563  *	The return value is NULL and emsg->c[0]=='\0' at EOF.
564  *	The return value is NULL and emsg->c[0]!='\0' after an error.
565  */
566 char *
axa_fgetln(FILE * f,const char * file_name,uint * line_num,char ** bufp,size_t * buf_sizep)567 axa_fgetln(FILE *f,			/* source */
568 	   const char *file_name,	/* for error messages */
569 	   uint *line_num,
570 	   char **bufp,			/* destination must be freed */
571 	   size_t *buf_sizep)
572 {
573 	char *buf, *p, *line;
574 	size_t buf_size, len, delta;
575 
576 	if (*bufp == NULL) {
577 		AXA_ASSERT(*buf_sizep == 0);
578 		buf = axa_malloc(*buf_sizep = 81);
579 		*bufp = buf;
580 	}
581 	for (;;) {
582 		buf = *bufp;
583 		buf_size = *buf_sizep;
584 		for (;;) {
585 			if (buf_size < 80) {
586 				delta = (*buf_sizep/81+2)*81 - buf_size;
587 				p = axa_malloc(*buf_sizep + delta);
588 				len = buf - *bufp;
589 				if (len > 0)
590 					memcpy(p, *bufp, len);
591 				*buf_sizep += delta;
592 				buf_size += delta;
593 				free(*bufp);
594 				*bufp = p;
595 				buf = p+len;
596 			}
597 
598 			if (fgets(buf, buf_size, f) == NULL) {
599 				*buf = '\0';
600 				if (ferror(f) != 0) {
601 					axa_error_msg("fgets(%s): \"%s\"",
602 						      file_name,
603 						      strerror(errno));
604 					return (NULL);
605 				}
606 				break;
607 			}
608 
609 			/* Expand the buffer and get more if the buffer
610 			 * was too small for the line */
611 			len = strlen(buf);
612 			if (len >= buf_size-1 && buf[len-1] != '\n') {
613 				buf_size -= len;
614 				buf += len;
615 				continue;
616 			}
617 
618 			++*line_num;
619 
620 			/* trim trailing '\n' and check for continuation */
621 			while (len >0
622 			       && (buf[len-1] == '\n' || buf[len-1] == '\r')) {
623 				buf[--len] = '\0';
624 			}
625 			if (len == 0
626 			    || ( buf[--len] != '\\' || len >= 10*1024))
627 				break;
628 			buf[len]= '\0';
629 			buf_size -= len;
630 			buf += len;
631 		}
632 
633 		/* Trim leading blanks and comments */
634 		line = *bufp+strspn(*bufp, AXA_WHITESPACE);
635 		p = strpbrk(line, "\r\n#");
636 		if (p != NULL)
637 			*p = '\0';
638 
639 		/* skip blank lines */
640 		if (*line != '\0')
641 			return (line);
642 		if (feof(f))
643 			return (NULL);
644 	}
645 }
646 
647 /*
648  * Strip leading and trailing white space.
649  */
650 const char *
axa_strip_white(const char * str,size_t * lenp)651 axa_strip_white(const char *str, size_t *lenp)
652 {
653 	const char *end;
654 	char c;
655 
656 	str += strspn(str, AXA_WHITESPACE);
657 	end = str+strlen(str);
658 	while (end > str) {
659 		c = *(end-1);
660 		if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
661 			break;
662 		--end;
663 	}
664 	*lenp = end-str;
665 	return (str);
666 }
667 
668 /* Copy the next token from a string to a buffer and return the
669  * size of the string put into the buffer.
670  * Honor quotes and backslash.
671  * The caller must skip leading token separators (e.g. blanks) if necessary.
672  * When the separators include whitespace and whitespace ends the token,
673  *	then all trailing whitespace is skipped */
674 ssize_t					/* # of bytes or <0 for failure */
axa_get_token(char * buf,size_t buf_len,const char ** stringp,const char * seps)675 axa_get_token(char *buf,		/* put the token here */
676 	      size_t buf_len,
677 	      const char **stringp,	/* input string pointer */
678 	      const char *seps)		/* string of token separators */
679 {
680 	int token_len;
681 	bool quot_ok, esc_ok;
682 	const char *string;
683 	char c, quote;
684 
685 	token_len = 0;
686 
687 	/* Quietly skip without a buffer but fail with a zero-length buffer. */
688 	if (buf_len == 0 && buf != NULL)
689 		return (-1);
690 
691 	quot_ok = (strpbrk(seps, "\"'") == NULL);
692 	esc_ok = (strchr(seps, '\\') == NULL);
693 	string = *stringp;
694 
695 	for (;;) {
696 		c = *string;
697 		if (c == '\0') {
698 			if (buf != NULL)
699 				*buf = '\0';
700 			*stringp = string;
701 			return (token_len);
702 		}
703 		if (quot_ok && strchr("\"'",c ) != NULL) {
704 			quote = c;
705 			while ((c = *++string) != quote) {
706 				if (c == '\0') {
707 					if (buf != NULL)
708 					    *buf = '\0';
709 					*stringp = string;
710 					return (token_len);
711 				}
712 				++token_len;
713 
714 				if (buf == NULL)
715 					continue;
716 				if (--buf_len == 0) {
717 					*buf = '\0';
718 					*stringp = string;
719 					return (-1);
720 				}
721 				*buf++ = c;
722 			}
723 			++string;
724 			continue;
725 		}
726 
727 		++string;
728 		if (c == '\\' && esc_ok) {
729 			c = *string++;
730 		} else if (strchr(seps, c) != NULL) {
731 			/* We found a separator.  Eat it and stop.
732 			 * If it is whitespace, then eat all trailing
733 			 * whitespace. */
734 			if (strchr(AXA_WHITESPACE, c) != NULL)
735 				string += strspn(string, AXA_WHITESPACE);
736 
737 			if (buf != NULL)
738 				*buf = '\0';
739 			*stringp = string;
740 			return (token_len);
741 		}
742 		++token_len;
743 
744 		if (buf == NULL)
745 			continue;
746 		if (--buf_len == 0) {
747 			*buf = '\0';
748 			*stringp = string;
749 			return (-1);
750 		}
751 		*buf++ = c;
752 	}
753 }
754