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