susmb.c (29964B)
1 /* susmb - mount SMB shares via FUSE and Samba 2 * Copyright (C) 2025 Hiltjo Posthuma 3 * Copyright (C) 2006-2013 Geoff Johnstone 4 * 5 * Portions of this file are taken from Samba 3.2's libsmbclient.h: 6 * Copyright (C) Andrew Tridgell 1998 7 * Copyright (C) Richard Sharpe 2000 8 * Copyright (C) John Terpsra 2000 9 * Copyright (C) Tom Jansen (Ninja ISD) 2002 10 * Copyright (C) Derrell Lipman 2003-2008 11 * 12 * This program is free software; you can redistribute it and/or modify 13 * it under the terms of the GNU General Public License version 3 as 14 * published by the Free Software Foundation. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program. If not, see <http://www.gnu.org/licenses/>. 23 */ 24 25 /* Marks unused parameters */ 26 #define UNUSED __attribute__ ((unused)) 27 28 #define SUSMB_VERSION "0.9" 29 30 #include <libsmbclient.h> 31 32 /* Required FUSE API version */ 33 #define FUSE_USE_VERSION 26 34 35 #include <fuse.h> 36 37 #include <sys/types.h> 38 #include <sys/statvfs.h> 39 40 /* struct timeval needed by libsmbclient.h */ 41 #include <sys/time.h> 42 43 #include <dirent.h> 44 #include <err.h> 45 #include <errno.h> 46 #include <limits.h> 47 #include <pwd.h> 48 #include <stdarg.h> 49 #include <stdbool.h> 50 #include <stddef.h> 51 #include <stdio.h> 52 #include <stdlib.h> 53 #include <string.h> 54 #include <unistd.h> 55 56 /* use libbsd stdlib.h for arc4random() */ 57 #ifdef __linux__ 58 #include <bsd/stdlib.h> 59 #endif 60 61 #ifndef __OpenBSD__ 62 #define unveil(p1,p2) 0 63 #endif 64 65 /* ctype-like macros, but always compatible with ASCII / UTF-8 */ 66 #define ISALPHA(c) ((((unsigned)c) | 32) - 'a' < 26) 67 #define ISCNTRL(c) ((c) < ' ' || (c) == 0x7f) 68 #define ISDIGIT(c) (((unsigned)c) - '0' < 10) 69 #define ISSPACE(c) ((c) == ' ' || ((((unsigned)c) - '\t') < 5)) 70 #define TOLOWER(c) ((((unsigned)c) - 'A' < 26) ? ((c) | 32) : (c)) 71 72 /* URI */ 73 struct uri { 74 char proto[48]; /* scheme including ":" or "://" */ 75 char userinfo[256]; /* username [:password] */ 76 char host[256]; 77 char port[6]; /* numeric port */ 78 char path[1024]; 79 char query[1024]; 80 char fragment[1024]; 81 }; 82 83 int uri_parse(const char *s, struct uri *u); 84 85 char * make_url(const char *path); 86 bool create_smb_context(SMBCCTX **pctx); 87 void destroy_smb_context(SMBCCTX *ctx_, int shutdown); 88 89 int usmb_statfs(const char *path UNUSED, struct statvfs *vfs UNUSED); 90 int compat_truncate(const char *path, SMBCFILE *file, off_t size); 91 92 void show_about(FILE *fp); 93 void show_version(FILE *fp); 94 void usage(void); 95 96 void build_fuse_args(const char *options, const char *mountpoint, 97 int debug, int nofork, 98 int *out_argc, char ***out_argv); 99 100 int usmb_fuse_main(int argc, char *argv[], 101 const struct fuse_operations *op, size_t op_size, 102 void *user_data); 103 104 int usmb_getattr(const char *filename, struct stat *st); 105 int usmb_fgetattr(const char *filename, struct stat *st, 106 struct fuse_file_info *fi); 107 int usmb_unlink(const char *filename); 108 int usmb_open(const char *filename, struct fuse_file_info *fi); 109 int usmb_release(const char *filename, struct fuse_file_info *fi); 110 int usmb_read(const char *filename, char *buff, size_t len, off_t off, 111 struct fuse_file_info *fi); 112 int usmb_write(const char *filename, const char *buff, size_t len, off_t off, 113 struct fuse_file_info *fi); 114 int usmb_mknod(const char *filename, mode_t mode, dev_t dev); 115 int usmb_create(const char *filename, mode_t mode, 116 struct fuse_file_info *fi); 117 int usmb_rename(const char *from, const char *to); 118 int usmb_utime(const char *filename, struct utimbuf *utb); 119 int usmb_truncate(const char *filename, off_t newsize); 120 int usmb_chmod(const char *filename, mode_t mode); 121 int usmb_ftruncate(const char *path, off_t size, 122 struct fuse_file_info *fi); 123 124 /* directory operations */ 125 126 int usmb_mkdir(const char *dirname, mode_t mode); 127 int usmb_rmdir(const char *dirname); 128 int usmb_opendir(const char *dirname, struct fuse_file_info *fi); 129 int usmb_readdir(const char *path, void *h, fuse_fill_dir_t filler, 130 off_t offset, struct fuse_file_info *fi); 131 int usmb_releasedir(const char *path, struct fuse_file_info *fi); 132 int usmb_setxattr(const char *path, const char *name, const char *value, 133 size_t size, int flags); 134 int usmb_getxattr(const char *path, const char *name, char *value, 135 size_t size); 136 int usmb_listxattr(const char *path, char *list, size_t size); 137 int usmb_removexattr(const char *path, const char *name); 138 139 void *emalloc(size_t size); 140 void *erealloc(void *ptr, size_t size); 141 char *estrdup(const char *s); 142 143 char * xstrdup(const char *in); 144 void clear_and_free(char *ptr); 145 void free_errno(void *ptr); 146 147 /* globals */ 148 149 static SMBCCTX *ctx; 150 static int opt_debug, opt_nofork; 151 static char *opt_server, *opt_share, *opt_mountpoint, *opt_options, 152 *opt_domain, *opt_username, *opt_password; 153 static int disconnect; 154 static char *sharename; 155 static const char *argv0 = "susmb"; 156 157 /* for privdrop */ 158 static uid_t opt_uid; 159 static gid_t opt_gid; 160 static int opt_privdrop; 161 162 /* fuse_file_info uses a uint64_t for a "File handle" */ 163 static inline uint64_t 164 smbcfile_to_fd(SMBCFILE *file) 165 { 166 return (uint64_t)(uintptr_t)file; 167 } 168 169 static inline SMBCFILE * 170 fd_to_smbcfile(uint64_t fd) 171 { 172 return (SMBCFILE *)(uintptr_t)fd; 173 } 174 175 void * 176 emalloc(size_t size) 177 { 178 void *p; 179 180 p = malloc(size); 181 if (p == NULL) 182 err(1, "malloc"); 183 return p; 184 } 185 186 void * 187 erealloc(void *ptr, size_t size) 188 { 189 void *p; 190 191 p = realloc(ptr, size); 192 if (p == NULL) 193 err(1, "realloc"); 194 return p; 195 } 196 197 char * 198 estrdup(const char *s) 199 { 200 char *p; 201 202 p = strdup(s); 203 if (p == NULL) 204 err(1, "strdup"); 205 return p; 206 } 207 208 /* Parse URI string `s` into an uri structure `u`. 209 * Returns 0 on success or -1 on failure */ 210 int 211 uri_parse(const char *s, struct uri *u) 212 { 213 const char *p = s; 214 char *endptr; 215 size_t i; 216 long l; 217 218 u->proto[0] = u->userinfo[0] = u->host[0] = u->port[0] = '\0'; 219 u->path[0] = u->query[0] = u->fragment[0] = '\0'; 220 221 /* protocol-relative */ 222 if (*p == '/' && *(p + 1) == '/') { 223 p += 2; /* skip "//" */ 224 goto parseauth; 225 } 226 227 /* scheme / protocol part */ 228 for (; ISALPHA((unsigned char)*p) || ISDIGIT((unsigned char)*p) || 229 *p == '+' || *p == '-' || *p == '.'; p++) 230 ; 231 /* scheme, except if empty and starts with ":" then it is a path */ 232 if (*p == ':' && p != s) { 233 if (*(p + 1) == '/' && *(p + 2) == '/') 234 p += 3; /* skip "://" */ 235 else 236 p++; /* skip ":" */ 237 238 if ((size_t)(p - s) >= sizeof(u->proto)) 239 return -1; /* protocol too long */ 240 memcpy(u->proto, s, p - s); 241 u->proto[p - s] = '\0'; 242 243 if (*(p - 1) != '/') 244 goto parsepath; 245 } else { 246 p = s; /* no scheme format, reset to start */ 247 goto parsepath; 248 } 249 250 parseauth: 251 /* userinfo (username:password) */ 252 i = strcspn(p, "@/?#"); 253 if (p[i] == '@') { 254 if (i >= sizeof(u->userinfo)) 255 return -1; /* userinfo too long */ 256 memcpy(u->userinfo, p, i); 257 u->userinfo[i] = '\0'; 258 p += i + 1; 259 } 260 261 /* IPv6 address */ 262 if (*p == '[') { 263 /* bracket not found, host too short or too long */ 264 i = strcspn(p, "]"); 265 if (p[i] != ']' || i < 3) 266 return -1; 267 i++; /* including "]" */ 268 } else { 269 /* domain / host part, skip until port, path or end. */ 270 i = strcspn(p, ":/?#"); 271 } 272 if (i >= sizeof(u->host)) 273 return -1; /* host too long */ 274 memcpy(u->host, p, i); 275 u->host[i] = '\0'; 276 p += i; 277 278 /* port */ 279 if (*p == ':') { 280 p++; 281 if ((i = strcspn(p, "/?#")) >= sizeof(u->port)) 282 return -1; /* port too long */ 283 memcpy(u->port, p, i); 284 u->port[i] = '\0'; 285 /* check for valid port: range 1 - 65535, may be empty */ 286 errno = 0; 287 l = strtol(u->port, &endptr, 10); 288 if (i && (errno || *endptr || l <= 0 || l > 65535)) 289 return -1; 290 p += i; 291 } 292 293 parsepath: 294 /* path */ 295 if ((i = strcspn(p, "?#")) >= sizeof(u->path)) 296 return -1; /* path too long */ 297 memcpy(u->path, p, i); 298 u->path[i] = '\0'; 299 p += i; 300 301 /* query */ 302 if (*p == '?') { 303 p++; 304 if ((i = strcspn(p, "#")) >= sizeof(u->query)) 305 return -1; /* query too long */ 306 memcpy(u->query, p, i); 307 u->query[i] = '\0'; 308 p += i; 309 } 310 311 /* fragment */ 312 if (*p == '#') { 313 p++; 314 if ((i = strlen(p)) >= sizeof(u->fragment)) 315 return -1; /* fragment too long */ 316 memcpy(u->fragment, p, i); 317 u->fragment[i] = '\0'; 318 } 319 320 return 0; 321 } 322 323 char * 324 xstrdup(const char *in) 325 { 326 if (in != NULL) 327 return estrdup(in); 328 return NULL; 329 } 330 331 void 332 clear_and_free(char *ptr) 333 { 334 if (ptr != NULL) { 335 explicit_bzero(ptr, strlen(ptr)); 336 free(ptr); 337 } 338 } 339 340 void 341 free_errno(void *ptr) 342 { 343 int saved_errno = errno; 344 free(ptr); 345 errno = saved_errno; 346 } 347 348 int 349 usmb_statfs(const char *path, struct statvfs *vfs) 350 { 351 if (path == NULL || vfs == NULL) 352 return -EINVAL; 353 354 char *url = make_url(path); 355 if (url == NULL) 356 return -ENOMEM; 357 358 memset(vfs, 0, sizeof(*vfs)); 359 360 int ret = (0 > smbc_getFunctionStatVFS(ctx) (ctx, url, vfs)) ? -errno : 0; 361 free(url); 362 363 return ret; 364 } 365 366 int 367 compat_truncate(const char *path UNUSED, SMBCFILE *file, off_t size) 368 { 369 return (0 > smbc_getFunctionFtruncate(ctx) (ctx, file, size)) ? -errno : 0; 370 } 371 372 static bool 373 change_blksiz(struct stat *st) 374 { 375 if (st == NULL) 376 return false; 377 378 /* change block size to improve performance of stdio FILE * operations, 379 only for regular files to be on the safe side. */ 380 if (S_ISREG(st->st_mode)) { 381 st->st_blksize = 32768; 382 return true; 383 } 384 385 return false; 386 } 387 388 /* Samba gets st_nlink wrong for directories. */ 389 /* still wrong in 2025-03-03 with Samba 4.20 */ 390 static bool 391 fix_nlink(const char *url, struct stat *st) 392 { 393 if (!S_ISDIR(st->st_mode)) 394 return true; 395 396 SMBCFILE *file = smbc_getFunctionOpendir(ctx) (ctx, url); 397 if (file == NULL) 398 return false; 399 400 st->st_nlink = 0; 401 errno = ERANGE; 402 403 struct smbc_dirent *dirent; 404 while (NULL != (dirent = smbc_getFunctionReaddir(ctx) (ctx, file))) { 405 if (SMBC_DIR == dirent->smbc_type) { 406 if (INT_MAX == st->st_nlink++) { 407 break; 408 } 409 } 410 } 411 412 (void)smbc_getFunctionClosedir(ctx) (ctx, file); 413 414 return (dirent == NULL); 415 } 416 417 int 418 usmb_getattr(const char *filename, struct stat *st) 419 { 420 char *url = make_url(filename); 421 if (url == NULL) 422 return -ENOMEM; 423 424 int ret = smbc_getFunctionStat(ctx) (ctx, url, st); 425 426 if ((0 > ret) || !fix_nlink(url, st)) 427 ret = -errno; 428 429 change_blksiz(st); 430 431 free(url); 432 433 return ret; 434 } 435 436 int 437 usmb_fgetattr(const char *filename UNUSED, struct stat *st, 438 struct fuse_file_info *fi) 439 { 440 SMBCFILE *file = fd_to_smbcfile(fi->fh); 441 442 if (0 > smbc_getFunctionFstat(ctx) (ctx, file, st)) 443 return -errno; 444 445 if (S_ISDIR(st->st_mode)) { 446 char *url = make_url(filename); 447 if (url == NULL) 448 return -ENOMEM; 449 450 bool ok = fix_nlink(url, st); 451 free_errno(url); 452 453 if (!ok) 454 return -errno; 455 } 456 457 change_blksiz(st); 458 459 return 0; 460 } 461 462 int 463 usmb_unlink(const char *filename) 464 { 465 char *url = make_url(filename); 466 if (url == NULL) 467 return -ENOMEM; 468 469 int ret = (0 > smbc_getFunctionUnlink(ctx) (ctx, url)) ? -errno : 0; 470 free(url); 471 472 return ret; 473 } 474 475 int 476 usmb_open(const char *filename, struct fuse_file_info *fi) 477 { 478 char *url = make_url(filename); 479 480 if (url == NULL) 481 return -ENOMEM; 482 483 SMBCFILE *file = smbc_getFunctionOpen(ctx) (ctx, url, fi->flags, 0); 484 485 int ret = (file == NULL) ? -errno : 0; 486 free(url); 487 fi->fh = smbcfile_to_fd(file); 488 489 return ret; 490 } 491 492 int 493 usmb_release(const char *filename UNUSED, struct fuse_file_info *fi) 494 { 495 SMBCFILE *file = fd_to_smbcfile(fi->fh); 496 return (0 > smbc_getFunctionClose(ctx) (ctx, file)) ? -errno : 0; 497 } 498 499 int 500 usmb_read(const char *filename UNUSED, char *buff, size_t len, off_t off, 501 struct fuse_file_info *fi) 502 { 503 SMBCFILE *file = fd_to_smbcfile(fi->fh); 504 505 if (0 > smbc_getFunctionLseek(ctx) (ctx, file, off, SEEK_SET)) { 506 return -errno; 507 } 508 509 int bytes = smbc_getFunctionRead(ctx) (ctx, file, buff, len); 510 511 return (0 > bytes) ? -errno : (int)bytes; 512 } 513 514 int 515 usmb_write(const char *filename UNUSED, const char *buff, size_t len, 516 off_t off, struct fuse_file_info *fi) 517 { 518 SMBCFILE *file = fd_to_smbcfile(fi->fh); 519 size_t written = 0; 520 int bytes = 0; 521 522 if (0 > smbc_getFunctionLseek(ctx)(ctx, file, off, SEEK_SET)) 523 return -errno; 524 525 const smbc_write_fn write_fn = smbc_getFunctionWrite(ctx); 526 while (written < len) { 527 bytes = write_fn(ctx, file, (char *)buff, len); 528 if (0 > bytes) 529 break; 530 531 written += bytes; 532 buff += bytes; 533 534 /* avoids infinite loops. */ 535 if (bytes == 0) 536 break; 537 } 538 539 return (0 > bytes) ? -errno : (int)written; 540 } 541 542 /* File systems must support mknod on OpenBSD */ 543 int 544 usmb_mknod(const char *filename, mode_t mode, __attribute__((unused)) dev_t dev) 545 { 546 char *url = make_url(filename); 547 if (url == NULL) 548 return -ENOMEM; 549 550 if (S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode)) 551 return -EPERM; 552 553 SMBCFILE *file = smbc_getFunctionCreat(ctx) (ctx, url, mode); 554 int ret = (file == NULL) ? -errno : 0; 555 556 /* File must not be open when mknod returns. */ 557 if (ret == 0) 558 smbc_getFunctionClose(ctx) (ctx, file); 559 free(url); 560 561 return ret; 562 } 563 564 int 565 usmb_create(const char *filename, mode_t mode, struct fuse_file_info *fi) 566 { 567 char *url = make_url(filename); 568 if (url == NULL) 569 return -ENOMEM; 570 571 SMBCFILE *file = smbc_getFunctionCreat(ctx) (ctx, url, mode); 572 573 int ret = (file == NULL) ? -errno : 0; 574 fi->fh = smbcfile_to_fd(file); 575 576 free(url); 577 578 return ret; 579 } 580 581 int 582 usmb_rename(const char *from, const char *to) 583 { 584 char *fromurl = make_url(from); 585 if (fromurl == NULL) 586 return -ENOMEM; 587 588 char *tourl = make_url(to); 589 if (tourl == NULL) { 590 free(fromurl); 591 return -ENOMEM; 592 } 593 594 int ret = 595 (0 > smbc_getFunctionRename(ctx)(ctx, fromurl, ctx, tourl)) ? -errno : 0; 596 free(tourl); 597 free(fromurl); 598 599 return ret; 600 } 601 602 int 603 usmb_utime(const char *filename, struct utimbuf *utb) 604 { 605 struct utimbuf tmp_utb; 606 607 if (utb == NULL) { 608 for (;;) { 609 time_t now = time(NULL); 610 if (now != (time_t)-1) { 611 tmp_utb.actime = tmp_utb.modtime = now; 612 break; 613 } 614 615 if (EINTR != errno) 616 return -errno; 617 618 usleep(1000); /* sleep a bit to not hog the CPU */ 619 } 620 utb = &tmp_utb; 621 } 622 623 char *url = make_url(filename); 624 if (url == NULL) 625 return -ENOMEM; 626 627 struct timeval tv[2] = { 628 { .tv_sec = utb->actime, .tv_usec = 0 }, 629 { .tv_sec = utb->modtime, .tv_usec = 0 }, 630 }; 631 632 int ret = (0 > smbc_getFunctionUtimes (ctx) (ctx, url, tv)) ? -errno : 0; 633 free(url); 634 635 return ret; 636 } 637 638 int 639 usmb_chmod(const char *filename, mode_t mode) 640 { 641 char *url = make_url(filename); 642 643 if (url == NULL) 644 return -ENOMEM; 645 646 int ret = (0 > smbc_getFunctionChmod(ctx) (ctx, url, mode)) ? -errno : 0; 647 free(url); 648 649 return ret; 650 } 651 652 int 653 usmb_truncate(const char *filename, off_t newsize) 654 { 655 char *url = make_url(filename); 656 if (url == NULL) 657 return -ENOMEM; 658 659 SMBCFILE *file = smbc_getFunctionOpen(ctx) (ctx, url, O_WRONLY, 0); 660 if (file == NULL) { 661 int ret = -errno; 662 free(url); 663 return ret; 664 } 665 666 int ret = compat_truncate(filename, file, newsize); 667 668 smbc_getFunctionClose(ctx) (ctx, file); 669 free(url); 670 671 return ret; 672 } 673 674 int 675 usmb_ftruncate(const char *path, off_t size, 676 struct fuse_file_info *fi) 677 { 678 return compat_truncate(path, fd_to_smbcfile(fi->fh), size); 679 } 680 681 /* directory operations below */ 682 683 int 684 usmb_mkdir(const char *dirname, mode_t mode) 685 { 686 char *url = make_url(dirname); 687 688 if (url == NULL) 689 return -ENOMEM; 690 691 int ret = smbc_getFunctionMkdir(ctx) (ctx, url, mode) ? -errno : 0; 692 free(url); 693 694 return ret; 695 } 696 697 int 698 usmb_rmdir(const char *dirname) 699 { 700 char *url = make_url(dirname); 701 if (url == NULL) 702 return -ENOMEM; 703 704 int ret = smbc_getFunctionRmdir(ctx) (ctx, url) ? -errno : 0; 705 free(url); 706 707 return ret; 708 } 709 710 int 711 usmb_opendir(const char *dirname, struct fuse_file_info *fi) 712 { 713 char *url = make_url(dirname); 714 if (url == NULL) 715 return -ENOMEM; 716 717 SMBCFILE *file = smbc_getFunctionOpendir(ctx) (ctx, url); 718 719 int ret = (file == NULL) ? -errno : 0; 720 free(url); 721 722 fi->fh = smbcfile_to_fd(file); 723 724 return ret; 725 } 726 727 int 728 usmb_readdir(const char *path, void *h, fuse_fill_dir_t filler, 729 off_t offset UNUSED, struct fuse_file_info *fi UNUSED) 730 { 731 SMBCCTX *ctx_ = NULL; 732 SMBCFILE *file = NULL; 733 char *url = NULL; 734 struct smbc_dirent *dirent; 735 struct stat stbuf; 736 int ret = 0; 737 738 if (!create_smb_context(&ctx_)) 739 return -errno; 740 741 do { 742 url = make_url(path); 743 if (url == NULL) { 744 ret = -ENOMEM; 745 break; 746 } 747 748 file = smbc_getFunctionOpendir(ctx_) (ctx_, url); 749 if (file == NULL) { 750 ret = -errno; 751 break; 752 } 753 754 smbc_getFunctionLseekdir(ctx_) (ctx_, file, 0); 755 756 while (NULL != (dirent = smbc_getFunctionReaddir(ctx_) (ctx_, file))) { 757 memset(&stbuf, 0, sizeof(stbuf)); 758 /* required for at least OpenBSD for getcwd() to work properly 759 as described in the fuse_new(3) man page near readdir_ino */ 760 stbuf.st_ino = arc4random(); 761 762 switch (dirent->smbc_type) { 763 case SMBC_DIR: 764 stbuf.st_mode = S_IFDIR; 765 break; 766 case SMBC_FILE: 767 stbuf.st_mode = S_IFREG; 768 break; 769 case SMBC_LINK: 770 stbuf.st_mode = S_IFLNK; 771 break; 772 default: 773 break; 774 } 775 776 if (1 == filler(h, dirent->name, &stbuf, 0)) { /* if error */ 777 ret = -1; 778 break; 779 } 780 } 781 } while (false /*CONSTCOND*/); 782 783 if (file != NULL) 784 (void)smbc_getFunctionClosedir(ctx_) (ctx_, file); 785 786 free(url); 787 destroy_smb_context(ctx_, 0); 788 789 return ret; 790 } 791 792 int 793 usmb_releasedir(const char *path UNUSED, struct fuse_file_info *fi) 794 { 795 SMBCFILE *file = fd_to_smbcfile(fi->fh); 796 797 return (0 > smbc_getFunctionClosedir(ctx) (ctx, file)) ? -errno : 0; 798 } 799 800 int 801 usmb_setxattr(const char *path, const char *name, const char *value, 802 size_t size, int flags) 803 { 804 char *url = make_url(path); 805 if (url == NULL) 806 return -ENOMEM; 807 808 int ret = smbc_getFunctionSetxattr(ctx) (ctx, url, name, 809 value, size, flags) ? -errno : 0; 810 free(url); 811 812 return ret; 813 } 814 815 int 816 usmb_getxattr(const char *path, const char *name, char *value, size_t size) 817 { 818 char *url = make_url(path); 819 if (url == NULL) 820 return -ENOMEM; 821 822 int ret = smbc_getFunctionGetxattr(ctx) (ctx, url, name, 823 value, size) ? -errno : 0; 824 free(url); 825 826 return ret; 827 } 828 829 int 830 usmb_listxattr(const char *path, char *list, size_t size) 831 { 832 char *url = make_url(path); 833 if (url == NULL) 834 return -ENOMEM; 835 836 int ret = smbc_getFunctionListxattr(ctx) (ctx, url, list, size) ? -errno : 0; 837 free(url); 838 839 return ret; 840 } 841 842 int 843 usmb_removexattr(const char *path, const char *name) 844 { 845 char *url = make_url(path); 846 if (url == NULL) 847 return -ENOMEM; 848 849 int ret = smbc_getFunctionRemovexattr(ctx) (ctx, url, name) ? -errno : 0; 850 free(url); 851 852 return ret; 853 } 854 855 char * 856 make_url(const char *path) 857 { 858 size_t len; 859 char *p; 860 861 /* no path or path is empty */ 862 if ((path == NULL) || (path[0] == '\0')) { 863 return xstrdup(sharename); 864 } else { 865 len = strlen(sharename) + strlen(path) + 1; 866 p = emalloc(len); 867 snprintf(p, len, "%s%s", sharename, path); 868 return p; 869 } 870 } 871 872 static void 873 auth_fn(const char *srv UNUSED, const char *shr UNUSED, 874 char *wg, int wglen, char *un, int unlen, 875 char *pw, int pwlen) 876 { 877 /* snprintf is used in this way to behave similar to strlcpy(), for portability */ 878 879 if (opt_domain != NULL) 880 snprintf(wg, wglen, "%s", opt_domain); 881 else if (wglen) 882 wg[0] = '\0'; /* no domain */ 883 884 snprintf(un, unlen, "%s", opt_username); 885 snprintf(pw, pwlen, "%s", opt_password); 886 } 887 888 void 889 destroy_smb_context(SMBCCTX *ctx_, int shutdown) 890 { 891 /* Samba frees the workgroup and user strings but we want to persist them. */ 892 smbc_setWorkgroup(ctx_, NULL); 893 smbc_setUser(ctx_, NULL); 894 smbc_free_context(ctx_, shutdown); 895 } 896 897 bool 898 create_smb_context(SMBCCTX **pctx) 899 { 900 *pctx = smbc_new_context(); 901 902 if (*pctx == NULL) { 903 perror("Cannot create SMB context"); 904 return false; 905 } 906 907 smbc_setWorkgroup(*pctx, opt_domain); 908 smbc_setUser(*pctx, opt_username); 909 smbc_setTimeout(*pctx, 5000); 910 smbc_setFunctionAuthData(*pctx, auth_fn); 911 912 if (smbc_init_context(*pctx) == NULL) { 913 perror("Cannot initialise SMB context"); 914 destroy_smb_context(*pctx, 1); 915 return false; 916 } 917 918 return true; 919 } 920 921 static void * 922 usmb_init(struct fuse_conn_info *conn UNUSED) 923 { 924 return NULL; 925 } 926 927 static void 928 usmb_destroy(void *unused UNUSED) 929 { 930 } 931 932 // probably won't (can't ?) implement these: 933 // readlink symlink flush fsync 934 935 // no easy way of implementing these: 936 // access 937 938 #define SET_ELEMENT(name,value) name = value 939 940 static struct fuse_operations fuse_ops = { 941 SET_ELEMENT (.getattr, usmb_getattr), 942 SET_ELEMENT (.readlink, NULL), 943 SET_ELEMENT (.getdir, NULL), 944 SET_ELEMENT (.mknod, usmb_mknod), 945 SET_ELEMENT (.mkdir, usmb_mkdir), 946 SET_ELEMENT (.unlink, usmb_unlink), 947 SET_ELEMENT (.rmdir, usmb_rmdir), 948 SET_ELEMENT (.symlink, NULL), 949 SET_ELEMENT (.rename, usmb_rename), 950 SET_ELEMENT (.link, NULL), 951 SET_ELEMENT (.chmod, usmb_chmod), 952 SET_ELEMENT (.chown, NULL), // usmb_chown, --not implemented in libsmbclient 953 SET_ELEMENT (.truncate, usmb_truncate), 954 SET_ELEMENT (.utime, usmb_utime), 955 SET_ELEMENT (.open, usmb_open), 956 SET_ELEMENT (.read, usmb_read), 957 SET_ELEMENT (.write, usmb_write), 958 SET_ELEMENT (.statfs, usmb_statfs), 959 SET_ELEMENT (.flush, NULL), 960 SET_ELEMENT (.release, usmb_release), 961 SET_ELEMENT (.fsync, NULL), 962 SET_ELEMENT (.setxattr, usmb_setxattr), 963 SET_ELEMENT (.getxattr, usmb_getxattr), 964 SET_ELEMENT (.listxattr, usmb_listxattr), 965 SET_ELEMENT (.removexattr, usmb_removexattr), 966 SET_ELEMENT (.opendir, usmb_opendir), 967 SET_ELEMENT (.readdir, usmb_readdir), 968 SET_ELEMENT (.releasedir, usmb_releasedir), 969 SET_ELEMENT (.fsyncdir, NULL), 970 SET_ELEMENT (.init, usmb_init), 971 SET_ELEMENT (.destroy, usmb_destroy), 972 SET_ELEMENT (.access, NULL), 973 SET_ELEMENT (.create, usmb_create), 974 SET_ELEMENT (.ftruncate, usmb_ftruncate), 975 SET_ELEMENT (.fgetattr, usmb_fgetattr), 976 SET_ELEMENT (.lock, NULL), // TODO: implement 977 SET_ELEMENT (.utimens, NULL), // TODO: implement 978 SET_ELEMENT (.bmap, NULL), // TODO: implement 979 }; 980 981 static char * 982 create_share_name(const char *server_, const char *sharename) 983 { 984 /* len: + 2 for "/" and NUL terminator */ 985 size_t len = strlen("smb://") + strlen(server_) + strlen(sharename) + 2; 986 char *p; 987 988 p = emalloc(len); 989 snprintf(p, len, "smb://%s/%s", server_, sharename); 990 991 return p; 992 } 993 994 static bool 995 check_credentials(void) 996 { 997 char *url = make_url(""); 998 if (url == NULL) { 999 errno = ENOMEM; 1000 return false; 1001 } 1002 1003 struct stat stat_; 1004 bool ret = (0 == (smbc_getFunctionStat(ctx) (ctx, url, &stat_))); 1005 1006 free_errno(url); 1007 1008 return ret; 1009 } 1010 1011 static bool 1012 get_context(void) 1013 { 1014 ctx = NULL; 1015 1016 if (disconnect) 1017 return false; 1018 1019 disconnect = 1; 1020 if (!create_smb_context(&ctx)) 1021 return false; 1022 1023 if (!check_credentials()) { 1024 perror("Connection failed"); 1025 destroy_smb_context(ctx, 1); 1026 ctx = NULL; 1027 return NULL; 1028 } 1029 1030 disconnect = 0; 1031 1032 return (ctx != NULL); 1033 } 1034 1035 void 1036 show_about(FILE *fp) 1037 { 1038 fprintf(fp, "susmb - mount SMB shares via FUSE and Samba\n" 1039 "\n" 1040 "Copyright (C) 2025 Hiltjo Posthuma.\n" 1041 "Copyright (C) 2006-2013 Geoff Johnstone.\n" 1042 "\n" 1043 "Licensed under the GNU General Public License.\n" 1044 "susmb comes with ABSOLUTELY NO WARRANTY; " 1045 "for details please see\n" 1046 "http://www.gnu.org/licenses/gpl.txt\n" 1047 "\n" 1048 "Please send bug reports, patches etc. to hiltjo@codemadness.org\n"); 1049 } 1050 1051 void 1052 show_version(FILE *fp) 1053 { 1054 show_about(fp); 1055 fputc('\n', fp); 1056 fprintf(fp, "susmb version: %s\n" 1057 "FUSE version: %d.%d\n" 1058 "Samba version: %s\n", 1059 SUSMB_VERSION, 1060 FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION, 1061 smbc_version()); 1062 } 1063 1064 void 1065 usage(void) 1066 { 1067 fprintf(stdout, 1068 "Usage: %s [-dfv] [-o options] [-u user] [-g gid] <smb://domain\\user@server/sharename> <mountpoint>\n" 1069 "\n" 1070 "Options:\n" 1071 " -d Debug mode\n" 1072 " -f Foreground operation\n" 1073 " -o Additional FUSE options\n" 1074 " -u Privdrop to user and its group or uid\n" 1075 " -g Privdrop to group id\n" 1076 " -v Show program, FUSE and Samba versions\n", argv0); 1077 exit(1); 1078 } 1079 1080 /* FUSE args are: 1081 * 1082 * argv[0] 1083 * -s 1084 * -d -- if debug mode requested 1085 * -f -- if foreground mode requested 1086 * -o ... -- if any mount options in the config file 1087 * mount point 1088 */ 1089 #define MAXARGS 12 1090 void build_fuse_args(const char *options, const char *mountpoint, 1091 int debug, int nofork, 1092 int *out_argc, char ***out_argv) 1093 { 1094 static char SUSMB[] = "susmb"; 1095 static char MINUS_S[] = "-s"; 1096 static char MINUS_D[] = "-d"; 1097 static char MINUS_F[] = "-f"; 1098 static char MINUS_O[] = "-o"; 1099 static char *argv[MAXARGS]; 1100 int argc = 0; 1101 1102 argv[argc++] = SUSMB; 1103 argv[argc++] = MINUS_S; 1104 1105 if (debug) 1106 argv[argc++] = MINUS_D; 1107 1108 if (nofork) 1109 argv[argc++] = MINUS_F; 1110 1111 if ((options != NULL) && (options[0] != '\0')) { 1112 argv[argc++] = MINUS_O; 1113 argv[argc++] = (char *)options; 1114 } 1115 1116 argv[argc++] = (char *)mountpoint; 1117 argv[argc] = NULL; 1118 1119 *out_argc = argc; 1120 *out_argv = argv; 1121 } 1122 1123 int usmb_fuse_main(int argc, char *argv[], 1124 const struct fuse_operations *op, size_t op_size, 1125 void *user_data) 1126 { 1127 struct fuse_args args = FUSE_ARGS_INIT(argc, argv); 1128 struct fuse *fuse = NULL; 1129 struct fuse_chan *chan = NULL; 1130 struct fuse_session *session = NULL; 1131 int fd, res = 1; 1132 int fflag = 0, sflag = 0; 1133 char *mountpoint = NULL; 1134 1135 if (fuse_parse_cmdline(&args, &mountpoint, &sflag, &fflag) != 0) 1136 return 1; 1137 1138 if (mountpoint == NULL || *mountpoint == '\0') { 1139 warnx("error: no mountpoint specified"); 1140 res = 2; 1141 goto out1; 1142 } 1143 1144 chan = fuse_mount(mountpoint, &args); 1145 if (chan == NULL) { 1146 res = 4; 1147 goto out2; 1148 } 1149 1150 fuse = fuse_new(chan, &args, op, op_size, user_data); 1151 if (fuse == NULL) { 1152 res = 3; 1153 goto out1; 1154 } 1155 1156 /* daemonize */ 1157 if (!fflag) { 1158 switch (fork()) { 1159 case -1: 1160 res = 5; 1161 warn("fork"); 1162 goto out3; 1163 case 0: 1164 break; 1165 default: 1166 _exit(0); 1167 } 1168 1169 if (setsid() == -1) { 1170 res = 5; 1171 warn("setsid"); 1172 goto out3; 1173 } 1174 1175 (void)chdir("/"); /* nochdir */ 1176 1177 /* noclose */ 1178 if ((fd = open("/dev/null", O_RDWR)) != -1) { 1179 (void)dup2(fd, STDIN_FILENO); 1180 (void)dup2(fd, STDOUT_FILENO); 1181 (void)dup2(fd, STDERR_FILENO); 1182 if (fd > 2) 1183 (void)close(fd); 1184 } 1185 } 1186 1187 /* setup signal handlers: can only be used if privdrop is not used */ 1188 if (!opt_privdrop) { 1189 session = fuse_get_session(fuse); 1190 if (fuse_set_signal_handlers(session) != 0) { 1191 res = 6; 1192 goto out3; 1193 } 1194 } 1195 1196 /* privdrop */ 1197 if (opt_privdrop) { 1198 if (setresgid(opt_uid, opt_uid, opt_uid) == -1) 1199 err(1, "setresgid"); 1200 if (setresuid(opt_gid, opt_gid, opt_gid) == -1) 1201 err(1, "setresuid"); 1202 } 1203 1204 res = fuse_loop(fuse); 1205 if (res) 1206 res = 8; 1207 1208 if (!opt_privdrop) { 1209 if (session) 1210 fuse_remove_signal_handlers(session); 1211 } 1212 1213 out3: 1214 if (chan) 1215 fuse_unmount(mountpoint, chan); 1216 out2: 1217 if (fuse) 1218 fuse_destroy(fuse); 1219 out1: 1220 return res; 1221 } 1222 1223 int 1224 main(int argc, char **argv) 1225 { 1226 struct uri u; 1227 struct passwd *pw; 1228 char *tmp, *p; 1229 char **fuse_argv; 1230 int fuse_argc; 1231 int ch, ret = 1; 1232 long l; 1233 1234 while ((ch = getopt(argc, argv, "hvVdfo:u:g:")) != -1) { 1235 switch (ch) { 1236 case 'd': 1237 opt_debug = 1; 1238 break; 1239 case 'f': 1240 opt_nofork = 1; 1241 break; 1242 case 'o': 1243 opt_options = xstrdup(optarg); 1244 break; 1245 case 'h': 1246 usage(); 1247 break; 1248 case 'u': 1249 opt_privdrop = 1; 1250 /* by username: use uid and gid from passwd entry */ 1251 if ((pw = getpwnam(optarg)) != NULL) { 1252 opt_uid = pw->pw_uid; 1253 opt_gid = pw->pw_gid; 1254 } else { 1255 /* try to parse number */ 1256 errno = 0; 1257 l = strtol(optarg, NULL, 10); 1258 if (l <= 0 || errno) 1259 usage(); 1260 opt_uid = (uid_t)l; 1261 } 1262 break; 1263 case 'g': 1264 opt_privdrop = 1; 1265 /* parse gid as number */ 1266 errno = 0; 1267 l = strtol(optarg, NULL, 10); 1268 if (l <= 0 || errno) 1269 usage(); 1270 opt_gid = (gid_t)l; 1271 break; 1272 case 'v': 1273 case 'V': 1274 show_version(stdout); 1275 exit(0); 1276 break; 1277 default: 1278 usage(); 1279 } 1280 } 1281 1282 argc -= optind; 1283 argv += optind; 1284 1285 if (opt_privdrop && (opt_uid == 0 || opt_gid == 0)) 1286 usage(); 1287 1288 /* password is read from enviroment variable. 1289 It is assumed the environment is secure */ 1290 if ((tmp = getenv("SMB_PASS")) != NULL) 1291 opt_password = xstrdup(tmp); 1292 1293 /* options were succesfully parsed */ 1294 if (ch == '?' || ch == ':') { 1295 usage(); 1296 return 0; 1297 } 1298 1299 if (argc != 2) 1300 usage(); 1301 1302 /* parse URI */ 1303 tmp = xstrdup(argv[0]); 1304 if (uri_parse(tmp, &u) == -1) 1305 usage(); 1306 1307 /* check required options and format */ 1308 if (strcmp(u.proto, "smb://") || 1309 u.userinfo[0] == '\0' || 1310 u.host[0] == '\0' || 1311 u.path[0] != '/') { 1312 usage(); 1313 } 1314 1315 /* password in userinfo field is not allowed */ 1316 if (strchr(u.userinfo, ':')) { 1317 fprintf(stderr, "password must be specified via $SMB_PASS\n\n"); 1318 usage(); 1319 } 1320 1321 /* split domain\user if '\' is found */ 1322 if ((p = strchr(u.userinfo, '\\'))) { 1323 *p = '\0'; 1324 opt_domain = xstrdup(u.userinfo); 1325 opt_username = xstrdup(p + 1); 1326 } else { 1327 opt_domain = xstrdup(""); 1328 opt_username = xstrdup(u.userinfo); 1329 } 1330 1331 opt_server = xstrdup(u.host); 1332 opt_share = xstrdup(u.path + 1); /* share name, "/Sharename" -> "Sharename". */ 1333 free(tmp); 1334 1335 opt_mountpoint = xstrdup(argv[1]); 1336 1337 if (opt_mountpoint == NULL || opt_mountpoint[0] == '\0' || 1338 opt_server == NULL || opt_server[0] == '\0' || 1339 opt_share == NULL || opt_share[0] == '\0' || 1340 opt_username == NULL || opt_username[0] == '\0' || 1341 opt_password == NULL) { 1342 usage(); 1343 } 1344 1345 if (unveil("/", "") == -1) 1346 err(1, "unveil"); 1347 /* required for daemonize mode and ignoring output */ 1348 if (unveil("/dev/null", "rw") == -1) 1349 err(1, "unveil"); 1350 /* read-write permissions to OpenBSD FUSE driver */ 1351 if (unveil("/dev/fuse0", "rw") == -1) 1352 err(1, "unveil"); 1353 /* (r)ead, (w)rite, e(x)ecute, (c)reate permissions to mountpoint */ 1354 if (unveil(opt_mountpoint, "rwxc") == -1) 1355 err(1, "unveil"); 1356 /* lock further unveil calls */ 1357 if (unveil(NULL, NULL) == -1) 1358 err(1, "unveil"); 1359 1360 sharename = create_share_name(opt_server, opt_share); 1361 if (sharename != NULL) { 1362 if (get_context()) { 1363 build_fuse_args(opt_options, opt_mountpoint, opt_debug, opt_nofork, &fuse_argc, &fuse_argv); 1364 ret = usmb_fuse_main(fuse_argc, fuse_argv, &fuse_ops, sizeof(fuse_ops), NULL); 1365 destroy_smb_context(ctx, 1); 1366 } 1367 } 1368 1369 free(sharename); 1370 clear_and_free(opt_password); 1371 free(opt_username); 1372 free(opt_domain); 1373 free(opt_options); 1374 free(opt_mountpoint); 1375 free(opt_share); 1376 free(opt_server); 1377 1378 return ret; 1379 }