1161764Sobrien/*	$NetBSD: conf.c,v 1.57 2006/02/01 14:20:12 christos Exp $	*/
279968Sobrien
379968Sobrien/*-
4161764Sobrien * Copyright (c) 1997-2005 The NetBSD Foundation, Inc.
579968Sobrien * All rights reserved.
679968Sobrien *
779968Sobrien * This code is derived from software contributed to The NetBSD Foundation
879968Sobrien * by Simon Burge and Luke Mewburn.
979968Sobrien *
1079968Sobrien * Redistribution and use in source and binary forms, with or without
1179968Sobrien * modification, are permitted provided that the following conditions
1279968Sobrien * are met:
1379968Sobrien * 1. Redistributions of source code must retain the above copyright
1479968Sobrien *    notice, this list of conditions and the following disclaimer.
1579968Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1679968Sobrien *    notice, this list of conditions and the following disclaimer in the
1779968Sobrien *    documentation and/or other materials provided with the distribution.
1879968Sobrien * 3. All advertising materials mentioning features or use of this software
1979968Sobrien *    must display the following acknowledgement:
2079968Sobrien *        This product includes software developed by the NetBSD
2179968Sobrien *        Foundation, Inc. and its contributors.
2279968Sobrien * 4. Neither the name of The NetBSD Foundation nor the names of its
2379968Sobrien *    contributors may be used to endorse or promote products derived
2479968Sobrien *    from this software without specific prior written permission.
2579968Sobrien *
2679968Sobrien * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2779968Sobrien * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2879968Sobrien * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2979968Sobrien * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
3079968Sobrien * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
3179968Sobrien * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
3279968Sobrien * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
3379968Sobrien * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
3479968Sobrien * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
3579968Sobrien * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3679968Sobrien * POSSIBILITY OF SUCH DAMAGE.
3779968Sobrien */
3879968Sobrien
39108746Sobrien#include <sys/cdefs.h>
40108746Sobrien#ifndef lint
41161764Sobrien__RCSID("$NetBSD: conf.c,v 1.57 2006/02/01 14:20:12 christos Exp $");
42108746Sobrien#endif /* not lint */
4379968Sobrien
44108746Sobrien#include <sys/types.h>
45108746Sobrien#include <sys/param.h>
46108746Sobrien#include <sys/socket.h>
47108746Sobrien#include <sys/stat.h>
48108746Sobrien
49108746Sobrien#include <ctype.h>
50108746Sobrien#include <errno.h>
51108746Sobrien#include <fcntl.h>
52108746Sobrien#include <glob.h>
53108746Sobrien#include <netdb.h>
54108746Sobrien#include <signal.h>
55108746Sobrien#include <stdio.h>
56108746Sobrien#include <stdlib.h>
57108746Sobrien#include <string.h>
58108746Sobrien#include <stringlist.h>
59108746Sobrien#include <syslog.h>
60108746Sobrien#include <time.h>
61108746Sobrien#include <unistd.h>
62108746Sobrien#include <util.h>
63108746Sobrien
64108746Sobrien#ifdef KERBEROS5
65108746Sobrien#include <krb5/krb5.h>
66108746Sobrien#endif
67108746Sobrien
6879968Sobrien#include "extern.h"
6979968Sobrien#include "pathnames.h"
7079968Sobrien
7179968Sobrienstatic char *strend(const char *, char *);
7279968Sobrienstatic int filetypematch(char *, int);
7379968Sobrien
7479968Sobrien
7579968Sobrien		/* class defaults */
7679968Sobrien#define DEFAULT_LIMIT		-1		/* unlimited connections */
7779968Sobrien#define DEFAULT_MAXFILESIZE	-1		/* unlimited file size */
7879968Sobrien#define DEFAULT_MAXTIMEOUT	7200		/* 2 hours */
7979968Sobrien#define DEFAULT_TIMEOUT		900		/* 15 minutes */
80161764Sobrien#define DEFAULT_UMASK		027		/* rw-r----- */
8179968Sobrien
8279968Sobrien/*
8379968Sobrien * Initialise curclass to an `empty' state
8479968Sobrien */
8579968Sobrienvoid
8679968Sobrieninit_curclass(void)
8779968Sobrien{
8879968Sobrien	struct ftpconv	*conv, *cnext;
8979968Sobrien
9079968Sobrien	for (conv = curclass.conversions; conv != NULL; conv = cnext) {
9179968Sobrien		REASSIGN(conv->suffix, NULL);
9279968Sobrien		REASSIGN(conv->types, NULL);
9379968Sobrien		REASSIGN(conv->disable, NULL);
9479968Sobrien		REASSIGN(conv->command, NULL);
9579968Sobrien		cnext = conv->next;
9679968Sobrien		free(conv);
9779968Sobrien	}
9879968Sobrien
9979968Sobrien	memset((char *)&curclass.advertise, 0, sizeof(curclass.advertise));
10079968Sobrien	curclass.advertise.su_len = 0;		/* `not used' */
10179968Sobrien	REASSIGN(curclass.chroot, NULL);
10279968Sobrien	REASSIGN(curclass.classname, NULL);
10379968Sobrien	curclass.conversions =	NULL;
10479968Sobrien	REASSIGN(curclass.display, NULL);
10579968Sobrien	REASSIGN(curclass.homedir, NULL);
10679968Sobrien	curclass.limit =	DEFAULT_LIMIT;
10779968Sobrien	REASSIGN(curclass.limitfile, NULL);
10879968Sobrien	curclass.maxfilesize =	DEFAULT_MAXFILESIZE;
10979968Sobrien	curclass.maxrateget =	0;
11079968Sobrien	curclass.maxrateput =	0;
11179968Sobrien	curclass.maxtimeout =	DEFAULT_MAXTIMEOUT;
112161764Sobrien	REASSIGN(curclass.motd, ftpd_strdup(_NAME_FTPLOGINMESG));
11379968Sobrien	REASSIGN(curclass.notify, NULL);
11479968Sobrien	curclass.portmin =	0;
11579968Sobrien	curclass.portmax =	0;
11679968Sobrien	curclass.rateget =	0;
11779968Sobrien	curclass.rateput =	0;
11879968Sobrien	curclass.timeout =	DEFAULT_TIMEOUT;
11979968Sobrien	    /* curclass.type is set elsewhere */
12079968Sobrien	curclass.umask =	DEFAULT_UMASK;
121108746Sobrien	curclass.mmapsize =	0;
122108746Sobrien	curclass.readsize =	0;
123108746Sobrien	curclass.writesize =	0;
124108746Sobrien	curclass.sendbufsize =	0;
125108746Sobrien	curclass.sendlowat =	0;
12679968Sobrien
12779968Sobrien	CURCLASS_FLAGS_SET(checkportcmd);
12892282Sobrien	CURCLASS_FLAGS_CLR(denyquick);
129161764Sobrien	CURCLASS_FLAGS_CLR(hidesymlinks);
13079968Sobrien	CURCLASS_FLAGS_SET(modify);
13179968Sobrien	CURCLASS_FLAGS_SET(passive);
13292282Sobrien	CURCLASS_FLAGS_CLR(private);
13379968Sobrien	CURCLASS_FLAGS_CLR(sanenames);
13479968Sobrien	CURCLASS_FLAGS_SET(upload);
13579968Sobrien}
13679968Sobrien
13779968Sobrien/*
13879968Sobrien * Parse the configuration file, looking for the named class, and
13979968Sobrien * define curclass to contain the appropriate settings.
14079968Sobrien */
14179968Sobrienvoid
14279968Sobrienparse_conf(const char *findclass)
14379968Sobrien{
14479968Sobrien	FILE		*f;
14579968Sobrien	char		*buf, *p;
14679968Sobrien	size_t		 len;
14779968Sobrien	LLT		 llval;
14879968Sobrien	int		 none, match;
149108746Sobrien	char		*endp, errbuf[100];
15079968Sobrien	char		*class, *word, *arg, *template;
15179968Sobrien	const char	*infile;
15279968Sobrien	size_t		 line;
15379968Sobrien	struct ftpconv	*conv, *cnext;
15479968Sobrien
15579968Sobrien	init_curclass();
156161764Sobrien	REASSIGN(curclass.classname, ftpd_strdup(findclass));
15779968Sobrien			/* set more guest defaults */
15879968Sobrien	if (strcasecmp(findclass, "guest") == 0) {
15979968Sobrien		CURCLASS_FLAGS_CLR(modify);
16079968Sobrien		curclass.umask = 0707;
16179968Sobrien	}
16279968Sobrien
163161764Sobrien	infile = conffilename(_NAME_FTPDCONF);
16479968Sobrien	if ((f = fopen(infile, "r")) == NULL)
16579968Sobrien		return;
16679968Sobrien
16779968Sobrien	line = 0;
16879968Sobrien	template = NULL;
16979968Sobrien	for (;
17079968Sobrien	    (buf = fparseln(f, &len, &line, NULL, FPARSELN_UNESCCOMM |
171108746Sobrien			    FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL;
17279968Sobrien	    free(buf)) {
17379968Sobrien		none = match = 0;
17479968Sobrien		p = buf;
17579968Sobrien		if (len < 1)
17679968Sobrien			continue;
17779968Sobrien		if (p[len - 1] == '\n')
17879968Sobrien			p[--len] = '\0';
17979968Sobrien		if (EMPTYSTR(p))
18079968Sobrien			continue;
18179968Sobrien
18279968Sobrien		NEXTWORD(p, word);
18379968Sobrien		NEXTWORD(p, class);
18479968Sobrien		NEXTWORD(p, arg);
18579968Sobrien		if (EMPTYSTR(word) || EMPTYSTR(class))
18679968Sobrien			continue;
18779968Sobrien		if (strcasecmp(class, "none") == 0)
18879968Sobrien			none = 1;
18979968Sobrien		if (! (strcasecmp(class, findclass) == 0 ||
19079968Sobrien		       (template != NULL && strcasecmp(class, template) == 0) ||
19179968Sobrien		       none ||
19279968Sobrien		       strcasecmp(class, "all") == 0) )
19379968Sobrien			continue;
19479968Sobrien
195108746Sobrien#define CONF_FLAG(Field)						\
196108746Sobrien	do {								\
197108746Sobrien		if (none ||						\
198108746Sobrien		    (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))	\
199108746Sobrien			CURCLASS_FLAGS_CLR(Field);			\
200108746Sobrien		else							\
201108746Sobrien			CURCLASS_FLAGS_SET(Field);			\
20279968Sobrien	} while (0)
20379968Sobrien
204108746Sobrien#define CONF_STRING(Field)						\
205108746Sobrien	do {								\
206108746Sobrien		if (none || EMPTYSTR(arg))				\
207108746Sobrien			arg = NULL;					\
208108746Sobrien		else							\
209161764Sobrien			arg = ftpd_strdup(arg);				\
210108746Sobrien		REASSIGN(curclass.Field, arg);				\
21179968Sobrien	} while (0)
21279968Sobrien
213108746Sobrien#define CONF_LL(Field,Arg,Min,Max)					\
214108746Sobrien	do {								\
215108746Sobrien		if (none || EMPTYSTR(Arg))				\
216108746Sobrien			goto nextline;					\
217108746Sobrien		llval = strsuftollx(#Field, Arg, Min, Max,		\
218108746Sobrien		    errbuf, sizeof(errbuf));				\
219108746Sobrien		if (errbuf[0]) {					\
220108746Sobrien			syslog(LOG_WARNING, "%s line %d: %s",		\
221108746Sobrien			    infile, (int)line, errbuf);			\
222108746Sobrien			goto nextline;					\
223108746Sobrien		}							\
224108746Sobrien		curclass.Field = llval;					\
225108746Sobrien	} while(0)
22679968Sobrien
22779968Sobrien		if (0)  {
22879968Sobrien			/* no-op */
22979968Sobrien
23092282Sobrien		} else if ((strcasecmp(word, "advertise") == 0)
23192282Sobrien			|| (strcasecmp(word, "advertize") == 0)) {
23279968Sobrien			struct addrinfo	hints, *res;
23379968Sobrien			int		error;
23479968Sobrien
23579968Sobrien			memset((char *)&curclass.advertise, 0,
23679968Sobrien			    sizeof(curclass.advertise));
23779968Sobrien			curclass.advertise.su_len = 0;
23879968Sobrien			if (none || EMPTYSTR(arg))
23979968Sobrien				continue;
24079968Sobrien			res = NULL;
24179968Sobrien			memset(&hints, 0, sizeof(hints));
24279968Sobrien					/*
24379968Sobrien					 * only get addresses of the family
24479968Sobrien					 * that we're listening on
24579968Sobrien					 */
24679968Sobrien			hints.ai_family = ctrl_addr.su_family;
24779968Sobrien			hints.ai_socktype = SOCK_STREAM;
24879968Sobrien			error = getaddrinfo(arg, "0", &hints, &res);
24979968Sobrien			if (error) {
25079968Sobrien				syslog(LOG_WARNING, "%s line %d: %s",
25179968Sobrien				    infile, (int)line, gai_strerror(error));
25279968Sobrien advertiseparsefail:
25379968Sobrien				if (res)
25479968Sobrien					freeaddrinfo(res);
25579968Sobrien				continue;
25679968Sobrien			}
25779968Sobrien			if (res->ai_next) {
25879968Sobrien				syslog(LOG_WARNING,
25979968Sobrien    "%s line %d: multiple addresses returned for `%s'; please be more specific",
26079968Sobrien				    infile, (int)line, arg);
26179968Sobrien				goto advertiseparsefail;
26279968Sobrien			}
26379968Sobrien			if (sizeof(curclass.advertise) < res->ai_addrlen || (
26479968Sobrien#ifdef INET6
26579968Sobrien			    res->ai_family != AF_INET6 &&
26679968Sobrien#endif
26779968Sobrien			    res->ai_family != AF_INET)) {
26879968Sobrien				syslog(LOG_WARNING,
26979968Sobrien    "%s line %d: unsupported protocol %d for `%s'",
27079968Sobrien				    infile, (int)line, res->ai_family, arg);
27179968Sobrien				goto advertiseparsefail;
27279968Sobrien			}
27379968Sobrien			memcpy(&curclass.advertise, res->ai_addr,
27479968Sobrien			    res->ai_addrlen);
27579968Sobrien			curclass.advertise.su_len = res->ai_addrlen;
27679968Sobrien			freeaddrinfo(res);
27779968Sobrien
27879968Sobrien		} else if (strcasecmp(word, "checkportcmd") == 0) {
27979968Sobrien			CONF_FLAG(checkportcmd);
28079968Sobrien
28179968Sobrien		} else if (strcasecmp(word, "chroot") == 0) {
28279968Sobrien			CONF_STRING(chroot);
28379968Sobrien
28479968Sobrien		} else if (strcasecmp(word, "classtype") == 0) {
28579968Sobrien			if (!none && !EMPTYSTR(arg)) {
28679968Sobrien				if (strcasecmp(arg, "GUEST") == 0)
28779968Sobrien					curclass.type = CLASS_GUEST;
28879968Sobrien				else if (strcasecmp(arg, "CHROOT") == 0)
28979968Sobrien					curclass.type = CLASS_CHROOT;
29079968Sobrien				else if (strcasecmp(arg, "REAL") == 0)
29179968Sobrien					curclass.type = CLASS_REAL;
29279968Sobrien				else {
29379968Sobrien					syslog(LOG_WARNING,
29479968Sobrien				    "%s line %d: unknown class type `%s'",
29579968Sobrien					    infile, (int)line, arg);
29679968Sobrien					continue;
29779968Sobrien				}
29879968Sobrien			}
29979968Sobrien
30079968Sobrien		} else if (strcasecmp(word, "conversion") == 0) {
30179968Sobrien			char *suffix, *types, *disable, *convcmd;
30279968Sobrien
30379968Sobrien			if (EMPTYSTR(arg)) {
30479968Sobrien				syslog(LOG_WARNING,
30579968Sobrien				    "%s line %d: %s requires a suffix",
30679968Sobrien				    infile, (int)line, word);
30779968Sobrien				continue;	/* need a suffix */
30879968Sobrien			}
30979968Sobrien			NEXTWORD(p, types);
31079968Sobrien			NEXTWORD(p, disable);
31179968Sobrien			convcmd = p;
31279968Sobrien			if (convcmd)
31379968Sobrien				convcmd += strspn(convcmd, " \t");
314161764Sobrien			suffix = ftpd_strdup(arg);
31579968Sobrien			if (none || EMPTYSTR(types) ||
31679968Sobrien			    EMPTYSTR(disable) || EMPTYSTR(convcmd)) {
31779968Sobrien				types = NULL;
31879968Sobrien				disable = NULL;
31979968Sobrien				convcmd = NULL;
32079968Sobrien			} else {
321161764Sobrien				types = ftpd_strdup(types);
322161764Sobrien				disable = ftpd_strdup(disable);
323161764Sobrien				convcmd = ftpd_strdup(convcmd);
32479968Sobrien			}
32579968Sobrien			for (conv = curclass.conversions; conv != NULL;
32679968Sobrien			    conv = conv->next) {
32779968Sobrien				if (strcmp(conv->suffix, suffix) == 0)
32879968Sobrien					break;
32979968Sobrien			}
33079968Sobrien			if (conv == NULL) {
33179968Sobrien				conv = (struct ftpconv *)
33279968Sobrien				    calloc(1, sizeof(struct ftpconv));
33379968Sobrien				if (conv == NULL) {
33479968Sobrien					syslog(LOG_WARNING, "can't malloc");
33579968Sobrien					continue;
33679968Sobrien				}
33779968Sobrien				conv->next = NULL;
33879968Sobrien				for (cnext = curclass.conversions;
33979968Sobrien				    cnext != NULL; cnext = cnext->next)
34079968Sobrien					if (cnext->next == NULL)
34179968Sobrien						break;
34279968Sobrien				if (cnext != NULL)
34379968Sobrien					cnext->next = conv;
34479968Sobrien				else
34579968Sobrien					curclass.conversions = conv;
34679968Sobrien			}
34779968Sobrien			REASSIGN(conv->suffix, suffix);
34879968Sobrien			REASSIGN(conv->types, types);
34979968Sobrien			REASSIGN(conv->disable, disable);
35079968Sobrien			REASSIGN(conv->command, convcmd);
35179968Sobrien
35292282Sobrien		} else if (strcasecmp(word, "denyquick") == 0) {
35392282Sobrien			CONF_FLAG(denyquick);
35492282Sobrien
35579968Sobrien		} else if (strcasecmp(word, "display") == 0) {
35679968Sobrien			CONF_STRING(display);
35779968Sobrien
358161764Sobrien		} else if (strcasecmp(word, "hidesymlinks") == 0) {
359161764Sobrien			CONF_FLAG(hidesymlinks);
360161764Sobrien
36179968Sobrien		} else if (strcasecmp(word, "homedir") == 0) {
36279968Sobrien			CONF_STRING(homedir);
36379968Sobrien
36479968Sobrien		} else if (strcasecmp(word, "limit") == 0) {
36579968Sobrien			curclass.limit = DEFAULT_LIMIT;
36679968Sobrien			REASSIGN(curclass.limitfile, NULL);
367108746Sobrien			CONF_LL(limit, arg, -1, LLTMAX);
36879968Sobrien			REASSIGN(curclass.limitfile,
369161764Sobrien			    EMPTYSTR(p) ? NULL : ftpd_strdup(p));
37079968Sobrien
37179968Sobrien		} else if (strcasecmp(word, "maxfilesize") == 0) {
37279968Sobrien			curclass.maxfilesize = DEFAULT_MAXFILESIZE;
373108746Sobrien			CONF_LL(maxfilesize, arg, -1, LLTMAX);
37479968Sobrien
37579968Sobrien		} else if (strcasecmp(word, "maxtimeout") == 0) {
37679968Sobrien			curclass.maxtimeout = DEFAULT_MAXTIMEOUT;
377108746Sobrien			CONF_LL(maxtimeout, arg,
378108746Sobrien			    MIN(30, curclass.timeout), LLTMAX);
37979968Sobrien
380108746Sobrien		} else if (strcasecmp(word, "mmapsize") == 0) {
381108746Sobrien			curclass.mmapsize = 0;
382108746Sobrien			CONF_LL(mmapsize, arg, 0, LLTMAX);
383108746Sobrien
384108746Sobrien		} else if (strcasecmp(word, "readsize") == 0) {
385108746Sobrien			curclass.readsize = 0;
386108746Sobrien			CONF_LL(readsize, arg, 0, LLTMAX);
387108746Sobrien
388108746Sobrien		} else if (strcasecmp(word, "writesize") == 0) {
389108746Sobrien			curclass.writesize = 0;
390108746Sobrien			CONF_LL(writesize, arg, 0, LLTMAX);
391108746Sobrien
392161764Sobrien		} else if (strcasecmp(word, "recvbufsize") == 0) {
393161764Sobrien			curclass.recvbufsize = 0;
394161764Sobrien			CONF_LL(recvbufsize, arg, 0, LLTMAX);
395161764Sobrien
396108746Sobrien		} else if (strcasecmp(word, "sendbufsize") == 0) {
397108746Sobrien			curclass.sendbufsize = 0;
398108746Sobrien			CONF_LL(sendbufsize, arg, 0, LLTMAX);
399108746Sobrien
400108746Sobrien		} else if (strcasecmp(word, "sendlowat") == 0) {
401108746Sobrien			curclass.sendlowat = 0;
402108746Sobrien			CONF_LL(sendlowat, arg, 0, LLTMAX);
403108746Sobrien
40479968Sobrien		} else if (strcasecmp(word, "modify") == 0) {
40579968Sobrien			CONF_FLAG(modify);
40679968Sobrien
40779968Sobrien		} else if (strcasecmp(word, "motd") == 0) {
40879968Sobrien			CONF_STRING(motd);
40979968Sobrien
41079968Sobrien		} else if (strcasecmp(word, "notify") == 0) {
41179968Sobrien			CONF_STRING(notify);
41279968Sobrien
41379968Sobrien		} else if (strcasecmp(word, "passive") == 0) {
41479968Sobrien			CONF_FLAG(passive);
41579968Sobrien
41679968Sobrien		} else if (strcasecmp(word, "portrange") == 0) {
417108746Sobrien			long minport, maxport;
41879968Sobrien
41979968Sobrien			curclass.portmin = 0;
42079968Sobrien			curclass.portmax = 0;
42179968Sobrien			if (none || EMPTYSTR(arg))
42279968Sobrien				continue;
423108746Sobrien			if (EMPTYSTR(p)) {
42479968Sobrien				syslog(LOG_WARNING,
42579968Sobrien				   "%s line %d: missing maxport argument",
42679968Sobrien				   infile, (int)line);
42779968Sobrien				continue;
42879968Sobrien			}
429108746Sobrien			minport = strsuftollx("minport", arg, IPPORT_RESERVED,
430108746Sobrien			    IPPORT_ANONMAX, errbuf, sizeof(errbuf));
431108746Sobrien			if (errbuf[0]) {
432108746Sobrien				syslog(LOG_WARNING, "%s line %d: %s",
433108746Sobrien				    infile, (int)line, errbuf);
43479968Sobrien				continue;
43579968Sobrien			}
436108746Sobrien			maxport = strsuftollx("maxport", p, IPPORT_RESERVED,
437108746Sobrien			    IPPORT_ANONMAX, errbuf, sizeof(errbuf));
438108746Sobrien			if (errbuf[0]) {
439108746Sobrien				syslog(LOG_WARNING, "%s line %d: %s",
440108746Sobrien				    infile, (int)line, errbuf);
44179968Sobrien				continue;
44279968Sobrien			}
44379968Sobrien			if (minport >= maxport) {
44479968Sobrien				syslog(LOG_WARNING,
445108746Sobrien				    "%s line %d: minport %ld >= maxport %ld",
44679968Sobrien				    infile, (int)line, minport, maxport);
44779968Sobrien				continue;
44879968Sobrien			}
449108746Sobrien			curclass.portmin = (int)minport;
450108746Sobrien			curclass.portmax = (int)maxport;
45179968Sobrien
45292282Sobrien		} else if (strcasecmp(word, "private") == 0) {
45392282Sobrien			CONF_FLAG(private);
45492282Sobrien
45579968Sobrien		} else if (strcasecmp(word, "rateget") == 0) {
456108746Sobrien			curclass.maxrateget = curclass.rateget = 0;
457108746Sobrien			CONF_LL(rateget, arg, 0, LLTMAX);
458108746Sobrien			curclass.maxrateget = curclass.rateget;
45979968Sobrien
46079968Sobrien		} else if (strcasecmp(word, "rateput") == 0) {
461108746Sobrien			curclass.maxrateput = curclass.rateput = 0;
462108746Sobrien			CONF_LL(rateput, arg, 0, LLTMAX);
463108746Sobrien			curclass.maxrateput = curclass.rateput;
46479968Sobrien
46579968Sobrien		} else if (strcasecmp(word, "sanenames") == 0) {
46679968Sobrien			CONF_FLAG(sanenames);
46779968Sobrien
46879968Sobrien		} else if (strcasecmp(word, "timeout") == 0) {
46979968Sobrien			curclass.timeout = DEFAULT_TIMEOUT;
470108746Sobrien			CONF_LL(timeout, arg, 30, curclass.maxtimeout);
47179968Sobrien
47279968Sobrien		} else if (strcasecmp(word, "template") == 0) {
47379968Sobrien			if (none)
47479968Sobrien				continue;
475161764Sobrien			REASSIGN(template, EMPTYSTR(arg) ? NULL : ftpd_strdup(arg));
47679968Sobrien
47779968Sobrien		} else if (strcasecmp(word, "umask") == 0) {
478108746Sobrien			u_long fumask;
47979968Sobrien
48079968Sobrien			curclass.umask = DEFAULT_UMASK;
48179968Sobrien			if (none || EMPTYSTR(arg))
48279968Sobrien				continue;
483108746Sobrien			errno = 0;
484108746Sobrien			endp = NULL;
485108746Sobrien			fumask = strtoul(arg, &endp, 8);
486108746Sobrien			if (errno || *arg == '\0' || *endp != '\0' ||
487108746Sobrien			    fumask > 0777) {
48879968Sobrien				syslog(LOG_WARNING,
48979968Sobrien				    "%s line %d: invalid umask %s",
49079968Sobrien				    infile, (int)line, arg);
49179968Sobrien				continue;
49279968Sobrien			}
493108746Sobrien			curclass.umask = (mode_t)fumask;
49479968Sobrien
49579968Sobrien		} else if (strcasecmp(word, "upload") == 0) {
49679968Sobrien			CONF_FLAG(upload);
49779968Sobrien			if (! CURCLASS_FLAGS_ISSET(upload))
49879968Sobrien				CURCLASS_FLAGS_CLR(modify);
49979968Sobrien
50079968Sobrien		} else {
50179968Sobrien			syslog(LOG_WARNING,
50279968Sobrien			    "%s line %d: unknown directive '%s'",
50379968Sobrien			    infile, (int)line, word);
50479968Sobrien			continue;
50579968Sobrien		}
506108746Sobrien nextline:
507108746Sobrien		;
50879968Sobrien	}
50979968Sobrien	REASSIGN(template, NULL);
51079968Sobrien	fclose(f);
51179968Sobrien}
51279968Sobrien
51379968Sobrien/*
51479968Sobrien * Show file listed in curclass.display first time in, and list all the
51579968Sobrien * files named in curclass.notify in the current directory.
51679968Sobrien * Send back responses with the prefix `code' + "-".
51779968Sobrien * If code == -1, flush the internal cache of directory names and return.
51879968Sobrien */
51979968Sobrienvoid
52079968Sobrienshow_chdir_messages(int code)
52179968Sobrien{
52279968Sobrien	static StringList *slist = NULL;
52379968Sobrien
52479968Sobrien	struct stat st;
52579968Sobrien	struct tm *t;
52679968Sobrien	glob_t	 gl;
52779968Sobrien	time_t	 now, then;
52879968Sobrien	int	 age;
52992282Sobrien	char	 curwd[MAXPATHLEN];
53079968Sobrien	char	*cp, **rlist;
53179968Sobrien
53279968Sobrien	if (code == -1) {
53379968Sobrien		if (slist != NULL)
53479968Sobrien			sl_free(slist, 1);
53579968Sobrien		slist = NULL;
53679968Sobrien		return;
53779968Sobrien	}
53879968Sobrien
53979968Sobrien	if (quietmessages)
54079968Sobrien		return;
54179968Sobrien
54279968Sobrien		/* Setup list for directory cache */
54379968Sobrien	if (slist == NULL)
54479968Sobrien		slist = sl_init();
54579968Sobrien	if (slist == NULL) {
54679968Sobrien		syslog(LOG_WARNING, "can't allocate memory for stringlist");
54779968Sobrien		return;
54879968Sobrien	}
54979968Sobrien
55079968Sobrien		/* Check if this directory has already been visited */
55192282Sobrien	if (getcwd(curwd, sizeof(curwd) - 1) == NULL) {
55279968Sobrien		syslog(LOG_WARNING, "can't getcwd: %s", strerror(errno));
55379968Sobrien		return;
55479968Sobrien	}
55592282Sobrien	if (sl_find(slist, curwd) != NULL)
55679968Sobrien		return;
55779968Sobrien
558161764Sobrien	cp = ftpd_strdup(curwd);
55979968Sobrien	if (sl_add(slist, cp) == -1)
56079968Sobrien		syslog(LOG_WARNING, "can't add `%s' to stringlist", cp);
56179968Sobrien
56279968Sobrien		/* First check for a display file */
56379968Sobrien	(void)display_file(curclass.display, code);
56479968Sobrien
56579968Sobrien		/* Now see if there are any notify files */
56679968Sobrien	if (EMPTYSTR(curclass.notify))
56779968Sobrien		return;
56879968Sobrien
56992282Sobrien	memset(&gl, 0, sizeof(gl));
570108746Sobrien	if (glob(curclass.notify, GLOB_BRACE|GLOB_LIMIT, NULL, &gl) != 0
57179968Sobrien	    || gl.gl_matchc == 0) {
57279968Sobrien		globfree(&gl);
57379968Sobrien		return;
57479968Sobrien	}
57579968Sobrien	time(&now);
57679968Sobrien	for (rlist = gl.gl_pathv; *rlist != NULL; rlist++) {
57779968Sobrien		if (stat(*rlist, &st) != 0)
57879968Sobrien			continue;
57979968Sobrien		if (!S_ISREG(st.st_mode))
58079968Sobrien			continue;
58179968Sobrien		then = st.st_mtime;
58279968Sobrien		if (code != 0) {
58379968Sobrien			reply(-code, "%s", "");
58479968Sobrien			code = 0;
58579968Sobrien		}
58679968Sobrien		reply(-code, "Please read the file %s", *rlist);
58779968Sobrien		t = localtime(&now);
58879968Sobrien		age = 365 * t->tm_year + t->tm_yday;
58979968Sobrien		t = localtime(&then);
59079968Sobrien		age -= 365 * t->tm_year + t->tm_yday;
59179968Sobrien		reply(-code, "  it was last modified on %.24s - %d day%s ago",
59279968Sobrien		    ctime(&then), age, PLURAL(age));
59379968Sobrien	}
59479968Sobrien	globfree(&gl);
59579968Sobrien}
59679968Sobrien
59779968Sobrienint
59879968Sobriendisplay_file(const char *file, int code)
59979968Sobrien{
60079968Sobrien	FILE   *f;
60192282Sobrien	char   *buf, *p;
60292282Sobrien	char	curwd[MAXPATHLEN];
60379968Sobrien	size_t	len;
60479968Sobrien	off_t	lastnum;
60579968Sobrien	time_t	now;
60679968Sobrien
60779968Sobrien	lastnum = 0;
60879968Sobrien	if (quietmessages)
60979968Sobrien		return (0);
61079968Sobrien
61179968Sobrien	if (EMPTYSTR(file))
61279968Sobrien		return(0);
61379968Sobrien	if ((f = fopen(file, "r")) == NULL)
61479968Sobrien		return (0);
61579968Sobrien	reply(-code, "%s", "");
61679968Sobrien
61779968Sobrien	for (;
61879968Sobrien	    (buf = fparseln(f, &len, NULL, "\0\0\0", 0)) != NULL; free(buf)) {
61979968Sobrien		if (len > 0)
62079968Sobrien			if (buf[len - 1] == '\n')
62179968Sobrien				buf[--len] = '\0';
62279968Sobrien		cprintf(stdout, "    ");
62379968Sobrien
62479968Sobrien		for (p = buf; *p; p++) {
62579968Sobrien			if (*p == '%') {
62679968Sobrien				p++;
62779968Sobrien				switch (*p) {
62879968Sobrien
62979968Sobrien				case 'c':
63079968Sobrien					cprintf(stdout, "%s",
63179968Sobrien					    curclass.classname ?
63279968Sobrien					    curclass.classname : "<unknown>");
63379968Sobrien					break;
63479968Sobrien
63579968Sobrien				case 'C':
63692282Sobrien					if (getcwd(curwd, sizeof(curwd)-1)
63792282Sobrien					    == NULL){
63879968Sobrien						syslog(LOG_WARNING,
63979968Sobrien						    "can't getcwd: %s",
64079968Sobrien						    strerror(errno));
64179968Sobrien						continue;
64279968Sobrien					}
64392282Sobrien					cprintf(stdout, "%s", curwd);
64479968Sobrien					break;
64579968Sobrien
64679968Sobrien				case 'E':
64779968Sobrien					if (! EMPTYSTR(emailaddr))
64879968Sobrien						cprintf(stdout, "%s",
64979968Sobrien						    emailaddr);
65079968Sobrien					break;
65179968Sobrien
65279968Sobrien				case 'L':
65379968Sobrien					cprintf(stdout, "%s", hostname);
65479968Sobrien					break;
65579968Sobrien
65679968Sobrien				case 'M':
65779968Sobrien					if (curclass.limit == -1) {
65879968Sobrien						cprintf(stdout, "unlimited");
65979968Sobrien						lastnum = 0;
66079968Sobrien					} else {
661108746Sobrien						cprintf(stdout, LLF,
662108746Sobrien						    (LLT)curclass.limit);
66379968Sobrien						lastnum = curclass.limit;
66479968Sobrien					}
66579968Sobrien					break;
66679968Sobrien
66779968Sobrien				case 'N':
66879968Sobrien					cprintf(stdout, "%d", connections);
66979968Sobrien					lastnum = connections;
67079968Sobrien					break;
67179968Sobrien
67279968Sobrien				case 'R':
67379968Sobrien					cprintf(stdout, "%s", remotehost);
67479968Sobrien					break;
67579968Sobrien
67679968Sobrien				case 's':
67779968Sobrien					if (lastnum != 1)
67879968Sobrien						cprintf(stdout, "s");
67979968Sobrien					break;
68079968Sobrien
68179968Sobrien				case 'S':
68279968Sobrien					if (lastnum != 1)
68379968Sobrien						cprintf(stdout, "S");
68479968Sobrien					break;
68579968Sobrien
68679968Sobrien				case 'T':
68779968Sobrien					now = time(NULL);
68879968Sobrien					cprintf(stdout, "%.24s", ctime(&now));
68979968Sobrien					break;
69079968Sobrien
69179968Sobrien				case 'U':
69279968Sobrien					cprintf(stdout, "%s",
69379968Sobrien					    pw ? pw->pw_name : "<unknown>");
69479968Sobrien					break;
69579968Sobrien
69679968Sobrien				case '%':
69779968Sobrien					CPUTC('%', stdout);
69879968Sobrien					break;
69979968Sobrien
70079968Sobrien				}
70179968Sobrien			} else
70279968Sobrien				CPUTC(*p, stdout);
70379968Sobrien		}
70479968Sobrien		cprintf(stdout, "\r\n");
70579968Sobrien	}
70679968Sobrien
70779968Sobrien	(void)fflush(stdout);
70879968Sobrien	(void)fclose(f);
70979968Sobrien	return (1);
71079968Sobrien}
71179968Sobrien
71279968Sobrien/*
71379968Sobrien * Parse src, expanding '%' escapes, into dst (which must be at least
71479968Sobrien * MAXPATHLEN long).
71579968Sobrien */
71679968Sobrienvoid
71779968Sobrienformat_path(char *dst, const char *src)
71879968Sobrien{
71979968Sobrien	size_t len;
72079968Sobrien	const char *p;
72179968Sobrien
72279968Sobrien	dst[0] = '\0';
72379968Sobrien	len = 0;
72479968Sobrien	if (src == NULL)
72579968Sobrien		return;
72679968Sobrien	for (p = src; *p && len < MAXPATHLEN; p++) {
72779968Sobrien		if (*p == '%') {
72879968Sobrien			p++;
72979968Sobrien			switch (*p) {
73079968Sobrien
73179968Sobrien			case 'c':
73279968Sobrien				len += strlcpy(dst + len, curclass.classname,
73379968Sobrien				    MAXPATHLEN - len);
73479968Sobrien				break;
73579968Sobrien
73679968Sobrien			case 'd':
73779968Sobrien				len += strlcpy(dst + len, pw->pw_dir,
73879968Sobrien				    MAXPATHLEN - len);
73979968Sobrien				break;
74079968Sobrien
74179968Sobrien			case 'u':
74279968Sobrien				len += strlcpy(dst + len, pw->pw_name,
74379968Sobrien				    MAXPATHLEN - len);
74479968Sobrien				break;
74579968Sobrien
74679968Sobrien			case '%':
74779968Sobrien				dst[len++] = '%';
74879968Sobrien				break;
74979968Sobrien
75079968Sobrien			}
75179968Sobrien		} else
75279968Sobrien			dst[len++] = *p;
75379968Sobrien	}
75479968Sobrien	if (len < MAXPATHLEN)
75579968Sobrien		dst[len] = '\0';
75679968Sobrien	dst[MAXPATHLEN - 1] = '\0';
75779968Sobrien}
75879968Sobrien
75979968Sobrien/*
76079968Sobrien * Find s2 at the end of s1.  If found, return a string up to (but
76179968Sobrien * not including) s2, otherwise returns NULL.
76279968Sobrien */
76379968Sobrienstatic char *
76479968Sobrienstrend(const char *s1, char *s2)
76579968Sobrien{
76679968Sobrien	static	char buf[MAXPATHLEN];
76779968Sobrien
76879968Sobrien	char	*start;
76979968Sobrien	size_t	l1, l2;
77079968Sobrien
77179968Sobrien	l1 = strlen(s1);
77279968Sobrien	l2 = strlen(s2);
77379968Sobrien
77492282Sobrien	if (l2 >= l1 || l1 >= sizeof(buf))
77579968Sobrien		return(NULL);
77679968Sobrien
77779968Sobrien	strlcpy(buf, s1, sizeof(buf));
77879968Sobrien	start = buf + (l1 - l2);
77979968Sobrien
78079968Sobrien	if (strcmp(start, s2) == 0) {
78179968Sobrien		*start = '\0';
78279968Sobrien		return(buf);
78379968Sobrien	} else
78479968Sobrien		return(NULL);
78579968Sobrien}
78679968Sobrien
78779968Sobrienstatic int
78879968Sobrienfiletypematch(char *types, int mode)
78979968Sobrien{
79079968Sobrien	for ( ; types[0] != '\0'; types++)
79179968Sobrien		switch (*types) {
79279968Sobrien		  case 'd':
79379968Sobrien			if (S_ISDIR(mode))
79479968Sobrien				return(1);
79579968Sobrien			break;
79679968Sobrien		  case 'f':
79779968Sobrien			if (S_ISREG(mode))
79879968Sobrien				return(1);
79979968Sobrien			break;
80079968Sobrien		}
80179968Sobrien	return(0);
80279968Sobrien}
80379968Sobrien
80479968Sobrien/*
80579968Sobrien * Look for a conversion.  If we succeed, return a pointer to the
80679968Sobrien * command to execute for the conversion.
80779968Sobrien *
80879968Sobrien * The command is stored in a static array so there's no memory
80979968Sobrien * leak problems, and not too much to change in ftpd.c.  This
81079968Sobrien * routine doesn't need to be re-entrant unless we start using a
81179968Sobrien * multi-threaded ftpd, and that's not likely for a while...
81279968Sobrien */
81379968Sobrienchar **
81479968Sobriendo_conversion(const char *fname)
81579968Sobrien{
81679968Sobrien	struct ftpconv	*cp;
81779968Sobrien	struct stat	 st;
81879968Sobrien	int		 o_errno;
81979968Sobrien	char		*base = NULL;
82079968Sobrien	char		*cmd, *p, *lp, **argv;
82179968Sobrien	StringList	*sl;
82279968Sobrien
82379968Sobrien	o_errno = errno;
82479968Sobrien	sl = NULL;
82579968Sobrien	cmd = NULL;
82679968Sobrien	for (cp = curclass.conversions; cp != NULL; cp = cp->next) {
82779968Sobrien		if (cp->suffix == NULL) {
82879968Sobrien			syslog(LOG_WARNING,
82979968Sobrien			    "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!");
83079968Sobrien			continue;
83179968Sobrien		}
83279968Sobrien		if ((base = strend(fname, cp->suffix)) == NULL)
83379968Sobrien			continue;
83479968Sobrien		if (cp->types == NULL || cp->disable == NULL ||
83579968Sobrien		    cp->command == NULL)
83679968Sobrien			continue;
83779968Sobrien					/* Is it enabled? */
83879968Sobrien		if (strcmp(cp->disable, ".") != 0 &&
83979968Sobrien		    stat(cp->disable, &st) == 0)
84079968Sobrien				continue;
84179968Sobrien					/* Does the base exist? */
84279968Sobrien		if (stat(base, &st) < 0)
84379968Sobrien			continue;
84479968Sobrien					/* Is the file type ok */
84579968Sobrien		if (!filetypematch(cp->types, st.st_mode))
84679968Sobrien			continue;
84779968Sobrien		break;			/* "We have a winner!" */
84879968Sobrien	}
84979968Sobrien
85079968Sobrien	/* If we got through the list, no conversion */
85179968Sobrien	if (cp == NULL)
85279968Sobrien		goto cleanup_do_conv;
85379968Sobrien
85479968Sobrien	/* Split up command into an argv */
85579968Sobrien	if ((sl = sl_init()) == NULL)
85679968Sobrien		goto cleanup_do_conv;
857161764Sobrien	cmd = ftpd_strdup(cp->command);
85879968Sobrien	p = cmd;
85979968Sobrien	while (p) {
86079968Sobrien		NEXTWORD(p, lp);
86179968Sobrien		if (strcmp(lp, "%s") == 0)
86279968Sobrien			lp = base;
863161764Sobrien		if (sl_add(sl, ftpd_strdup(lp)) == -1)
86479968Sobrien			goto cleanup_do_conv;
86579968Sobrien	}
86679968Sobrien
86779968Sobrien	if (sl_add(sl, NULL) == -1)
86879968Sobrien		goto cleanup_do_conv;
86979968Sobrien	argv = sl->sl_str;
87079968Sobrien	free(cmd);
87179968Sobrien	free(sl);
87279968Sobrien	return(argv);
87379968Sobrien
87479968Sobrien cleanup_do_conv:
87579968Sobrien	if (sl)
87679968Sobrien		sl_free(sl, 1);
87779968Sobrien	free(cmd);
87879968Sobrien	errno = o_errno;
87979968Sobrien	return(NULL);
88079968Sobrien}
88179968Sobrien
88279968Sobrien/*
88379968Sobrien * Count the number of current connections, reading from
88479968Sobrien *	/var/run/ftpd.pids-<class>
88579968Sobrien * Does a kill -0 on each pid in that file, and only counts
88679968Sobrien * processes that exist (or frees the slot if it doesn't).
88779968Sobrien * Adds getpid() to the first free slot. Truncates the file
88879968Sobrien * if possible.
88979968Sobrien */
89079968Sobrienvoid
89179968Sobriencount_users(void)
89279968Sobrien{
89379968Sobrien	char	fn[MAXPATHLEN];
89479968Sobrien	int	fd, i, last;
89579968Sobrien	size_t	count;
89679968Sobrien	pid_t  *pids, mypid;
89779968Sobrien	struct stat sb;
89879968Sobrien
89979968Sobrien	(void)strlcpy(fn, _PATH_CLASSPIDS, sizeof(fn));
90079968Sobrien	(void)strlcat(fn, curclass.classname, sizeof(fn));
90179968Sobrien	pids = NULL;
90279968Sobrien	connections = 1;
90379968Sobrien
90479968Sobrien	if ((fd = open(fn, O_RDWR | O_CREAT, 0600)) == -1)
90579968Sobrien		return;
90679968Sobrien	if (lockf(fd, F_TLOCK, 0) == -1)
90779968Sobrien		goto cleanup_count;
90879968Sobrien	if (fstat(fd, &sb) == -1)
90979968Sobrien		goto cleanup_count;
91079968Sobrien	if ((pids = malloc(sb.st_size + sizeof(pid_t))) == NULL)
91179968Sobrien		goto cleanup_count;
91279968Sobrien	count = read(fd, pids, sb.st_size);
91379968Sobrien	if (count < 0 || count != sb.st_size)
91479968Sobrien		goto cleanup_count;
91579968Sobrien	count /= sizeof(pid_t);
91679968Sobrien	mypid = getpid();
91779968Sobrien	last = 0;
91879968Sobrien	for (i = 0; i < count; i++) {
91979968Sobrien		if (pids[i] == 0)
92079968Sobrien			continue;
92179968Sobrien		if (kill(pids[i], 0) == -1 && errno != EPERM) {
92279968Sobrien			if (mypid != 0) {
92379968Sobrien				pids[i] = mypid;
92479968Sobrien				mypid = 0;
92579968Sobrien				last = i;
92679968Sobrien			}
92779968Sobrien		} else {
92879968Sobrien			connections++;
92979968Sobrien			last = i;
93079968Sobrien		}
93179968Sobrien	}
93279968Sobrien	if (mypid != 0) {
93379968Sobrien		if (pids[last] != 0)
93479968Sobrien			last++;
93579968Sobrien		pids[last] = mypid;
93679968Sobrien	}
93779968Sobrien	count = (last + 1) * sizeof(pid_t);
93879968Sobrien	if (lseek(fd, 0, SEEK_SET) == -1)
93979968Sobrien		goto cleanup_count;
94079968Sobrien	if (write(fd, pids, count) == -1)
94179968Sobrien		goto cleanup_count;
94279968Sobrien	(void)ftruncate(fd, count);
94379968Sobrien
94479968Sobrien cleanup_count:
94579968Sobrien	if (lseek(fd, 0, SEEK_SET) != -1)
94679968Sobrien		(void)lockf(fd, F_ULOCK, 0);
94779968Sobrien	close(fd);
94879968Sobrien	REASSIGN(pids, NULL);
94979968Sobrien}
950