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