Home | History | Annotate | Line # | Download | only in ftpd
conf.c revision 1.26
      1 /*	$NetBSD: conf.c,v 1.26 2000/01/09 10:08:45 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.26 2000/01/09 10:08:45 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.rateget =	0;
    105 	curclass.rateput =	0;
    106 	curclass.timeout =	900;		/* 15 minutes */
    107 	curclass.umask =	027;
    108 	curclass.upload =	1;
    109 
    110 }
    111 
    112 /*
    113  * Parse the configuration file, looking for the named class, and
    114  * define curclass to contain the appropriate settings.
    115  */
    116 void
    117 parse_conf(findclass)
    118 	char *findclass;
    119 {
    120 	FILE		*f;
    121 	char		*buf, *p;
    122 	size_t		 len;
    123 	int		 none, match, rate;
    124 	char		*endp;
    125 	char		*class, *word, *arg, *template;
    126 	const char	*infile;
    127 	size_t		 line;
    128 	unsigned int	 timeout;
    129 	struct ftpconv	*conv, *cnext;
    130 
    131 	init_curclass();
    132 	REASSIGN(curclass.classname, xstrdup(findclass));
    133 	if (strcasecmp(findclass, "guest") == 0) {
    134 		curclass.modify = 0;
    135 		curclass.umask = 0707;
    136 	}
    137 
    138 	infile = conffilename(_PATH_FTPDCONF);
    139 	if ((f = fopen(infile, "r")) == NULL)
    140 		return;
    141 
    142 	line = 0;
    143 	template = NULL;
    144 	for (;
    145 	    (buf = fparseln(f, &len, &line, NULL, FPARSELN_UNESCCOMM |
    146 	    		FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL;
    147 	    free(buf)) {
    148 		none = match = 0;
    149 		p = buf;
    150 		if (len < 1)
    151 			continue;
    152 		if (p[len - 1] == '\n')
    153 			p[--len] = '\0';
    154 		if (EMPTYSTR(p))
    155 			continue;
    156 
    157 		NEXTWORD(p, word);
    158 		NEXTWORD(p, class);
    159 		NEXTWORD(p, arg);
    160 		if (EMPTYSTR(word) || EMPTYSTR(class))
    161 			continue;
    162 		if (strcasecmp(class, "none") == 0)
    163 			none = 1;
    164 		if ((strcasecmp(class, findclass) != 0 &&
    165 		    (template != NULL && strcasecmp(class, template) != 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,
    265 			    EMPTYSTR(p) ? NULL : xstrdup(p));
    266 
    267 		} else if (strcasecmp(word, "maxtimeout") == 0) {
    268 			if (none || EMPTYSTR(arg))
    269 				continue;
    270 			timeout = (unsigned int)strtoul(arg, &endp, 10);
    271 			if (*endp != 0) {
    272 				syslog(LOG_WARNING,
    273 				    "%s line %d: invalid maxtimeout %s",
    274 				    infile, (int)line, arg);
    275 				continue;
    276 			}
    277 			if (timeout < 30) {
    278 				syslog(LOG_WARNING,
    279 				    "%s line %d: maxtimeout %d < 30 seconds",
    280 				    infile, (int)line, timeout);
    281 				continue;
    282 			}
    283 			if (timeout < curclass.timeout) {
    284 				syslog(LOG_WARNING,
    285 				    "%s line %d: maxtimeout %d < timeout (%d)",
    286 				    infile, (int)line, timeout,
    287 				    curclass.timeout);
    288 				continue;
    289 			}
    290 			curclass.maxtimeout = timeout;
    291 
    292 		} else if (strcasecmp(word, "modify") == 0) {
    293 			if (none ||
    294 			    (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))
    295 				curclass.modify = 0;
    296 			else
    297 				curclass.modify = 1;
    298 
    299 		} else if (strcasecmp(word, "motd") == 0) {
    300 			if (none || EMPTYSTR(arg))
    301 				arg = NULL;
    302 			else
    303 				arg = xstrdup(arg);
    304 			REASSIGN(curclass.motd, arg);
    305 
    306 
    307 		} else if (strcasecmp(word, "notify") == 0) {
    308 			if (none || EMPTYSTR(arg))
    309 				arg = NULL;
    310 			else
    311 				arg = xstrdup(arg);
    312 			REASSIGN(curclass.notify, arg);
    313 
    314 		} else if (strcasecmp(word, "passive") == 0) {
    315 			if (none ||
    316 			    (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))
    317 				curclass.passive = 0;
    318 			else
    319 				curclass.passive = 1;
    320 
    321 		} else if (strcasecmp(word, "rateget") == 0) {
    322 			if (none || EMPTYSTR(arg))
    323 				continue;
    324 			rate = strsuftoi(arg);
    325 			if (rate == -1) {
    326 				syslog(LOG_WARNING,
    327 				    "%s line %d: invalid rateget %s",
    328 				    infile, (int)line, arg);
    329 				continue;
    330 			}
    331 			curclass.maxrateget = rate;
    332 			curclass.rateget = rate;
    333 
    334 		} else if (strcasecmp(word, "rateput") == 0) {
    335 			if (none || EMPTYSTR(arg))
    336 				continue;
    337 			rate = strsuftoi(arg);
    338 			if (rate == -1) {
    339 				syslog(LOG_WARNING,
    340 				    "%s line %d: invalid rateput %s",
    341 				    infile, (int)line, arg);
    342 				continue;
    343 			}
    344 			curclass.maxrateput = rate;
    345 			curclass.rateput = rate;
    346 
    347 		} else if (strcasecmp(word, "timeout") == 0) {
    348 			if (none || EMPTYSTR(arg))
    349 				continue;
    350 			timeout = (unsigned int)strtoul(arg, &endp, 10);
    351 			if (*endp != 0) {
    352 				syslog(LOG_WARNING,
    353 				    "%s line %d: invalid timeout %s",
    354 				    infile, (int)line, arg);
    355 				continue;
    356 			}
    357 			if (timeout < 30) {
    358 				syslog(LOG_WARNING,
    359 				    "%s line %d: timeout %d < 30 seconds",
    360 				    infile, (int)line, timeout);
    361 				continue;
    362 			}
    363 			if (timeout > curclass.maxtimeout) {
    364 				syslog(LOG_WARNING,
    365 				    "%s line %d: timeout %d > maxtimeout (%d)",
    366 				    infile, (int)line, timeout,
    367 				    curclass.maxtimeout);
    368 				continue;
    369 			}
    370 			curclass.timeout = timeout;
    371 
    372 		} else if (strcasecmp(word, "template") == 0) {
    373 			if (none)
    374 				continue;
    375 			REASSIGN(template, EMPTYSTR(arg) ? NULL : xstrdup(arg));
    376 
    377 		} else if (strcasecmp(word, "umask") == 0) {
    378 			mode_t umask;
    379 
    380 			if (none || EMPTYSTR(arg))
    381 				continue;
    382 			umask = (mode_t)strtoul(arg, &endp, 8);
    383 			if (*endp != 0 || umask > 0777) {
    384 				syslog(LOG_WARNING,
    385 				    "%s line %d: invalid umask %s",
    386 				    infile, (int)line, arg);
    387 				continue;
    388 			}
    389 			curclass.umask = umask;
    390 
    391 		} else if (strcasecmp(word, "upload") == 0) {
    392 			if (none ||
    393 			    (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0)) {
    394 				curclass.modify = 0;
    395 				curclass.upload = 0;
    396 			} else
    397 				curclass.upload = 1;
    398 
    399 		} else {
    400 			syslog(LOG_WARNING,
    401 			    "%s line %d: unknown directive '%s'",
    402 			    infile, (int)line, word);
    403 			continue;
    404 		}
    405 	}
    406 	REASSIGN(template, NULL);
    407 	fclose(f);
    408 }
    409 
    410 /*
    411  * Show file listed in curclass.display first time in, and list all the
    412  * files named in curclass.notify in the current directory.  Send back
    413  * responses with the prefix `code' + "-".
    414  */
    415 void
    416 show_chdir_messages(code)
    417 	int	code;
    418 {
    419 	static StringList *slist = NULL;
    420 
    421 	struct stat st;
    422 	struct tm *t;
    423 	glob_t	 gl;
    424 	time_t	 now, then;
    425 	int	 age;
    426 	char	 cwd[MAXPATHLEN];
    427 	char	*cp, **rlist;
    428 
    429 		/* Setup list for directory cache */
    430 	if (slist == NULL)
    431 		slist = sl_init();
    432 	if (slist == NULL) {
    433 		syslog(LOG_WARNING, "can't allocate memory for stringlist");
    434 		return;
    435 	}
    436 
    437 		/* Check if this directory has already been visited */
    438 	if (getcwd(cwd, sizeof(cwd) - 1) == NULL) {
    439 		syslog(LOG_WARNING, "can't getcwd: %s", strerror(errno));
    440 		return;
    441 	}
    442 	if (sl_find(slist, cwd) != NULL)
    443 		return;
    444 
    445 	cp = xstrdup(cwd);
    446 	if (sl_add(slist, cp) == -1)
    447 		syslog(LOG_WARNING, "can't add `%s' to stringlist", cp);
    448 
    449 		/* First check for a display file */
    450 	(void)format_file(curclass.display, code);
    451 
    452 		/* Now see if there are any notify files */
    453 	if (EMPTYSTR(curclass.notify))
    454 		return;
    455 
    456 	if (glob(curclass.notify, 0, NULL, &gl) != 0 || gl.gl_matchc == 0)
    457 		return;
    458 	time(&now);
    459 	for (rlist = gl.gl_pathv; *rlist != NULL; rlist++) {
    460 		if (stat(*rlist, &st) != 0)
    461 			continue;
    462 		if (!S_ISREG(st.st_mode))
    463 			continue;
    464 		then = st.st_mtime;
    465 		if (code != 0) {
    466 			lreply(code, "");
    467 			code = 0;
    468 		}
    469 		lreply(code, "Please read the file %s", *rlist);
    470 		t = localtime(&now);
    471 		age = 365 * t->tm_year + t->tm_yday;
    472 		t = localtime(&then);
    473 		age -= 365 * t->tm_year + t->tm_yday;
    474 		lreply(code, "  it was last modified on %.24s - %d day%s ago",
    475 		    ctime(&then), age, PLURAL(age));
    476 	}
    477 	globfree(&gl);
    478 }
    479 
    480 int
    481 format_file(file, code)
    482 	const char *file;
    483 	int code;
    484 {
    485 	FILE   *f;
    486 	char   *buf, *p, *cwd;
    487 	size_t	len;
    488 	off_t	b;
    489 	time_t	now;
    490 
    491 #define PUTC(x)	putchar(x), b++
    492 
    493 	if (EMPTYSTR(file))
    494 		return(0);
    495 	if ((f = fopen(file, "r")) == NULL)
    496 		return (0);
    497 	lreply(code, "");
    498 
    499 	b = 0;
    500 	for (;
    501 	    (buf = fparseln(f, &len, NULL, "\0\0\0", 0)) != NULL; free(buf)) {
    502 		if (len > 0)
    503 			if (buf[len - 1] == '\n')
    504 				buf[--len] = '\0';
    505 		b += printf("    ");
    506 
    507 		for (p = buf; *p; p++) {
    508 			if (*p == '%') {
    509 				p++;
    510 				switch (*p) {
    511 
    512 				case 'c':
    513 					b += printf("%s",
    514 					    curclass.classname ?
    515 					    curclass.classname : "<unknown>");
    516 					break;
    517 
    518 				case 'C':
    519 					if (getcwd(cwd, sizeof(cwd)-1) == NULL){
    520 						syslog(LOG_WARNING,
    521 						    "can't getcwd: %s",
    522 						    strerror(errno));
    523 						continue;
    524 					}
    525 					b += printf("%s", cwd);
    526 					break;
    527 
    528 				case 'E':
    529 						/* XXXX email address */
    530 					break;
    531 
    532 				case 'L':
    533 					b += printf("%s", hostname);
    534 					break;
    535 
    536 				case 'M':
    537 					if (curclass.limit == -1)
    538 						b += printf("unlimited");
    539 					else
    540 						b += printf("%d",
    541 						    curclass.limit);
    542 					break;
    543 
    544 				case 'N':
    545 					if (connections > 0)
    546 						b += printf("%d", connections);
    547 					break;
    548 
    549 				case 'R':
    550 					b += printf("%s", remotehost);
    551 					break;
    552 
    553 				case 'T':
    554 					now = time(NULL);
    555 					b += printf("%.24s", ctime(&now));
    556 					break;
    557 
    558 				case 'U':
    559 					b += printf("%s",
    560 					    pw ? pw->pw_name : "<unknown>");
    561 					break;
    562 
    563 				case '%':
    564 					PUTC('%');
    565 					break;
    566 
    567 				}
    568 			} else {
    569 				PUTC(*p);
    570 			}
    571 		}
    572 		PUTC('\r');
    573 		PUTC('\n');
    574 	}
    575 
    576 	total_bytes += b;
    577 	total_bytes_out += b;
    578 	(void)fflush(stdout);
    579 	(void)fclose(f);
    580 	return (1);
    581 }
    582 
    583 /*
    584  * Find s2 at the end of s1.  If found, return a string up to (but
    585  * not including) s2, otherwise returns NULL.
    586  */
    587 static char *
    588 strend(s1, s2)
    589 	const char *s1;
    590 	char *s2;
    591 {
    592 	static	char buf[MAXPATHLEN];
    593 
    594 	char	*start;
    595 	size_t	l1, l2;
    596 
    597 	l1 = strlen(s1);
    598 	l2 = strlen(s2);
    599 
    600 	if (l2 >= l1)
    601 		return(NULL);
    602 
    603 	strlcpy(buf, s1, sizeof(buf));
    604 	start = buf + (l1 - l2);
    605 
    606 	if (strcmp(start, s2) == 0) {
    607 		*start = '\0';
    608 		return(buf);
    609 	} else
    610 		return(NULL);
    611 }
    612 
    613 static int
    614 filetypematch(types, mode)
    615 	char	*types;
    616 	int	mode;
    617 {
    618 	for ( ; types[0] != '\0'; types++)
    619 		switch (*types) {
    620 		  case 'd':
    621 			if (S_ISDIR(mode))
    622 				return(1);
    623 			break;
    624 		  case 'f':
    625 			if (S_ISREG(mode))
    626 				return(1);
    627 			break;
    628 		}
    629 	return(0);
    630 }
    631 
    632 /*
    633  * Look for a conversion.  If we succeed, return a pointer to the
    634  * command to execute for the conversion.
    635  *
    636  * The command is stored in a static array so there's no memory
    637  * leak problems, and not too much to change in ftpd.c.  This
    638  * routine doesn't need to be re-entrant unless we start using a
    639  * multi-threaded ftpd, and that's not likely for a while...
    640  */
    641 char **
    642 do_conversion(fname)
    643 	const char *fname;
    644 {
    645 	struct ftpconv	*cp;
    646 	struct stat	 st;
    647 	int		 o_errno;
    648 	char		*base = NULL;
    649 	char		*cmd, *p, *lp, **argv;
    650 	StringList	*sl;
    651 
    652 	o_errno = errno;
    653 	sl = NULL;
    654 	cmd = NULL;
    655 	for (cp = curclass.conversions; cp != NULL; cp = cp->next) {
    656 		if (cp->suffix == NULL) {
    657 			syslog(LOG_WARNING,
    658 			    "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!");
    659 			continue;
    660 		}
    661 		if ((base = strend(fname, cp->suffix)) == NULL)
    662 			continue;
    663 		if (cp->types == NULL || cp->disable == NULL ||
    664 		    cp->command == NULL)
    665 			continue;
    666 					/* Is it enabled? */
    667 		if (strcmp(cp->disable, ".") != 0 &&
    668 		    stat(cp->disable, &st) == 0)
    669 				continue;
    670 					/* Does the base exist? */
    671 		if (stat(base, &st) < 0)
    672 			continue;
    673 					/* Is the file type ok */
    674 		if (!filetypematch(cp->types, st.st_mode))
    675 			continue;
    676 		break;			/* "We have a winner!" */
    677 	}
    678 
    679 	/* If we got through the list, no conversion */
    680 	if (cp == NULL)
    681 		goto cleanup_do_conv;
    682 
    683 	/* Split up command into an argv */
    684 	if ((sl = sl_init()) == NULL)
    685 		goto cleanup_do_conv;
    686 	cmd = xstrdup(cp->command);
    687 	p = cmd;
    688 	while (p) {
    689 		NEXTWORD(p, lp);
    690 		if (strcmp(lp, "%s") == 0)
    691 			lp = base;
    692 		if (sl_add(sl, xstrdup(lp)) == -1)
    693 			goto cleanup_do_conv;
    694 	}
    695 
    696 	if (sl_add(sl, NULL) == -1)
    697 		goto cleanup_do_conv;
    698 	argv = sl->sl_str;
    699 	free(cmd);
    700 	free(sl);
    701 	return(argv);
    702 
    703  cleanup_do_conv:
    704 	if (sl)
    705 		sl_free(sl, 1);
    706 	free(cmd);
    707 	errno = o_errno;
    708 	return(NULL);
    709 }
    710 
    711 /*
    712  * Convert the string `arg' to an int, which may have an optional SI suffix
    713  * (`b', `k', `m', `g'). Returns the number for success, -1 otherwise.
    714  */
    715 int
    716 strsuftoi(arg)
    717 	const char *arg;
    718 {
    719 	char *cp;
    720 	long val;
    721 
    722 	if (!isdigit((unsigned char)arg[0]))
    723 		return (-1);
    724 
    725 	val = strtol(arg, &cp, 10);
    726 	if (cp != NULL) {
    727 		if (cp[0] != '\0' && cp[1] != '\0')
    728 			 return (-1);
    729 		switch (tolower((unsigned char)cp[0])) {
    730 		case '\0':
    731 		case 'b':
    732 			break;
    733 		case 'k':
    734 			val <<= 10;
    735 			break;
    736 		case 'm':
    737 			val <<= 20;
    738 			break;
    739 		case 'g':
    740 			val <<= 30;
    741 			break;
    742 		default:
    743 			return (-1);
    744 		}
    745 	}
    746 	if (val < 0 || val > INT_MAX)
    747 		return (-1);
    748 
    749 	return (val);
    750 }
    751 
    752 /*
    753  * Count the number of current connections, reading from
    754  *	/var/run/ftpd.pids-<class>
    755  * Does a kill -0 on each pid in that file, and only counts
    756  * processes that exist (or frees the slot if it doesn't).
    757  * Adds getpid() to the first free slot. Truncates the file
    758  * if possible.
    759  */
    760 void
    761 count_users()
    762 {
    763 	char	fn[MAXPATHLEN];
    764 	int	fd, i, last;
    765 	size_t	count;
    766 	pid_t  *pids, mypid;
    767 	struct stat sb;
    768 
    769 	(void)strlcpy(fn, _PATH_CLASSPIDS, sizeof(fn));
    770 	(void)strlcat(fn, curclass.classname, sizeof(fn));
    771 	pids = NULL;
    772 	connections = 1;
    773 
    774 	if ((fd = open(fn, O_RDWR | O_CREAT | O_EXLOCK, 0600)) == -1)
    775 		return;
    776 	if (fstat(fd, &sb) == -1)
    777 		goto cleanup_count;
    778 	if ((pids = malloc(sb.st_size + sizeof(pid_t))) == NULL)
    779 		goto cleanup_count;
    780 	count = read(fd, pids, sb.st_size);
    781 	if (count < 0 || count != sb.st_size)
    782 		goto cleanup_count;
    783 	count /= sizeof(pid_t);
    784 	mypid = getpid();
    785 	last = 0;
    786 	for (i = 0; i < count; i++) {
    787 		if (pids[i] == 0)
    788 			continue;
    789 		if (kill(pids[i], 0) == -1 && errno != EPERM) {
    790 			if (mypid != 0) {
    791 				pids[i] = mypid;
    792 				mypid = 0;
    793 				last = i;
    794 			}
    795 		} else {
    796 			connections++;
    797 			last = i;
    798 		}
    799 	}
    800 	if (mypid != 0) {
    801 		if (pids[last] != 0)
    802 			last++;
    803 		pids[last] = mypid;
    804 	}
    805 	count = (last + 1) * sizeof(pid_t);
    806 	if (lseek(fd, 0, SEEK_SET) == -1)
    807 		goto cleanup_count;
    808 	if (write(fd, pids, count) == -1)
    809 		goto cleanup_count;
    810 	(void)ftruncate(fd, count);
    811 
    812  cleanup_count:
    813 	(void)flock(fd, LOCK_UN);
    814 	close(fd);
    815 	REASSIGN(pids, NULL);
    816 }
    817