xref: /netbsd-src/libexec/ftpd/conf.c (revision 5e4c038a45edbc7d63b7c2daa76e29f88b64a4e3)
1 /*	$NetBSD: conf.c,v 1.47 2002/05/30 00:24:47 enami Exp $	*/
2 
3 /*-
4  * Copyright (c) 1997-2001 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.47 2002/05/30 00:24:47 enami Exp $");
42 #endif /* not lint */
43 
44 #include <sys/types.h>
45 #include <sys/param.h>
46 #include <sys/socket.h>
47 #include <sys/stat.h>
48 
49 #include <ctype.h>
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <glob.h>
53 #include <netdb.h>
54 #include <setjmp.h>
55 #include <signal.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <stringlist.h>
60 #include <syslog.h>
61 #include <time.h>
62 #include <unistd.h>
63 #include <util.h>
64 
65 #ifdef KERBEROS5
66 #include <krb5/krb5.h>
67 #endif
68 
69 #include "extern.h"
70 #include "pathnames.h"
71 
72 static char *strend(const char *, char *);
73 static int filetypematch(char *, int);
74 
75 
76 		/* class defaults */
77 #define DEFAULT_LIMIT		-1		/* unlimited connections */
78 #define DEFAULT_MAXFILESIZE	-1		/* unlimited file size */
79 #define DEFAULT_MAXTIMEOUT	7200		/* 2 hours */
80 #define DEFAULT_TIMEOUT		900		/* 15 minutes */
81 #define DEFAULT_UMASK		027		/* 15 minutes */
82 
83 /*
84  * Initialise curclass to an `empty' state
85  */
86 void
87 init_curclass(void)
88 {
89 	struct ftpconv	*conv, *cnext;
90 
91 	for (conv = curclass.conversions; conv != NULL; conv = cnext) {
92 		REASSIGN(conv->suffix, NULL);
93 		REASSIGN(conv->types, NULL);
94 		REASSIGN(conv->disable, NULL);
95 		REASSIGN(conv->command, NULL);
96 		cnext = conv->next;
97 		free(conv);
98 	}
99 
100 	memset((char *)&curclass.advertise, 0, sizeof(curclass.advertise));
101 	curclass.advertise.su_len = 0;		/* `not used' */
102 	REASSIGN(curclass.chroot, NULL);
103 	REASSIGN(curclass.classname, NULL);
104 	curclass.conversions =	NULL;
105 	REASSIGN(curclass.display, NULL);
106 	REASSIGN(curclass.homedir, NULL);
107 	curclass.limit =	DEFAULT_LIMIT;
108 	REASSIGN(curclass.limitfile, NULL);
109 	curclass.maxfilesize =	DEFAULT_MAXFILESIZE;
110 	curclass.maxrateget =	0;
111 	curclass.maxrateput =	0;
112 	curclass.maxtimeout =	DEFAULT_MAXTIMEOUT;
113 	REASSIGN(curclass.motd, xstrdup(_PATH_FTPLOGINMESG));
114 	REASSIGN(curclass.notify, NULL);
115 	curclass.portmin =	0;
116 	curclass.portmax =	0;
117 	curclass.rateget =	0;
118 	curclass.rateput =	0;
119 	curclass.timeout =	DEFAULT_TIMEOUT;
120 	    /* curclass.type is set elsewhere */
121 	curclass.umask =	DEFAULT_UMASK;
122 	curclass.mmapsize =	0;
123 	curclass.readsize =	0;
124 	curclass.writesize =	0;
125 	curclass.sendbufsize =	0;
126 	curclass.sendlowat =	0;
127 
128 	CURCLASS_FLAGS_SET(checkportcmd);
129 	CURCLASS_FLAGS_CLR(denyquick);
130 	CURCLASS_FLAGS_SET(modify);
131 	CURCLASS_FLAGS_SET(passive);
132 	CURCLASS_FLAGS_CLR(private);
133 	CURCLASS_FLAGS_CLR(sanenames);
134 	CURCLASS_FLAGS_SET(upload);
135 }
136 
137 /*
138  * Parse the configuration file, looking for the named class, and
139  * define curclass to contain the appropriate settings.
140  */
141 void
142 parse_conf(const char *findclass)
143 {
144 	FILE		*f;
145 	char		*buf, *p;
146 	size_t		 len;
147 	LLT		 llval;
148 	int		 none, match;
149 	char		*endp;
150 	char		*class, *word, *arg, *template;
151 	const char	*infile;
152 	size_t		 line;
153 	unsigned int	 timeout;
154 	struct ftpconv	*conv, *cnext;
155 
156 	init_curclass();
157 	REASSIGN(curclass.classname, xstrdup(findclass));
158 			/* set more guest defaults */
159 	if (strcasecmp(findclass, "guest") == 0) {
160 		CURCLASS_FLAGS_CLR(modify);
161 		curclass.umask = 0707;
162 	}
163 
164 	infile = conffilename(_PATH_FTPDCONF);
165 	if ((f = fopen(infile, "r")) == NULL)
166 		return;
167 
168 	line = 0;
169 	template = NULL;
170 	for (;
171 	    (buf = fparseln(f, &len, &line, NULL, FPARSELN_UNESCCOMM |
172 	    		FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL;
173 	    free(buf)) {
174 		none = match = 0;
175 		p = buf;
176 		if (len < 1)
177 			continue;
178 		if (p[len - 1] == '\n')
179 			p[--len] = '\0';
180 		if (EMPTYSTR(p))
181 			continue;
182 
183 		NEXTWORD(p, word);
184 		NEXTWORD(p, class);
185 		NEXTWORD(p, arg);
186 		if (EMPTYSTR(word) || EMPTYSTR(class))
187 			continue;
188 		if (strcasecmp(class, "none") == 0)
189 			none = 1;
190 		if (! (strcasecmp(class, findclass) == 0 ||
191 		       (template != NULL && strcasecmp(class, template) == 0) ||
192 		       none ||
193 		       strcasecmp(class, "all") == 0) )
194 			continue;
195 
196 #define CONF_FLAG(x) \
197 	do { \
198 		if (none || \
199 		    (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0)) \
200 			CURCLASS_FLAGS_CLR(x); \
201 		else \
202 			CURCLASS_FLAGS_SET(x); \
203 	} while (0)
204 
205 #define CONF_STRING(x) \
206 	do { \
207 		if (none || EMPTYSTR(arg)) \
208 			arg = NULL; \
209 		else \
210 			arg = xstrdup(arg); \
211 		REASSIGN(curclass.x, arg); \
212 	} while (0)
213 
214 
215 		if (0)  {
216 			/* no-op */
217 
218 		} else if ((strcasecmp(word, "advertise") == 0)
219 			|| (strcasecmp(word, "advertize") == 0)) {
220 			struct addrinfo	hints, *res;
221 			int		error;
222 
223 			memset((char *)&curclass.advertise, 0,
224 			    sizeof(curclass.advertise));
225 			curclass.advertise.su_len = 0;
226 			if (none || EMPTYSTR(arg))
227 				continue;
228 			res = NULL;
229 			memset(&hints, 0, sizeof(hints));
230 					/*
231 					 * only get addresses of the family
232 					 * that we're listening on
233 					 */
234 			hints.ai_family = ctrl_addr.su_family;
235 			hints.ai_socktype = SOCK_STREAM;
236 			error = getaddrinfo(arg, "0", &hints, &res);
237 			if (error) {
238 				syslog(LOG_WARNING, "%s line %d: %s",
239 				    infile, (int)line, gai_strerror(error));
240  advertiseparsefail:
241 				if (res)
242 					freeaddrinfo(res);
243 				continue;
244 			}
245 			if (res->ai_next) {
246 				syslog(LOG_WARNING,
247     "%s line %d: multiple addresses returned for `%s'; please be more specific",
248 				    infile, (int)line, arg);
249 				goto advertiseparsefail;
250 			}
251 			if (sizeof(curclass.advertise) < res->ai_addrlen || (
252 #ifdef INET6
253 			    res->ai_family != AF_INET6 &&
254 #endif
255 			    res->ai_family != AF_INET)) {
256 				syslog(LOG_WARNING,
257     "%s line %d: unsupported protocol %d for `%s'",
258 				    infile, (int)line, res->ai_family, arg);
259 				goto advertiseparsefail;
260 			}
261 			memcpy(&curclass.advertise, res->ai_addr,
262 			    res->ai_addrlen);
263 			curclass.advertise.su_len = res->ai_addrlen;
264 			freeaddrinfo(res);
265 
266 		} else if (strcasecmp(word, "checkportcmd") == 0) {
267 			CONF_FLAG(checkportcmd);
268 
269 		} else if (strcasecmp(word, "chroot") == 0) {
270 			CONF_STRING(chroot);
271 
272 		} else if (strcasecmp(word, "classtype") == 0) {
273 			if (!none && !EMPTYSTR(arg)) {
274 				if (strcasecmp(arg, "GUEST") == 0)
275 					curclass.type = CLASS_GUEST;
276 				else if (strcasecmp(arg, "CHROOT") == 0)
277 					curclass.type = CLASS_CHROOT;
278 				else if (strcasecmp(arg, "REAL") == 0)
279 					curclass.type = CLASS_REAL;
280 				else {
281 					syslog(LOG_WARNING,
282 				    "%s line %d: unknown class type `%s'",
283 					    infile, (int)line, arg);
284 					continue;
285 				}
286 			}
287 
288 		} else if (strcasecmp(word, "conversion") == 0) {
289 			char *suffix, *types, *disable, *convcmd;
290 
291 			if (EMPTYSTR(arg)) {
292 				syslog(LOG_WARNING,
293 				    "%s line %d: %s requires a suffix",
294 				    infile, (int)line, word);
295 				continue;	/* need a suffix */
296 			}
297 			NEXTWORD(p, types);
298 			NEXTWORD(p, disable);
299 			convcmd = p;
300 			if (convcmd)
301 				convcmd += strspn(convcmd, " \t");
302 			suffix = xstrdup(arg);
303 			if (none || EMPTYSTR(types) ||
304 			    EMPTYSTR(disable) || EMPTYSTR(convcmd)) {
305 				types = NULL;
306 				disable = NULL;
307 				convcmd = NULL;
308 			} else {
309 				types = xstrdup(types);
310 				disable = xstrdup(disable);
311 				convcmd = xstrdup(convcmd);
312 			}
313 			for (conv = curclass.conversions; conv != NULL;
314 			    conv = conv->next) {
315 				if (strcmp(conv->suffix, suffix) == 0)
316 					break;
317 			}
318 			if (conv == NULL) {
319 				conv = (struct ftpconv *)
320 				    calloc(1, sizeof(struct ftpconv));
321 				if (conv == NULL) {
322 					syslog(LOG_WARNING, "can't malloc");
323 					continue;
324 				}
325 				conv->next = NULL;
326 				for (cnext = curclass.conversions;
327 				    cnext != NULL; cnext = cnext->next)
328 					if (cnext->next == NULL)
329 						break;
330 				if (cnext != NULL)
331 					cnext->next = conv;
332 				else
333 					curclass.conversions = conv;
334 			}
335 			REASSIGN(conv->suffix, suffix);
336 			REASSIGN(conv->types, types);
337 			REASSIGN(conv->disable, disable);
338 			REASSIGN(conv->command, convcmd);
339 
340 		} else if (strcasecmp(word, "denyquick") == 0) {
341 			CONF_FLAG(denyquick);
342 
343 		} else if (strcasecmp(word, "display") == 0) {
344 			CONF_STRING(display);
345 
346 		} else if (strcasecmp(word, "homedir") == 0) {
347 			CONF_STRING(homedir);
348 
349 		} else if (strcasecmp(word, "limit") == 0) {
350 			int limit;
351 
352 			curclass.limit = DEFAULT_LIMIT;
353 			REASSIGN(curclass.limitfile, NULL);
354 			if (none || EMPTYSTR(arg))
355 				continue;
356 			limit = (int)strtol(arg, &endp, 10);
357 			if (*endp != 0) {
358 				syslog(LOG_WARNING,
359 				    "%s line %d: invalid limit %s",
360 				    infile, (int)line, arg);
361 				continue;
362 			}
363 			curclass.limit = limit;
364 			REASSIGN(curclass.limitfile,
365 			    EMPTYSTR(p) ? NULL : xstrdup(p));
366 
367 		} else if (strcasecmp(word, "maxfilesize") == 0) {
368 			curclass.maxfilesize = DEFAULT_MAXFILESIZE;
369 			if (none || EMPTYSTR(arg))
370 				continue;
371 			llval = strsuftoll(arg);
372 			if (llval == -1) {
373 				syslog(LOG_WARNING,
374 				    "%s line %d: invalid maxfilesize %s",
375 				    infile, (int)line, arg);
376 				continue;
377 			}
378 			curclass.maxfilesize = llval;
379 
380 		} else if (strcasecmp(word, "maxtimeout") == 0) {
381 			curclass.maxtimeout = DEFAULT_MAXTIMEOUT;
382 			if (none || EMPTYSTR(arg))
383 				continue;
384 			timeout = (unsigned int)strtoul(arg, &endp, 10);
385 			if (*endp != 0) {
386 				syslog(LOG_WARNING,
387 				    "%s line %d: invalid maxtimeout %s",
388 				    infile, (int)line, arg);
389 				continue;
390 			}
391 			if (timeout < 30) {
392 				syslog(LOG_WARNING,
393 				    "%s line %d: maxtimeout %d < 30 seconds",
394 				    infile, (int)line, timeout);
395 				continue;
396 			}
397 			if (timeout < curclass.timeout) {
398 				syslog(LOG_WARNING,
399 				    "%s line %d: maxtimeout %d < timeout (%d)",
400 				    infile, (int)line, timeout,
401 				    curclass.timeout);
402 				continue;
403 			}
404 			curclass.maxtimeout = timeout;
405 
406 		} else if (strcasecmp(word, "mmapsize") == 0) {
407 			curclass.mmapsize = 0;
408 			if (none || EMPTYSTR(arg))
409 				continue;
410 			llval = strsuftoll(arg);
411 			if (llval == -1) {
412 				syslog(LOG_WARNING,
413 				    "%s line %d: invalid mmapsize %s",
414 				    infile, (int)line, arg);
415 				continue;
416 			}
417 			curclass.mmapsize = llval;
418 
419 		} else if (strcasecmp(word, "readsize") == 0) {
420 			curclass.readsize = 0;
421 			if (none || EMPTYSTR(arg))
422 				continue;
423 			llval = strsuftoll(arg);
424 			if (llval == -1) {
425 				syslog(LOG_WARNING,
426 				    "%s line %d: invalid readsize %s",
427 				    infile, (int)line, arg);
428 				continue;
429 			}
430 			curclass.readsize = llval;
431 
432 		} else if (strcasecmp(word, "writesize") == 0) {
433 			curclass.writesize = 0;
434 			if (none || EMPTYSTR(arg))
435 				continue;
436 			llval = strsuftoll(arg);
437 			if (llval == -1) {
438 				syslog(LOG_WARNING,
439 				    "%s line %d: invalid writesize %s",
440 				    infile, (int)line, arg);
441 				continue;
442 			}
443 			curclass.writesize = llval;
444 
445 		} else if (strcasecmp(word, "sendbufsize") == 0) {
446 			curclass.sendbufsize = 0;
447 			if (none || EMPTYSTR(arg))
448 				continue;
449 			llval = strsuftoll(arg);
450 			if (llval == -1) {
451 				syslog(LOG_WARNING,
452 				    "%s line %d: invalid sendbufsize %s",
453 				    infile, (int)line, arg);
454 				continue;
455 			}
456 			curclass.sendbufsize = llval;
457 
458 		} else if (strcasecmp(word, "sendlowat") == 0) {
459 			curclass.sendlowat = 0;
460 			if (none || EMPTYSTR(arg))
461 				continue;
462 			llval = strsuftoll(arg);
463 			if (llval == -1) {
464 				syslog(LOG_WARNING,
465 				    "%s line %d: invalid sendlowat %s",
466 				    infile, (int)line, arg);
467 				continue;
468 			}
469 			curclass.sendlowat = llval;
470 
471 		} else if (strcasecmp(word, "modify") == 0) {
472 			CONF_FLAG(modify);
473 
474 		} else if (strcasecmp(word, "motd") == 0) {
475 			CONF_STRING(motd);
476 
477 		} else if (strcasecmp(word, "notify") == 0) {
478 			CONF_STRING(notify);
479 
480 		} else if (strcasecmp(word, "passive") == 0) {
481 			CONF_FLAG(passive);
482 
483 		} else if (strcasecmp(word, "portrange") == 0) {
484 			int minport, maxport;
485 			char *min, *max;
486 
487 			curclass.portmin = 0;
488 			curclass.portmax = 0;
489 			if (none || EMPTYSTR(arg))
490 				continue;
491 			min = arg;
492 			NEXTWORD(p, max);
493 			if (EMPTYSTR(max)) {
494 				syslog(LOG_WARNING,
495 				   "%s line %d: missing maxport argument",
496 				   infile, (int)line);
497 				continue;
498 			}
499 			minport = (int)strtol(min, &endp, 10);
500 			if (*endp != 0 || minport < IPPORT_RESERVED ||
501 			    minport > IPPORT_ANONMAX) {
502 				syslog(LOG_WARNING,
503 				    "%s line %d: invalid minport %s",
504 				    infile, (int)line, min);
505 				continue;
506 			}
507 			maxport = (int)strtol(max, &endp, 10);
508 			if (*endp != 0 || maxport < IPPORT_RESERVED ||
509 			    maxport > IPPORT_ANONMAX) {
510 				syslog(LOG_WARNING,
511 				    "%s line %d: invalid maxport %s",
512 				    infile, (int)line, max);
513 				continue;
514 			}
515 			if (minport >= maxport) {
516 				syslog(LOG_WARNING,
517 				    "%s line %d: minport %d >= maxport %d",
518 				    infile, (int)line, minport, maxport);
519 				continue;
520 			}
521 			curclass.portmin = minport;
522 			curclass.portmax = maxport;
523 
524 		} else if (strcasecmp(word, "private") == 0) {
525 			CONF_FLAG(private);
526 
527 		} else if (strcasecmp(word, "rateget") == 0) {
528 			curclass.maxrateget = 0;
529 			curclass.rateget = 0;
530 			if (none || EMPTYSTR(arg))
531 				continue;
532 			llval = strsuftoll(arg);
533 			if (llval == -1) {
534 				syslog(LOG_WARNING,
535 				    "%s line %d: invalid rateget %s",
536 				    infile, (int)line, arg);
537 				continue;
538 			}
539 			curclass.maxrateget = llval;
540 			curclass.rateget = llval;
541 
542 		} else if (strcasecmp(word, "rateput") == 0) {
543 			curclass.maxrateput = 0;
544 			curclass.rateput = 0;
545 			if (none || EMPTYSTR(arg))
546 				continue;
547 			llval = strsuftoll(arg);
548 			if (llval == -1) {
549 				syslog(LOG_WARNING,
550 				    "%s line %d: invalid rateput %s",
551 				    infile, (int)line, arg);
552 				continue;
553 			}
554 			curclass.maxrateput = llval;
555 			curclass.rateput = llval;
556 
557 		} else if (strcasecmp(word, "sanenames") == 0) {
558 			CONF_FLAG(sanenames);
559 
560 		} else if (strcasecmp(word, "timeout") == 0) {
561 			curclass.timeout = DEFAULT_TIMEOUT;
562 			if (none || EMPTYSTR(arg))
563 				continue;
564 			timeout = (unsigned int)strtoul(arg, &endp, 10);
565 			if (*endp != 0) {
566 				syslog(LOG_WARNING,
567 				    "%s line %d: invalid timeout %s",
568 				    infile, (int)line, arg);
569 				continue;
570 			}
571 			if (timeout < 30) {
572 				syslog(LOG_WARNING,
573 				    "%s line %d: timeout %d < 30 seconds",
574 				    infile, (int)line, timeout);
575 				continue;
576 			}
577 			if (timeout > curclass.maxtimeout) {
578 				syslog(LOG_WARNING,
579 				    "%s line %d: timeout %d > maxtimeout (%d)",
580 				    infile, (int)line, timeout,
581 				    curclass.maxtimeout);
582 				continue;
583 			}
584 			curclass.timeout = timeout;
585 
586 		} else if (strcasecmp(word, "template") == 0) {
587 			if (none)
588 				continue;
589 			REASSIGN(template, EMPTYSTR(arg) ? NULL : xstrdup(arg));
590 
591 		} else if (strcasecmp(word, "umask") == 0) {
592 			mode_t fumask;
593 
594 			curclass.umask = DEFAULT_UMASK;
595 			if (none || EMPTYSTR(arg))
596 				continue;
597 			fumask = (mode_t)strtoul(arg, &endp, 8);
598 			if (*endp != 0 || fumask > 0777) {
599 				syslog(LOG_WARNING,
600 				    "%s line %d: invalid umask %s",
601 				    infile, (int)line, arg);
602 				continue;
603 			}
604 			curclass.umask = fumask;
605 
606 		} else if (strcasecmp(word, "upload") == 0) {
607 			CONF_FLAG(upload);
608 			if (! CURCLASS_FLAGS_ISSET(upload))
609 				CURCLASS_FLAGS_CLR(modify);
610 
611 		} else {
612 			syslog(LOG_WARNING,
613 			    "%s line %d: unknown directive '%s'",
614 			    infile, (int)line, word);
615 			continue;
616 		}
617 	}
618 	REASSIGN(template, NULL);
619 	fclose(f);
620 }
621 
622 /*
623  * Show file listed in curclass.display first time in, and list all the
624  * files named in curclass.notify in the current directory.
625  * Send back responses with the prefix `code' + "-".
626  * If code == -1, flush the internal cache of directory names and return.
627  */
628 void
629 show_chdir_messages(int code)
630 {
631 	static StringList *slist = NULL;
632 
633 	struct stat st;
634 	struct tm *t;
635 	glob_t	 gl;
636 	time_t	 now, then;
637 	int	 age;
638 	char	 curwd[MAXPATHLEN];
639 	char	*cp, **rlist;
640 
641 	if (code == -1) {
642 		if (slist != NULL)
643 			sl_free(slist, 1);
644 		slist = NULL;
645 		return;
646 	}
647 
648 	if (quietmessages)
649 		return;
650 
651 		/* Setup list for directory cache */
652 	if (slist == NULL)
653 		slist = sl_init();
654 	if (slist == NULL) {
655 		syslog(LOG_WARNING, "can't allocate memory for stringlist");
656 		return;
657 	}
658 
659 		/* Check if this directory has already been visited */
660 	if (getcwd(curwd, sizeof(curwd) - 1) == NULL) {
661 		syslog(LOG_WARNING, "can't getcwd: %s", strerror(errno));
662 		return;
663 	}
664 	if (sl_find(slist, curwd) != NULL)
665 		return;
666 
667 	cp = xstrdup(curwd);
668 	if (sl_add(slist, cp) == -1)
669 		syslog(LOG_WARNING, "can't add `%s' to stringlist", cp);
670 
671 		/* First check for a display file */
672 	(void)display_file(curclass.display, code);
673 
674 		/* Now see if there are any notify files */
675 	if (EMPTYSTR(curclass.notify))
676 		return;
677 
678 	memset(&gl, 0, sizeof(gl));
679 	if (glob(curclass.notify, GLOB_LIMIT, NULL, &gl) != 0
680 	    || gl.gl_matchc == 0) {
681 		globfree(&gl);
682 		return;
683 	}
684 	time(&now);
685 	for (rlist = gl.gl_pathv; *rlist != NULL; rlist++) {
686 		if (stat(*rlist, &st) != 0)
687 			continue;
688 		if (!S_ISREG(st.st_mode))
689 			continue;
690 		then = st.st_mtime;
691 		if (code != 0) {
692 			reply(-code, "%s", "");
693 			code = 0;
694 		}
695 		reply(-code, "Please read the file %s", *rlist);
696 		t = localtime(&now);
697 		age = 365 * t->tm_year + t->tm_yday;
698 		t = localtime(&then);
699 		age -= 365 * t->tm_year + t->tm_yday;
700 		reply(-code, "  it was last modified on %.24s - %d day%s ago",
701 		    ctime(&then), age, PLURAL(age));
702 	}
703 	globfree(&gl);
704 }
705 
706 int
707 display_file(const char *file, int code)
708 {
709 	FILE   *f;
710 	char   *buf, *p;
711 	char	curwd[MAXPATHLEN];
712 	size_t	len;
713 	off_t	lastnum;
714 	time_t	now;
715 
716 	lastnum = 0;
717 	if (quietmessages)
718 		return (0);
719 
720 	if (EMPTYSTR(file))
721 		return(0);
722 	if ((f = fopen(file, "r")) == NULL)
723 		return (0);
724 	reply(-code, "%s", "");
725 
726 	for (;
727 	    (buf = fparseln(f, &len, NULL, "\0\0\0", 0)) != NULL; free(buf)) {
728 		if (len > 0)
729 			if (buf[len - 1] == '\n')
730 				buf[--len] = '\0';
731 		cprintf(stdout, "    ");
732 
733 		for (p = buf; *p; p++) {
734 			if (*p == '%') {
735 				p++;
736 				switch (*p) {
737 
738 				case 'c':
739 					cprintf(stdout, "%s",
740 					    curclass.classname ?
741 					    curclass.classname : "<unknown>");
742 					break;
743 
744 				case 'C':
745 					if (getcwd(curwd, sizeof(curwd)-1)
746 					    == NULL){
747 						syslog(LOG_WARNING,
748 						    "can't getcwd: %s",
749 						    strerror(errno));
750 						continue;
751 					}
752 					cprintf(stdout, "%s", curwd);
753 					break;
754 
755 				case 'E':
756 					if (! EMPTYSTR(emailaddr))
757 						cprintf(stdout, "%s",
758 						    emailaddr);
759 					break;
760 
761 				case 'L':
762 					cprintf(stdout, "%s", hostname);
763 					break;
764 
765 				case 'M':
766 					if (curclass.limit == -1) {
767 						cprintf(stdout, "unlimited");
768 						lastnum = 0;
769 					} else {
770 						cprintf(stdout, "%d",
771 						    curclass.limit);
772 						lastnum = curclass.limit;
773 					}
774 					break;
775 
776 				case 'N':
777 					cprintf(stdout, "%d", connections);
778 					lastnum = connections;
779 					break;
780 
781 				case 'R':
782 					cprintf(stdout, "%s", remotehost);
783 					break;
784 
785 				case 's':
786 					if (lastnum != 1)
787 						cprintf(stdout, "s");
788 					break;
789 
790 				case 'S':
791 					if (lastnum != 1)
792 						cprintf(stdout, "S");
793 					break;
794 
795 				case 'T':
796 					now = time(NULL);
797 					cprintf(stdout, "%.24s", ctime(&now));
798 					break;
799 
800 				case 'U':
801 					cprintf(stdout, "%s",
802 					    pw ? pw->pw_name : "<unknown>");
803 					break;
804 
805 				case '%':
806 					CPUTC('%', stdout);
807 					break;
808 
809 				}
810 			} else
811 				CPUTC(*p, stdout);
812 		}
813 		cprintf(stdout, "\r\n");
814 	}
815 
816 	(void)fflush(stdout);
817 	(void)fclose(f);
818 	return (1);
819 }
820 
821 /*
822  * Parse src, expanding '%' escapes, into dst (which must be at least
823  * MAXPATHLEN long).
824  */
825 void
826 format_path(char *dst, const char *src)
827 {
828 	size_t len;
829 	const char *p;
830 
831 	dst[0] = '\0';
832 	len = 0;
833 	if (src == NULL)
834 		return;
835 	for (p = src; *p && len < MAXPATHLEN; p++) {
836 		if (*p == '%') {
837 			p++;
838 			switch (*p) {
839 
840 			case 'c':
841 				len += strlcpy(dst + len, curclass.classname,
842 				    MAXPATHLEN - len);
843 				break;
844 
845 			case 'd':
846 				len += strlcpy(dst + len, pw->pw_dir,
847 				    MAXPATHLEN - len);
848 				break;
849 
850 			case 'u':
851 				len += strlcpy(dst + len, pw->pw_name,
852 				    MAXPATHLEN - len);
853 				break;
854 
855 			case '%':
856 				dst[len++] = '%';
857 				break;
858 
859 			}
860 		} else
861 			dst[len++] = *p;
862 	}
863 	if (len < MAXPATHLEN)
864 		dst[len] = '\0';
865 	dst[MAXPATHLEN - 1] = '\0';
866 }
867 
868 /*
869  * Find s2 at the end of s1.  If found, return a string up to (but
870  * not including) s2, otherwise returns NULL.
871  */
872 static char *
873 strend(const char *s1, char *s2)
874 {
875 	static	char buf[MAXPATHLEN];
876 
877 	char	*start;
878 	size_t	l1, l2;
879 
880 	l1 = strlen(s1);
881 	l2 = strlen(s2);
882 
883 	if (l2 >= l1 || l1 >= sizeof(buf))
884 		return(NULL);
885 
886 	strlcpy(buf, s1, sizeof(buf));
887 	start = buf + (l1 - l2);
888 
889 	if (strcmp(start, s2) == 0) {
890 		*start = '\0';
891 		return(buf);
892 	} else
893 		return(NULL);
894 }
895 
896 static int
897 filetypematch(char *types, int mode)
898 {
899 	for ( ; types[0] != '\0'; types++)
900 		switch (*types) {
901 		  case 'd':
902 			if (S_ISDIR(mode))
903 				return(1);
904 			break;
905 		  case 'f':
906 			if (S_ISREG(mode))
907 				return(1);
908 			break;
909 		}
910 	return(0);
911 }
912 
913 /*
914  * Look for a conversion.  If we succeed, return a pointer to the
915  * command to execute for the conversion.
916  *
917  * The command is stored in a static array so there's no memory
918  * leak problems, and not too much to change in ftpd.c.  This
919  * routine doesn't need to be re-entrant unless we start using a
920  * multi-threaded ftpd, and that's not likely for a while...
921  */
922 char **
923 do_conversion(const char *fname)
924 {
925 	struct ftpconv	*cp;
926 	struct stat	 st;
927 	int		 o_errno;
928 	char		*base = NULL;
929 	char		*cmd, *p, *lp, **argv;
930 	StringList	*sl;
931 
932 	o_errno = errno;
933 	sl = NULL;
934 	cmd = NULL;
935 	for (cp = curclass.conversions; cp != NULL; cp = cp->next) {
936 		if (cp->suffix == NULL) {
937 			syslog(LOG_WARNING,
938 			    "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!");
939 			continue;
940 		}
941 		if ((base = strend(fname, cp->suffix)) == NULL)
942 			continue;
943 		if (cp->types == NULL || cp->disable == NULL ||
944 		    cp->command == NULL)
945 			continue;
946 					/* Is it enabled? */
947 		if (strcmp(cp->disable, ".") != 0 &&
948 		    stat(cp->disable, &st) == 0)
949 				continue;
950 					/* Does the base exist? */
951 		if (stat(base, &st) < 0)
952 			continue;
953 					/* Is the file type ok */
954 		if (!filetypematch(cp->types, st.st_mode))
955 			continue;
956 		break;			/* "We have a winner!" */
957 	}
958 
959 	/* If we got through the list, no conversion */
960 	if (cp == NULL)
961 		goto cleanup_do_conv;
962 
963 	/* Split up command into an argv */
964 	if ((sl = sl_init()) == NULL)
965 		goto cleanup_do_conv;
966 	cmd = xstrdup(cp->command);
967 	p = cmd;
968 	while (p) {
969 		NEXTWORD(p, lp);
970 		if (strcmp(lp, "%s") == 0)
971 			lp = base;
972 		if (sl_add(sl, xstrdup(lp)) == -1)
973 			goto cleanup_do_conv;
974 	}
975 
976 	if (sl_add(sl, NULL) == -1)
977 		goto cleanup_do_conv;
978 	argv = sl->sl_str;
979 	free(cmd);
980 	free(sl);
981 	return(argv);
982 
983  cleanup_do_conv:
984 	if (sl)
985 		sl_free(sl, 1);
986 	free(cmd);
987 	errno = o_errno;
988 	return(NULL);
989 }
990 
991 /*
992  * Convert the string `arg' to a long long, which may have an optional SI suffix
993  * (`b', `k', `m', `g', `t'). Returns the number for success, -1 otherwise.
994  */
995 LLT
996 strsuftoll(const char *arg)
997 {
998 	char *cp;
999 	LLT val;
1000 
1001 	if (!isdigit((unsigned char)arg[0]))
1002 		return (-1);
1003 
1004 	val = STRTOLL(arg, &cp, 10);
1005 	if (cp != NULL) {
1006 		if (cp[0] != '\0' && cp[1] != '\0')
1007 			 return (-1);
1008 		switch (tolower((unsigned char)cp[0])) {
1009 		case '\0':
1010 		case 'b':
1011 			break;
1012 		case 'k':
1013 			val <<= 10;
1014 			break;
1015 		case 'm':
1016 			val <<= 20;
1017 			break;
1018 		case 'g':
1019 			val <<= 30;
1020 			break;
1021 #ifndef NO_LONG_LONG
1022 		case 't':
1023 			val <<= 40;
1024 			break;
1025 #endif
1026 		default:
1027 			return (-1);
1028 		}
1029 	}
1030 	if (val < 0)
1031 		return (-1);
1032 
1033 	return (val);
1034 }
1035 
1036 /*
1037  * Count the number of current connections, reading from
1038  *	/var/run/ftpd.pids-<class>
1039  * Does a kill -0 on each pid in that file, and only counts
1040  * processes that exist (or frees the slot if it doesn't).
1041  * Adds getpid() to the first free slot. Truncates the file
1042  * if possible.
1043  */
1044 void
1045 count_users(void)
1046 {
1047 	char	fn[MAXPATHLEN];
1048 	int	fd, i, last;
1049 	size_t	count;
1050 	pid_t  *pids, mypid;
1051 	struct stat sb;
1052 
1053 	(void)strlcpy(fn, _PATH_CLASSPIDS, sizeof(fn));
1054 	(void)strlcat(fn, curclass.classname, sizeof(fn));
1055 	pids = NULL;
1056 	connections = 1;
1057 
1058 	if ((fd = open(fn, O_RDWR | O_CREAT, 0600)) == -1)
1059 		return;
1060 	if (lockf(fd, F_TLOCK, 0) == -1)
1061 		goto cleanup_count;
1062 	if (fstat(fd, &sb) == -1)
1063 		goto cleanup_count;
1064 	if ((pids = malloc(sb.st_size + sizeof(pid_t))) == NULL)
1065 		goto cleanup_count;
1066 	count = read(fd, pids, sb.st_size);
1067 	if (count < 0 || count != sb.st_size)
1068 		goto cleanup_count;
1069 	count /= sizeof(pid_t);
1070 	mypid = getpid();
1071 	last = 0;
1072 	for (i = 0; i < count; i++) {
1073 		if (pids[i] == 0)
1074 			continue;
1075 		if (kill(pids[i], 0) == -1 && errno != EPERM) {
1076 			if (mypid != 0) {
1077 				pids[i] = mypid;
1078 				mypid = 0;
1079 				last = i;
1080 			}
1081 		} else {
1082 			connections++;
1083 			last = i;
1084 		}
1085 	}
1086 	if (mypid != 0) {
1087 		if (pids[last] != 0)
1088 			last++;
1089 		pids[last] = mypid;
1090 	}
1091 	count = (last + 1) * sizeof(pid_t);
1092 	if (lseek(fd, 0, SEEK_SET) == -1)
1093 		goto cleanup_count;
1094 	if (write(fd, pids, count) == -1)
1095 		goto cleanup_count;
1096 	(void)ftruncate(fd, count);
1097 
1098  cleanup_count:
1099 	if (lseek(fd, 0, SEEK_SET) != -1)
1100 		(void)lockf(fd, F_ULOCK, 0);
1101 	close(fd);
1102 	REASSIGN(pids, NULL);
1103 }
1104