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