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