susmb

fork from usmb 20130204: mount SMB/CIFS shares via FUSE
git clone git://git.codemadness.org/susmb
Log | Files | Refs | README | LICENSE

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 }