1 /* $NetBSD: cmds.c,v 1.35 2016/01/17 14:46:07 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.35 2016/01/17 14:46:07 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 (void) closedir(dirp); 254 return; 255 } 256 257 memset(&f, 0, sizeof(f)); 258 f.stat = &sb; 259 f.flags |= FE_MLSD; 260 while ((dp = readdir(dirp)) != NULL) { 261 snprintf(name, sizeof(name), "%s/%s", path, dp->d_name); 262 if (ISDOTDIR(dp->d_name)) { /* special case curdir: */ 263 if (! hastypefact) 264 continue; 265 f.pdirstat = NULL; /* require stat of parent */ 266 f.display = path; /* set name to real name */ 267 f.flags |= FE_ISCURDIR; /* flag name is curdir */ 268 } else { 269 if (ISDOTDOTDIR(dp->d_name)) { 270 if (! hastypefact) 271 continue; 272 f.pdirstat = NULL; 273 } else 274 f.pdirstat = &pdirstat; /* cache parent stat */ 275 f.display = dp->d_name; 276 f.flags &= ~FE_ISCURDIR; 277 } 278 if (stat(name, &sb) == -1) 279 continue; 280 f.path = name; 281 mlsname(dout, &f); 282 } 283 (void)closedir(dirp); 284 285 if (ferror(dout) != 0) 286 perror_reply(550, "Data connection"); 287 else 288 reply(226, "MLSD complete."); 289 closedataconn(dout); 290 total_xfers_out++; 291 total_xfers++; 292 } 293 294 void 295 mlst(const char *path) 296 { 297 struct stat sb; 298 factelem f; 299 300 if (path == NULL) 301 path = "."; 302 if (stat(path, &sb) == -1) { 303 perror_reply(550, path); 304 return; 305 } 306 reply(-250, "MLST %s", path); 307 memset(&f, 0, sizeof(f)); 308 f.path = path; 309 f.display = path; 310 f.stat = &sb; 311 f.pdirstat = NULL; 312 CPUTC(' ', stdout); 313 mlsname(stdout, &f); 314 reply(250, "End"); 315 } 316 317 318 void 319 opts(const char *command) 320 { 321 struct tab *c; 322 char *ep; 323 324 if ((ep = strchr(command, ' ')) != NULL) 325 *ep++ = '\0'; 326 c = lookup(cmdtab, command); 327 if (c == NULL) { 328 reply(502, "Unknown command '%s'.", command); 329 return; 330 } 331 if (! CMD_IMPLEMENTED(c)) { 332 reply(502, "%s command not implemented.", c->name); 333 return; 334 } 335 if (! CMD_HAS_OPTIONS(c)) { 336 reply(501, "%s command does not support persistent options.", 337 c->name); 338 return; 339 } 340 341 /* special case: MLST */ 342 if (strcasecmp(command, "MLST") == 0) { 343 int enabled[FACTTABSIZE]; 344 size_t i, onedone; 345 size_t len; 346 char *p; 347 348 for (i = 0; i < sizeof(enabled) / sizeof(int); i++) 349 enabled[i] = 0; 350 if (ep == NULL || *ep == '\0') 351 goto displaymlstopts; 352 353 /* don't like spaces, and need trailing ; */ 354 len = strlen(ep); 355 if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') { 356 badmlstopt: 357 reply(501, "Invalid MLST options"); 358 return; 359 } 360 ep[len - 1] = '\0'; 361 while ((p = strsep(&ep, ";")) != NULL) { 362 if (*p == '\0') 363 goto badmlstopt; 364 for (i = 0; i < FACTTABSIZE; i++) 365 if (strcasecmp(p, facttab[i].name) == 0) { 366 enabled[i] = 1; 367 break; 368 } 369 } 370 371 displaymlstopts: 372 for (i = 0; i < FACTTABSIZE; i++) 373 facttab[i].enabled = enabled[i]; 374 cprintf(stdout, "200 MLST OPTS"); 375 for (i = onedone = 0; i < FACTTABSIZE; i++) { 376 if (facttab[i].enabled) { 377 cprintf(stdout, "%s%s;", onedone ? "" : " ", 378 facttab[i].name); 379 onedone++; 380 } 381 } 382 cprintf(stdout, "\r\n"); 383 fflush(stdout); 384 return; 385 } 386 387 /* default cases */ 388 if (ep != NULL && *ep != '\0') 389 REASSIGN(c->options, ftpd_strdup(ep)); 390 if (c->options != NULL) 391 reply(200, "Options for %s are '%s'.", c->name, 392 c->options); 393 else 394 reply(200, "No options defined for %s.", c->name); 395 } 396 397 void 398 pwd(void) 399 { 400 char path[MAXPATHLEN]; 401 402 if (getcwd(path, sizeof(path) - 1) == NULL) { 403 if (chdir(cached_path) < 0) { 404 reply(550, "Can't get the current directory: %s.", 405 strerror(errno)); 406 return; 407 } 408 (void)strlcpy(path, cached_path, MAXPATHLEN); 409 } 410 replydirname(path, "is the current directory."); 411 } 412 413 void 414 removedir(const char *name) 415 { 416 char *p = NULL; 417 418 if (rmdir(name) < 0) { 419 p = strerror(errno); 420 perror_reply(550, name); 421 } else 422 ack("RMD"); 423 logxfer("rmdir", -1, name, NULL, NULL, p); 424 } 425 426 char * 427 renamefrom(const char *name) 428 { 429 struct stat st; 430 431 if (stat(name, &st) < 0) { 432 perror_reply(550, name); 433 return (NULL); 434 } 435 reply(350, "File exists, ready for destination name"); 436 return (ftpd_strdup(name)); 437 } 438 439 void 440 renamecmd(const char *from, const char *to) 441 { 442 char *p = NULL; 443 444 if (rename(from, to) < 0) { 445 p = strerror(errno); 446 perror_reply(550, "rename"); 447 } else 448 ack("RNTO"); 449 logxfer("rename", -1, from, to, NULL, p); 450 } 451 452 void 453 sizecmd(const char *filename) 454 { 455 switch (type) { 456 case TYPE_L: 457 case TYPE_I: 458 { 459 struct stat stbuf; 460 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) 461 reply(550, "%s: not a plain file.", filename); 462 else 463 reply(213, ULLF, (ULLT)stbuf.st_size); 464 break; 465 } 466 case TYPE_A: 467 { 468 FILE *fin; 469 int c; 470 off_t count; 471 struct stat stbuf; 472 fin = fopen(filename, "r"); 473 if (fin == NULL) { 474 perror_reply(550, filename); 475 return; 476 } 477 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) { 478 reply(550, "%s: not a plain file.", filename); 479 (void) fclose(fin); 480 return; 481 } 482 if (stbuf.st_size > 10240) { 483 reply(550, "%s: file too large for SIZE.", filename); 484 (void) fclose(fin); 485 return; 486 } 487 488 count = 0; 489 while((c = getc(fin)) != EOF) { 490 if (c == '\n') /* will get expanded to \r\n */ 491 count++; 492 count++; 493 } 494 (void) fclose(fin); 495 496 reply(213, LLF, (LLT)count); 497 break; 498 } 499 default: 500 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); 501 } 502 } 503 504 void 505 statfilecmd(const char *filename) 506 { 507 FILE *fin; 508 int c; 509 int atstart; 510 const char *argv[] = { INTERNAL_LS, "-lgA", "", NULL }; 511 512 argv[2] = filename; 513 fin = ftpd_popen(argv, "r", STDOUT_FILENO); 514 reply(-211, "status of %s:", filename); 515 /* XXX: use fgetln() or fparseln() here? */ 516 atstart = 1; 517 while ((c = getc(fin)) != EOF) { 518 if (c == '\n') { 519 if (ferror(stdout)){ 520 perror_reply(421, "control connection"); 521 (void) ftpd_pclose(fin); 522 dologout(1); 523 /* NOTREACHED */ 524 } 525 if (ferror(fin)) { 526 perror_reply(551, filename); 527 (void) ftpd_pclose(fin); 528 return; 529 } 530 CPUTC('\r', stdout); 531 } 532 if (atstart && isdigit(c)) 533 CPUTC(' ', stdout); 534 CPUTC(c, stdout); 535 atstart = (c == '\n'); 536 } 537 (void) ftpd_pclose(fin); 538 reply(211, "End of Status"); 539 } 540 541 /* -- */ 542 543 static void 544 ack(const char *s) 545 { 546 547 reply(250, "%s command successful.", s); 548 } 549 550 /* 551 * Encode len bytes starting at clear using base64 encoding into encoded, 552 * which should be at least ((len + 2) * 4 / 3 + 1) in size. 553 * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary 554 * with `='. 555 */ 556 static void 557 base64_encode(const char *clear, size_t len, char *encoded, int nulterm) 558 { 559 static const char base64[] = 560 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 561 const char *c; 562 char *e, termchar; 563 int i; 564 565 /* determine whether to pad with '=' or NUL terminate */ 566 termchar = nulterm ? '\0' : '='; 567 c = clear; 568 e = encoded; 569 /* convert all but last 2 bytes */ 570 for (i = len; i > 2; i -= 3, c += 3) { 571 *e++ = base64[(c[0] >> 2) & 0x3f]; 572 *e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)]; 573 *e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)]; 574 *e++ = base64[(c[2]) & 0x3f]; 575 } 576 /* handle slop at end */ 577 if (i > 0) { 578 *e++ = base64[(c[0] >> 2) & 0x3f]; 579 *e++ = base64[((c[0] << 4) & 0x30) | 580 (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)]; 581 *e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar; 582 *e++ = termchar; 583 } 584 *e = '\0'; 585 } 586 587 static void 588 fact_modify(const char *fact, FILE *fd, factelem *fe) 589 { 590 struct tm *t; 591 592 t = gmtime(&(fe->stat->st_mtime)); 593 cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact, 594 TM_YEAR_BASE + t->tm_year, 595 t->tm_mon+1, t->tm_mday, 596 t->tm_hour, t->tm_min, t->tm_sec); 597 } 598 599 static void 600 fact_perm(const char *fact, FILE *fd, factelem *fe) 601 { 602 int rok, wok, xok, pdirwok; 603 struct stat *pdir, dir; 604 605 if (fe->stat->st_uid == geteuid()) { 606 rok = ((fe->stat->st_mode & S_IRUSR) != 0); 607 wok = ((fe->stat->st_mode & S_IWUSR) != 0); 608 xok = ((fe->stat->st_mode & S_IXUSR) != 0); 609 } else if (matchgroup(fe->stat->st_gid)) { 610 rok = ((fe->stat->st_mode & S_IRGRP) != 0); 611 wok = ((fe->stat->st_mode & S_IWGRP) != 0); 612 xok = ((fe->stat->st_mode & S_IXGRP) != 0); 613 } else { 614 rok = ((fe->stat->st_mode & S_IROTH) != 0); 615 wok = ((fe->stat->st_mode & S_IWOTH) != 0); 616 xok = ((fe->stat->st_mode & S_IXOTH) != 0); 617 } 618 619 cprintf(fd, "%s=", fact); 620 621 /* 622 * if parent info not provided, look it up, but 623 * only if the current class has modify rights, 624 * since we only need this info in such a case. 625 */ 626 pdir = fe->pdirstat; 627 if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) { 628 size_t len; 629 char realdir[MAXPATHLEN], *p; 630 631 len = strlcpy(realdir, fe->path, sizeof(realdir)); 632 if (len < sizeof(realdir) - 4) { 633 if (S_ISDIR(fe->stat->st_mode)) 634 strlcat(realdir, "/..", sizeof(realdir)); 635 else { 636 /* if has a /, move back to it */ 637 /* otherwise use '..' */ 638 if ((p = strrchr(realdir, '/')) != NULL) { 639 if (p == realdir) 640 p++; 641 *p = '\0'; 642 } else 643 strlcpy(realdir, "..", sizeof(realdir)); 644 } 645 if (stat(realdir, &dir) == 0) 646 pdir = &dir; 647 } 648 } 649 pdirwok = 0; 650 if (pdir != NULL) { 651 if (pdir->st_uid == geteuid()) 652 pdirwok = ((pdir->st_mode & S_IWUSR) != 0); 653 else if (matchgroup(pdir->st_gid)) 654 pdirwok = ((pdir->st_mode & S_IWGRP) != 0); 655 else 656 pdirwok = ((pdir->st_mode & S_IWOTH) != 0); 657 } 658 659 /* 'a': can APPE to file */ 660 if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode)) 661 CPUTC('a', fd); 662 663 /* 'c': can create or append to files in directory */ 664 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) 665 CPUTC('c', fd); 666 667 /* 'd': can delete file or directory */ 668 if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) { 669 int candel; 670 671 candel = 1; 672 if (S_ISDIR(fe->stat->st_mode)) { 673 DIR *dirp; 674 struct dirent *dp; 675 676 if ((dirp = opendir(fe->display)) == NULL) 677 candel = 0; 678 else { 679 while ((dp = readdir(dirp)) != NULL) { 680 if (ISDOTDIR(dp->d_name) || 681 ISDOTDOTDIR(dp->d_name)) 682 continue; 683 candel = 0; 684 break; 685 } 686 closedir(dirp); 687 } 688 } 689 if (candel) 690 CPUTC('d', fd); 691 } 692 693 /* 'e': can enter directory */ 694 if (xok && S_ISDIR(fe->stat->st_mode)) 695 CPUTC('e', fd); 696 697 /* 'f': can rename file or directory */ 698 if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) 699 CPUTC('f', fd); 700 701 /* 'l': can list directory */ 702 if (rok && xok && S_ISDIR(fe->stat->st_mode)) 703 CPUTC('l', fd); 704 705 /* 'm': can create directory */ 706 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) 707 CPUTC('m', fd); 708 709 /* 'p': can remove files in directory */ 710 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) 711 CPUTC('p', fd); 712 713 /* 'r': can RETR file */ 714 if (rok && S_ISREG(fe->stat->st_mode)) 715 CPUTC('r', fd); 716 717 /* 'w': can STOR file */ 718 if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode)) 719 CPUTC('w', fd); 720 721 CPUTC(';', fd); 722 } 723 724 static void 725 fact_size(const char *fact, FILE *fd, factelem *fe) 726 { 727 728 if (S_ISREG(fe->stat->st_mode)) 729 cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size); 730 } 731 732 static void 733 fact_type(const char *fact, FILE *fd, factelem *fe) 734 { 735 736 cprintf(fd, "%s=", fact); 737 switch (fe->stat->st_mode & S_IFMT) { 738 case S_IFDIR: 739 if (fe->flags & FE_MLSD) { 740 if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display)) 741 cprintf(fd, "cdir"); 742 else if (ISDOTDOTDIR(fe->display)) 743 cprintf(fd, "pdir"); 744 else 745 cprintf(fd, "dir"); 746 } else { 747 cprintf(fd, "dir"); 748 } 749 break; 750 case S_IFREG: 751 cprintf(fd, "file"); 752 break; 753 case S_IFIFO: 754 cprintf(fd, "OS.unix=fifo"); 755 break; 756 case S_IFLNK: /* XXX: probably a NO-OP with stat() */ 757 cprintf(fd, "OS.unix=slink"); 758 break; 759 case S_IFSOCK: 760 cprintf(fd, "OS.unix=socket"); 761 break; 762 case S_IFBLK: 763 case S_IFCHR: 764 cprintf(fd, "OS.unix=%s-" ULLF "/" ULLF, 765 S_ISBLK(fe->stat->st_mode) ? "blk" : "chr", 766 (ULLT)major(fe->stat->st_rdev), 767 (ULLT)minor(fe->stat->st_rdev)); 768 break; 769 default: 770 cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT); 771 break; 772 } 773 CPUTC(';', fd); 774 } 775 776 static void 777 fact_unique(const char *fact, FILE *fd, factelem *fe) 778 { 779 char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2]; 780 char tbuf[sizeof(dev_t) + sizeof(ino_t)]; 781 782 memcpy(tbuf, 783 (char *)&(fe->stat->st_dev), sizeof(dev_t)); 784 memcpy(tbuf + sizeof(dev_t), 785 (char *)&(fe->stat->st_ino), sizeof(ino_t)); 786 base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1); 787 cprintf(fd, "%s=%s;", fact, obuf); 788 } 789 790 static int 791 matchgroup(gid_t gid) 792 { 793 int i; 794 795 for (i = 0; i < gidcount; i++) 796 if (gid == gidlist[i]) 797 return(1); 798 return (0); 799 } 800 801 static void 802 mlsname(FILE *fp, factelem *fe) 803 { 804 char realfile[MAXPATHLEN]; 805 int userf = 0; 806 size_t i; 807 808 for (i = 0; i < FACTTABSIZE; i++) { 809 if (facttab[i].enabled) 810 (facttab[i].display)(facttab[i].name, fp, fe); 811 } 812 if ((fe->flags & FE_MLSD) && 813 !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) { 814 /* if MLSD and not "." entry, display as-is */ 815 userf = 0; 816 } else { 817 /* if MLST, or MLSD and "." entry, realpath(3) it */ 818 if (realpath(fe->display, realfile) != NULL) 819 userf = 1; 820 } 821 cprintf(fp, " %s\r\n", userf ? realfile : fe->display); 822 } 823 824 static void 825 replydirname(const char *name, const char *message) 826 { 827 char *p, *ep; 828 char npath[MAXPATHLEN * 2]; 829 830 p = npath; 831 ep = &npath[sizeof(npath) - 1]; 832 while (*name) { 833 if (*name == '"') { 834 if (ep - p < 2) 835 break; 836 *p++ = *name++; 837 *p++ = '"'; 838 } else { 839 if (ep - p < 1) 840 break; 841 *p++ = *name++; 842 } 843 } 844 *p = '\0'; 845 reply(257, "\"%s\" %s", npath, message); 846 } 847 848 static void 849 discover_path(char *last_path, const char *new_path) 850 { 851 char tp[MAXPATHLEN + 1] = ""; 852 char tq[MAXPATHLEN + 1] = ""; 853 char *cp; 854 char *cq; 855 int sz1, sz2; 856 int nomorelink; 857 struct stat st1, st2; 858 859 if (new_path[0] != '/') { 860 (void)strlcpy(tp, last_path, MAXPATHLEN); 861 (void)strlcat(tp, "/", MAXPATHLEN); 862 } 863 (void)strlcat(tp, new_path, MAXPATHLEN); 864 (void)strlcat(tp, "/", MAXPATHLEN); 865 866 /* 867 * resolve symlinks. A symlink may introduce another symlink, so we 868 * loop trying to resolve symlinks until we don't find any of them. 869 */ 870 do { 871 /* Collapse any // into / */ 872 while ((cp = strstr(tp, "//")) != NULL) 873 (void)memmove(cp, cp + 1, strlen(cp) - 1 + 1); 874 875 /* Collapse any /./ into / */ 876 while ((cp = strstr(tp, "/./")) != NULL) 877 (void)memmove(cp, cp + 2, strlen(cp) - 2 + 1); 878 879 cp = tp; 880 nomorelink = 1; 881 882 while ((cp = strstr(cp + 1, "/")) != NULL) { 883 sz1 = (unsigned long)cp - (unsigned long)tp; 884 if (sz1 > MAXPATHLEN) 885 goto bad; 886 *cp = 0; 887 sz2 = readlink(tp, tq, MAXPATHLEN); 888 *cp = '/'; 889 890 /* If this is not a symlink, move to next / */ 891 if (sz2 <= 0) 892 continue; 893 894 /* 895 * We found a symlink, so we will have to 896 * do one more pass to check there is no 897 * more symlink in the path 898 */ 899 nomorelink = 0; 900 901 /* 902 * Null terminate the string and remove trailing / 903 */ 904 tq[sz2] = 0; 905 sz2 = strlen(tq); 906 if (tq[sz2 - 1] == '/') 907 tq[--sz2] = 0; 908 909 /* 910 * Is this an absolute link or a relative link? 911 */ 912 if (tq[0] == '/') { 913 /* absolute link */ 914 if (strlen(cp) + sz2 > MAXPATHLEN) 915 goto bad; 916 memmove(tp + sz2, cp, strlen(cp) + 1); 917 memcpy(tp, tq, sz2); 918 } else { 919 /* relative link */ 920 for (cq = cp - 1; *cq != '/'; cq--); 921 if (strlen(tp) - 922 ((unsigned long)cq - (unsigned long)cp) 923 + 1 + sz2 > MAXPATHLEN) 924 goto bad; 925 (void)memmove(cq + 1 + sz2, 926 cp, strlen(cp) + 1); 927 (void)memcpy(cq + 1, tq, sz2); 928 } 929 930 /* 931 * start over, looking for new symlinks 932 */ 933 break; 934 } 935 } while (nomorelink == 0); 936 937 /* Collapse any /foo/../ into /foo/ */ 938 while ((cp = strstr(tp, "/../")) != NULL) { 939 /* ^/../foo/ becomes ^/foo/ */ 940 if (cp == tp) { 941 (void)memmove(cp, cp + 3, 942 strlen(cp) - 3 + 1); 943 } else { 944 for (cq = cp - 1; *cq != '/'; cq--); 945 (void)memmove(cq, cp + 3, 946 strlen(cp) - 3 + 1); 947 } 948 } 949 950 /* strip strailing / */ 951 if (strlen(tp) != 1) 952 tp[strlen(tp) - 1] = '\0'; 953 954 /* check that the path is correct */ 955 if (stat(tp, &st1) == -1 || stat(".", &st2) == -1) 956 goto bad; 957 if ((st1.st_dev != st2.st_dev) || (st1.st_ino != st2.st_ino)) 958 goto bad; 959 960 (void)strlcpy(last_path, tp, MAXPATHLEN); 961 return; 962 963 bad: 964 (void)strlcat(last_path, "/", MAXPATHLEN); 965 (void)strlcat(last_path, new_path, MAXPATHLEN); 966 return; 967 } 968 969