conf.c revision 1.24 1 /* $NetBSD: conf.c,v 1.24 1999/12/12 14:05:54 lukem Exp $ */
2
3 /*-
4 * Copyright (c) 1997-1999 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.24 1999/12/12 14:05:54 lukem Exp $");
42 #endif /* not lint */
43
44 #include <sys/types.h>
45 #include <sys/param.h>
46 #include <sys/stat.h>
47
48 #include <ctype.h>
49 #include <errno.h>
50 #include <glob.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <stringlist.h>
55 #include <syslog.h>
56 #include <time.h>
57 #include <unistd.h>
58 #include <util.h>
59
60 #ifdef KERBEROS5
61 #include <krb5/krb5.h>
62 #endif
63
64 #include "extern.h"
65 #include "pathnames.h"
66
67 static char *strend __P((const char *, char *));
68 static int filetypematch __P((char *, int));
69
70 struct ftpclass curclass;
71
72
73 /*
74 * Parse the configuration file, looking for the named class, and
75 * define curclass to contain the appropriate settings.
76 */
77 void
78 parse_conf(findclass)
79 char *findclass;
80 {
81 FILE *f;
82 char *buf, *p;
83 size_t len;
84 int none, match, rate;
85 char *endp;
86 char *class, *word, *arg;
87 const char *infile;
88 size_t line;
89 unsigned int timeout;
90 struct ftpconv *conv, *cnext;
91
92 REASSIGN(curclass.classname, xstrdup(findclass));
93 for (conv = curclass.conversions; conv != NULL; conv = cnext) {
94 REASSIGN(conv->suffix, NULL);
95 REASSIGN(conv->types, NULL);
96 REASSIGN(conv->disable, NULL);
97 REASSIGN(conv->command, NULL);
98 cnext = conv->next;
99 free(conv);
100 }
101 curclass.checkportcmd = 0;
102 curclass.conversions = NULL;
103 REASSIGN(curclass.display, NULL);
104 curclass.maxrateget = 0;
105 curclass.maxrateput = 0;
106 curclass.maxtimeout = 7200; /* 2 hours */
107 curclass.modify = 1;
108 REASSIGN(curclass.motd, xstrdup(_PATH_FTPLOGINMESG));
109 REASSIGN(curclass.notify, NULL);
110 curclass.passive = 1;
111 curclass.maxrateget = 0;
112 curclass.maxrateput = 0;
113 curclass.rateget = 0;
114 curclass.rateput = 0;
115 curclass.timeout = 900; /* 15 minutes */
116 curclass.umask = 027;
117 curclass.upload = 1;
118
119 if (strcasecmp(findclass, "guest") == 0) {
120 curclass.modify = 0;
121 curclass.umask = 0707;
122 }
123
124 infile = conffilename(_PATH_FTPDCONF);
125 if ((f = fopen(infile, "r")) == NULL)
126 return;
127
128 line = 0;
129 for (;
130 (buf = fparseln(f, &len, &line, NULL, FPARSELN_UNESCCOMM |
131 FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL;
132 free(buf)) {
133 none = match = 0;
134 p = buf;
135 if (len < 1)
136 continue;
137 if (p[len - 1] == '\n')
138 p[--len] = '\0';
139 if (EMPTYSTR(p))
140 continue;
141
142 NEXTWORD(p, word);
143 NEXTWORD(p, class);
144 NEXTWORD(p, arg);
145 if (EMPTYSTR(word) || EMPTYSTR(class))
146 continue;
147 if (strcasecmp(class, "none") == 0)
148 none = 1;
149 if (strcasecmp(class, findclass) != 0 &&
150 !none && strcasecmp(class, "all") != 0)
151 continue;
152
153 if (strcasecmp(word, "checkportcmd") == 0) {
154 if (none ||
155 (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))
156 curclass.checkportcmd = 0;
157 else
158 curclass.checkportcmd = 1;
159
160 } else if (strcasecmp(word, "classtype") == 0) {
161 if (!none && !EMPTYSTR(arg)) {
162 if (strcasecmp(arg, "GUEST") == 0)
163 curclass.type = CLASS_GUEST;
164 else if (strcasecmp(arg, "CHROOT") == 0)
165 curclass.type = CLASS_CHROOT;
166 else if (strcasecmp(arg, "REAL") == 0)
167 curclass.type = CLASS_REAL;
168 else {
169 syslog(LOG_WARNING,
170 "%s line %d: unknown class type `%s'",
171 infile, (int)line, arg);
172 continue;
173 }
174 }
175
176 } else if (strcasecmp(word, "conversion") == 0) {
177 char *suffix, *types, *disable, *convcmd;
178
179 if (EMPTYSTR(arg)) {
180 syslog(LOG_WARNING,
181 "%s line %d: %s requires a suffix",
182 infile, (int)line, word);
183 continue; /* need a suffix */
184 }
185 NEXTWORD(p, types);
186 NEXTWORD(p, disable);
187 convcmd = p;
188 if (convcmd)
189 convcmd += strspn(convcmd, " \t");
190 suffix = xstrdup(arg);
191 if (none || EMPTYSTR(types) ||
192 EMPTYSTR(disable) || EMPTYSTR(convcmd)) {
193 types = NULL;
194 disable = NULL;
195 convcmd = NULL;
196 } else {
197 types = xstrdup(types);
198 disable = xstrdup(disable);
199 convcmd = xstrdup(convcmd);
200 }
201 for (conv = curclass.conversions; conv != NULL;
202 conv = conv->next) {
203 if (strcmp(conv->suffix, suffix) == 0)
204 break;
205 }
206 if (conv == NULL) {
207 conv = (struct ftpconv *)
208 calloc(1, sizeof(struct ftpconv));
209 if (conv == NULL) {
210 syslog(LOG_WARNING, "can't malloc");
211 continue;
212 }
213 conv->next = NULL;
214 for (cnext = curclass.conversions;
215 cnext != NULL; cnext = cnext->next)
216 if (cnext->next == NULL)
217 break;
218 if (cnext != NULL)
219 cnext->next = conv;
220 else
221 curclass.conversions = conv;
222 }
223 REASSIGN(conv->suffix, suffix);
224 REASSIGN(conv->types, types);
225 REASSIGN(conv->disable, disable);
226 REASSIGN(conv->command, convcmd);
227
228 } else if (strcasecmp(word, "display") == 0) {
229 if (none || EMPTYSTR(arg))
230 arg = NULL;
231 else
232 arg = xstrdup(arg);
233 REASSIGN(curclass.display, arg);
234
235 } else if (strcasecmp(word, "maxtimeout") == 0) {
236 if (none || EMPTYSTR(arg))
237 continue;
238 timeout = (unsigned int)strtoul(arg, &endp, 10);
239 if (*endp != 0) {
240 syslog(LOG_WARNING,
241 "%s line %d: invalid maxtimeout %s",
242 infile, (int)line, arg);
243 continue;
244 }
245 if (timeout < 30) {
246 syslog(LOG_WARNING,
247 "%s line %d: maxtimeout %d < 30 seconds",
248 infile, (int)line, timeout);
249 continue;
250 }
251 if (timeout < curclass.timeout) {
252 syslog(LOG_WARNING,
253 "%s line %d: maxtimeout %d < timeout (%d)",
254 infile, (int)line, timeout,
255 curclass.timeout);
256 continue;
257 }
258 curclass.maxtimeout = timeout;
259
260 } else if (strcasecmp(word, "modify") == 0) {
261 if (none ||
262 (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))
263 curclass.modify = 0;
264 else
265 curclass.modify = 1;
266
267 } else if (strcasecmp(word, "motd") == 0) {
268 if (none || EMPTYSTR(arg))
269 arg = NULL;
270 else
271 arg = xstrdup(arg);
272 REASSIGN(curclass.motd, arg);
273
274
275 } else if (strcasecmp(word, "notify") == 0) {
276 if (none || EMPTYSTR(arg))
277 arg = NULL;
278 else
279 arg = xstrdup(arg);
280 REASSIGN(curclass.notify, arg);
281
282 } else if (strcasecmp(word, "passive") == 0) {
283 if (none ||
284 (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))
285 curclass.passive = 0;
286 else
287 curclass.passive = 1;
288
289 } else if (strcasecmp(word, "rateget") == 0) {
290 if (none || EMPTYSTR(arg))
291 continue;
292 rate = strsuftoi(arg);
293 if (rate == -1) {
294 syslog(LOG_WARNING,
295 "%s line %d: invalid rateget %s",
296 infile, (int)line, arg);
297 continue;
298 }
299 curclass.maxrateget = rate;
300 curclass.rateget = rate;
301
302 } else if (strcasecmp(word, "rateput") == 0) {
303 if (none || EMPTYSTR(arg))
304 continue;
305 rate = strsuftoi(arg);
306 if (rate == -1) {
307 syslog(LOG_WARNING,
308 "%s line %d: invalid rateput %s",
309 infile, (int)line, arg);
310 continue;
311 }
312 curclass.maxrateput = rate;
313 curclass.rateput = rate;
314
315 } else if (strcasecmp(word, "timeout") == 0) {
316 if (none || EMPTYSTR(arg))
317 continue;
318 timeout = (unsigned int)strtoul(arg, &endp, 10);
319 if (*endp != 0) {
320 syslog(LOG_WARNING,
321 "%s line %d: invalid timeout %s",
322 infile, (int)line, arg);
323 continue;
324 }
325 if (timeout < 30) {
326 syslog(LOG_WARNING,
327 "%s line %d: timeout %d < 30 seconds",
328 infile, (int)line, timeout);
329 continue;
330 }
331 if (timeout > curclass.maxtimeout) {
332 syslog(LOG_WARNING,
333 "%s line %d: timeout %d > maxtimeout (%d)",
334 infile, (int)line, timeout,
335 curclass.maxtimeout);
336 continue;
337 }
338 curclass.timeout = timeout;
339
340 } else if (strcasecmp(word, "umask") == 0) {
341 mode_t umask;
342
343 if (none || EMPTYSTR(arg))
344 continue;
345 umask = (mode_t)strtoul(arg, &endp, 8);
346 if (*endp != 0 || umask > 0777) {
347 syslog(LOG_WARNING,
348 "%s line %d: invalid umask %s",
349 infile, (int)line, arg);
350 continue;
351 }
352 curclass.umask = umask;
353
354 } else if (strcasecmp(word, "upload") == 0) {
355 if (none ||
356 (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0)) {
357 curclass.modify = 0;
358 curclass.upload = 0;
359 } else
360 curclass.upload = 1;
361
362 } else {
363 syslog(LOG_WARNING,
364 "%s line %d: unknown directive '%s'",
365 infile, (int)line, word);
366 continue;
367 }
368 }
369 fclose(f);
370 }
371
372 /*
373 * Show file listed in curclass.display first time in, and list all the
374 * files named in curclass.notify in the current directory. Send back
375 * responses with the prefix `code' + "-".
376 */
377 void
378 show_chdir_messages(code)
379 int code;
380 {
381 static StringList *slist = NULL;
382
383 struct stat st;
384 struct tm *t;
385 glob_t gl;
386 time_t now, then;
387 int age;
388 char cwd[MAXPATHLEN + 1];
389 char *cp, **rlist;
390
391 /* Setup list for directory cache */
392 if (slist == NULL)
393 slist = sl_init();
394 if (slist == NULL) {
395 syslog(LOG_WARNING, "can't allocate memory for stringlist");
396 return;
397 }
398
399 /* Check if this directory has already been visited */
400 if (getcwd(cwd, sizeof(cwd) - 1) == NULL) {
401 syslog(LOG_WARNING, "can't getcwd: %s", strerror(errno));
402 return;
403 }
404 if (sl_find(slist, cwd) != NULL)
405 return;
406
407 cp = xstrdup(cwd);
408 if (sl_add(slist, cp) == -1)
409 syslog(LOG_WARNING, "can't add `%s' to stringlist", cp);
410
411 /* First check for a display file */
412 (void)format_file(curclass.display, code);
413
414 /* Now see if there are any notify files */
415 if (EMPTYSTR(curclass.notify))
416 return;
417
418 if (glob(curclass.notify, 0, NULL, &gl) != 0 || gl.gl_matchc == 0)
419 return;
420 time(&now);
421 for (rlist = gl.gl_pathv; *rlist != NULL; rlist++) {
422 if (stat(*rlist, &st) != 0)
423 continue;
424 if (!S_ISREG(st.st_mode))
425 continue;
426 then = st.st_mtime;
427 if (code != 0) {
428 lreply(code, "");
429 code = 0;
430 }
431 lreply(code, "Please read the file %s", *rlist);
432 t = localtime(&now);
433 age = 365 * t->tm_year + t->tm_yday;
434 t = localtime(&then);
435 age -= 365 * t->tm_year + t->tm_yday;
436 lreply(code, " it was last modified on %.24s - %d day%s ago",
437 ctime(&then), age, PLURAL(age));
438 }
439 globfree(&gl);
440 }
441
442 int
443 format_file(file, code)
444 const char *file;
445 int code;
446 {
447 FILE *f;
448 char *buf, *p, *cwd;
449 size_t len;
450 off_t b;
451 time_t now;
452
453 #define PUTC(x) putchar(x), b++
454
455 if (EMPTYSTR(file))
456 return(0);
457 if ((f = fopen(file, "r")) == NULL)
458 return (0);
459 lreply(code, "");
460
461 b = 0;
462 for (;
463 (buf = fparseln(f, &len, NULL, "\0\0\0", 0)) != NULL; free(buf)) {
464 if (len > 0)
465 if (buf[len - 1] == '\n')
466 buf[--len] = '\0';
467 b += printf(" ");
468
469 for (p = buf; *p; p++) {
470 if (*p == '%') {
471 p++;
472 switch (*p) {
473 case 'C':
474 if (getcwd(cwd, sizeof(cwd)-1) == NULL){
475 syslog(LOG_WARNING,
476 "can't getcwd: %s",
477 strerror(errno));
478 continue;
479 }
480 b += printf("%s", cwd);
481 break;
482 case 'E':
483 /* XXXX email address */
484 break;
485 case 'L':
486 b += printf("%s", hostname);
487 break;
488 case 'R':
489 b += printf("%s", remotehost);
490 break;
491 case 'T':
492 now = time(NULL);
493 b += printf("%.25s", ctime(&now));
494 break;
495 case 'U':
496 b += printf("%s",
497 pw ? pw->pw_name : "<unknown>");
498 break;
499 case '%':
500 PUTC('%');
501 break;
502 }
503 } else {
504 PUTC(*p);
505 }
506 }
507 PUTC('\r');
508 PUTC('\n');
509 }
510
511 total_bytes += b;
512 total_bytes_out += b;
513 (void)fflush(stdout);
514 (void)fclose(f);
515 return (1);
516 }
517
518 /*
519 * Find s2 at the end of s1. If found, return a string up to (but
520 * not including) s2, otherwise returns NULL.
521 */
522 static char *
523 strend(s1, s2)
524 const char *s1;
525 char *s2;
526 {
527 static char buf[MAXPATHLEN + 1];
528
529 char *start;
530 size_t l1, l2;
531
532 l1 = strlen(s1);
533 l2 = strlen(s2);
534
535 if (l2 >= l1)
536 return(NULL);
537
538 strlcpy(buf, s1, sizeof(buf));
539 start = buf + (l1 - l2);
540
541 if (strcmp(start, s2) == 0) {
542 *start = '\0';
543 return(buf);
544 } else
545 return(NULL);
546 }
547
548 static int
549 filetypematch(types, mode)
550 char *types;
551 int mode;
552 {
553 for ( ; types[0] != '\0'; types++)
554 switch (*types) {
555 case 'd':
556 if (S_ISDIR(mode))
557 return(1);
558 break;
559 case 'f':
560 if (S_ISREG(mode))
561 return(1);
562 break;
563 }
564 return(0);
565 }
566
567 /*
568 * Look for a conversion. If we succeed, return a pointer to the
569 * command to execute for the conversion.
570 *
571 * The command is stored in a static array so there's no memory
572 * leak problems, and not too much to change in ftpd.c. This
573 * routine doesn't need to be re-entrant unless we start using a
574 * multi-threaded ftpd, and that's not likely for a while...
575 */
576 char **
577 do_conversion(fname)
578 const char *fname;
579 {
580 struct ftpconv *cp;
581 struct stat st;
582 int o_errno;
583 char *base = NULL;
584 char *cmd, *p, *lp, **argv;
585 StringList *sl;
586
587 o_errno = errno;
588 sl = NULL;
589 cmd = NULL;
590 for (cp = curclass.conversions; cp != NULL; cp = cp->next) {
591 if (cp->suffix == NULL) {
592 syslog(LOG_WARNING,
593 "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!");
594 continue;
595 }
596 if ((base = strend(fname, cp->suffix)) == NULL)
597 continue;
598 if (cp->types == NULL || cp->disable == NULL ||
599 cp->command == NULL)
600 continue;
601 /* Is it enabled? */
602 if (strcmp(cp->disable, ".") != 0 &&
603 stat(cp->disable, &st) == 0)
604 continue;
605 /* Does the base exist? */
606 if (stat(base, &st) < 0)
607 continue;
608 /* Is the file type ok */
609 if (!filetypematch(cp->types, st.st_mode))
610 continue;
611 break; /* "We have a winner!" */
612 }
613
614 /* If we got through the list, no conversion */
615 if (cp == NULL)
616 goto cleanup_do_conv;
617
618 /* Split up command into an argv */
619 if ((sl = sl_init()) == NULL)
620 goto cleanup_do_conv;
621 cmd = xstrdup(cp->command);
622 p = cmd;
623 while (p) {
624 NEXTWORD(p, lp);
625 if (strcmp(lp, "%s") == 0)
626 lp = base;
627 if (sl_add(sl, xstrdup(lp)) == -1)
628 goto cleanup_do_conv;
629 }
630
631 if (sl_add(sl, NULL) == -1)
632 goto cleanup_do_conv;
633 argv = sl->sl_str;
634 free(cmd);
635 free(sl);
636 return(argv);
637
638 cleanup_do_conv:
639 if (sl)
640 sl_free(sl, 1);
641 free(cmd);
642 errno = o_errno;
643 return(NULL);
644 }
645
646 /*
647 * Convert the string `arg' to an int, which may have an optional SI suffix
648 * (`b', `k', `m', `g'). Returns the number for success, -1 otherwise.
649 */
650 int
651 strsuftoi(arg)
652 const char *arg;
653 {
654 char *cp;
655 long val;
656
657 if (!isdigit((unsigned char)arg[0]))
658 return (-1);
659
660 val = strtol(arg, &cp, 10);
661 if (cp != NULL) {
662 if (cp[0] != '\0' && cp[1] != '\0')
663 return (-1);
664 switch (tolower((unsigned char)cp[0])) {
665 case '\0':
666 case 'b':
667 break;
668 case 'k':
669 val <<= 10;
670 break;
671 case 'm':
672 val <<= 20;
673 break;
674 case 'g':
675 val <<= 30;
676 break;
677 default:
678 return (-1);
679 }
680 }
681 if (val < 0 || val > INT_MAX)
682 return (-1);
683
684 return (val);
685 }
686