1 /* $NetBSD: cmds.c,v 1.33 2013/07/03 14:15:47 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1999-2009 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by 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 /* 33 * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994 34 * The Regents of the University of California. All rights reserved. 35 * 36 * Redistribution and use in source and binary forms, with or without 37 * modification, are permitted provided that the following conditions 38 * are met: 39 * 1. Redistributions of source code must retain the above copyright 40 * notice, this list of conditions and the following disclaimer. 41 * 2. Redistributions in binary form must reproduce the above copyright 42 * notice, this list of conditions and the following disclaimer in the 43 * documentation and/or other materials provided with the distribution. 44 * 3. Neither the name of the University nor the names of its contributors 45 * may be used to endorse or promote products derived from this software 46 * without specific prior written permission. 47 * 48 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 49 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 50 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 51 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 52 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 53 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 54 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 55 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 56 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 57 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 58 * SUCH DAMAGE. 59 */ 60 61 /* 62 * Copyright (C) 1997 and 1998 WIDE Project. 63 * All rights reserved. 64 * 65 * Redistribution and use in source and binary forms, with or without 66 * modification, are permitted provided that the following conditions 67 * are met: 68 * 1. Redistributions of source code must retain the above copyright 69 * notice, this list of conditions and the following disclaimer. 70 * 2. Redistributions in binary form must reproduce the above copyright 71 * notice, this list of conditions and the following disclaimer in the 72 * documentation and/or other materials provided with the distribution. 73 * 3. Neither the name of the project nor the names of its contributors 74 * may be used to endorse or promote products derived from this software 75 * without specific prior written permission. 76 * 77 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 78 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 79 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 80 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 81 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 82 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 83 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 84 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 85 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 86 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 87 * SUCH DAMAGE. 88 */ 89 90 91 #include <sys/cdefs.h> 92 #ifndef lint 93 __RCSID("$NetBSD: cmds.c,v 1.33 2013/07/03 14:15:47 christos Exp $"); 94 #endif /* not lint */ 95 96 #include <sys/param.h> 97 #include <sys/stat.h> 98 99 #include <arpa/ftp.h> 100 101 #include <dirent.h> 102 #include <errno.h> 103 #include <stdio.h> 104 #include <stdlib.h> 105 #include <string.h> 106 #include <tzfile.h> 107 #include <unistd.h> 108 #include <ctype.h> 109 110 #ifdef KERBEROS5 111 #include <krb5/krb5.h> 112 #endif 113 114 #include "extern.h" 115 116 typedef enum { 117 FE_MLSD = 1<<0, /* if op is MLSD (MLST otherwise ) */ 118 FE_ISCURDIR = 1<<1, /* if name is the current directory */ 119 } factflag_t; 120 121 typedef struct { 122 const char *path; /* full pathname */ 123 const char *display; /* name to display */ 124 struct stat *stat; /* stat of path */ 125 struct stat *pdirstat; /* stat of path's parent dir */ 126 factflag_t flags; /* flags */ 127 } factelem; 128 129 static void ack(const char *); 130 static void base64_encode(const char *, size_t, char *, int); 131 static void fact_type(const char *, FILE *, factelem *); 132 static void fact_size(const char *, FILE *, factelem *); 133 static void fact_modify(const char *, FILE *, factelem *); 134 static void fact_perm(const char *, FILE *, factelem *); 135 static void fact_unique(const char *, FILE *, factelem *); 136 static int matchgroup(gid_t); 137 static void mlsname(FILE *, factelem *); 138 static void replydirname(const char *, const char *); 139 140 struct ftpfact { 141 const char *name; /* name of fact */ 142 int enabled; /* if fact is enabled */ 143 void (*display)(const char *, FILE *, factelem *); 144 /* function to display fact */ 145 }; 146 147 struct ftpfact facttab[] = { 148 { "Type", 1, fact_type }, 149 #define FACT_TYPE 0 150 { "Size", 1, fact_size }, 151 { "Modify", 1, fact_modify }, 152 { "Perm", 1, fact_perm }, 153 { "Unique", 1, fact_unique }, 154 /* "Create" */ 155 /* "Lang" */ 156 /* "Media-Type" */ 157 /* "CharSet" */ 158 }; 159 160 #define FACTTABSIZE (sizeof(facttab) / sizeof(struct ftpfact)) 161 162 static char cached_path[MAXPATHLEN + 1] = "/"; 163 static void discover_path(char *, const char *); 164 165 void 166 cwd(const char *path) 167 { 168 169 if (chdir(path) < 0) 170 perror_reply(550, path); 171 else { 172 show_chdir_messages(250); 173 ack("CWD"); 174 if (getcwd(cached_path, MAXPATHLEN) == NULL) { 175 discover_path(cached_path, path); 176 } 177 } 178 } 179 180 void 181 delete(const char *name) 182 { 183 char *p = NULL; 184 185 if (remove(name) < 0) { 186 p = strerror(errno); 187 perror_reply(550, name); 188 } else 189 ack("DELE"); 190 logxfer("delete", -1, name, NULL, NULL, p); 191 } 192 193 void 194 feat(void) 195 { 196 size_t i; 197 198 reply(-211, "Features supported"); 199 cprintf(stdout, " MDTM\r\n"); 200 cprintf(stdout, " MLST "); 201 for (i = 0; i < FACTTABSIZE; i++) 202 cprintf(stdout, "%s%s;", facttab[i].name, 203 facttab[i].enabled ? "*" : ""); 204 cprintf(stdout, "\r\n"); 205 cprintf(stdout, " REST STREAM\r\n"); 206 cprintf(stdout, " SIZE\r\n"); 207 cprintf(stdout, " TVFS\r\n"); 208 reply(211, "End"); 209 } 210 211 void 212 makedir(const char *name) 213 { 214 char *p = NULL; 215 216 if (mkdir(name, 0777) < 0) { 217 p = strerror(errno); 218 perror_reply(550, name); 219 } else 220 replydirname(name, "directory created."); 221 logxfer("mkdir", -1, name, NULL, NULL, p); 222 } 223 224 void 225 mlsd(const char *path) 226 { 227 struct dirent *dp; 228 struct stat sb, pdirstat; 229 factelem f; 230 FILE *dout; 231 DIR *dirp; 232 char name[MAXPATHLEN]; 233 int hastypefact; 234 235 hastypefact = facttab[FACT_TYPE].enabled; 236 if (path == NULL) 237 path = "."; 238 if (stat(path, &pdirstat) == -1) { 239 mlsdperror: 240 perror_reply(550, path); 241 return; 242 } 243 if (! S_ISDIR(pdirstat.st_mode)) { 244 errno = ENOTDIR; 245 perror_reply(501, path); 246 return; 247 } 248 if ((dirp = opendir(path)) == NULL) 249 goto mlsdperror; 250 251 dout = dataconn("MLSD", (off_t)-1, "w"); 252 if (dout == NULL) 253 return; 254 255 memset(&f, 0, sizeof(f)); 256 f.stat = &sb; 257 f.flags |= FE_MLSD; 258 while ((dp = readdir(dirp)) != NULL) { 259 snprintf(name, sizeof(name), "%s/%s", path, dp->d_name); 260 if (ISDOTDIR(dp->d_name)) { /* special case curdir: */ 261 if (! hastypefact) 262 continue; 263 f.pdirstat = NULL; /* require stat of parent */ 264 f.display = path; /* set name to real name */ 265 f.flags |= FE_ISCURDIR; /* flag name is curdir */ 266 } else { 267 if (ISDOTDOTDIR(dp->d_name)) { 268 if (! hastypefact) 269 continue; 270 f.pdirstat = NULL; 271 } else 272 f.pdirstat = &pdirstat; /* cache parent stat */ 273 f.display = dp->d_name; 274 f.flags &= ~FE_ISCURDIR; 275 } 276 if (stat(name, &sb) == -1) 277 continue; 278 f.path = name; 279 mlsname(dout, &f); 280 } 281 (void)closedir(dirp); 282 283 if (ferror(dout) != 0) 284 perror_reply(550, "Data connection"); 285 else 286 reply(226, "MLSD complete."); 287 closedataconn(dout); 288 total_xfers_out++; 289 total_xfers++; 290 } 291 292 void 293 mlst(const char *path) 294 { 295 struct stat sb; 296 factelem f; 297 298 if (path == NULL) 299 path = "."; 300 if (stat(path, &sb) == -1) { 301 perror_reply(550, path); 302 return; 303 } 304 reply(-250, "MLST %s", path); 305 memset(&f, 0, sizeof(f)); 306 f.path = path; 307 f.display = path; 308 f.stat = &sb; 309 f.pdirstat = NULL; 310 CPUTC(' ', stdout); 311 mlsname(stdout, &f); 312 reply(250, "End"); 313 } 314 315 316 void 317 opts(const char *command) 318 { 319 struct tab *c; 320 char *ep; 321 322 if ((ep = strchr(command, ' ')) != NULL) 323 *ep++ = '\0'; 324 c = lookup(cmdtab, command); 325 if (c == NULL) { 326 reply(502, "Unknown command '%s'.", command); 327 return; 328 } 329 if (! CMD_IMPLEMENTED(c)) { 330 reply(502, "%s command not implemented.", c->name); 331 return; 332 } 333 if (! CMD_HAS_OPTIONS(c)) { 334 reply(501, "%s command does not support persistent options.", 335 c->name); 336 return; 337 } 338 339 /* special case: MLST */ 340 if (strcasecmp(command, "MLST") == 0) { 341 int enabled[FACTTABSIZE]; 342 size_t i, onedone; 343 size_t len; 344 char *p; 345 346 for (i = 0; i < sizeof(enabled) / sizeof(int); i++) 347 enabled[i] = 0; 348 if (ep == NULL || *ep == '\0') 349 goto displaymlstopts; 350 351 /* don't like spaces, and need trailing ; */ 352 len = strlen(ep); 353 if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') { 354 badmlstopt: 355 reply(501, "Invalid MLST options"); 356 return; 357 } 358 ep[len - 1] = '\0'; 359 while ((p = strsep(&ep, ";")) != NULL) { 360 if (*p == '\0') 361 goto badmlstopt; 362 for (i = 0; i < FACTTABSIZE; i++) 363 if (strcasecmp(p, facttab[i].name) == 0) { 364 enabled[i] = 1; 365 break; 366 } 367 } 368 369 displaymlstopts: 370 for (i = 0; i < FACTTABSIZE; i++) 371 facttab[i].enabled = enabled[i]; 372 cprintf(stdout, "200 MLST OPTS"); 373 for (i = onedone = 0; i < FACTTABSIZE; i++) { 374 if (facttab[i].enabled) { 375 cprintf(stdout, "%s%s;", onedone ? "" : " ", 376 facttab[i].name); 377 onedone++; 378 } 379 } 380 cprintf(stdout, "\r\n"); 381 fflush(stdout); 382 return; 383 } 384 385 /* default cases */ 386 if (ep != NULL && *ep != '\0') 387 REASSIGN(c->options, ftpd_strdup(ep)); 388 if (c->options != NULL) 389 reply(200, "Options for %s are '%s'.", c->name, 390 c->options); 391 else 392 reply(200, "No options defined for %s.", c->name); 393 } 394 395 void 396 pwd(void) 397 { 398 char path[MAXPATHLEN]; 399 400 if (getcwd(path, sizeof(path) - 1) == NULL) { 401 if (chdir(cached_path) < 0) { 402 reply(550, "Can't get the current directory: %s.", 403 strerror(errno)); 404 return; 405 } 406 (void)strlcpy(path, cached_path, MAXPATHLEN); 407 } 408 replydirname(path, "is the current directory."); 409 } 410 411 void 412 removedir(const char *name) 413 { 414 char *p = NULL; 415 416 if (rmdir(name) < 0) { 417 p = strerror(errno); 418 perror_reply(550, name); 419 } else 420 ack("RMD"); 421 logxfer("rmdir", -1, name, NULL, NULL, p); 422 } 423 424 char * 425 renamefrom(const char *name) 426 { 427 struct stat st; 428 429 if (stat(name, &st) < 0) { 430 perror_reply(550, name); 431 return (NULL); 432 } 433 reply(350, "File exists, ready for destination name"); 434 return (ftpd_strdup(name)); 435 } 436 437 void 438 renamecmd(const char *from, const char *to) 439 { 440 char *p = NULL; 441 442 if (rename(from, to) < 0) { 443 p = strerror(errno); 444 perror_reply(550, "rename"); 445 } else 446 ack("RNTO"); 447 logxfer("rename", -1, from, to, NULL, p); 448 } 449 450 void 451 sizecmd(const char *filename) 452 { 453 switch (type) { 454 case TYPE_L: 455 case TYPE_I: 456 { 457 struct stat stbuf; 458 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) 459 reply(550, "%s: not a plain file.", filename); 460 else 461 reply(213, ULLF, (ULLT)stbuf.st_size); 462 break; 463 } 464 case TYPE_A: 465 { 466 FILE *fin; 467 int c; 468 off_t count; 469 struct stat stbuf; 470 fin = fopen(filename, "r"); 471 if (fin == NULL) { 472 perror_reply(550, filename); 473 return; 474 } 475 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) { 476 reply(550, "%s: not a plain file.", filename); 477 (void) fclose(fin); 478 return; 479 } 480 if (stbuf.st_size > 10240) { 481 reply(550, "%s: file too large for SIZE.", filename); 482 (void) fclose(fin); 483 return; 484 } 485 486 count = 0; 487 while((c = getc(fin)) != EOF) { 488 if (c == '\n') /* will get expanded to \r\n */ 489 count++; 490 count++; 491 } 492 (void) fclose(fin); 493 494 reply(213, LLF, (LLT)count); 495 break; 496 } 497 default: 498 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); 499 } 500 } 501 502 void 503 statfilecmd(const char *filename) 504 { 505 FILE *fin; 506 int c; 507 int atstart; 508 const char *argv[] = { INTERNAL_LS, "-lgA", "", NULL }; 509 510 argv[2] = filename; 511 fin = ftpd_popen(argv, "r", STDOUT_FILENO); 512 reply(-211, "status of %s:", filename); 513 /* XXX: use fgetln() or fparseln() here? */ 514 atstart = 1; 515 while ((c = getc(fin)) != EOF) { 516 if (c == '\n') { 517 if (ferror(stdout)){ 518 perror_reply(421, "control connection"); 519 (void) ftpd_pclose(fin); 520 dologout(1); 521 /* NOTREACHED */ 522 } 523 if (ferror(fin)) { 524 perror_reply(551, filename); 525 (void) ftpd_pclose(fin); 526 return; 527 } 528 CPUTC('\r', stdout); 529 } 530 if (atstart && isdigit(c)) 531 CPUTC(' ', stdout); 532 CPUTC(c, stdout); 533 atstart = (c == '\n'); 534 } 535 (void) ftpd_pclose(fin); 536 reply(211, "End of Status"); 537 } 538 539 /* -- */ 540 541 static void 542 ack(const char *s) 543 { 544 545 reply(250, "%s command successful.", s); 546 } 547 548 /* 549 * Encode len bytes starting at clear using base64 encoding into encoded, 550 * which should be at least ((len + 2) * 4 / 3 + 1) in size. 551 * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary 552 * with `='. 553 */ 554 static void 555 base64_encode(const char *clear, size_t len, char *encoded, int nulterm) 556 { 557 static const char base64[] = 558 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 559 const char *c; 560 char *e, termchar; 561 int i; 562 563 /* determine whether to pad with '=' or NUL terminate */ 564 termchar = nulterm ? '\0' : '='; 565 c = clear; 566 e = encoded; 567 /* convert all but last 2 bytes */ 568 for (i = len; i > 2; i -= 3, c += 3) { 569 *e++ = base64[(c[0] >> 2) & 0x3f]; 570 *e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)]; 571 *e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)]; 572 *e++ = base64[(c[2]) & 0x3f]; 573 } 574 /* handle slop at end */ 575 if (i > 0) { 576 *e++ = base64[(c[0] >> 2) & 0x3f]; 577 *e++ = base64[((c[0] << 4) & 0x30) | 578 (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)]; 579 *e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar; 580 *e++ = termchar; 581 } 582 *e = '\0'; 583 } 584 585 static void 586 fact_modify(const char *fact, FILE *fd, factelem *fe) 587 { 588 struct tm *t; 589 590 t = gmtime(&(fe->stat->st_mtime)); 591 cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact, 592 TM_YEAR_BASE + t->tm_year, 593 t->tm_mon+1, t->tm_mday, 594 t->tm_hour, t->tm_min, t->tm_sec); 595 } 596 597 static void 598 fact_perm(const char *fact, FILE *fd, factelem *fe) 599 { 600 int rok, wok, xok, pdirwok; 601 struct stat *pdir; 602 603 if (fe->stat->st_uid == geteuid()) { 604 rok = ((fe->stat->st_mode & S_IRUSR) != 0); 605 wok = ((fe->stat->st_mode & S_IWUSR) != 0); 606 xok = ((fe->stat->st_mode & S_IXUSR) != 0); 607 } else if (matchgroup(fe->stat->st_gid)) { 608 rok = ((fe->stat->st_mode & S_IRGRP) != 0); 609 wok = ((fe->stat->st_mode & S_IWGRP) != 0); 610 xok = ((fe->stat->st_mode & S_IXGRP) != 0); 611 } else { 612 rok = ((fe->stat->st_mode & S_IROTH) != 0); 613 wok = ((fe->stat->st_mode & S_IWOTH) != 0); 614 xok = ((fe->stat->st_mode & S_IXOTH) != 0); 615 } 616 617 cprintf(fd, "%s=", fact); 618 619 /* 620 * if parent info not provided, look it up, but 621 * only if the current class has modify rights, 622 * since we only need this info in such a case. 623 */ 624 pdir = fe->pdirstat; 625 if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) { 626 size_t len; 627 char realdir[MAXPATHLEN], *p; 628 struct stat dir; 629 630 len = strlcpy(realdir, fe->path, sizeof(realdir)); 631 if (len < sizeof(realdir) - 4) { 632 if (S_ISDIR(fe->stat->st_mode)) 633 strlcat(realdir, "/..", sizeof(realdir)); 634 else { 635 /* if has a /, move back to it */ 636 /* otherwise use '..' */ 637 if ((p = strrchr(realdir, '/')) != NULL) { 638 if (p == realdir) 639 p++; 640 *p = '\0'; 641 } else 642 strlcpy(realdir, "..", sizeof(realdir)); 643 } 644 if (stat(realdir, &dir) == 0) 645 pdir = &dir; 646 } 647 } 648 pdirwok = 0; 649 if (pdir != NULL) { 650 if (pdir->st_uid == geteuid()) 651 pdirwok = ((pdir->st_mode & S_IWUSR) != 0); 652 else if (matchgroup(pdir->st_gid)) 653 pdirwok = ((pdir->st_mode & S_IWGRP) != 0); 654 else 655 pdirwok = ((pdir->st_mode & S_IWOTH) != 0); 656 } 657 658 /* 'a': can APPE to file */ 659 if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode)) 660 CPUTC('a', fd); 661 662 /* 'c': can create or append to files in directory */ 663 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) 664 CPUTC('c', fd); 665 666 /* 'd': can delete file or directory */ 667 if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) { 668 int candel; 669 670 candel = 1; 671 if (S_ISDIR(fe->stat->st_mode)) { 672 DIR *dirp; 673 struct dirent *dp; 674 675 if ((dirp = opendir(fe->display)) == NULL) 676 candel = 0; 677 else { 678 while ((dp = readdir(dirp)) != NULL) { 679 if (ISDOTDIR(dp->d_name) || 680 ISDOTDOTDIR(dp->d_name)) 681 continue; 682 candel = 0; 683 break; 684 } 685 closedir(dirp); 686 } 687 } 688 if (candel) 689 CPUTC('d', fd); 690 } 691 692 /* 'e': can enter directory */ 693 if (xok && S_ISDIR(fe->stat->st_mode)) 694 CPUTC('e', fd); 695 696 /* 'f': can rename file or directory */ 697 if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) 698 CPUTC('f', fd); 699 700 /* 'l': can list directory */ 701 if (rok && xok && S_ISDIR(fe->stat->st_mode)) 702 CPUTC('l', fd); 703 704 /* 'm': can create directory */ 705 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) 706 CPUTC('m', fd); 707 708 /* 'p': can remove files in directory */ 709 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) 710 CPUTC('p', fd); 711 712 /* 'r': can RETR file */ 713 if (rok && S_ISREG(fe->stat->st_mode)) 714 CPUTC('r', fd); 715 716 /* 'w': can STOR file */ 717 if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode)) 718 CPUTC('w', fd); 719 720 CPUTC(';', fd); 721 } 722 723 static void 724 fact_size(const char *fact, FILE *fd, factelem *fe) 725 { 726 727 if (S_ISREG(fe->stat->st_mode)) 728 cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size); 729 } 730 731 static void 732 fact_type(const char *fact, FILE *fd, factelem *fe) 733 { 734 735 cprintf(fd, "%s=", fact); 736 switch (fe->stat->st_mode & S_IFMT) { 737 case S_IFDIR: 738 if (fe->flags & FE_MLSD) { 739 if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display)) 740 cprintf(fd, "cdir"); 741 else if (ISDOTDOTDIR(fe->display)) 742 cprintf(fd, "pdir"); 743 else 744 cprintf(fd, "dir"); 745 } else { 746 cprintf(fd, "dir"); 747 } 748 break; 749 case S_IFREG: 750 cprintf(fd, "file"); 751 break; 752 case S_IFIFO: 753 cprintf(fd, "OS.unix=fifo"); 754 break; 755 case S_IFLNK: /* XXX: probably a NO-OP with stat() */ 756 cprintf(fd, "OS.unix=slink"); 757 break; 758 case S_IFSOCK: 759 cprintf(fd, "OS.unix=socket"); 760 break; 761 case S_IFBLK: 762 case S_IFCHR: 763 cprintf(fd, "OS.unix=%s-" ULLF "/" ULLF, 764 S_ISBLK(fe->stat->st_mode) ? "blk" : "chr", 765 (ULLT)major(fe->stat->st_rdev), 766 (ULLT)minor(fe->stat->st_rdev)); 767 break; 768 default: 769 cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT); 770 break; 771 } 772 CPUTC(';', fd); 773 } 774 775 static void 776 fact_unique(const char *fact, FILE *fd, factelem *fe) 777 { 778 char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2]; 779 char tbuf[sizeof(dev_t) + sizeof(ino_t)]; 780 781 memcpy(tbuf, 782 (char *)&(fe->stat->st_dev), sizeof(dev_t)); 783 memcpy(tbuf + sizeof(dev_t), 784 (char *)&(fe->stat->st_ino), sizeof(ino_t)); 785 base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1); 786 cprintf(fd, "%s=%s;", fact, obuf); 787 } 788 789 static int 790 matchgroup(gid_t gid) 791 { 792 int i; 793 794 for (i = 0; i < gidcount; i++) 795 if (gid == gidlist[i]) 796 return(1); 797 return (0); 798 } 799 800 static void 801 mlsname(FILE *fp, factelem *fe) 802 { 803 char realfile[MAXPATHLEN]; 804 int userf = 0; 805 size_t i; 806 807 for (i = 0; i < FACTTABSIZE; i++) { 808 if (facttab[i].enabled) 809 (facttab[i].display)(facttab[i].name, fp, fe); 810 } 811 if ((fe->flags & FE_MLSD) && 812 !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) { 813 /* if MLSD and not "." entry, display as-is */ 814 userf = 0; 815 } else { 816 /* if MLST, or MLSD and "." entry, realpath(3) it */ 817 if (realpath(fe->display, realfile) != NULL) 818 userf = 1; 819 } 820 cprintf(fp, " %s\r\n", userf ? realfile : fe->display); 821 } 822 823 static void 824 replydirname(const char *name, const char *message) 825 { 826 char *p, *ep; 827 char npath[MAXPATHLEN * 2]; 828 829 p = npath; 830 ep = &npath[sizeof(npath) - 1]; 831 while (*name) { 832 if (*name == '"') { 833 if (ep - p < 2) 834 break; 835 *p++ = *name++; 836 *p++ = '"'; 837 } else { 838 if (ep - p < 1) 839 break; 840 *p++ = *name++; 841 } 842 } 843 *p = '\0'; 844 reply(257, "\"%s\" %s", npath, message); 845 } 846 847 static void 848 discover_path(char *last_path, const char *new_path) 849 { 850 char tp[MAXPATHLEN + 1] = ""; 851 char tq[MAXPATHLEN + 1] = ""; 852 char *cp; 853 char *cq; 854 int sz1, sz2; 855 int nomorelink; 856 struct stat st1, st2; 857 858 if (new_path[0] != '/') { 859 (void)strlcpy(tp, last_path, MAXPATHLEN); 860 (void)strlcat(tp, "/", MAXPATHLEN); 861 } 862 (void)strlcat(tp, new_path, MAXPATHLEN); 863 (void)strlcat(tp, "/", MAXPATHLEN); 864 865 /* 866 * resolve symlinks. A symlink may introduce another symlink, so we 867 * loop trying to resolve symlinks until we don't find any of them. 868 */ 869 do { 870 /* Collapse any // into / */ 871 while ((cp = strstr(tp, "//")) != NULL) 872 (void)memmove(cp, cp + 1, strlen(cp) - 1 + 1); 873 874 /* Collapse any /./ into / */ 875 while ((cp = strstr(tp, "/./")) != NULL) 876 (void)memmove(cp, cp + 2, strlen(cp) - 2 + 1); 877 878 cp = tp; 879 nomorelink = 1; 880 881 while ((cp = strstr(cp + 1, "/")) != NULL) { 882 sz1 = (unsigned long)cp - (unsigned long)tp; 883 if (sz1 > MAXPATHLEN) 884 goto bad; 885 *cp = 0; 886 sz2 = readlink(tp, tq, MAXPATHLEN); 887 *cp = '/'; 888 889 /* If this is not a symlink, move to next / */ 890 if (sz2 <= 0) 891 continue; 892 893 /* 894 * We found a symlink, so we will have to 895 * do one more pass to check there is no 896 * more symlink in the path 897 */ 898 nomorelink = 0; 899 900 /* 901 * Null terminate the string and remove trailing / 902 */ 903 tq[sz2] = 0; 904 sz2 = strlen(tq); 905 if (tq[sz2 - 1] == '/') 906 tq[--sz2] = 0; 907 908 /* 909 * Is this an absolute link or a relative link? 910 */ 911 if (tq[0] == '/') { 912 /* absolute link */ 913 if (strlen(cp) + sz2 > MAXPATHLEN) 914 goto bad; 915 memmove(tp + sz2, cp, strlen(cp) + 1); 916 memcpy(tp, tq, sz2); 917 } else { 918 /* relative link */ 919 for (cq = cp - 1; *cq != '/'; cq--); 920 if (strlen(tp) - 921 ((unsigned long)cq - (unsigned long)cp) 922 + 1 + sz2 > MAXPATHLEN) 923 goto bad; 924 (void)memmove(cq + 1 + sz2, 925 cp, strlen(cp) + 1); 926 (void)memcpy(cq + 1, tq, sz2); 927 } 928 929 /* 930 * start over, looking for new symlinks 931 */ 932 break; 933 } 934 } while (nomorelink == 0); 935 936 /* Collapse any /foo/../ into /foo/ */ 937 while ((cp = strstr(tp, "/../")) != NULL) { 938 /* ^/../foo/ becomes ^/foo/ */ 939 if (cp == tp) { 940 (void)memmove(cp, cp + 3, 941 strlen(cp) - 3 + 1); 942 } else { 943 for (cq = cp - 1; *cq != '/'; cq--); 944 (void)memmove(cq, cp + 3, 945 strlen(cp) - 3 + 1); 946 } 947 } 948 949 /* strip strailing / */ 950 if (strlen(tp) != 1) 951 tp[strlen(tp) - 1] = '\0'; 952 953 /* check that the path is correct */ 954 if (stat(tp, &st1) == -1 || stat(".", &st2) == -1) 955 goto bad; 956 if ((st1.st_dev != st2.st_dev) || (st1.st_ino != st2.st_ino)) 957 goto bad; 958 959 (void)strlcpy(last_path, tp, MAXPATHLEN); 960 return; 961 962 bad: 963 (void)strlcat(last_path, "/", MAXPATHLEN); 964 (void)strlcat(last_path, new_path, MAXPATHLEN); 965 return; 966 } 967 968