conf.c revision 1.15 1 /* $NetBSD: conf.c,v 1.15 1998/09/06 10:39:40 lukem Exp $ */
2
3 /*-
4 * Copyright (c) 1997 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.15 1998/09/06 10:39:40 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 <errno.h>
49 #include <glob.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <time.h>
53 #include <unistd.h>
54 #include <setjmp.h>
55 #include <string.h>
56 #include <stringlist.h>
57 #include <syslog.h>
58
59 #include "extern.h"
60 #include "pathnames.h"
61
62 static char *strend __P((const char *, char *));
63 static int filetypematch __P((char *, int));
64
65 struct ftpclass curclass;
66
67
68 /*
69 * Parse the configuration file, looking for the named class, and
70 * define curclass to contain the appropriate settings.
71 */
72 void
73 parse_conf(findclass)
74 char *findclass;
75 {
76 FILE *f;
77 char *buf, *p;
78 size_t len;
79 int none, match;
80 char *endp;
81 char *class, *word, *arg;
82 const char *infile;
83 int line;
84 unsigned int timeout;
85 struct ftpconv *conv, *cnext;
86
87 #define REASSIGN(X,Y) if (X) free(X); (X)=(Y)
88 #define NEXTWORD(W) while ((W = strsep(&buf, " \t")) != NULL && *W == '\0')
89 #define EMPTYSTR(W) (W == NULL || *W == '\0')
90
91 REASSIGN(curclass.classname, findclass);
92 for (conv = curclass.conversions; conv != NULL; conv=cnext) {
93 REASSIGN(conv->suffix, NULL);
94 REASSIGN(conv->types, NULL);
95 REASSIGN(conv->disable, NULL);
96 REASSIGN(conv->command, NULL);
97 cnext = conv->next;
98 free(conv);
99 }
100 curclass.checkportcmd = 0;
101 curclass.conversions = NULL;
102 REASSIGN(curclass.display, NULL);
103 curclass.maxtimeout = 7200; /* 2 hours */
104 curclass.modify = 1;
105 REASSIGN(curclass.notify, NULL);
106 curclass.passive = 1;
107 curclass.timeout = 900; /* 15 minutes */
108 curclass.umask = 027;
109
110 if (strcasecmp(findclass, "guest") == 0) {
111 curclass.modify = 0;
112 curclass.umask = 0707;
113 }
114
115 infile = conffilename(_PATH_FTPDCONF);
116 if ((f = fopen(infile, "r")) == NULL)
117 return;
118
119 line = 0;
120 while ((buf = fgetln(f, &len)) != NULL) {
121 none = match = 0;
122 line++;
123 if (len < 1)
124 continue;
125 if (buf[len - 1] != '\n') {
126 syslog(LOG_WARNING,
127 "%s line %d is partially truncated?", infile, line);
128 continue;
129 }
130 buf[--len] = '\0';
131 if ((p = strchr(buf, '#')) != NULL)
132 *p = '\0';
133 if (EMPTYSTR(buf))
134 continue;
135
136 NEXTWORD(word);
137 NEXTWORD(class);
138 NEXTWORD(arg);
139 if (EMPTYSTR(word) || EMPTYSTR(class))
140 continue;
141 if (strcasecmp(class, "none") == 0)
142 none = 1;
143 if (strcasecmp(class, findclass) != 0 &&
144 !none && strcasecmp(class, "all") != 0)
145 continue;
146
147 if (strcasecmp(word, "checkportcmd") == 0) {
148 if (none ||
149 (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))
150 curclass.checkportcmd = 0;
151 else
152 curclass.checkportcmd = 1;
153 } else if (strcasecmp(word, "conversion") == 0) {
154 char *suffix, *types, *disable, *convcmd;
155
156 if (EMPTYSTR(arg)) {
157 syslog(LOG_WARNING,
158 "%s line %d: %s requires a suffix",
159 infile, line, word);
160 continue; /* need a suffix */
161 }
162 NEXTWORD(types);
163 NEXTWORD(disable);
164 convcmd = buf;
165 if (convcmd)
166 convcmd += strspn(convcmd, " \t");
167 suffix = strdup(arg);
168 if (suffix == NULL) {
169 syslog(LOG_WARNING, "can't strdup");
170 continue;
171 }
172 if (none || EMPTYSTR(types) ||
173 EMPTYSTR(disable) || EMPTYSTR(convcmd)) {
174 types = NULL;
175 disable = NULL;
176 convcmd = NULL;
177 } else {
178 types = strdup(types);
179 disable = strdup(disable);
180 convcmd = strdup(convcmd);
181 if (types == NULL || disable == NULL ||
182 convcmd == NULL) {
183 syslog(LOG_WARNING, "can't strdup");
184 if (types)
185 free(types);
186 if (disable)
187 free(disable);
188 if (convcmd)
189 free(convcmd);
190 continue;
191 }
192 }
193 for (conv = curclass.conversions; conv != NULL;
194 conv = conv->next) {
195 if (strcmp(conv->suffix, suffix) == 0)
196 break;
197 }
198 if (conv == NULL) {
199 conv = (struct ftpconv *)
200 calloc(1, sizeof(struct ftpconv));
201 if (conv == NULL) {
202 syslog(LOG_WARNING, "can't malloc");
203 continue;
204 }
205 conv->next = curclass.conversions;
206 curclass.conversions = conv;
207 }
208 REASSIGN(conv->suffix, suffix);
209 REASSIGN(conv->types, types);
210 REASSIGN(conv->disable, disable);
211 REASSIGN(conv->command, convcmd);
212 } else if (strcasecmp(word, "display") == 0) {
213 if (none || EMPTYSTR(arg))
214 arg = NULL;
215 else
216 arg = strdup(arg);
217 REASSIGN(curclass.display, arg);
218 } else if (strcasecmp(word, "maxtimeout") == 0) {
219 if (none || EMPTYSTR(arg))
220 continue;
221 timeout = (unsigned int)strtoul(arg, &endp, 10);
222 if (*endp != 0) {
223 syslog(LOG_WARNING,
224 "%s line %d: invalid maxtimeout %s",
225 infile, line, arg);
226 continue;
227 }
228 if (timeout < 30) {
229 syslog(LOG_WARNING,
230 "%s line %d: maxtimeout %d < 30 seconds",
231 infile, line, timeout);
232 continue;
233 }
234 if (timeout < curclass.timeout) {
235 syslog(LOG_WARNING,
236 "%s line %d: maxtimeout %d < timeout (%d)",
237 infile, line, timeout, curclass.timeout);
238 continue;
239 }
240 curclass.maxtimeout = timeout;
241 } else if (strcasecmp(word, "modify") == 0) {
242 if (none ||
243 (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))
244 curclass.modify = 0;
245 else
246 curclass.modify = 1;
247 } else if (strcasecmp(word, "notify") == 0) {
248 if (none || EMPTYSTR(arg))
249 arg = NULL;
250 else
251 arg = strdup(arg);
252 REASSIGN(curclass.notify, arg);
253 } else if (strcasecmp(word, "passive") == 0) {
254 if (none ||
255 (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0))
256 curclass.passive = 0;
257 else
258 curclass.passive = 1;
259 } else if (strcasecmp(word, "timeout") == 0) {
260 if (none || EMPTYSTR(arg))
261 continue;
262 timeout = (unsigned int)strtoul(arg, &endp, 10);
263 if (*endp != 0) {
264 syslog(LOG_WARNING,
265 "%s line %d: invalid timeout %s",
266 infile, line, arg);
267 continue;
268 }
269 if (timeout < 30) {
270 syslog(LOG_WARNING,
271 "%s line %d: timeout %d < 30 seconds",
272 infile, line, timeout);
273 continue;
274 }
275 if (timeout > curclass.maxtimeout) {
276 syslog(LOG_WARNING,
277 "%s line %d: timeout %d > maxtimeout (%d)",
278 infile, line, timeout, curclass.maxtimeout);
279 continue;
280 }
281 curclass.timeout = timeout;
282 } else if (strcasecmp(word, "umask") == 0) {
283 mode_t umask;
284
285 if (none || EMPTYSTR(arg))
286 continue;
287 umask = (mode_t)strtoul(arg, &endp, 8);
288 if (*endp != 0 || umask > 0777) {
289 syslog(LOG_WARNING,
290 "%s line %d: invalid umask %s",
291 infile, line, arg);
292 continue;
293 }
294 curclass.umask = umask;
295 } else {
296 syslog(LOG_WARNING,
297 "%s line %d: unknown directive '%s'",
298 infile, line, word);
299 continue;
300 }
301 }
302 #undef REASSIGN
303 #undef NEXTWORD
304 #undef EMPTYSTR
305 fclose(f);
306 }
307
308 /*
309 * Show file listed in curclass.display first time in, and list all the
310 * files named in curclass.notify in the current directory. Send back
311 * responses with the "reply" prefix.
312 */
313 void
314 show_chdir_messages(code)
315 int code;
316 {
317 static StringList *slist = NULL;
318
319 struct stat st;
320 struct tm *t;
321 glob_t gl;
322 time_t now, then;
323 int age;
324 char cwd[MAXPATHLEN + 1];
325 char line[BUFSIZ];
326 char *cp, **rlist;
327 FILE *f;
328
329 /* Setup list for directory cache */
330 if (slist == NULL)
331 slist = sl_init();
332
333 /* Check if this directory has already been visited */
334 if (getcwd(cwd, sizeof(cwd) - 1) == NULL) {
335 syslog(LOG_WARNING, "can't getcwd: %s", strerror(errno));
336 return;
337 }
338 if (sl_find(slist, cwd) != NULL)
339 return;
340
341 cp = strdup(cwd);
342 if (cp == NULL) {
343 syslog(LOG_WARNING, "can't strdup");
344 return;
345 }
346 sl_add(slist, cp);
347
348 /* First check for a display file */
349 if (curclass.display != NULL && curclass.display[0] &&
350 (f = fopen(curclass.display, "r")) != NULL) {
351 while (fgets(line, BUFSIZ, f)) {
352 if ((cp = strchr(line, '\n')) != NULL)
353 *cp = '\0';
354 lreply(code, "%s", line);
355 }
356 fclose(f);
357 lreply(code, "");
358 }
359
360 /* Now see if there are any notify files */
361 if (curclass.notify == NULL || curclass.notify[0] == '\0')
362 return;
363
364 if (glob(curclass.notify, 0, NULL, &gl) != 0 || gl.gl_matchc == 0)
365 return;
366 time(&now);
367 for (rlist = gl.gl_pathv; *rlist != NULL; rlist++) {
368 if (stat(*rlist, &st) != 0)
369 continue;
370 if (!S_ISREG(st.st_mode))
371 continue;
372 then = st.st_mtime;
373 lreply(code, "Please read the file %s", *rlist);
374 t = localtime(&now);
375 age = 365 * t->tm_year + t->tm_yday;
376 t = localtime(&then);
377 age -= 365 * t->tm_year + t->tm_yday;
378 lreply(code, " it was last modified on %.24s - %d day%s ago",
379 ctime(&then), age, age == 1 ? "" : "s");
380 }
381 globfree(&gl);
382 }
383
384 /*
385 * Find s2 at the end of s1. If found, return a string up and up (but
386 * not including) s2, otherwise returns NULL.
387 */
388 static char *
389 strend(s1, s2)
390 const char *s1;
391 char *s2;
392 {
393 static char buf[MAXPATHLEN + 1];
394
395 char *start;
396 size_t l1, l2;
397
398 l1 = strlen(s1);
399 l2 = strlen(s2);
400
401 if (l2 >= l1)
402 return(NULL);
403
404 strncpy(buf, s1, MAXPATHLEN);
405 start = buf + (l1 - l2);
406
407 if (strcmp(start, s2) == 0) {
408 *start = '\0';
409 return(buf);
410 } else
411 return(NULL);
412 }
413
414 static int
415 filetypematch(types, mode)
416 char *types;
417 int mode;
418 {
419 for ( ; types[0] != '\0'; types++)
420 switch (*types) {
421 case 'd':
422 if (S_ISDIR(mode))
423 return(1);
424 break;
425 case 'f':
426 if (S_ISREG(mode))
427 return(1);
428 break;
429 }
430 return(0);
431 }
432
433 /*
434 * Look for a conversion. If we succeed, return a pointer to the
435 * command to execute for the conversion.
436 *
437 * The command is stored in a static array so there's no memory
438 * leak problems, and not too much to change in ftpd.c. This
439 * routine doesn't need to be re-entrant unless we start using a
440 * multi-threaded ftpd, and that's not likely for a while...
441 */
442 char *
443 do_conversion(fname)
444 const char *fname;
445 {
446 static char cmd[LINE_MAX];
447
448 struct ftpconv *cp;
449 struct stat st;
450 int o_errno;
451 char *base = NULL;
452
453 o_errno = errno;
454 for (cp = curclass.conversions; cp != NULL; cp = cp->next) {
455 if (cp->suffix == NULL) {
456 syslog(LOG_WARNING,
457 "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!");
458 continue;
459 }
460 if ((base = strend(fname, cp->suffix)) == NULL)
461 continue;
462 if (cp->types == NULL || cp->disable == NULL ||
463 cp->command == NULL)
464 continue;
465 /* Is it enabled? */
466 if (strcmp(cp->disable, ".") != 0 &&
467 stat(cp->disable, &st) == 0)
468 continue;
469 /* Does the base exist? */
470 if (stat(base, &st) < 0)
471 continue;
472 /* Is the file type ok */
473 if (!filetypematch(cp->types, st.st_mode))
474 continue;
475 break; /* "We have a winner!" */
476 }
477
478 /* If we got through the list, no conversion */
479 if (cp == NULL) {
480 errno = o_errno;
481 return(NULL);
482 }
483
484 snprintf(cmd, LINE_MAX, cp->command, base);
485 syslog(LOG_DEBUG, "get command: %s", cmd);
486 return(cmd);
487 }
488