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