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