/*
 * smb-share-enum
 *
 * By Jon Hart <jhart [at] spoofed.org>
 *
 * Written to emulate what the Windows tool 'Legion' does and perhaps
 * a little more.
 *
 * This grew from the need to, in a pinch, find all Windows/Samba shares on
 * a network that were writable.
 *
 * Requires libsmbclient and/or libsmbclient-dev, available as source
 * and/or packages for most major Linux/BSD distributions.
 *
 * Build instructions:
 *
 *    `gcc -Wall -lsmbclient -o smb-share-enum smb-share-enum.c`
 *
 * Sample run in normal mode:
 *
 * smb://TUVALU/D$ is writable
 * smb://TUVALU/C$ is writable
 * smb://TUVALU/WAREZ is writable
 * smb://CONGO/tmp is writable
 *
 * Sample run in 'browse only' mode:
 *
 * Workgroup SPOOFED.ORG
 *    Server CONGO
 *       Share smb://CONGO/IPC$
 *       Share smb://CONGO/CD
 *       Share smb://CONGO/mp3
 *       Share smb://CONGO/tmp
 *  Workgroup MSHOME
 *     Server WINXP
 *        Share smb://WINXP/Jon's Camera
 *        Share smb://WINXP/ADMIN$
 *        Share smb://WINXP/F$
 *        Share smb://WINXP/2006
 *        Share smb://WINXP/HPDeskJet
 *        Share smb://WINXP/print$
 *        Share smb://WINXP/SharedDocs
 *        Share smb://WINXP/IPC$
 *        Share smb://WINXP/E$
 *
 *
 * This tool is not particularly fast, but I'm not sure that it an be made
 * any faster.  I also need to rework the output in verbose mode, as it gets
 * ugly.
 */


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <malloc.h>
#include <string.h>
#include <sys/time.h>
#include <libsmbclient.h>

int browse_depth = 0;

typedef struct smbitem smbitem;

struct smbitem {
    smbitem	*next;
    int type;
    char	name[1];
};

#define OPT_KRB 0
#define OPT_USER 1
#define OPT_PASSWD 2
#define OPT_WORKGROUP 3
#define OPT_DEBUG 4
#define OPT_VERBOSE 5
#define OPT_BROWSE 6

struct opt {
   char *name; /* name */
   int set;
   char *value;
};

struct opt opts[]  = {
   {"kerberos", 0, NULL} ,
   {"user", 0, NULL},
   {"password", 0, NULL},
   {"workgroup", 0, NULL},
   {"debug", 0, NULL},
   {"verbose", 0, NULL},
   {"browse", 0, NULL}
};


void delete_smbctx(SMBCCTX* ctx);
void recurse(SMBCCTX *ctx, char *smb_group, char *smb_path, int maxlen);
void test_write(SMBCCTX *ctx, char *smb_path);

void smbc_auth_fn (
   const char *server,
   const char *share,
   char *wrkgrp, int wrkgrplen,
   char *user, int userlen,
   char *passwd, int passwdlen) {
}


SMBCCTX* create_smbctx() {
   SMBCCTX	*ctx;

   if ((ctx = smbc_new_context()) == NULL) {
      fprintf(stderr, "Failed to create new context: %s\n", strerror(errno));
      delete_smbctx(ctx);
      return NULL;
   }

   if (opts[OPT_DEBUG].set) {
      ctx->debug = atoi(opts[OPT_DEBUG].value);
   } else {
      ctx->debug = 0;
   }

   /* if this isn't here, even though it points to a useless function,
    * nothing works.  XXXX FIGURE THIS OUT.
    */
   ctx->callbacks.auth_fn = smbc_auth_fn;

   if (opts[OPT_WORKGROUP].set) {
      if ((ctx->workgroup = malloc(strlen(opts[OPT_WORKGROUP].value))) == NULL) {
         fprintf(stderr, "Couldn't malloc() for ctx->workgroup: %s\n", strerror(errno));
         delete_smbctx(ctx);
         return NULL;
      } else {
         strncpy(ctx->workgroup, opts[OPT_WORKGROUP].value, strlen(opts[OPT_WORKGROUP].value));
      }
   }

   if (opts[OPT_USER].set) {
      if ((ctx->user = malloc(strlen(opts[OPT_USER].value))) == NULL) {
         fprintf(stderr, "Couldn't malloc() ctx->user: %s\n", strerror(errno));
         delete_smbctx(ctx);
         return NULL;
      } else {
         strncpy(ctx->user, opts[OPT_USER].value, strlen(opts[OPT_USER].value));
      }
   }

   if (opts[OPT_KRB].set) {
      ctx->flags = SMB_CTX_FLAG_USE_KERBEROS;
   }

   if ((ctx = smbc_init_context(ctx)) == NULL) {
      fprintf(stderr, "Failed to init context: %s\n", strerror(errno));
      return NULL;
   }

   return ctx;
}

void delete_smbctx(SMBCCTX* ctx) {
   if (ctx->callbacks.purge_cached_fn(ctx) == 1) {
      fprintf(stderr, "Couldn't remove servers!\n");
   }
   if (smbc_free_context(ctx, 0) == 1) {
      fprintf(stderr, "Couldn't free context: %s\n", strerror(errno));
   }
}

smbitem* get_smbitem_list(SMBCCTX *ctx, char *smb_path) {
    SMBCFILE		*fd;
    struct smbc_dirent	*dirent;
    smbitem		*list = NULL, *item;

   if ((fd = ctx->opendir(ctx, smb_path)) == NULL) {
      fprintf(stderr, "Couldn't browse %s: %s\n", smb_path, strerror(errno));
      return NULL;
   }

   while ((dirent = ctx->readdir(ctx, fd)) != NULL) {
      if (strcmp(dirent->name, "") == 0) continue;
      if (strcmp(dirent->name, ".") == 0) continue;
      if (strcmp(dirent->name, "..") == 0) continue;

      if ((item = malloc(sizeof(smbitem) + strlen(dirent->name))) == NULL) {
         fprintf(stderr, "Couldn't malloc() for item: %s\n", strerror(errno));
         continue;
      }

      item->next = list;
      item->type = dirent->smbc_type;
      strcpy(item->name, dirent->name);
      list = item;
   }

   ctx->closedir(ctx, fd);
   return (list);
}

void help(char *name) {
   fprintf(stderr, "%s -- A SMB share enumerator, write tester\n", name);
   fprintf(stderr, "by Jon Hart <jhart [at] spoofed.org>\n");

   fprintf(stderr, "\nUsage:\n");
   fprintf(stderr, "\t%s [options] [IP | hostname | NETBIOS name | Workgroup/Domain]\n", name);
   fprintf(stderr, "\tOptions:\n");
   fprintf(stderr, "\t-b             # 'browse only'\n");
   fprintf(stderr, "\t-d <level>     # set debug level of underlying SMB stuff\n");
   fprintf(stderr, "\t-k             # user kerberos (don't forget to kinit first...)\n");
   fprintf(stderr, "\t-u <username>\n");
   fprintf(stderr, "\t-v             # be verbose\n");
   fprintf(stderr, "\t-w <workgroup> # workgroup/domain\n");
   fprintf(stderr, "\n\tHint: don't specify a hostname, IP, name, etc to force autodiscovery!\n");
}

void recurse(SMBCCTX *ctx, char *smb_group, char *smb_path, int maxlen) {
   int 	len, i;
   smbitem	*list, *item;
   len = strlen(smb_path);
   list = get_smbitem_list(ctx, smb_path);

   while (list != NULL) {
      switch (list->type) {
         case SMBC_WORKGROUP:
            browse_depth=1;
            if (opts[OPT_VERBOSE].set || opts[OPT_BROWSE].set) {
               printf("Workgroup %s\n", list->name);
            }
            smb_group = list->name;
            if ((size_t) maxlen < 7 + strlen(list->name))
               break;
            strcpy(smb_path + 6, list->name);
            recurse(ctx, smb_group, smb_path, maxlen);
            browse_depth--;
            ctx->callbacks.purge_cached_fn(ctx);
            break;
         case SMBC_SERVER:
            browse_depth++;
            if (opts[OPT_VERBOSE].set || opts[OPT_BROWSE].set) {
               for (i = 1; i < browse_depth; i++) {
                  printf("\t");
               }
               printf("Server %s\n", list->name);
            }

            if ((size_t) maxlen < 7 + strlen(list->name))
               break;
            strcpy(smb_path + 6, list->name);
            recurse(ctx, smb_group, smb_path, maxlen);
            browse_depth--;
            ctx->callbacks.purge_cached_fn(ctx);
            break;
         case SMBC_FILE_SHARE:
         case SMBC_PRINTER_SHARE:
         case SMBC_COMMS_SHARE:
         case SMBC_IPC_SHARE:
            browse_depth++;
            if ((size_t) maxlen < len + strlen(list->name) + 2) break;

            smb_path[len] = '/';
            strcpy(smb_path + len + 1, list->name);

            if (opts[OPT_VERBOSE].set || opts[OPT_BROWSE].set) {
                  for (i = 1; i < browse_depth; i++) {
                     printf("\t");
                  }
               printf("Share %s\n", smb_path);
            }

            if (!opts[OPT_BROWSE].set && list->type == SMBC_FILE_SHARE) {
               test_write(ctx, smb_path);
            }
            browse_depth--;
            break;
	   }
      item = list;
      list = list->next;
      free(item);
   }
   free(list);
   smb_path[len] = '\0';
}

void test_write(SMBCCTX *ctx, char *share) {
   SMBCFILE *fd;
   int i, file_len, full_path_len;
   char *full_path;
   char *file;
   struct timeval tv;
   gettimeofday(&tv, NULL);
   srandom((u_int) tv.tv_usec);

   file_len = strlen("/XXXXXXXXXX-write.txt");
   full_path_len = file_len + strlen(share);
   if ((file = malloc((size_t) (file_len + 1))) == NULL) {
      fprintf(stderr, "Couldn't malloc() for file: %s\n", strerror(errno));
      return;
   }

   if ((memset(file, '\0', (size_t) file_len + 1)) == NULL) {
      free(file);
      fprintf(stderr, "Couldn't memset() file: %s\n", strerror(errno));
      return;
   }

   /* file will be of the format '/XXXXXXXXXX-write.txt' */
   sprintf(file, "/%010ld-write.txt", random());


   if ((full_path = malloc((size_t) full_path_len + 1)) == NULL) {
      free(file);
      fprintf(stderr, "Couldn't malloc() for full_path: %s\n", strerror(errno));
      return;
   }

   if ((memset(full_path, '\0', (size_t) full_path_len + 1)) == NULL) {
      free(file);
      free(full_path);
      fprintf(stderr, "Couldn't memset() full_path: %s\n", strerror(errno));
      return;
   }

   strncpy(full_path, share, strlen(share));
   strncat(full_path, file, strlen(file));

   if ((fd = ctx->creat(ctx, full_path, 700)) == NULL) {
      if (opts[OPT_VERBOSE].set) {
         fprintf(stderr, "Couldn't write %s: %s\n", full_path, strerror(errno));
      }
      free(file);
      free(full_path);
      return;
   }

   if (opts[OPT_VERBOSE].set) {
      for (i = 1; i <= browse_depth; i++) {
         printf("\t");
      }
      printf("Wrote %s\n", full_path);
   }

   fd = (SMBCFILE *) ctx->close_fn(ctx, fd);

   if ((fd = (SMBCFILE *) ctx->unlink(ctx, full_path)) == 0) {
      if (opts[OPT_VERBOSE].set) {
         for (i = 1; i <= browse_depth; i++) {
            printf("\t");
         }
         printf("Removed %s\n", full_path);
      }
      fd = (SMBCFILE *) ctx->close_fn(ctx, fd);
   } else {
      fprintf(stderr, "Couldn't remove %s: %s\n", full_path, strerror(errno));
   }

   printf("%s is writable\n", share);

   free(full_path);
   free(file);
   return;
}


int main(int argc, char *argv[]) {
    int i, c;
    SMBCCTX	*ctx;
    char	smb_path[32768] = "smb://";

   while ((c = getopt(argc, argv, "bd:hku:vw:")) != EOF) {
      switch (c) {
         case 'b':
            opts[OPT_BROWSE].set = 1;
            break;
         case 'd':
            opts[OPT_DEBUG].set = 1;
            opts[OPT_DEBUG].value = optarg;
            break;
         case 'h':
            help(argv[0]);
            return(0);
         case 'k':
            opts[OPT_KRB].set = 1;
            break;
         case 'u':
            opts[OPT_USER].set = 1;
            opts[OPT_USER].value = optarg;
            break;
         case 'v':
            opts[OPT_VERBOSE].set = 1;
            break;
         case 'w':
            opts[OPT_WORKGROUP].set = 1;
            opts[OPT_WORKGROUP].value = optarg;
            strncat(smb_path, optarg, strlen(optarg));
            break;
         default:
            break;
      }
   }

   if ((ctx = create_smbctx()) == NULL) {
      fprintf(stderr, "Couldn't create context: %s\n", strerror(errno));
      if (smbc_free_context(ctx, 0) == 1) {
         fprintf(stderr, "Couldn't free context: %s\n", strerror(errno));
      }
      return(1);
   }

   if (optind == argc) {
      /* no args specified.  will find/check everything that responds */
      if (opts[OPT_VERBOSE].set) {
         printf("Scanning all visible workgroups, domains, hosts, etc...\n");
      }
      recurse(ctx, "", smb_path, sizeof(smb_path));
   } else {
      /* check each of the args specified, which can be an IP address, hostname
       * netbios name, workgroup, etc
       */
      for (i = optind; i < argc; i++) {
         if (opts[OPT_VERBOSE].set) {
            printf("Scanning %s\n", argv[i]);
         }
         strncpy(smb_path + 6, argv[i], sizeof(smb_path) - 7);
         smb_path[sizeof(smb_path) - 1] = '\0';
         recurse(ctx, "", smb_path, sizeof(smb_path));
      }
   }

   delete_smbctx(ctx);
   return(0);
}
