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 }