URI:
       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 }