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