susmb.c - susmb - fork from usmb 20130204: mount SMB/CIFS shares via FUSE
(HTM) git clone git://git.codemadness.org/susmb
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) 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 }