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