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