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