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