Home | History | Annotate | Line # | Download | only in ftpd
conf.c revision 1.25
      1 /*	$NetBSD: conf.c,v 1.25 2000/01/08 11:09:56 lukem Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1997-2000 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Simon Burge and Luke Mewburn.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  * 3. All advertising materials mentioning features or use of this software
     19  *    must display the following acknowledgement:
     20  *        This product includes software developed by the NetBSD
     21  *        Foundation, Inc. and its contributors.
     22  * 4. Neither the name of The NetBSD Foundation nor the names of its
     23  *    contributors may be used to endorse or promote products derived
     24  *    from this software without specific prior written permission.
     25  *
     26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     36  * POSSIBILITY OF SUCH DAMAGE.
     37  */
     38 
     39 #include <sys/cdefs.h>
     40 #ifndef lint
     41 __RCSID("$NetBSD: conf.c,v 1.25 2000/01/08 11:09:56 lukem Exp $");
     42 #endif /* not lint */
     43 
     44 #include <sys/types.h>
     45 #include <sys/param.h>
     46 #include <sys/stat.h>
     47 
     48 #include <ctype.h>
     49 #include <errno.h>
     50 #include <fcntl.h>
     51 #include <glob.h>
     52 #include <signal.h>
     53 #include <stdio.h>
     54 #include <stdlib.h>
     55 #include <string.h>
     56 #include <stringlist.h>
     57 #include <syslog.h>
     58 #include <time.h>
     59 #include <unistd.h>
     60 #include <util.h>
     61 
     62 #ifdef KERBEROS5
     63 #include <krb5/krb5.h>
     64 #endif
     65 
     66 #include "extern.h"
     67 #include "pathnames.h"
     68 
     69 static char *strend __P((const char *, char *));
     70 static int filetypematch __P((char *, int));
     71 
     72 struct ftpclass curclass;
     73 
     74 
     75 /*
     76  * Initialise curclass to an `empty' state
     77  */
     78 void
     79 init_curclass()
     80 {
     81 	struct ftpconv	*conv, *cnext;
     82 
     83 	for (conv = curclass.conversions; conv != NULL; conv = cnext) {
     84 		REASSIGN(conv->suffix, NULL);
     85 		REASSIGN(conv->types, NULL);
     86 		REASSIGN(conv->disable, NULL);
     87 		REASSIGN(conv->command, NULL);
     88 		cnext = conv->next;
     89 		free(conv);
     90 	}
     91 	curclass.checkportcmd = 0;
     92 	REASSIGN(curclass.classname, NULL);
     93 	curclass.conversions =	NULL;
     94 	REASSIGN(curclass.display, NULL);
     95 	curclass.limit =	-1;		/* unlimited connections */
     96 	REASSIGN(curclass.limitfile, NULL);
     97 	curclass.maxrateget =	0;
     98 	curclass.maxrateput =	0;
     99 	curclass.maxtimeout =	7200;		/* 2 hours */
    100 	curclass.modify =	1;
    101 	REASSIGN(curclass.motd, xstrdup(_PATH_FTPLOGINMESG));
    102 	REASSIGN(curclass.notify, NULL);
    103 	curclass.passive =	1;
    104 	curclass.maxrateget =	0;
    105 	curclass.maxrateput =	0;
    106 	curclass.rateget =	0;
    107 	curclass.rateput =	0;
    108 	curclass.timeout =	900;		/* 15 minutes */
    109 	curclass.umask =	027;
    110 	curclass.upload =	1;
    111 
    112 }
    113 
    114 /*
    115  * Parse the configuration file, looking for the named class, and
    116  * define curclass to contain the appropriate settings.
    117  */
    118 void
    119 parse_conf(findclass)
    120 	char *findclass;
    121 {
    122 	FILE		*f;
    123 	char		*buf, *p;
    124 	size_t		 len;
    125 	int		 none, match, rate;
    126 	char		*endp;
    127 	char		*class, *word, *arg;
    128 	const char	*infile;
    129 	size_t		 line;
    130 	unsigned int	 timeout;
    131 	struct ftpconv	*conv, *cnext;
    132 
    133 	init_curclass();
    134 	REASSIGN(curclass.classname, xstrdup(findclass));
    135 	if (strcasecmp(findclass, "guest") == 0) {
    136 		curclass.modify = 0;
    137 		curclass.umask = 0707;
    138 	}
    139 
    140 	infile = conffilename(_PATH_FTPDCONF);
    141 	if ((f = fopen(infile, "r")) == NULL)
    142 		return;
    143 
    144 	line = 0;
    145 	for (;
    146 	    (buf = fparseln(f, &len, &line, NULL, FPARSELN_UNESCCOMM |
    147 	    		FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL;
    148 	    free(buf)) {
    149 		none = match = 0;
    150 		p = buf;
    151 		if (len < 1)
    152 			continue;
    153 		if (p[len - 1] == '\n')
    154 			p[--len] = '\0';
    155 		if (EMPTYSTR(p))
    156 			continue;
    157 
    158 		NEXTWORD(p, word);
    159 		NEXTWORD(p, class);
    160 		NEXTWORD(p, arg);
    161 		if (EMPTYSTR(word) || EMPTYSTR(class))
    162 			continue;
    163 		if (strcasecmp(class, "none") == 0)
    164 			none = 1;
    165 		if (strcasecmp(class, findclass) != 0 &&
    166 		    !none && strcasecmp(class, "all") != 0)
    167 			continue;
    168 
    169 		if (strcasecmp(word, "checkportcmd") == 0) {
    170 			if (none ||
    171 			    (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))
    172 				curclass.checkportcmd = 0;
    173 			else
    174 				curclass.checkportcmd = 1;
    175 
    176 		} else if (strcasecmp(word, "classtype") == 0) {
    177 			if (!none && !EMPTYSTR(arg)) {
    178 				if (strcasecmp(arg, "GUEST") == 0)
    179 					curclass.type = CLASS_GUEST;
    180 				else if (strcasecmp(arg, "CHROOT") == 0)
    181 					curclass.type = CLASS_CHROOT;
    182 				else if (strcasecmp(arg, "REAL") == 0)
    183 					curclass.type = CLASS_REAL;
    184 				else {
    185 					syslog(LOG_WARNING,
    186 				    "%s line %d: unknown class type `%s'",
    187 					    infile, (int)line, arg);
    188 					continue;
    189 				}
    190 			}
    191 
    192 		} else if (strcasecmp(word, "conversion") == 0) {
    193 			char *suffix, *types, *disable, *convcmd;
    194 
    195 			if (EMPTYSTR(arg)) {
    196 				syslog(LOG_WARNING,
    197 				    "%s line %d: %s requires a suffix",
    198 				    infile, (int)line, word);
    199 				continue;	/* need a suffix */
    200 			}
    201 			NEXTWORD(p, types);
    202 			NEXTWORD(p, disable);
    203 			convcmd = p;
    204 			if (convcmd)
    205 				convcmd += strspn(convcmd, " \t");
    206 			suffix = xstrdup(arg);
    207 			if (none || EMPTYSTR(types) ||
    208 			    EMPTYSTR(disable) || EMPTYSTR(convcmd)) {
    209 				types = NULL;
    210 				disable = NULL;
    211 				convcmd = NULL;
    212 			} else {
    213 				types = xstrdup(types);
    214 				disable = xstrdup(disable);
    215 				convcmd = xstrdup(convcmd);
    216 			}
    217 			for (conv = curclass.conversions; conv != NULL;
    218 			    conv = conv->next) {
    219 				if (strcmp(conv->suffix, suffix) == 0)
    220 					break;
    221 			}
    222 			if (conv == NULL) {
    223 				conv = (struct ftpconv *)
    224 				    calloc(1, sizeof(struct ftpconv));
    225 				if (conv == NULL) {
    226 					syslog(LOG_WARNING, "can't malloc");
    227 					continue;
    228 				}
    229 				conv->next = NULL;
    230 				for (cnext = curclass.conversions;
    231 				    cnext != NULL; cnext = cnext->next)
    232 					if (cnext->next == NULL)
    233 						break;
    234 				if (cnext != NULL)
    235 					cnext->next = conv;
    236 				else
    237 					curclass.conversions = conv;
    238 			}
    239 			REASSIGN(conv->suffix, suffix);
    240 			REASSIGN(conv->types, types);
    241 			REASSIGN(conv->disable, disable);
    242 			REASSIGN(conv->command, convcmd);
    243 
    244 		} else if (strcasecmp(word, "display") == 0) {
    245 			if (none || EMPTYSTR(arg))
    246 				arg = NULL;
    247 			else
    248 				arg = xstrdup(arg);
    249 			REASSIGN(curclass.display, arg);
    250 
    251 		} else if (strcasecmp(word, "limit") == 0) {
    252 			int limit;
    253 
    254 			if (none || EMPTYSTR(arg))
    255 				continue;
    256 			limit = (int)strtol(arg, &endp, 10);
    257 			if (*endp != 0) {
    258 				syslog(LOG_WARNING,
    259 				    "%s line %d: invalid limit %s",
    260 				    infile, (int)line, arg);
    261 				continue;
    262 			}
    263 			curclass.limit = limit;
    264 			REASSIGN(curclass.limitfile, xstrdup(p));
    265 
    266 		} else if (strcasecmp(word, "maxtimeout") == 0) {
    267 			if (none || EMPTYSTR(arg))
    268 				continue;
    269 			timeout = (unsigned int)strtoul(arg, &endp, 10);
    270 			if (*endp != 0) {
    271 				syslog(LOG_WARNING,
    272 				    "%s line %d: invalid maxtimeout %s",
    273 				    infile, (int)line, arg);
    274 				continue;
    275 			}
    276 			if (timeout < 30) {
    277 				syslog(LOG_WARNING,
    278 				    "%s line %d: maxtimeout %d < 30 seconds",
    279 				    infile, (int)line, timeout);
    280 				continue;
    281 			}
    282 			if (timeout < curclass.timeout) {
    283 				syslog(LOG_WARNING,
    284 				    "%s line %d: maxtimeout %d < timeout (%d)",
    285 				    infile, (int)line, timeout,
    286 				    curclass.timeout);
    287 				continue;
    288 			}
    289 			curclass.maxtimeout = timeout;
    290 
    291 		} else if (strcasecmp(word, "modify") == 0) {
    292 			if (none ||
    293 			    (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))
    294 				curclass.modify = 0;
    295 			else
    296 				curclass.modify = 1;
    297 
    298 		} else if (strcasecmp(word, "motd") == 0) {
    299 			if (none || EMPTYSTR(arg))
    300 				arg = NULL;
    301 			else
    302 				arg = xstrdup(arg);
    303 			REASSIGN(curclass.motd, arg);
    304 
    305 
    306 		} else if (strcasecmp(word, "notify") == 0) {
    307 			if (none || EMPTYSTR(arg))
    308 				arg = NULL;
    309 			else
    310 				arg = xstrdup(arg);
    311 			REASSIGN(curclass.notify, arg);
    312 
    313 		} else if (strcasecmp(word, "passive") == 0) {
    314 			if (none ||
    315 			    (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))
    316 				curclass.passive = 0;
    317 			else
    318 				curclass.passive = 1;
    319 
    320 		} else if (strcasecmp(word, "rateget") == 0) {
    321 			if (none || EMPTYSTR(arg))
    322 				continue;
    323 			rate = strsuftoi(arg);
    324 			if (rate == -1) {
    325 				syslog(LOG_WARNING,
    326 				    "%s line %d: invalid rateget %s",
    327 				    infile, (int)line, arg);
    328 				continue;
    329 			}
    330 			curclass.maxrateget = rate;
    331 			curclass.rateget = rate;
    332 
    333 		} else if (strcasecmp(word, "rateput") == 0) {
    334 			if (none || EMPTYSTR(arg))
    335 				continue;
    336 			rate = strsuftoi(arg);
    337 			if (rate == -1) {
    338 				syslog(LOG_WARNING,
    339 				    "%s line %d: invalid rateput %s",
    340 				    infile, (int)line, arg);
    341 				continue;
    342 			}
    343 			curclass.maxrateput = rate;
    344 			curclass.rateput = rate;
    345 
    346 		} else if (strcasecmp(word, "timeout") == 0) {
    347 			if (none || EMPTYSTR(arg))
    348 				continue;
    349 			timeout = (unsigned int)strtoul(arg, &endp, 10);
    350 			if (*endp != 0) {
    351 				syslog(LOG_WARNING,
    352 				    "%s line %d: invalid timeout %s",
    353 				    infile, (int)line, arg);
    354 				continue;
    355 			}
    356 			if (timeout < 30) {
    357 				syslog(LOG_WARNING,
    358 				    "%s line %d: timeout %d < 30 seconds",
    359 				    infile, (int)line, timeout);
    360 				continue;
    361 			}
    362 			if (timeout > curclass.maxtimeout) {
    363 				syslog(LOG_WARNING,
    364 				    "%s line %d: timeout %d > maxtimeout (%d)",
    365 				    infile, (int)line, timeout,
    366 				    curclass.maxtimeout);
    367 				continue;
    368 			}
    369 			curclass.timeout = timeout;
    370 
    371 		} else if (strcasecmp(word, "umask") == 0) {
    372 			mode_t umask;
    373 
    374 			if (none || EMPTYSTR(arg))
    375 				continue;
    376 			umask = (mode_t)strtoul(arg, &endp, 8);
    377 			if (*endp != 0 || umask > 0777) {
    378 				syslog(LOG_WARNING,
    379 				    "%s line %d: invalid umask %s",
    380 				    infile, (int)line, arg);
    381 				continue;
    382 			}
    383 			curclass.umask = umask;
    384 
    385 		} else if (strcasecmp(word, "upload") == 0) {
    386 			if (none ||
    387 			    (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0)) {
    388 				curclass.modify = 0;
    389 				curclass.upload = 0;
    390 			} else
    391 				curclass.upload = 1;
    392 
    393 		} else {
    394 			syslog(LOG_WARNING,
    395 			    "%s line %d: unknown directive '%s'",
    396 			    infile, (int)line, word);
    397 			continue;
    398 		}
    399 	}
    400 	fclose(f);
    401 }
    402 
    403 /*
    404  * Show file listed in curclass.display first time in, and list all the
    405  * files named in curclass.notify in the current directory.  Send back
    406  * responses with the prefix `code' + "-".
    407  */
    408 void
    409 show_chdir_messages(code)
    410 	int	code;
    411 {
    412 	static StringList *slist = NULL;
    413 
    414 	struct stat st;
    415 	struct tm *t;
    416 	glob_t	 gl;
    417 	time_t	 now, then;
    418 	int	 age;
    419 	char	 cwd[MAXPATHLEN];
    420 	char	*cp, **rlist;
    421 
    422 		/* Setup list for directory cache */
    423 	if (slist == NULL)
    424 		slist = sl_init();
    425 	if (slist == NULL) {
    426 		syslog(LOG_WARNING, "can't allocate memory for stringlist");
    427 		return;
    428 	}
    429 
    430 		/* Check if this directory has already been visited */
    431 	if (getcwd(cwd, sizeof(cwd) - 1) == NULL) {
    432 		syslog(LOG_WARNING, "can't getcwd: %s", strerror(errno));
    433 		return;
    434 	}
    435 	if (sl_find(slist, cwd) != NULL)
    436 		return;
    437 
    438 	cp = xstrdup(cwd);
    439 	if (sl_add(slist, cp) == -1)
    440 		syslog(LOG_WARNING, "can't add `%s' to stringlist", cp);
    441 
    442 		/* First check for a display file */
    443 	(void)format_file(curclass.display, code);
    444 
    445 		/* Now see if there are any notify files */
    446 	if (EMPTYSTR(curclass.notify))
    447 		return;
    448 
    449 	if (glob(curclass.notify, 0, NULL, &gl) != 0 || gl.gl_matchc == 0)
    450 		return;
    451 	time(&now);
    452 	for (rlist = gl.gl_pathv; *rlist != NULL; rlist++) {
    453 		if (stat(*rlist, &st) != 0)
    454 			continue;
    455 		if (!S_ISREG(st.st_mode))
    456 			continue;
    457 		then = st.st_mtime;
    458 		if (code != 0) {
    459 			lreply(code, "");
    460 			code = 0;
    461 		}
    462 		lreply(code, "Please read the file %s", *rlist);
    463 		t = localtime(&now);
    464 		age = 365 * t->tm_year + t->tm_yday;
    465 		t = localtime(&then);
    466 		age -= 365 * t->tm_year + t->tm_yday;
    467 		lreply(code, "  it was last modified on %.24s - %d day%s ago",
    468 		    ctime(&then), age, PLURAL(age));
    469 	}
    470 	globfree(&gl);
    471 }
    472 
    473 int
    474 format_file(file, code)
    475 	const char *file;
    476 	int code;
    477 {
    478 	FILE   *f;
    479 	char   *buf, *p, *cwd;
    480 	size_t	len;
    481 	off_t	b;
    482 	time_t	now;
    483 
    484 #define PUTC(x)	putchar(x), b++
    485 
    486 	if (EMPTYSTR(file))
    487 		return(0);
    488 	if ((f = fopen(file, "r")) == NULL)
    489 		return (0);
    490 	lreply(code, "");
    491 
    492 	b = 0;
    493 	for (;
    494 	    (buf = fparseln(f, &len, NULL, "\0\0\0", 0)) != NULL; free(buf)) {
    495 		if (len > 0)
    496 			if (buf[len - 1] == '\n')
    497 				buf[--len] = '\0';
    498 		b += printf("    ");
    499 
    500 		for (p = buf; *p; p++) {
    501 			if (*p == '%') {
    502 				p++;
    503 				switch (*p) {
    504 
    505 				case 'c':
    506 					b += printf("%s",
    507 					    curclass.classname ?
    508 					    curclass.classname : "<unknown>");
    509 					break;
    510 
    511 				case 'C':
    512 					if (getcwd(cwd, sizeof(cwd)-1) == NULL){
    513 						syslog(LOG_WARNING,
    514 						    "can't getcwd: %s",
    515 						    strerror(errno));
    516 						continue;
    517 					}
    518 					b += printf("%s", cwd);
    519 					break;
    520 
    521 				case 'E':
    522 						/* XXXX email address */
    523 					break;
    524 
    525 				case 'L':
    526 					b += printf("%s", hostname);
    527 					break;
    528 
    529 				case 'M':
    530 					if (curclass.limit == -1)
    531 						b += printf("unlimited");
    532 					else
    533 						b += printf("%d",
    534 						    curclass.limit);
    535 					break;
    536 
    537 				case 'N':
    538 					if (connections > 0)
    539 						b += printf("%d", connections);
    540 					break;
    541 
    542 				case 'R':
    543 					b += printf("%s", remotehost);
    544 					break;
    545 
    546 				case 'T':
    547 					now = time(NULL);
    548 					b += printf("%.24s", ctime(&now));
    549 					break;
    550 
    551 				case 'U':
    552 					b += printf("%s",
    553 					    pw ? pw->pw_name : "<unknown>");
    554 					break;
    555 
    556 				case '%':
    557 					PUTC('%');
    558 					break;
    559 
    560 				}
    561 			} else {
    562 				PUTC(*p);
    563 			}
    564 		}
    565 		PUTC('\r');
    566 		PUTC('\n');
    567 	}
    568 
    569 	total_bytes += b;
    570 	total_bytes_out += b;
    571 	(void)fflush(stdout);
    572 	(void)fclose(f);
    573 	return (1);
    574 }
    575 
    576 /*
    577  * Find s2 at the end of s1.  If found, return a string up to (but
    578  * not including) s2, otherwise returns NULL.
    579  */
    580 static char *
    581 strend(s1, s2)
    582 	const char *s1;
    583 	char *s2;
    584 {
    585 	static	char buf[MAXPATHLEN];
    586 
    587 	char	*start;
    588 	size_t	l1, l2;
    589 
    590 	l1 = strlen(s1);
    591 	l2 = strlen(s2);
    592 
    593 	if (l2 >= l1)
    594 		return(NULL);
    595 
    596 	strlcpy(buf, s1, sizeof(buf));
    597 	start = buf + (l1 - l2);
    598 
    599 	if (strcmp(start, s2) == 0) {
    600 		*start = '\0';
    601 		return(buf);
    602 	} else
    603 		return(NULL);
    604 }
    605 
    606 static int
    607 filetypematch(types, mode)
    608 	char	*types;
    609 	int	mode;
    610 {
    611 	for ( ; types[0] != '\0'; types++)
    612 		switch (*types) {
    613 		  case 'd':
    614 			if (S_ISDIR(mode))
    615 				return(1);
    616 			break;
    617 		  case 'f':
    618 			if (S_ISREG(mode))
    619 				return(1);
    620 			break;
    621 		}
    622 	return(0);
    623 }
    624 
    625 /*
    626  * Look for a conversion.  If we succeed, return a pointer to the
    627  * command to execute for the conversion.
    628  *
    629  * The command is stored in a static array so there's no memory
    630  * leak problems, and not too much to change in ftpd.c.  This
    631  * routine doesn't need to be re-entrant unless we start using a
    632  * multi-threaded ftpd, and that's not likely for a while...
    633  */
    634 char **
    635 do_conversion(fname)
    636 	const char *fname;
    637 {
    638 	struct ftpconv	*cp;
    639 	struct stat	 st;
    640 	int		 o_errno;
    641 	char		*base = NULL;
    642 	char		*cmd, *p, *lp, **argv;
    643 	StringList	*sl;
    644 
    645 	o_errno = errno;
    646 	sl = NULL;
    647 	cmd = NULL;
    648 	for (cp = curclass.conversions; cp != NULL; cp = cp->next) {
    649 		if (cp->suffix == NULL) {
    650 			syslog(LOG_WARNING,
    651 			    "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!");
    652 			continue;
    653 		}
    654 		if ((base = strend(fname, cp->suffix)) == NULL)
    655 			continue;
    656 		if (cp->types == NULL || cp->disable == NULL ||
    657 		    cp->command == NULL)
    658 			continue;
    659 					/* Is it enabled? */
    660 		if (strcmp(cp->disable, ".") != 0 &&
    661 		    stat(cp->disable, &st) == 0)
    662 				continue;
    663 					/* Does the base exist? */
    664 		if (stat(base, &st) < 0)
    665 			continue;
    666 					/* Is the file type ok */
    667 		if (!filetypematch(cp->types, st.st_mode))
    668 			continue;
    669 		break;			/* "We have a winner!" */
    670 	}
    671 
    672 	/* If we got through the list, no conversion */
    673 	if (cp == NULL)
    674 		goto cleanup_do_conv;
    675 
    676 	/* Split up command into an argv */
    677 	if ((sl = sl_init()) == NULL)
    678 		goto cleanup_do_conv;
    679 	cmd = xstrdup(cp->command);
    680 	p = cmd;
    681 	while (p) {
    682 		NEXTWORD(p, lp);
    683 		if (strcmp(lp, "%s") == 0)
    684 			lp = base;
    685 		if (sl_add(sl, xstrdup(lp)) == -1)
    686 			goto cleanup_do_conv;
    687 	}
    688 
    689 	if (sl_add(sl, NULL) == -1)
    690 		goto cleanup_do_conv;
    691 	argv = sl->sl_str;
    692 	free(cmd);
    693 	free(sl);
    694 	return(argv);
    695 
    696  cleanup_do_conv:
    697 	if (sl)
    698 		sl_free(sl, 1);
    699 	free(cmd);
    700 	errno = o_errno;
    701 	return(NULL);
    702 }
    703 
    704 /*
    705  * Convert the string `arg' to an int, which may have an optional SI suffix
    706  * (`b', `k', `m', `g'). Returns the number for success, -1 otherwise.
    707  */
    708 int
    709 strsuftoi(arg)
    710 	const char *arg;
    711 {
    712 	char *cp;
    713 	long val;
    714 
    715 	if (!isdigit((unsigned char)arg[0]))
    716 		return (-1);
    717 
    718 	val = strtol(arg, &cp, 10);
    719 	if (cp != NULL) {
    720 		if (cp[0] != '\0' && cp[1] != '\0')
    721 			 return (-1);
    722 		switch (tolower((unsigned char)cp[0])) {
    723 		case '\0':
    724 		case 'b':
    725 			break;
    726 		case 'k':
    727 			val <<= 10;
    728 			break;
    729 		case 'm':
    730 			val <<= 20;
    731 			break;
    732 		case 'g':
    733 			val <<= 30;
    734 			break;
    735 		default:
    736 			return (-1);
    737 		}
    738 	}
    739 	if (val < 0 || val > INT_MAX)
    740 		return (-1);
    741 
    742 	return (val);
    743 }
    744 
    745 void
    746 count_users()
    747 {
    748 	char	fn[MAXPATHLEN];
    749 	int	fd, i, last;
    750 	size_t	count;
    751 	pid_t  *pids, mypid;
    752 	struct stat sb;
    753 
    754 	(void)strlcpy(fn, _PATH_CLASSPIDS, sizeof(fn));
    755 	(void)strlcat(fn, curclass.classname, sizeof(fn));
    756 	pids = NULL;
    757 	connections = 1;
    758 
    759 	if ((fd = open(fn, O_RDWR | O_CREAT | O_EXLOCK, 0600)) == -1)
    760 		return;
    761 	if (fstat(fd, &sb) == -1)
    762 		goto cleanup_count;
    763 	if ((pids = malloc(sb.st_size + sizeof(pid_t))) == NULL)
    764 		goto cleanup_count;
    765 	count = read(fd, pids, sb.st_size);
    766 	if (count < 0 || count != sb.st_size)
    767 		goto cleanup_count;
    768 	count /= sizeof(pid_t);
    769 	mypid = getpid();
    770 	last = 0;
    771 	for (i = 0; i < count; i++) {
    772 		if (pids[i] == 0)
    773 			continue;
    774 		if (kill(pids[i], 0) == -1 && errno != EPERM) {
    775 			if (mypid != 0) {
    776 				pids[i] = mypid;
    777 				mypid = 0;
    778 				last = i;
    779 			}
    780 		} else {
    781 			connections++;
    782 			last = i;
    783 		}
    784 	}
    785 	if (mypid != 0) {
    786 		if (pids[last] != 0)
    787 			last++;
    788 		pids[last] = mypid;
    789 	}
    790 	count = (last + 1) * sizeof(pid_t);
    791 	if (lseek(fd, 0, SEEK_SET) == -1)
    792 		goto cleanup_count;
    793 	if (write(fd, pids, count) == -1)
    794 		goto cleanup_count;
    795 	(void)ftruncate(fd, count);
    796 
    797  cleanup_count:
    798 	(void)flock(fd, LOCK_UN);
    799 	close(fd);
    800 	REASSIGN(pids, NULL);
    801 }
    802