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