1 /* $NetBSD: cmds.c,v 1.16 2002/02/13 15:15:23 lukem 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. All advertising materials mentioning features or use of this software 52 * must display the following acknowledgement: 53 * This product includes software developed by the University of 54 * California, Berkeley and its contributors. 55 * 4. Neither the name of the University nor the names of its contributors 56 * may be used to endorse or promote products derived from this software 57 * without specific prior written permission. 58 * 59 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 60 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 61 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 62 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 63 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 64 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 65 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 66 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 67 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 68 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 69 * SUCH DAMAGE. 70 */ 71 72 /* 73 * Copyright (C) 1997 and 1998 WIDE Project. 74 * All rights reserved. 75 * 76 * Redistribution and use in source and binary forms, with or without 77 * modification, are permitted provided that the following conditions 78 * are met: 79 * 1. Redistributions of source code must retain the above copyright 80 * notice, this list of conditions and the following disclaimer. 81 * 2. Redistributions in binary form must reproduce the above copyright 82 * notice, this list of conditions and the following disclaimer in the 83 * documentation and/or other materials provided with the distribution. 84 * 3. Neither the name of the project nor the names of its contributors 85 * may be used to endorse or promote products derived from this software 86 * without specific prior written permission. 87 * 88 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 89 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 90 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 91 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 92 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 93 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 94 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 95 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 96 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 97 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 98 * SUCH DAMAGE. 99 */ 100 101 102 #include <sys/cdefs.h> 103 #ifndef lint 104 __RCSID("$NetBSD: cmds.c,v 1.16 2002/02/13 15:15:23 lukem Exp $"); 105 #endif /* not lint */ 106 107 #include <sys/param.h> 108 #include <sys/stat.h> 109 110 #include <arpa/ftp.h> 111 112 #include <dirent.h> 113 #include <errno.h> 114 #include <setjmp.h> 115 #include <stdio.h> 116 #include <stdlib.h> 117 #include <string.h> 118 #include <tzfile.h> 119 #include <unistd.h> 120 121 #ifdef KERBEROS5 122 #include <krb5/krb5.h> 123 #endif 124 125 #include "extern.h" 126 127 typedef enum { 128 FE_MLSD = 1<<0, /* if op is MLSD (MLST otherwise ) */ 129 FE_ISCURDIR = 1<<1, /* if name is the current directory */ 130 } factflag_t; 131 132 typedef struct { 133 const char *path; /* full pathname */ 134 const char *display; /* name to display */ 135 struct stat *stat; /* stat of path */ 136 struct stat *pdirstat; /* stat of path's parent dir */ 137 factflag_t flags; /* flags */ 138 } factelem; 139 140 static void ack(const char *); 141 static void base64_encode(const char *, size_t, char *, int); 142 static void fact_type(const char *, FILE *, factelem *); 143 static void fact_size(const char *, FILE *, factelem *); 144 static void fact_modify(const char *, FILE *, factelem *); 145 static void fact_perm(const char *, FILE *, factelem *); 146 static void fact_unique(const char *, FILE *, factelem *); 147 static int matchgroup(gid_t); 148 static void mlsname(FILE *, factelem *); 149 static void replydirname(const char *, const char *); 150 151 struct ftpfact { 152 const char *name; /* name of fact */ 153 int enabled; /* if fact is enabled */ 154 void (*display)(const char *, FILE *, factelem *); 155 /* function to display fact */ 156 }; 157 158 struct ftpfact facttab[] = { 159 { "Type", 1, fact_type }, 160 #define FACT_TYPE 0 161 { "Size", 1, fact_size }, 162 { "Modify", 1, fact_modify }, 163 { "Perm", 1, fact_perm }, 164 { "Unique", 1, fact_unique }, 165 /* "Create" */ 166 /* "Lang" */ 167 /* "Media-Type" */ 168 /* "CharSet" */ 169 }; 170 171 #define FACTTABSIZE (sizeof(facttab) / sizeof(struct ftpfact)) 172 173 174 void 175 cwd(const char *path) 176 { 177 178 if (chdir(path) < 0) 179 perror_reply(550, path); 180 else { 181 show_chdir_messages(250); 182 ack("CWD"); 183 } 184 } 185 186 void 187 delete(const char *name) 188 { 189 char *p = NULL; 190 191 if (remove(name) < 0) { 192 p = strerror(errno); 193 perror_reply(550, name); 194 } else 195 ack("DELE"); 196 logxfer("delete", -1, name, NULL, NULL, p); 197 } 198 199 void 200 feat(void) 201 { 202 int i; 203 204 reply(-211, "Features supported"); 205 cprintf(stdout, " MDTM\r\n"); 206 cprintf(stdout, " MLST "); 207 for (i = 0; i < FACTTABSIZE; i++) 208 cprintf(stdout, "%s%s;", facttab[i].name, 209 facttab[i].enabled ? "*" : ""); 210 cprintf(stdout, "\r\n"); 211 cprintf(stdout, " REST STREAM\r\n"); 212 cprintf(stdout, " SIZE\r\n"); 213 cprintf(stdout, " TVFS\r\n"); 214 reply(211, "End"); 215 } 216 217 void 218 makedir(const char *name) 219 { 220 char *p = NULL; 221 222 if (mkdir(name, 0777) < 0) { 223 p = strerror(errno); 224 perror_reply(550, name); 225 } else 226 replydirname(name, "directory created."); 227 logxfer("mkdir", -1, name, NULL, NULL, p); 228 } 229 230 void 231 mlsd(const char *path) 232 { 233 struct dirent *dp; 234 struct stat sb, pdirstat; 235 factelem f; 236 FILE *dout; 237 DIR *dirp; 238 char name[MAXPATHLEN]; 239 int hastypefact; 240 241 hastypefact = facttab[FACT_TYPE].enabled; 242 if (path == NULL) 243 path = "."; 244 if (stat(path, &pdirstat) == -1) { 245 mlsdperror: 246 perror_reply(550, path); 247 return; 248 } 249 if (! S_ISDIR(pdirstat.st_mode)) { 250 errno = ENOTDIR; 251 perror_reply(501, path); 252 return; 253 } 254 if ((dirp = opendir(path)) == NULL) 255 goto mlsdperror; 256 257 dout = dataconn("MLSD", (off_t)-1, "w"); 258 if (dout == NULL) 259 return; 260 261 memset(&f, 0, sizeof(f)); 262 f.stat = &sb; 263 f.flags |= FE_MLSD; 264 while ((dp = readdir(dirp)) != NULL) { 265 snprintf(name, sizeof(name), "%s/%s", path, dp->d_name); 266 if (ISDOTDIR(dp->d_name)) { /* special case curdir: */ 267 if (! hastypefact) 268 continue; 269 f.pdirstat = NULL; /* require stat of parent */ 270 f.display = path; /* set name to real name */ 271 f.flags |= FE_ISCURDIR; /* flag name is curdir */ 272 } else { 273 if (ISDOTDOTDIR(dp->d_name)) { 274 if (! hastypefact) 275 continue; 276 f.pdirstat = NULL; 277 } else 278 f.pdirstat = &pdirstat; /* cache parent stat */ 279 f.display = dp->d_name; 280 f.flags &= ~FE_ISCURDIR; 281 } 282 if (stat(name, &sb) == -1) 283 continue; 284 f.path = name; 285 mlsname(dout, &f); 286 } 287 (void)closedir(dirp); 288 289 if (ferror(dout) != 0) 290 perror_reply(550, "Data connection"); 291 else 292 reply(226, "MLSD complete."); 293 closedataconn(dout); 294 total_xfers_out++; 295 total_xfers++; 296 } 297 298 void 299 mlst(const char *path) 300 { 301 struct stat sb; 302 factelem f; 303 304 if (path == NULL) 305 path = "."; 306 if (stat(path, &sb) == -1) { 307 perror_reply(550, path); 308 return; 309 } 310 reply(-250, "MLST %s", path); 311 memset(&f, 0, sizeof(f)); 312 f.path = path; 313 f.display = path; 314 f.stat = &sb; 315 f.pdirstat = NULL; 316 CPUTC(' ', stdout); 317 mlsname(stdout, &f); 318 reply(250, "End"); 319 } 320 321 322 void 323 opts(const char *command) 324 { 325 struct tab *c; 326 char *ep; 327 328 if ((ep = strchr(command, ' ')) != NULL) 329 *ep++ = '\0'; 330 c = lookup(cmdtab, command); 331 if (c == NULL) { 332 reply(502, "Unknown command %s.", command); 333 return; 334 } 335 if (! CMD_IMPLEMENTED(c)) { 336 reply(501, "%s command not implemented.", c->name); 337 return; 338 } 339 if (! CMD_HAS_OPTIONS(c)) { 340 reply(501, "%s command does not support persistent options.", 341 c->name); 342 return; 343 } 344 345 /* special case: MLST */ 346 if (strcasecmp(command, "MLST") == 0) { 347 int enabled[FACTTABSIZE]; 348 int i, onedone; 349 size_t len; 350 char *p; 351 352 for (i = 0; i < sizeof(enabled) / sizeof(int); i++) 353 enabled[i] = 0; 354 if (ep == NULL || *ep == '\0') 355 goto displaymlstopts; 356 357 /* don't like spaces, and need trailing ; */ 358 len = strlen(ep); 359 if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') { 360 badmlstopt: 361 reply(501, "Invalid MLST options"); 362 return; 363 } 364 ep[len - 1] = '\0'; 365 while ((p = strsep(&ep, ";")) != NULL) { 366 if (*p == '\0') 367 goto badmlstopt; 368 for (i = 0; i < FACTTABSIZE; i++) 369 if (strcasecmp(p, facttab[i].name) == 0) { 370 enabled[i] = 1; 371 break; 372 } 373 } 374 375 displaymlstopts: 376 for (i = 0; i < FACTTABSIZE; i++) 377 facttab[i].enabled = enabled[i]; 378 cprintf(stdout, "200 MLST OPTS"); 379 for (i = onedone = 0; i < FACTTABSIZE; i++) { 380 if (facttab[i].enabled) { 381 cprintf(stdout, "%s%s;", onedone ? "" : " ", 382 facttab[i].name); 383 onedone++; 384 } 385 } 386 cprintf(stdout, "\r\n"); 387 fflush(stdout); 388 return; 389 } 390 391 /* default cases */ 392 if (ep != NULL && *ep != '\0') 393 REASSIGN(c->options, xstrdup(ep)); 394 if (c->options != NULL) 395 reply(200, "Options for %s are '%s'.", c->name, 396 c->options); 397 else 398 reply(200, "No options defined for %s.", c->name); 399 } 400 401 void 402 pwd(void) 403 { 404 char path[MAXPATHLEN]; 405 406 if (getcwd(path, sizeof(path) - 1) == NULL) 407 reply(550, "Can't get the current directory: %s.", 408 strerror(errno)); 409 else 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 (xstrdup(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 char *argv[] = { INTERNAL_LS, "-lgA", "", NULL }; 510 511 argv[2] = (char *)filename; 512 fin = ftpd_popen(argv, "r", STDOUT_FILENO); 513 reply(-211, "status of %s:", filename); 514 /* XXX: use fgetln() or fparseln() here? */ 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 CPUTC(c, stdout); 531 } 532 (void) ftpd_pclose(fin); 533 reply(211, "End of Status"); 534 } 535 536 /* -- */ 537 538 static void 539 ack(const char *s) 540 { 541 542 reply(250, "%s command successful.", s); 543 } 544 545 /* 546 * Encode len bytes starting at clear using base64 encoding into encoded, 547 * which should be at least ((len + 2) * 4 / 3 + 1) in size. 548 * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary 549 * with `='. 550 */ 551 static void 552 base64_encode(const char *clear, size_t len, char *encoded, int nulterm) 553 { 554 static const char base64[] = 555 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 556 const char *c; 557 char *e, termchar; 558 int i; 559 560 /* determine whether to pad with '=' or NUL terminate */ 561 termchar = nulterm ? '\0' : '='; 562 c = clear; 563 e = encoded; 564 /* convert all but last 2 bytes */ 565 for (i = len; i > 2; i -= 3, c += 3) { 566 *e++ = base64[(c[0] >> 2) & 0x3f]; 567 *e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)]; 568 *e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)]; 569 *e++ = base64[(c[2]) & 0x3f]; 570 } 571 /* handle slop at end */ 572 if (i > 0) { 573 *e++ = base64[(c[0] >> 2) & 0x3f]; 574 *e++ = base64[((c[0] << 4) & 0x30) | 575 (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)]; 576 *e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar; 577 *e++ = termchar; 578 } 579 *e = '\0'; 580 } 581 582 static void 583 fact_modify(const char *fact, FILE *fd, factelem *fe) 584 { 585 struct tm *t; 586 587 t = gmtime(&(fe->stat->st_mtime)); 588 cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact, 589 TM_YEAR_BASE + t->tm_year, 590 t->tm_mon+1, t->tm_mday, 591 t->tm_hour, t->tm_min, t->tm_sec); 592 } 593 594 static void 595 fact_perm(const char *fact, FILE *fd, factelem *fe) 596 { 597 int rok, wok, xok, pdirwok; 598 struct stat *pdir; 599 600 if (fe->stat->st_uid == geteuid()) { 601 rok = ((fe->stat->st_mode & S_IRUSR) != 0); 602 wok = ((fe->stat->st_mode & S_IWUSR) != 0); 603 xok = ((fe->stat->st_mode & S_IXUSR) != 0); 604 } else if (matchgroup(fe->stat->st_gid)) { 605 rok = ((fe->stat->st_mode & S_IRGRP) != 0); 606 wok = ((fe->stat->st_mode & S_IWGRP) != 0); 607 xok = ((fe->stat->st_mode & S_IXGRP) != 0); 608 } else { 609 rok = ((fe->stat->st_mode & S_IROTH) != 0); 610 wok = ((fe->stat->st_mode & S_IWOTH) != 0); 611 xok = ((fe->stat->st_mode & S_IXOTH) != 0); 612 } 613 614 cprintf(fd, "%s=", fact); 615 616 /* 617 * if parent info not provided, look it up, but 618 * only if the current class has modify rights, 619 * since we only need this info in such a case. 620 */ 621 pdir = fe->pdirstat; 622 if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) { 623 size_t len; 624 char realdir[MAXPATHLEN], *p; 625 struct stat dir; 626 627 len = strlcpy(realdir, fe->path, sizeof(realdir)); 628 if (len < sizeof(realdir) - 4) { 629 if (S_ISDIR(fe->stat->st_mode)) 630 strlcat(realdir, "/..", sizeof(realdir)); 631 else { 632 /* if has a /, move back to it */ 633 /* otherwise use '..' */ 634 if ((p = strrchr(realdir, '/')) != NULL) { 635 if (p == realdir) 636 p++; 637 *p = '\0'; 638 } else 639 strlcpy(realdir, "..", sizeof(realdir)); 640 } 641 if (stat(realdir, &dir) == 0) 642 pdir = &dir; 643 } 644 } 645 pdirwok = 0; 646 if (pdir != NULL) { 647 if (pdir->st_uid == geteuid()) 648 pdirwok = ((pdir->st_mode & S_IWUSR) != 0); 649 else if (matchgroup(pdir->st_gid)) 650 pdirwok = ((pdir->st_mode & S_IWGRP) != 0); 651 else 652 pdirwok = ((pdir->st_mode & S_IWOTH) != 0); 653 } 654 655 /* 'a': can APPE to file */ 656 if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode)) 657 CPUTC('a', fd); 658 659 /* 'c': can create or append to files in directory */ 660 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) 661 CPUTC('c', fd); 662 663 /* 'd': can delete file or directory */ 664 if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) { 665 int candel; 666 667 candel = 1; 668 if (S_ISDIR(fe->stat->st_mode)) { 669 DIR *dirp; 670 struct dirent *dp; 671 672 if ((dirp = opendir(fe->display)) == NULL) 673 candel = 0; 674 else { 675 while ((dp = readdir(dirp)) != NULL) { 676 if (ISDOTDIR(dp->d_name) || 677 ISDOTDOTDIR(dp->d_name)) 678 continue; 679 candel = 0; 680 break; 681 } 682 closedir(dirp); 683 } 684 } 685 if (candel) 686 CPUTC('d', fd); 687 } 688 689 /* 'e': can enter directory */ 690 if (xok && S_ISDIR(fe->stat->st_mode)) 691 CPUTC('e', fd); 692 693 /* 'f': can rename file or directory */ 694 if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) 695 CPUTC('f', fd); 696 697 /* 'l': can list directory */ 698 if (rok && xok && S_ISDIR(fe->stat->st_mode)) 699 CPUTC('l', fd); 700 701 /* 'm': can create directory */ 702 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) 703 CPUTC('m', fd); 704 705 /* 'p': can remove files in directory */ 706 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) 707 CPUTC('p', fd); 708 709 /* 'r': can RETR file */ 710 if (rok && S_ISREG(fe->stat->st_mode)) 711 CPUTC('r', fd); 712 713 /* 'w': can STOR file */ 714 if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode)) 715 CPUTC('w', fd); 716 717 CPUTC(';', fd); 718 } 719 720 static void 721 fact_size(const char *fact, FILE *fd, factelem *fe) 722 { 723 724 if (S_ISREG(fe->stat->st_mode)) 725 cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size); 726 } 727 728 static void 729 fact_type(const char *fact, FILE *fd, factelem *fe) 730 { 731 732 cprintf(fd, "%s=", fact); 733 switch (fe->stat->st_mode & S_IFMT) { 734 case S_IFDIR: 735 if (fe->flags & FE_MLSD) { 736 if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display)) 737 cprintf(fd, "cdir"); 738 else if (ISDOTDOTDIR(fe->display)) 739 cprintf(fd, "pdir"); 740 else 741 cprintf(fd, "dir"); 742 } else { 743 cprintf(fd, "dir"); 744 } 745 break; 746 case S_IFREG: 747 cprintf(fd, "file"); 748 break; 749 case S_IFIFO: 750 cprintf(fd, "OS.unix=fifo"); 751 break; 752 case S_IFLNK: /* XXX: probably a NO-OP with stat() */ 753 cprintf(fd, "OS.unix=slink"); 754 break; 755 case S_IFSOCK: 756 cprintf(fd, "OS.unix=socket"); 757 break; 758 case S_IFBLK: 759 case S_IFCHR: 760 cprintf(fd, "OS.unix=%s-%d/%d", 761 S_ISBLK(fe->stat->st_mode) ? "blk" : "chr", 762 major(fe->stat->st_rdev), minor(fe->stat->st_rdev)); 763 break; 764 default: 765 cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT); 766 break; 767 } 768 CPUTC(';', fd); 769 } 770 771 static void 772 fact_unique(const char *fact, FILE *fd, factelem *fe) 773 { 774 char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2]; 775 char tbuf[sizeof(dev_t) + sizeof(ino_t)]; 776 777 memcpy(tbuf, 778 (char *)&(fe->stat->st_dev), sizeof(dev_t)); 779 memcpy(tbuf + sizeof(dev_t), 780 (char *)&(fe->stat->st_ino), sizeof(ino_t)); 781 base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1); 782 cprintf(fd, "%s=%s;", fact, obuf); 783 } 784 785 static int 786 matchgroup(gid_t gid) 787 { 788 int i; 789 790 for (i = 0; i < gidcount; i++) 791 if (gid == gidlist[i]) 792 return(1); 793 return (0); 794 } 795 796 static void 797 mlsname(FILE *fp, factelem *fe) 798 { 799 char realfile[MAXPATHLEN]; 800 int i, userf; 801 802 for (i = 0; i < FACTTABSIZE; i++) { 803 if (facttab[i].enabled) 804 (facttab[i].display)(facttab[i].name, fp, fe); 805 } 806 if ((fe->flags & FE_MLSD) && 807 !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) { 808 /* if MLSD and not "." entry, display as-is */ 809 userf = 0; 810 } else { 811 /* if MLST, or MLSD and "." entry, realpath(3) it */ 812 if (realpath(fe->display, realfile) != NULL) 813 userf = 1; 814 } 815 cprintf(fp, " %s\r\n", userf ? realfile : fe->display); 816 } 817 818 static void 819 replydirname(const char *name, const char *message) 820 { 821 char *p, *ep; 822 char npath[MAXPATHLEN * 2]; 823 824 p = npath; 825 ep = &npath[sizeof(npath) - 1]; 826 while (*name) { 827 if (*name == '"') { 828 if (ep - p < 2) 829 break; 830 *p++ = *name++; 831 *p++ = '"'; 832 } else { 833 if (ep - p < 1) 834 break; 835 *p++ = *name++; 836 } 837 } 838 *p = '\0'; 839 reply(257, "\"%s\" %s", npath, message); 840 } 841