xref: /netbsd-src/libexec/ftpd/conf.c (revision cb93dc6375d2670db321b0eec7fc025a29cfbe5b)
1*cb93dc63Sshm /*	$NetBSD: conf.c,v 1.65 2023/09/29 14:49:03 shm Exp $	*/
231547ec6Slukem 
331547ec6Slukem /*-
40053962fSlukem  * Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
531547ec6Slukem  * All rights reserved.
631547ec6Slukem  *
731547ec6Slukem  * This code is derived from software contributed to The NetBSD Foundation
831547ec6Slukem  * by Simon Burge and Luke Mewburn.
931547ec6Slukem  *
1031547ec6Slukem  * Redistribution and use in source and binary forms, with or without
1131547ec6Slukem  * modification, are permitted provided that the following conditions
1231547ec6Slukem  * are met:
1331547ec6Slukem  * 1. Redistributions of source code must retain the above copyright
1431547ec6Slukem  *    notice, this list of conditions and the following disclaimer.
1531547ec6Slukem  * 2. Redistributions in binary form must reproduce the above copyright
1631547ec6Slukem  *    notice, this list of conditions and the following disclaimer in the
1731547ec6Slukem  *    documentation and/or other materials provided with the distribution.
1831547ec6Slukem  *
1931547ec6Slukem  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2031547ec6Slukem  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2131547ec6Slukem  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22a1582495Sjtc  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23a1582495Sjtc  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2431547ec6Slukem  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2531547ec6Slukem  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2631547ec6Slukem  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2731547ec6Slukem  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2831547ec6Slukem  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2931547ec6Slukem  * POSSIBILITY OF SUCH DAMAGE.
3031547ec6Slukem  */
3131547ec6Slukem 
322424c4f9Schristos #include <sys/cdefs.h>
3331547ec6Slukem #ifndef lint
34*cb93dc63Sshm __RCSID("$NetBSD: conf.c,v 1.65 2023/09/29 14:49:03 shm Exp $");
3531547ec6Slukem #endif /* not lint */
3631547ec6Slukem 
3731547ec6Slukem #include <sys/types.h>
3831547ec6Slukem #include <sys/param.h>
3950150481Slukem #include <sys/socket.h>
4031547ec6Slukem #include <sys/stat.h>
4131547ec6Slukem 
4221d03121Slukem #include <ctype.h>
4331547ec6Slukem #include <errno.h>
4416e88612Slukem #include <fcntl.h>
45bef47ea9Schristos #include <pwd.h>
4631547ec6Slukem #include <glob.h>
4750150481Slukem #include <netdb.h>
4816e88612Slukem #include <signal.h>
4931547ec6Slukem #include <stdio.h>
502424c4f9Schristos #include <stdlib.h>
5131547ec6Slukem #include <string.h>
5231547ec6Slukem #include <stringlist.h>
5331547ec6Slukem #include <syslog.h>
54397e2cfcSlukem #include <time.h>
55397e2cfcSlukem #include <unistd.h>
56397e2cfcSlukem #include <util.h>
5731547ec6Slukem 
58b2f939acSexplorer #ifdef KERBEROS5
59cee9ac24Schristos #include <krb5/krb5.h>
60b2f939acSexplorer #endif
61b2f939acSexplorer 
6231547ec6Slukem #include "extern.h"
6331547ec6Slukem #include "pathnames.h"
6431547ec6Slukem 
65dcc88422Slukem static char *strend(const char *, char *);
66dcc88422Slukem static int filetypematch(char *, int);
6731547ec6Slukem 
680e5bdd51Slukem 
6950150481Slukem 		/* class defaults */
7050150481Slukem #define DEFAULT_LIMIT		-1		/* unlimited connections */
7150150481Slukem #define DEFAULT_MAXFILESIZE	-1		/* unlimited file size */
7250150481Slukem #define DEFAULT_MAXTIMEOUT	7200		/* 2 hours */
7350150481Slukem #define DEFAULT_TIMEOUT		900		/* 15 minutes */
747ea2be42Slukem #define DEFAULT_UMASK		027		/* rw-r----- */
7550150481Slukem 
7631547ec6Slukem /*
7716e88612Slukem  * Initialise curclass to an `empty' state
7816e88612Slukem  */
7916e88612Slukem void
init_curclass(void)80dcc88422Slukem init_curclass(void)
8116e88612Slukem {
8216e88612Slukem 	struct ftpconv	*conv, *cnext;
8316e88612Slukem 
8416e88612Slukem 	for (conv = curclass.conversions; conv != NULL; conv = cnext) {
8516e88612Slukem 		REASSIGN(conv->suffix, NULL);
8616e88612Slukem 		REASSIGN(conv->types, NULL);
8716e88612Slukem 		REASSIGN(conv->disable, NULL);
8816e88612Slukem 		REASSIGN(conv->command, NULL);
8916e88612Slukem 		cnext = conv->next;
9016e88612Slukem 		free(conv);
9116e88612Slukem 	}
92c8493e94Slukem 
9350150481Slukem 	memset((char *)&curclass.advertise, 0, sizeof(curclass.advertise));
9450150481Slukem 	curclass.advertise.su_len = 0;		/* `not used' */
95e3a2c5ccSlukem 	REASSIGN(curclass.chroot, NULL);
9616e88612Slukem 	REASSIGN(curclass.classname, NULL);
9716e88612Slukem 	curclass.conversions =	NULL;
9816e88612Slukem 	REASSIGN(curclass.display, NULL);
99e3a2c5ccSlukem 	REASSIGN(curclass.homedir, NULL);
10050150481Slukem 	curclass.limit =	DEFAULT_LIMIT;
10116e88612Slukem 	REASSIGN(curclass.limitfile, NULL);
10250150481Slukem 	curclass.maxfilesize =	DEFAULT_MAXFILESIZE;
10316e88612Slukem 	curclass.maxrateget =	0;
10416e88612Slukem 	curclass.maxrateput =	0;
10550150481Slukem 	curclass.maxtimeout =	DEFAULT_MAXTIMEOUT;
1061edbda9aSchristos 	REASSIGN(curclass.motd, ftpd_strdup(_NAME_FTPLOGINMESG));
10716e88612Slukem 	REASSIGN(curclass.notify, NULL);
108c8493e94Slukem 	curclass.portmin =	0;
109c8493e94Slukem 	curclass.portmax =	0;
11016e88612Slukem 	curclass.rateget =	0;
11116e88612Slukem 	curclass.rateput =	0;
11250150481Slukem 	curclass.timeout =	DEFAULT_TIMEOUT;
113e3a2c5ccSlukem 	    /* curclass.type is set elsewhere */
11450150481Slukem 	curclass.umask =	DEFAULT_UMASK;
115260e9f55Senami 	curclass.mmapsize =	0;
116260e9f55Senami 	curclass.readsize =	0;
117260e9f55Senami 	curclass.writesize =	0;
118260e9f55Senami 	curclass.sendbufsize =	0;
119260e9f55Senami 	curclass.sendlowat =	0;
120999fd3d6Slukem 
121999fd3d6Slukem 	CURCLASS_FLAGS_SET(checkportcmd);
122c31e16f7Slukem 	CURCLASS_FLAGS_CLR(denyquick);
123f0b52873Sginsbach 	CURCLASS_FLAGS_CLR(hidesymlinks);
124999fd3d6Slukem 	CURCLASS_FLAGS_SET(modify);
125999fd3d6Slukem 	CURCLASS_FLAGS_SET(passive);
126c31e16f7Slukem 	CURCLASS_FLAGS_CLR(private);
127999fd3d6Slukem 	CURCLASS_FLAGS_CLR(sanenames);
128999fd3d6Slukem 	CURCLASS_FLAGS_SET(upload);
12916e88612Slukem }
13016e88612Slukem 
13116e88612Slukem /*
13231547ec6Slukem  * Parse the configuration file, looking for the named class, and
13331547ec6Slukem  * define curclass to contain the appropriate settings.
13431547ec6Slukem  */
13531547ec6Slukem void
parse_conf(const char * findclass)136dcc88422Slukem parse_conf(const char *findclass)
13731547ec6Slukem {
13831547ec6Slukem 	FILE		*f;
13931547ec6Slukem 	char		*buf, *p;
14031547ec6Slukem 	size_t		 len;
141999fd3d6Slukem 	LLT		 llval;
142999fd3d6Slukem 	int		 none, match;
143ee2d1afbSlukem 	char		*endp, errbuf[100];
1447e803788Slukem 	char		*class, *word, *arg, *template;
14531547ec6Slukem 	const char	*infile;
146397e2cfcSlukem 	size_t		 line;
14731547ec6Slukem 	struct ftpconv	*conv, *cnext;
14831547ec6Slukem 
14916e88612Slukem 	init_curclass();
1501edbda9aSchristos 	REASSIGN(curclass.classname, ftpd_strdup(findclass));
15150150481Slukem 			/* set more guest defaults */
15231547ec6Slukem 	if (strcasecmp(findclass, "guest") == 0) {
153999fd3d6Slukem 		CURCLASS_FLAGS_CLR(modify);
15483955f6aSlukem 		curclass.umask = 0707;
15531547ec6Slukem 	}
15631547ec6Slukem 
157d465dbd4Schristos 	infile = conffilename(_NAME_FTPDCONF);
15831547ec6Slukem 	if ((f = fopen(infile, "r")) == NULL)
15931547ec6Slukem 		return;
16031547ec6Slukem 
16131547ec6Slukem 	line = 0;
1627e803788Slukem 	template = NULL;
163397e2cfcSlukem 	for (;
164397e2cfcSlukem 	    (buf = fparseln(f, &len, &line, NULL, FPARSELN_UNESCCOMM |
165397e2cfcSlukem 			    FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL;
166397e2cfcSlukem 	    free(buf)) {
16731547ec6Slukem 		none = match = 0;
168397e2cfcSlukem 		p = buf;
1690bba8ce3Slukem 		if (len < 1)
1700bba8ce3Slukem 			continue;
171397e2cfcSlukem 		if (p[len - 1] == '\n')
172397e2cfcSlukem 			p[--len] = '\0';
173397e2cfcSlukem 		if (EMPTYSTR(p))
17431547ec6Slukem 			continue;
17531547ec6Slukem 
176397e2cfcSlukem 		NEXTWORD(p, word);
177397e2cfcSlukem 		NEXTWORD(p, class);
178397e2cfcSlukem 		NEXTWORD(p, arg);
17931547ec6Slukem 		if (EMPTYSTR(word) || EMPTYSTR(class))
18031547ec6Slukem 			continue;
18131547ec6Slukem 		if (strcasecmp(class, "none") == 0)
18231547ec6Slukem 			none = 1;
183559037c2Slukem 		if (! (strcasecmp(class, findclass) == 0 ||
184559037c2Slukem 		       (template != NULL && strcasecmp(class, template) == 0) ||
185559037c2Slukem 		       none ||
186559037c2Slukem 		       strcasecmp(class, "all") == 0) )
18731547ec6Slukem 			continue;
18831547ec6Slukem 
189ee2d1afbSlukem #define CONF_FLAG(Field)						\
190999fd3d6Slukem 	do {								\
191999fd3d6Slukem 		if (none ||						\
192999fd3d6Slukem 		    (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))	\
193ee2d1afbSlukem 			CURCLASS_FLAGS_CLR(Field);			\
194999fd3d6Slukem 		else							\
195ee2d1afbSlukem 			CURCLASS_FLAGS_SET(Field);			\
196999fd3d6Slukem 	} while (0)
197999fd3d6Slukem 
198ee2d1afbSlukem #define CONF_STRING(Field)						\
199999fd3d6Slukem 	do {								\
200999fd3d6Slukem 		if (none || EMPTYSTR(arg))				\
201999fd3d6Slukem 			arg = NULL;					\
202999fd3d6Slukem 		else							\
2031edbda9aSchristos 			arg = ftpd_strdup(arg);				\
204ee2d1afbSlukem 		REASSIGN(curclass.Field, arg);				\
205999fd3d6Slukem 	} while (0)
206999fd3d6Slukem 
207ee2d1afbSlukem #define CONF_LL(Field,Arg,Min,Max)					\
208ec6387d5Saidan 	do {								\
209ee2d1afbSlukem 		if (none || EMPTYSTR(Arg))				\
210ee2d1afbSlukem 			goto nextline;					\
211ee2d1afbSlukem 		llval = strsuftollx(#Field, Arg, Min, Max,		\
212ee2d1afbSlukem 		    errbuf, sizeof(errbuf));				\
213ee2d1afbSlukem 		if (errbuf[0]) {					\
214ee2d1afbSlukem 			syslog(LOG_WARNING, "%s line %d: %s",		\
215ee2d1afbSlukem 			    infile, (int)line, errbuf);			\
216ee2d1afbSlukem 			goto nextline;					\
217ec6387d5Saidan 		}							\
218ee2d1afbSlukem 		curclass.Field = llval;					\
219ec6387d5Saidan 	} while(0)
22050150481Slukem 
22150150481Slukem 		if (0)  {
22250150481Slukem 			/* no-op */
22350150481Slukem 
224afa0abb8Slukem 		} else if ((strcasecmp(word, "advertise") == 0)
225afa0abb8Slukem 			|| (strcasecmp(word, "advertize") == 0)) {
22650150481Slukem 			struct addrinfo	hints, *res;
22750150481Slukem 			int		error;
22850150481Slukem 
22950150481Slukem 			memset((char *)&curclass.advertise, 0,
23050150481Slukem 			    sizeof(curclass.advertise));
23150150481Slukem 			curclass.advertise.su_len = 0;
23250150481Slukem 			if (none || EMPTYSTR(arg))
23350150481Slukem 				continue;
23450150481Slukem 			res = NULL;
23550150481Slukem 			memset(&hints, 0, sizeof(hints));
23650150481Slukem 					/*
23750150481Slukem 					 * only get addresses of the family
23850150481Slukem 					 * that we're listening on
23950150481Slukem 					 */
24050150481Slukem 			hints.ai_family = ctrl_addr.su_family;
24150150481Slukem 			hints.ai_socktype = SOCK_STREAM;
24250150481Slukem 			error = getaddrinfo(arg, "0", &hints, &res);
24350150481Slukem 			if (error) {
24450150481Slukem 				syslog(LOG_WARNING, "%s line %d: %s",
24550150481Slukem 				    infile, (int)line, gai_strerror(error));
24650150481Slukem  advertiseparsefail:
24750150481Slukem 				if (res)
24850150481Slukem 					freeaddrinfo(res);
24950150481Slukem 				continue;
25050150481Slukem 			}
25150150481Slukem 			if (res->ai_next) {
25250150481Slukem 				syslog(LOG_WARNING,
25350150481Slukem     "%s line %d: multiple addresses returned for `%s'; please be more specific",
25450150481Slukem 				    infile, (int)line, arg);
25550150481Slukem 				goto advertiseparsefail;
25650150481Slukem 			}
25750150481Slukem 			if (sizeof(curclass.advertise) < res->ai_addrlen || (
25850150481Slukem #ifdef INET6
25950150481Slukem 			    res->ai_family != AF_INET6 &&
26050150481Slukem #endif
26150150481Slukem 			    res->ai_family != AF_INET)) {
26250150481Slukem 				syslog(LOG_WARNING,
26350150481Slukem     "%s line %d: unsupported protocol %d for `%s'",
26450150481Slukem 				    infile, (int)line, res->ai_family, arg);
26550150481Slukem 				goto advertiseparsefail;
26650150481Slukem 			}
26750150481Slukem 			memcpy(&curclass.advertise, res->ai_addr,
26850150481Slukem 			    res->ai_addrlen);
26950150481Slukem 			curclass.advertise.su_len = res->ai_addrlen;
27050150481Slukem 			freeaddrinfo(res);
27150150481Slukem 
27250150481Slukem 		} else if (strcasecmp(word, "checkportcmd") == 0) {
273999fd3d6Slukem 			CONF_FLAG(checkportcmd);
274397e2cfcSlukem 
275e3a2c5ccSlukem 		} else if (strcasecmp(word, "chroot") == 0) {
276999fd3d6Slukem 			CONF_STRING(chroot);
277e3a2c5ccSlukem 
27821d03121Slukem 		} else if (strcasecmp(word, "classtype") == 0) {
27921d03121Slukem 			if (!none && !EMPTYSTR(arg)) {
28021d03121Slukem 				if (strcasecmp(arg, "GUEST") == 0)
28121d03121Slukem 					curclass.type = CLASS_GUEST;
28221d03121Slukem 				else if (strcasecmp(arg, "CHROOT") == 0)
28321d03121Slukem 					curclass.type = CLASS_CHROOT;
28421d03121Slukem 				else if (strcasecmp(arg, "REAL") == 0)
28521d03121Slukem 					curclass.type = CLASS_REAL;
28621d03121Slukem 				else {
28721d03121Slukem 					syslog(LOG_WARNING,
28821d03121Slukem 				    "%s line %d: unknown class type `%s'",
28921d03121Slukem 					    infile, (int)line, arg);
29021d03121Slukem 					continue;
29121d03121Slukem 				}
29221d03121Slukem 			}
29321d03121Slukem 
29483955f6aSlukem 		} else if (strcasecmp(word, "conversion") == 0) {
2950bba8ce3Slukem 			char *suffix, *types, *disable, *convcmd;
2960bba8ce3Slukem 
29731547ec6Slukem 			if (EMPTYSTR(arg)) {
29831547ec6Slukem 				syslog(LOG_WARNING,
29931547ec6Slukem 				    "%s line %d: %s requires a suffix",
300397e2cfcSlukem 				    infile, (int)line, word);
30131547ec6Slukem 				continue;	/* need a suffix */
30231547ec6Slukem 			}
303397e2cfcSlukem 			NEXTWORD(p, types);
304397e2cfcSlukem 			NEXTWORD(p, disable);
305397e2cfcSlukem 			convcmd = p;
30631547ec6Slukem 			if (convcmd)
30731547ec6Slukem 				convcmd += strspn(convcmd, " \t");
3081edbda9aSchristos 			suffix = ftpd_strdup(arg);
30931547ec6Slukem 			if (none || EMPTYSTR(types) ||
31031547ec6Slukem 			    EMPTYSTR(disable) || EMPTYSTR(convcmd)) {
31131547ec6Slukem 				types = NULL;
31231547ec6Slukem 				disable = NULL;
31331547ec6Slukem 				convcmd = NULL;
31431547ec6Slukem 			} else {
3151edbda9aSchristos 				types = ftpd_strdup(types);
3161edbda9aSchristos 				disable = ftpd_strdup(disable);
3171edbda9aSchristos 				convcmd = ftpd_strdup(convcmd);
31831547ec6Slukem 			}
31931547ec6Slukem 			for (conv = curclass.conversions; conv != NULL;
32031547ec6Slukem 			    conv = conv->next) {
3210bba8ce3Slukem 				if (strcmp(conv->suffix, suffix) == 0)
32231547ec6Slukem 					break;
32331547ec6Slukem 			}
32431547ec6Slukem 			if (conv == NULL) {
32531547ec6Slukem 				conv = (struct ftpconv *)
32631547ec6Slukem 				    calloc(1, sizeof(struct ftpconv));
32731547ec6Slukem 				if (conv == NULL) {
32831547ec6Slukem 					syslog(LOG_WARNING, "can't malloc");
32931547ec6Slukem 					continue;
33031547ec6Slukem 				}
331397e2cfcSlukem 				conv->next = NULL;
332397e2cfcSlukem 				for (cnext = curclass.conversions;
333397e2cfcSlukem 				    cnext != NULL; cnext = cnext->next)
334397e2cfcSlukem 					if (cnext->next == NULL)
335397e2cfcSlukem 						break;
336397e2cfcSlukem 				if (cnext != NULL)
337397e2cfcSlukem 					cnext->next = conv;
338397e2cfcSlukem 				else
33931547ec6Slukem 					curclass.conversions = conv;
34031547ec6Slukem 			}
3410bba8ce3Slukem 			REASSIGN(conv->suffix, suffix);
34231547ec6Slukem 			REASSIGN(conv->types, types);
34331547ec6Slukem 			REASSIGN(conv->disable, disable);
34431547ec6Slukem 			REASSIGN(conv->command, convcmd);
345397e2cfcSlukem 
346c31e16f7Slukem 		} else if (strcasecmp(word, "denyquick") == 0) {
347c31e16f7Slukem 			CONF_FLAG(denyquick);
348c31e16f7Slukem 
34931547ec6Slukem 		} else if (strcasecmp(word, "display") == 0) {
350999fd3d6Slukem 			CONF_STRING(display);
351397e2cfcSlukem 
352f0b52873Sginsbach 		} else if (strcasecmp(word, "hidesymlinks") == 0) {
353f0b52873Sginsbach 			CONF_FLAG(hidesymlinks);
354f0b52873Sginsbach 
355e3a2c5ccSlukem 		} else if (strcasecmp(word, "homedir") == 0) {
356999fd3d6Slukem 			CONF_STRING(homedir);
357999fd3d6Slukem 
35816e88612Slukem 		} else if (strcasecmp(word, "limit") == 0) {
35950150481Slukem 			curclass.limit = DEFAULT_LIMIT;
36050150481Slukem 			REASSIGN(curclass.limitfile, NULL);
361ee2d1afbSlukem 			CONF_LL(limit, arg, -1, LLTMAX);
3627e803788Slukem 			REASSIGN(curclass.limitfile,
3631edbda9aSchristos 			    EMPTYSTR(p) ? NULL : ftpd_strdup(p));
36416e88612Slukem 
36550150481Slukem 		} else if (strcasecmp(word, "maxfilesize") == 0) {
36650150481Slukem 			curclass.maxfilesize = DEFAULT_MAXFILESIZE;
367ee2d1afbSlukem 			CONF_LL(maxfilesize, arg, -1, LLTMAX);
36850150481Slukem 
36931547ec6Slukem 		} else if (strcasecmp(word, "maxtimeout") == 0) {
37050150481Slukem 			curclass.maxtimeout = DEFAULT_MAXTIMEOUT;
371ee2d1afbSlukem 			CONF_LL(maxtimeout, arg,
372ee2d1afbSlukem 			    MIN(30, curclass.timeout), LLTMAX);
373397e2cfcSlukem 
374260e9f55Senami 		} else if (strcasecmp(word, "mmapsize") == 0) {
375260e9f55Senami 			curclass.mmapsize = 0;
3760053962fSlukem 			CONF_LL(mmapsize, arg, 0, SSIZE_MAX);
377260e9f55Senami 
378260e9f55Senami 		} else if (strcasecmp(word, "readsize") == 0) {
379260e9f55Senami 			curclass.readsize = 0;
3800053962fSlukem 			CONF_LL(readsize, arg, 0, SSIZE_MAX);
381260e9f55Senami 
382260e9f55Senami 		} else if (strcasecmp(word, "writesize") == 0) {
383260e9f55Senami 			curclass.writesize = 0;
3840053962fSlukem 			CONF_LL(writesize, arg, 0, SSIZE_MAX);
385260e9f55Senami 
386818f7caaSginsbach 		} else if (strcasecmp(word, "recvbufsize") == 0) {
387818f7caaSginsbach 			curclass.recvbufsize = 0;
3880053962fSlukem 			CONF_LL(recvbufsize, arg, 0, INT_MAX);
389818f7caaSginsbach 
390260e9f55Senami 		} else if (strcasecmp(word, "sendbufsize") == 0) {
391260e9f55Senami 			curclass.sendbufsize = 0;
3920053962fSlukem 			CONF_LL(sendbufsize, arg, 0, INT_MAX);
393260e9f55Senami 
394260e9f55Senami 		} else if (strcasecmp(word, "sendlowat") == 0) {
395260e9f55Senami 			curclass.sendlowat = 0;
3960053962fSlukem 			CONF_LL(sendlowat, arg, 0, INT_MAX);
397260e9f55Senami 
39831547ec6Slukem 		} else if (strcasecmp(word, "modify") == 0) {
399999fd3d6Slukem 			CONF_FLAG(modify);
400397e2cfcSlukem 
40121d03121Slukem 		} else if (strcasecmp(word, "motd") == 0) {
402999fd3d6Slukem 			CONF_STRING(motd);
40321d03121Slukem 
40431547ec6Slukem 		} else if (strcasecmp(word, "notify") == 0) {
405999fd3d6Slukem 			CONF_STRING(notify);
406397e2cfcSlukem 
407e3ab2046Stv 		} else if (strcasecmp(word, "passive") == 0) {
408999fd3d6Slukem 			CONF_FLAG(passive);
409397e2cfcSlukem 
410c8493e94Slukem 		} else if (strcasecmp(word, "portrange") == 0) {
41192ebc577Sitojun 			long minport, maxport;
412c8493e94Slukem 
413c8493e94Slukem 			curclass.portmin = 0;
414c8493e94Slukem 			curclass.portmax = 0;
41550150481Slukem 			if (none || EMPTYSTR(arg))
416c8493e94Slukem 				continue;
417ee2d1afbSlukem 			if (EMPTYSTR(p)) {
418c8493e94Slukem 				syslog(LOG_WARNING,
419c8493e94Slukem 				   "%s line %d: missing maxport argument",
420c8493e94Slukem 				   infile, (int)line);
421c8493e94Slukem 				continue;
422c8493e94Slukem 			}
423ee2d1afbSlukem 			minport = strsuftollx("minport", arg, IPPORT_RESERVED,
424ee2d1afbSlukem 			    IPPORT_ANONMAX, errbuf, sizeof(errbuf));
425ee2d1afbSlukem 			if (errbuf[0]) {
426ee2d1afbSlukem 				syslog(LOG_WARNING, "%s line %d: %s",
427ee2d1afbSlukem 				    infile, (int)line, errbuf);
428c8493e94Slukem 				continue;
429c8493e94Slukem 			}
430ee2d1afbSlukem 			maxport = strsuftollx("maxport", p, IPPORT_RESERVED,
431ee2d1afbSlukem 			    IPPORT_ANONMAX, errbuf, sizeof(errbuf));
432ee2d1afbSlukem 			if (errbuf[0]) {
433ee2d1afbSlukem 				syslog(LOG_WARNING, "%s line %d: %s",
434ee2d1afbSlukem 				    infile, (int)line, errbuf);
435c8493e94Slukem 				continue;
436c8493e94Slukem 			}
437c8493e94Slukem 			if (minport >= maxport) {
438c8493e94Slukem 				syslog(LOG_WARNING,
43992ebc577Sitojun 				    "%s line %d: minport %ld >= maxport %ld",
440c8493e94Slukem 				    infile, (int)line, minport, maxport);
441c8493e94Slukem 				continue;
442c8493e94Slukem 			}
44392ebc577Sitojun 			curclass.portmin = (int)minport;
44492ebc577Sitojun 			curclass.portmax = (int)maxport;
445c8493e94Slukem 
446c31e16f7Slukem 		} else if (strcasecmp(word, "private") == 0) {
447c31e16f7Slukem 			CONF_FLAG(private);
448c31e16f7Slukem 
44921d03121Slukem 		} else if (strcasecmp(word, "rateget") == 0) {
450ee2d1afbSlukem 			curclass.maxrateget = curclass.rateget = 0;
451ee2d1afbSlukem 			CONF_LL(rateget, arg, 0, LLTMAX);
452ee2d1afbSlukem 			curclass.maxrateget = curclass.rateget;
45321d03121Slukem 
45421d03121Slukem 		} else if (strcasecmp(word, "rateput") == 0) {
455ee2d1afbSlukem 			curclass.maxrateput = curclass.rateput = 0;
456ee2d1afbSlukem 			CONF_LL(rateput, arg, 0, LLTMAX);
457ee2d1afbSlukem 			curclass.maxrateput = curclass.rateput;
458999fd3d6Slukem 
459999fd3d6Slukem 		} else if (strcasecmp(word, "sanenames") == 0) {
460999fd3d6Slukem 			CONF_FLAG(sanenames);
46121d03121Slukem 
46231547ec6Slukem 		} else if (strcasecmp(word, "timeout") == 0) {
46350150481Slukem 			curclass.timeout = DEFAULT_TIMEOUT;
464ee2d1afbSlukem 			CONF_LL(timeout, arg, 30, curclass.maxtimeout);
465397e2cfcSlukem 
4667e803788Slukem 		} else if (strcasecmp(word, "template") == 0) {
4677e803788Slukem 			if (none)
4687e803788Slukem 				continue;
4691edbda9aSchristos 			REASSIGN(template, EMPTYSTR(arg) ? NULL : ftpd_strdup(arg));
4707e803788Slukem 
47131547ec6Slukem 		} else if (strcasecmp(word, "umask") == 0) {
472ef70558fSlukem 			unsigned long fumask;
47331547ec6Slukem 
47450150481Slukem 			curclass.umask = DEFAULT_UMASK;
47531547ec6Slukem 			if (none || EMPTYSTR(arg))
47631547ec6Slukem 				continue;
47792ebc577Sitojun 			errno = 0;
47892ebc577Sitojun 			endp = NULL;
47992ebc577Sitojun 			fumask = strtoul(arg, &endp, 8);
48092ebc577Sitojun 			if (errno || *arg == '\0' || *endp != '\0' ||
48192ebc577Sitojun 			    fumask > 0777) {
48231547ec6Slukem 				syslog(LOG_WARNING,
48331547ec6Slukem 				    "%s line %d: invalid umask %s",
484397e2cfcSlukem 				    infile, (int)line, arg);
48531547ec6Slukem 				continue;
48631547ec6Slukem 			}
48792ebc577Sitojun 			curclass.umask = (mode_t)fumask;
488397e2cfcSlukem 
48921d03121Slukem 		} else if (strcasecmp(word, "upload") == 0) {
490999fd3d6Slukem 			CONF_FLAG(upload);
491999fd3d6Slukem 			if (! CURCLASS_FLAGS_ISSET(upload))
492999fd3d6Slukem 				CURCLASS_FLAGS_CLR(modify);
49321d03121Slukem 
49431547ec6Slukem 		} else {
49531547ec6Slukem 			syslog(LOG_WARNING,
49631547ec6Slukem 			    "%s line %d: unknown directive '%s'",
497397e2cfcSlukem 			    infile, (int)line, word);
49831547ec6Slukem 			continue;
49931547ec6Slukem 		}
500ee2d1afbSlukem  nextline:
501ee2d1afbSlukem 		;
50231547ec6Slukem 	}
5037e803788Slukem 	REASSIGN(template, NULL);
50431547ec6Slukem 	fclose(f);
50531547ec6Slukem }
50631547ec6Slukem 
50731547ec6Slukem /*
50831547ec6Slukem  * Show file listed in curclass.display first time in, and list all the
50950150481Slukem  * files named in curclass.notify in the current directory.
51050150481Slukem  * Send back responses with the prefix `code' + "-".
51150150481Slukem  * If code == -1, flush the internal cache of directory names and return.
51231547ec6Slukem  */
51331547ec6Slukem void
show_chdir_messages(int code)514dcc88422Slukem show_chdir_messages(int code)
51531547ec6Slukem {
51631547ec6Slukem 	static StringList *slist = NULL;
51731547ec6Slukem 
51831547ec6Slukem 	struct stat st;
51931547ec6Slukem 	struct tm *t;
52031547ec6Slukem 	glob_t	 gl;
52131547ec6Slukem 	time_t	 now, then;
52231547ec6Slukem 	int	 age;
5233a491edaSlukem 	char	 curwd[MAXPATHLEN];
52431547ec6Slukem 	char	*cp, **rlist;
52531547ec6Slukem 
52650150481Slukem 	if (code == -1) {
52750150481Slukem 		if (slist != NULL)
52850150481Slukem 			sl_free(slist, 1);
52950150481Slukem 		slist = NULL;
53050150481Slukem 		return;
53150150481Slukem 	}
53250150481Slukem 
533ab88a150Slukem 	if (quietmessages)
534ab88a150Slukem 		return;
535ab88a150Slukem 
53631547ec6Slukem 		/* Setup list for directory cache */
53731547ec6Slukem 	if (slist == NULL)
53831547ec6Slukem 		slist = sl_init();
539d51504eeSlukem 	if (slist == NULL) {
540d51504eeSlukem 		syslog(LOG_WARNING, "can't allocate memory for stringlist");
541d51504eeSlukem 		return;
542d51504eeSlukem 	}
54331547ec6Slukem 
54431547ec6Slukem 		/* Check if this directory has already been visited */
5453a491edaSlukem 	if (getcwd(curwd, sizeof(curwd) - 1) == NULL) {
5467ccec6acSmouse 		syslog(LOG_WARNING, "can't getcwd: %s", strerror(errno));
54731547ec6Slukem 		return;
54831547ec6Slukem 	}
5493a491edaSlukem 	if (sl_find(slist, curwd) != NULL)
55031547ec6Slukem 		return;
55131547ec6Slukem 
5521edbda9aSchristos 	cp = ftpd_strdup(curwd);
553d51504eeSlukem 	if (sl_add(slist, cp) == -1)
554d51504eeSlukem 		syslog(LOG_WARNING, "can't add `%s' to stringlist", cp);
55531547ec6Slukem 
55631547ec6Slukem 		/* First check for a display file */
557e3a2c5ccSlukem 	(void)display_file(curclass.display, code);
55831547ec6Slukem 
55931547ec6Slukem 		/* Now see if there are any notify files */
56021d03121Slukem 	if (EMPTYSTR(curclass.notify))
56131547ec6Slukem 		return;
56231547ec6Slukem 
56353c91d8fSlukem 	memset(&gl, 0, sizeof(gl));
564adbaddc9Slukem 	if (glob(curclass.notify, GLOB_BRACE|GLOB_LIMIT, NULL, &gl) != 0
56555dd4165Schristos 	    || gl.gl_matchc == 0) {
56655dd4165Schristos 		globfree(&gl);
56731547ec6Slukem 		return;
56855dd4165Schristos 	}
56931547ec6Slukem 	time(&now);
57031547ec6Slukem 	for (rlist = gl.gl_pathv; *rlist != NULL; rlist++) {
57131547ec6Slukem 		if (stat(*rlist, &st) != 0)
57231547ec6Slukem 			continue;
5733ff72472Smycroft 		if (!S_ISREG(st.st_mode))
57431547ec6Slukem 			continue;
57531547ec6Slukem 		then = st.st_mtime;
5764b2b2847Slukem 		if (code != 0) {
577eac5778eSsommerfeld 			reply(-code, "%s", "");
5784b2b2847Slukem 			code = 0;
5794b2b2847Slukem 		}
58073f082e2Slukem 		reply(-code, "Please read the file %s", *rlist);
58131547ec6Slukem 		t = localtime(&now);
58231547ec6Slukem 		age = 365 * t->tm_year + t->tm_yday;
58331547ec6Slukem 		t = localtime(&then);
58431547ec6Slukem 		age -= 365 * t->tm_year + t->tm_yday;
58573f082e2Slukem 		reply(-code, "  it was last modified on %.24s - %d day%s ago",
58625cf35a4Slukem 		    ctime(&then), age, PLURAL(age));
58731547ec6Slukem 	}
58831547ec6Slukem 	globfree(&gl);
58931547ec6Slukem }
59031547ec6Slukem 
59121d03121Slukem int
display_file(const char * file,int code)592e3a2c5ccSlukem display_file(const char *file, int code)
59321d03121Slukem {
59421d03121Slukem 	FILE   *f;
5951571b4e9Skristerw 	char   *buf, *p;
5963a491edaSlukem 	char	curwd[MAXPATHLEN];
59721d03121Slukem 	size_t	len;
598999fd3d6Slukem 	off_t	lastnum;
59921d03121Slukem 	time_t	now;
60021d03121Slukem 
601999fd3d6Slukem 	lastnum = 0;
602ab88a150Slukem 	if (quietmessages)
603ab88a150Slukem 		return (0);
604ab88a150Slukem 
60521d03121Slukem 	if (EMPTYSTR(file))
60621d03121Slukem 		return(0);
60721d03121Slukem 	if ((f = fopen(file, "r")) == NULL)
60821d03121Slukem 		return (0);
609eac5778eSsommerfeld 	reply(-code, "%s", "");
61021d03121Slukem 
61121d03121Slukem 	for (;
61221d03121Slukem 	    (buf = fparseln(f, &len, NULL, "\0\0\0", 0)) != NULL; free(buf)) {
61321d03121Slukem 		if (len > 0)
61421d03121Slukem 			if (buf[len - 1] == '\n')
61521d03121Slukem 				buf[--len] = '\0';
61673f082e2Slukem 		cprintf(stdout, "    ");
61721d03121Slukem 
61821d03121Slukem 		for (p = buf; *p; p++) {
61921d03121Slukem 			if (*p == '%') {
62021d03121Slukem 				p++;
62121d03121Slukem 				switch (*p) {
62216e88612Slukem 
62316e88612Slukem 				case 'c':
62473f082e2Slukem 					cprintf(stdout, "%s",
62516e88612Slukem 					    curclass.classname ?
62616e88612Slukem 					    curclass.classname : "<unknown>");
62716e88612Slukem 					break;
62816e88612Slukem 
62921d03121Slukem 				case 'C':
6303a491edaSlukem 					if (getcwd(curwd, sizeof(curwd)-1)
6313a491edaSlukem 					    == NULL){
63221d03121Slukem 						syslog(LOG_WARNING,
63321d03121Slukem 						    "can't getcwd: %s",
63421d03121Slukem 						    strerror(errno));
63521d03121Slukem 						continue;
63621d03121Slukem 					}
6373a491edaSlukem 					cprintf(stdout, "%s", curwd);
63821d03121Slukem 					break;
63916e88612Slukem 
64021d03121Slukem 				case 'E':
641999fd3d6Slukem 					if (! EMPTYSTR(emailaddr))
642999fd3d6Slukem 						cprintf(stdout, "%s",
643999fd3d6Slukem 						    emailaddr);
64421d03121Slukem 					break;
64516e88612Slukem 
64621d03121Slukem 				case 'L':
64773f082e2Slukem 					cprintf(stdout, "%s", hostname);
64821d03121Slukem 					break;
64916e88612Slukem 
65016e88612Slukem 				case 'M':
651999fd3d6Slukem 					if (curclass.limit == -1) {
65273f082e2Slukem 						cprintf(stdout, "unlimited");
653999fd3d6Slukem 						lastnum = 0;
654999fd3d6Slukem 					} else {
655ee2d1afbSlukem 						cprintf(stdout, LLF,
656ee2d1afbSlukem 						    (LLT)curclass.limit);
657999fd3d6Slukem 						lastnum = curclass.limit;
658999fd3d6Slukem 					}
65916e88612Slukem 					break;
66016e88612Slukem 
66116e88612Slukem 				case 'N':
662999fd3d6Slukem 					cprintf(stdout, "%d", connections);
663999fd3d6Slukem 					lastnum = connections;
66416e88612Slukem 					break;
66516e88612Slukem 
66621d03121Slukem 				case 'R':
66773f082e2Slukem 					cprintf(stdout, "%s", remotehost);
66821d03121Slukem 					break;
66916e88612Slukem 
670999fd3d6Slukem 				case 's':
671999fd3d6Slukem 					if (lastnum != 1)
672999fd3d6Slukem 						cprintf(stdout, "s");
673999fd3d6Slukem 					break;
674999fd3d6Slukem 
675999fd3d6Slukem 				case 'S':
676999fd3d6Slukem 					if (lastnum != 1)
677999fd3d6Slukem 						cprintf(stdout, "S");
678999fd3d6Slukem 					break;
679999fd3d6Slukem 
68021d03121Slukem 				case 'T':
68121d03121Slukem 					now = time(NULL);
68273f082e2Slukem 					cprintf(stdout, "%.24s", ctime(&now));
68321d03121Slukem 					break;
68416e88612Slukem 
68521d03121Slukem 				case 'U':
68673f082e2Slukem 					cprintf(stdout, "%s",
68721d03121Slukem 					    pw ? pw->pw_name : "<unknown>");
68821d03121Slukem 					break;
68916e88612Slukem 
69021d03121Slukem 				case '%':
69173f082e2Slukem 					CPUTC('%', stdout);
69221d03121Slukem 					break;
69316e88612Slukem 
69421d03121Slukem 				}
69573f082e2Slukem 			} else
69673f082e2Slukem 				CPUTC(*p, stdout);
69721d03121Slukem 		}
69873f082e2Slukem 		cprintf(stdout, "\r\n");
69921d03121Slukem 	}
70021d03121Slukem 
70121d03121Slukem 	(void)fflush(stdout);
70221d03121Slukem 	(void)fclose(f);
70321d03121Slukem 	return (1);
70421d03121Slukem }
70521d03121Slukem 
70631547ec6Slukem /*
707e3a2c5ccSlukem  * Parse src, expanding '%' escapes, into dst (which must be at least
708e3a2c5ccSlukem  * MAXPATHLEN long).
709e3a2c5ccSlukem  */
710e3a2c5ccSlukem void
format_path(char * dst,const char * src)711e3a2c5ccSlukem format_path(char *dst, const char *src)
712e3a2c5ccSlukem {
713e3a2c5ccSlukem 	size_t len;
714e3a2c5ccSlukem 	const char *p;
715e3a2c5ccSlukem 
716e3a2c5ccSlukem 	dst[0] = '\0';
717e3a2c5ccSlukem 	len = 0;
718e3a2c5ccSlukem 	if (src == NULL)
719e3a2c5ccSlukem 		return;
720e3a2c5ccSlukem 	for (p = src; *p && len < MAXPATHLEN; p++) {
721e3a2c5ccSlukem 		if (*p == '%') {
722e3a2c5ccSlukem 			p++;
723e3a2c5ccSlukem 			switch (*p) {
724e3a2c5ccSlukem 
725e3a2c5ccSlukem 			case 'c':
726e3a2c5ccSlukem 				len += strlcpy(dst + len, curclass.classname,
727e3a2c5ccSlukem 				    MAXPATHLEN - len);
728e3a2c5ccSlukem 				break;
729e3a2c5ccSlukem 
730e3a2c5ccSlukem 			case 'd':
731e3a2c5ccSlukem 				len += strlcpy(dst + len, pw->pw_dir,
732e3a2c5ccSlukem 				    MAXPATHLEN - len);
733e3a2c5ccSlukem 				break;
734e3a2c5ccSlukem 
735e3a2c5ccSlukem 			case 'u':
736e3a2c5ccSlukem 				len += strlcpy(dst + len, pw->pw_name,
737e3a2c5ccSlukem 				    MAXPATHLEN - len);
738e3a2c5ccSlukem 				break;
739e3a2c5ccSlukem 
740e3a2c5ccSlukem 			case '%':
741e3a2c5ccSlukem 				dst[len++] = '%';
742e3a2c5ccSlukem 				break;
743e3a2c5ccSlukem 
744e3a2c5ccSlukem 			}
745e3a2c5ccSlukem 		} else
746e3a2c5ccSlukem 			dst[len++] = *p;
747e3a2c5ccSlukem 	}
748e3a2c5ccSlukem 	if (len < MAXPATHLEN)
749e3a2c5ccSlukem 		dst[len] = '\0';
750e3a2c5ccSlukem 	dst[MAXPATHLEN - 1] = '\0';
751e3a2c5ccSlukem }
752e3a2c5ccSlukem 
753e3a2c5ccSlukem /*
754397e2cfcSlukem  * Find s2 at the end of s1.  If found, return a string up to (but
75531547ec6Slukem  * not including) s2, otherwise returns NULL.
75631547ec6Slukem  */
75731547ec6Slukem static char *
strend(const char * s1,char * s2)758dcc88422Slukem strend(const char *s1, char *s2)
75931547ec6Slukem {
76016e88612Slukem 	static	char buf[MAXPATHLEN];
76131547ec6Slukem 
76231547ec6Slukem 	char	*start;
76331547ec6Slukem 	size_t	l1, l2;
76431547ec6Slukem 
76531547ec6Slukem 	l1 = strlen(s1);
76631547ec6Slukem 	l2 = strlen(s2);
76731547ec6Slukem 
76853c91d8fSlukem 	if (l2 >= l1 || l1 >= sizeof(buf))
76931547ec6Slukem 		return(NULL);
77031547ec6Slukem 
77121d03121Slukem 	strlcpy(buf, s1, sizeof(buf));
77231547ec6Slukem 	start = buf + (l1 - l2);
77331547ec6Slukem 
77431547ec6Slukem 	if (strcmp(start, s2) == 0) {
77531547ec6Slukem 		*start = '\0';
77631547ec6Slukem 		return(buf);
77731547ec6Slukem 	} else
77831547ec6Slukem 		return(NULL);
77931547ec6Slukem }
78031547ec6Slukem 
78131547ec6Slukem static int
filetypematch(char * types,int mode)782dcc88422Slukem filetypematch(char *types, int mode)
78331547ec6Slukem {
78431547ec6Slukem 	for ( ; types[0] != '\0'; types++)
78531547ec6Slukem 		switch (*types) {
78631547ec6Slukem 		  case 'd':
78731547ec6Slukem 			if (S_ISDIR(mode))
78831547ec6Slukem 				return(1);
78931547ec6Slukem 			break;
79031547ec6Slukem 		  case 'f':
79131547ec6Slukem 			if (S_ISREG(mode))
79231547ec6Slukem 				return(1);
79331547ec6Slukem 			break;
79431547ec6Slukem 		}
79531547ec6Slukem 	return(0);
79631547ec6Slukem }
79731547ec6Slukem 
79831547ec6Slukem /*
79931547ec6Slukem  * Look for a conversion.  If we succeed, return a pointer to the
80031547ec6Slukem  * command to execute for the conversion.
80131547ec6Slukem  *
80231547ec6Slukem  * The command is stored in a static array so there's no memory
80331547ec6Slukem  * leak problems, and not too much to change in ftpd.c.  This
80431547ec6Slukem  * routine doesn't need to be re-entrant unless we start using a
80531547ec6Slukem  * multi-threaded ftpd, and that's not likely for a while...
80631547ec6Slukem  */
8070053962fSlukem const char **
do_conversion(const char * fname)808dcc88422Slukem do_conversion(const char *fname)
80931547ec6Slukem {
81031547ec6Slukem 	struct ftpconv	*cp;
81131547ec6Slukem 	struct stat	 st;
81231547ec6Slukem 	int		 o_errno;
813c22375d4Schristos 	char		*base = NULL;
8140053962fSlukem 	char		*cmd, *p, *lp;
81541592955Schristos 	char	       **argv;
816397e2cfcSlukem 	StringList	*sl;
81731547ec6Slukem 
81831547ec6Slukem 	o_errno = errno;
819397e2cfcSlukem 	sl = NULL;
820397e2cfcSlukem 	cmd = NULL;
82131547ec6Slukem 	for (cp = curclass.conversions; cp != NULL; cp = cp->next) {
8220bba8ce3Slukem 		if (cp->suffix == NULL) {
8230bba8ce3Slukem 			syslog(LOG_WARNING,
8240bba8ce3Slukem 			    "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!");
8250bba8ce3Slukem 			continue;
8260bba8ce3Slukem 		}
82731547ec6Slukem 		if ((base = strend(fname, cp->suffix)) == NULL)
82831547ec6Slukem 			continue;
8290bba8ce3Slukem 		if (cp->types == NULL || cp->disable == NULL ||
83031547ec6Slukem 		    cp->command == NULL)
83131547ec6Slukem 			continue;
83231547ec6Slukem 					/* Is it enabled? */
83331547ec6Slukem 		if (strcmp(cp->disable, ".") != 0 &&
83431547ec6Slukem 		    stat(cp->disable, &st) == 0)
83531547ec6Slukem 				continue;
83631547ec6Slukem 					/* Does the base exist? */
83731547ec6Slukem 		if (stat(base, &st) < 0)
83831547ec6Slukem 			continue;
83931547ec6Slukem 					/* Is the file type ok */
84031547ec6Slukem 		if (!filetypematch(cp->types, st.st_mode))
84131547ec6Slukem 			continue;
84231547ec6Slukem 		break;			/* "We have a winner!" */
84331547ec6Slukem 	}
84431547ec6Slukem 
84531547ec6Slukem 	/* If we got through the list, no conversion */
846397e2cfcSlukem 	if (cp == NULL)
847397e2cfcSlukem 		goto cleanup_do_conv;
848397e2cfcSlukem 
849397e2cfcSlukem 	/* Split up command into an argv */
850397e2cfcSlukem 	if ((sl = sl_init()) == NULL)
851397e2cfcSlukem 		goto cleanup_do_conv;
8521edbda9aSchristos 	cmd = ftpd_strdup(cp->command);
853397e2cfcSlukem 	p = cmd;
854397e2cfcSlukem 	while (p) {
855397e2cfcSlukem 		NEXTWORD(p, lp);
856397e2cfcSlukem 		if (strcmp(lp, "%s") == 0)
857397e2cfcSlukem 			lp = base;
8581edbda9aSchristos 		if (sl_add(sl, ftpd_strdup(lp)) == -1)
859397e2cfcSlukem 			goto cleanup_do_conv;
86031547ec6Slukem 	}
86131547ec6Slukem 
862397e2cfcSlukem 	if (sl_add(sl, NULL) == -1)
863397e2cfcSlukem 		goto cleanup_do_conv;
86441592955Schristos 	argv = sl->sl_str;
865397e2cfcSlukem 	free(cmd);
866397e2cfcSlukem 	free(sl);
86741592955Schristos 	return (void *)(intptr_t)argv;
868397e2cfcSlukem 
869397e2cfcSlukem  cleanup_do_conv:
870397e2cfcSlukem 	if (sl)
871397e2cfcSlukem 		sl_free(sl, 1);
872397e2cfcSlukem 	free(cmd);
873397e2cfcSlukem 	errno = o_errno;
874397e2cfcSlukem 	return(NULL);
87531547ec6Slukem }
87621d03121Slukem 
87721d03121Slukem /*
8787e803788Slukem  * Count the number of current connections, reading from
8797e803788Slukem  *	/var/run/ftpd.pids-<class>
8807e803788Slukem  * Does a kill -0 on each pid in that file, and only counts
8817e803788Slukem  * processes that exist (or frees the slot if it doesn't).
8827e803788Slukem  * Adds getpid() to the first free slot. Truncates the file
8837e803788Slukem  * if possible.
8847e803788Slukem  */
88516e88612Slukem void
count_users(void)886dcc88422Slukem count_users(void)
88716e88612Slukem {
88816e88612Slukem 	char	fn[MAXPATHLEN];
8890053962fSlukem 	int	fd;
8900053962fSlukem 	size_t	i, last, count;
8910053962fSlukem 	ssize_t	scount;
89216e88612Slukem 	pid_t  *pids, mypid;
89316e88612Slukem 	struct stat sb;
894e9810351Slukem 	struct flock fl;
89516e88612Slukem 
89616e88612Slukem 	(void)strlcpy(fn, _PATH_CLASSPIDS, sizeof(fn));
89716e88612Slukem 	(void)strlcat(fn, curclass.classname, sizeof(fn));
89816e88612Slukem 	pids = NULL;
89916e88612Slukem 	connections = 1;
900e9810351Slukem 	fl.l_start = 0;
901e9810351Slukem 	fl.l_len = 0;
902e9810351Slukem 	fl.l_pid = 0;
903e9810351Slukem 	fl.l_type = F_WRLCK;
904e9810351Slukem 	fl.l_whence = SEEK_SET;
90516e88612Slukem 
906f62aa6c8Slukem 	if ((fd = open(fn, O_RDWR | O_CREAT, 0600)) == -1)
90716e88612Slukem 		return;
908e9810351Slukem 	if (fcntl(fd, F_SETLK, &fl) == -1)
909f62aa6c8Slukem 		goto cleanup_count;
91016e88612Slukem 	if (fstat(fd, &sb) == -1)
91116e88612Slukem 		goto cleanup_count;
912*cb93dc63Sshm 	if ((pids = calloc(sb.st_size + sizeof(pid_t), 1)) == NULL)
91316e88612Slukem 		goto cleanup_count;
9140053962fSlukem /* XXX: implement a better read loop */
9150053962fSlukem 	scount = read(fd, pids, sb.st_size);
9160053962fSlukem 	if (scount == -1 || scount != sb.st_size || scount < 0)
91716e88612Slukem 		goto cleanup_count;
9180053962fSlukem 	count = (size_t)scount / sizeof(pid_t);
91916e88612Slukem 	mypid = getpid();
92016e88612Slukem 	last = 0;
92116e88612Slukem 	for (i = 0; i < count; i++) {
92216e88612Slukem 		if (pids[i] == 0)
92316e88612Slukem 			continue;
92416e88612Slukem 		if (kill(pids[i], 0) == -1 && errno != EPERM) {
92516e88612Slukem 			if (mypid != 0) {
92616e88612Slukem 				pids[i] = mypid;
92716e88612Slukem 				mypid = 0;
92816e88612Slukem 				last = i;
92916e88612Slukem 			}
93016e88612Slukem 		} else {
93116e88612Slukem 			connections++;
93216e88612Slukem 			last = i;
93316e88612Slukem 		}
93416e88612Slukem 	}
93516e88612Slukem 	if (mypid != 0) {
93616e88612Slukem 		if (pids[last] != 0)
93716e88612Slukem 			last++;
93816e88612Slukem 		pids[last] = mypid;
93916e88612Slukem 	}
94016e88612Slukem 	count = (last + 1) * sizeof(pid_t);
94116e88612Slukem 	if (lseek(fd, 0, SEEK_SET) == -1)
94216e88612Slukem 		goto cleanup_count;
9430053962fSlukem /* XXX: implement a better write loop */
9440053962fSlukem 	scount = write(fd, pids, count);
9450053962fSlukem 	if (scount == -1 || (size_t)scount != count)
94616e88612Slukem 		goto cleanup_count;
94716e88612Slukem 	(void)ftruncate(fd, count);
94816e88612Slukem 
94916e88612Slukem  cleanup_count:
950e9810351Slukem 	fl.l_type = F_UNLCK;
951e9810351Slukem 	(void)fcntl(fd, F_SETLK, &fl);
95216e88612Slukem 	close(fd);
95316e88612Slukem 	REASSIGN(pids, NULL);
95416e88612Slukem }
955