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