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