/*
* smb-share-enum
*
* By Jon Hart
*
* 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
#include
#include
#include
#include
#include
#include
#include
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 \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 # 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 \n");
fprintf(stderr, "\t-v # be verbose\n");
fprintf(stderr, "\t-w # 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);
}