1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-07 21:21:31 +00:00
Files
nmap/nmap-update/nmap-update.c
2011-12-19 05:16:28 +00:00

1023 lines
20 KiB
C

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "nbase.h"
#include "config.h"
/* See the file tools/examples/minimal_client.c in the Subversion source
directory for an example of using the svn_client API. */
#if HAVE_SUBVERSION_1_SVN_CLIENT_H
#include <subversion-1/svn_client.h>
#include <subversion-1/svn_cmdline.h>
#include <subversion-1/svn_opt.h>
#include <subversion-1/svn_pools.h>
#include <subversion-1/svn_types.h>
#else
#include <svn_client.h>
#include <svn_cmdline.h>
#include <svn_opt.h>
#include <svn_pools.h>
#include <svn_types.h>
#endif
#define NMAP_VERSION "5.61TEST2"
#define NMAP_DATADIR "/usr/local/share/nmap"
#ifdef WIN32
#define PATHSEP "\\"
#else
#define PATHSEP "/"
#endif
static const char *SVN_REPO = "https://svn.nmap.org";
static const char *SVN_DIR = "/updates";
static const char *DEFAULT_CHANNELS[] = { NMAP_VERSION };
/* Internal error handling. */
#define NELEMS(a) (sizeof(a) / sizeof(*a))
#define internal_error(msg) \
do {\
fprintf(stderr, "%s:%d: internal error: %s.\n", __FILE__, __LINE__, msg); \
abort(); \
} while (0)
#define internal_assert(expr) \
do { \
if (!(expr)) \
internal_error("assertion failed: " #expr); \
} while (0)
static char *safe_strdup(const char *s)
{
char *t;
size_t len;
len = strlen(s);
t = safe_malloc(len + 1);
memcpy(t, s, len);
t[len] = '\0';
return t;
}
static int streq(const char *a, const char *b)
{
return strcmp(a, b) == 0;
}
static char *string_make(const char *begin, const char *end)
{
char *s;
s = safe_malloc(end - begin + 1);
memcpy(s, begin, end - begin);
s[end - begin] = '\0';
return s;
}
static char *strbuf_append(char **buf, size_t *size, size_t *offset, const char *s, size_t n)
{
internal_assert(*offset <= *size);
/* Double the buffer size if necessary. */
if (n >= *size - *offset) {
*size = (*size + n) * 2;
*buf = safe_realloc(*buf, *size + 1);
}
memcpy(*buf + *offset, s, n);
*offset += n;
(*buf)[*offset] = '\0';
return *buf;
}
/* Append a '\0'-terminated string as with strbuf_append. */
static char *strbuf_append_str(char **buf, size_t *size, size_t *offset, const char *s)
{
return strbuf_append(buf, size, offset, s, strlen(s));
}
static char *strbuf_append_char(char **buf, size_t *size, size_t *offset, char c)
{
return strbuf_append(buf, size, offset, &c, 1);
}
static char *strbuf_trim(char **buf, size_t *size, size_t *offset)
{
if (*offset < *size) {
*size = *offset;
*buf = safe_realloc(*buf, *size + 1);
}
internal_assert((*buf)[*size] == '\0');
return *buf;
}
static char *string_unescape(const char *escaped)
{
char *buf;
size_t size, offset;
const char *p;
buf = NULL;
size = 0;
offset = 0;
p = escaped;
while (*p != '\0') {
char hex[3], *tail;
unsigned long byte;
/* We support backslash escapes for '\\' and '"', and \xXX
hexadecimal only. */
if (*p == '\\') {
p++;
switch (*p) {
case '\\':
case '"':
strbuf_append_char(&buf, &size, &offset, *p);
p++;
break;
case 'x':
p++;
if (!(isxdigit(*p) && isxdigit(*(p + 1))))
goto bail;
memcpy(hex, p, 2);
hex[2] = '\0';
errno = 0;
byte = strtoul(hex, &tail, 16);
if (errno != 0 || byte > 255 || *tail != '\0')
goto bail;
strbuf_append_char(&buf, &size, &offset, (char) byte);
p += 2;
break;
default:
goto bail;
break;
}
} else {
strbuf_append_char(&buf, &size, &offset, *p);
p++;
}
}
return strbuf_trim(&buf, &size, &offset);
bail:
if (buf != NULL)
free(buf);
return NULL;
}
/* Return a newly allocated string that is the concatenation of all the va_list
args, separated by join:
str1 JOIN str2 JOIN str3 ...
The final argument must be NULL. */
static char *strs_vjoin(const char *join, const char *first, va_list ap)
{
char *buf;
size_t size, offset;
const char *p;
internal_assert(first != NULL);
buf = NULL;
size = 0;
offset = 0;
strbuf_append_str(&buf, &size, &offset, first);
while ((p = va_arg(ap, const char *)) != NULL) {
strbuf_append_str(&buf, &size, &offset, join);
strbuf_append_str(&buf, &size, &offset, p);
}
strbuf_trim(&buf, &size, &offset);
return buf;
}
static char *strs_cat(const char *first, ...)
{
va_list ap;
char *result;
va_start(ap, first);
result = strs_vjoin("", first, ap);
va_end(ap);
return result;
}
static char *path_join(const char *first, ...)
{
va_list ap;
char *result;
va_start(ap, first);
result = strs_vjoin(PATHSEP, first, ap);
va_end(ap);
return result;
}
static char *get_user_dir(const char *subdir) {
static struct passwd *pw;
errno = 0;
pw = getpwuid(getuid());
if (pw == NULL)
return NULL;
return path_join(pw->pw_dir, ".nmap", subdir, NULL);
}
static char *get_install_dir(void) {
return get_user_dir("updates");
}
static char *get_staging_dir(void) {
return get_user_dir("updates-staging");
}
static char *get_conf_filename(void) {
return get_user_dir("nmap-update.conf");
}
/* Configuration file parsing. */
enum token_type {
TOKEN_ERROR,
TOKEN_EOL,
TOKEN_EOF,
TOKEN_WORD,
TOKEN_EQUALS,
TOKEN_STRING,
};
struct config_parser {
FILE *fp;
unsigned long lineno;
};
struct config_entry {
char *key;
char *value;
};
static void config_entry_free(struct config_entry *entry)
{
free(entry->key);
free(entry->value);
}
static int config_parser_open(const char *filename, struct config_parser *cp)
{
cp->fp = fopen(filename, "r");
if (cp->fp == NULL)
return -1;
cp->lineno = 1;
return 0;
}
static int config_parser_close(struct config_parser *cp)
{
int ret;
ret = fclose(cp->fp);
if (ret == EOF)
return -1;
return 0;
}
static int is_word_char(int c)
{
return c != EOF && !isspace(c) && c != '"' && c != '#';
}
static char *read_quoted_string(struct config_parser *cp)
{
char *buf, *unescaped;
size_t size, offset;
int c;
buf = NULL;
size = 0;
offset = 0;
for (;;) {
errno = 0;
c = fgetc(cp->fp);
if (c == EOF)
/* EOF in the middle of a string is always an error. */
return NULL;
if (c == '\n')
return NULL;
if (c == '"')
break;
if (c == '\\') {
strbuf_append_char(&buf, &size, &offset, c);
errno = 0;
c = fgetc(cp->fp);
if (c == EOF)
return NULL;
}
strbuf_append_char(&buf, &size, &offset, c);
}
unescaped = string_unescape(buf);
free(buf);
return unescaped;
}
static enum token_type config_parser_read_token(struct config_parser *cp,
char **token)
{
size_t size, offset;
unsigned long prev_lineno;
int c;
*token = NULL;
size = 0;
offset = 0;
/* Skip comments and blank space. */
prev_lineno = cp->lineno;
do {
errno = 0;
while (isspace(c = fgetc(cp->fp))) {
if (c == '\n')
cp->lineno++;
}
if (c == EOF) {
if (errno != 0)
goto bail;
*token = NULL;
return TOKEN_EOF;
}
if (c == '#') {
while ((c = fgetc(cp->fp)) != EOF && c != '\n')
;
if (c == EOF) {
if (errno != 0)
goto bail;
*token = NULL;
return TOKEN_EOF;
} else if (c == '\n') {
cp->lineno++;
}
}
} while (isspace(c) || c == '#');
/* Collapse multiple consecutive line endings. */
if (cp->lineno != prev_lineno) {
ungetc(c, cp->fp);
*token = NULL;
return TOKEN_EOL;
}
if (c == '=') {
strbuf_append_char(token, &size, &offset, c);
return TOKEN_EQUALS;
} else if (is_word_char(c)) {
while (is_word_char(c)) {
strbuf_append_char(token, &size, &offset, c);
errno = 0;
c = fgetc(cp->fp);
if (c == EOF && errno != 0)
goto bail;
}
return TOKEN_WORD;
} else if (c == '"') {
char *qs;
qs = read_quoted_string(cp);
if (qs == NULL)
goto bail;
*token = safe_strdup(qs);
return TOKEN_STRING;
} else {
goto bail;
}
bail:
if (*token != NULL)
free(*token);
*token = NULL;
return TOKEN_ERROR;
}
static int config_parser_next(struct config_parser *cp, struct config_entry *entry)
{
char *token;
enum token_type type;
while ((type = config_parser_read_token(cp, &token)) == TOKEN_EOL)
;
if (type == TOKEN_EOF) {
free(token);
return 0;
}
if (type != TOKEN_WORD) {
free(token);
return -1;
}
entry->key = token;
type = config_parser_read_token(cp, &token);
if (type != TOKEN_EQUALS) {
free(token);
return -1;
}
free(token);
type = config_parser_read_token(cp, &token);
if (!(type == TOKEN_WORD || type == TOKEN_STRING)) {
free(token);
return -1;
}
entry->value = token;
return 1;
}
/* Global state. */
static char *program_name;
static struct {
int verbose;
const char *install_dir;
const char *staging_dir;
const char *conf_filename;
const char **channels;
unsigned int num_channels;
char *username;
char *password;
} options;
static void init_options(void)
{
options.verbose = 1;
options.install_dir = get_install_dir();
if (options.install_dir == NULL) {
fprintf(stderr, "Could not find an install directory: %s.\n",
strerror(errno));
exit(1);
}
options.staging_dir = get_staging_dir();
if (options.staging_dir == NULL) {
fprintf(stderr, "Could not find a staging directory: %s.\n",
strerror(errno));
exit(1);
}
options.conf_filename = get_conf_filename();
if (options.conf_filename == NULL) {
fprintf(stderr, "Could not find the configuration file: %s.\n",
strerror(errno));
exit(1);
}
options.channels = DEFAULT_CHANNELS;
options.num_channels = NELEMS(DEFAULT_CHANNELS);
options.username = NULL;
options.password = NULL;
}
static int read_config_file(const char *conf_filename)
{
struct config_parser cp;
struct config_entry entry;
int ret;
if (options.verbose)
printf("Trying to open configuration file %s.\n", conf_filename);
errno = 0;
if (config_parser_open(conf_filename, &cp) == -1) {
if (options.verbose)
printf("Failed to open %s: %s.\n", conf_filename, strerror(errno));
return -1;
}
while ((ret = config_parser_next(&cp, &entry)) > 0) {
if (streq(entry.key, "username")) {
if (options.username != NULL) {
fprintf(stderr, "Warning: %s:%lu: duplicate \"%s\".\n",
conf_filename, cp.lineno, entry.key);
free(options.username);
}
options.username = safe_strdup(entry.value);
} else if (streq(entry.key, "password")) {
if (options.password != NULL) {
fprintf(stderr, "Warning: %s:%lu: duplicate \"%s\".\n",
conf_filename, cp.lineno, entry.key);
free(options.password);
}
options.password = safe_strdup(entry.value);
} else {
fprintf(stderr, "Warning: %s:%lu: unknown key \"%s\".\n",
conf_filename, cp.lineno, entry.key);
}
config_entry_free(&entry);
}
if (ret == -1) {
fprintf(stderr, "Parse error on line %lu of %s.\n",
cp.lineno, conf_filename);
exit(1);
}
errno = 0;
if (config_parser_close(&cp) == -1) {
if (options.verbose)
printf("Failed to close %s: %s.\n", conf_filename, strerror(errno));
return -1;
}
return -1;
}
static void usage(FILE *fp)
{
internal_assert(program_name != NULL);
fprintf(fp, "\
Usage: %s [-d INSTALL_DIR] [CHANNEL...]\n\
Updates system-independent Nmap files. By default the new files are installed to\n\
%s. Each CHANNEL is a version number like \"" NMAP_VERSION "\".\n\
\n\
-d DIR install files to DIR (default %s).\n\
-h, --help show this help.\n\
", program_name, get_install_dir(), get_install_dir());
}
static void usage_error(void)
{
usage(stderr);
exit(1);
}
static int try_channels(const char *channels[], unsigned int num_channels);
static int stage_and_install(const char *channel);
static int stage_channel(const char *channel, const char *staging_dir);
static int install(const char *staging_dir, const char *install_dir);
static void summarize_options(void)
{
unsigned int i;
printf("Installing to directory: %s.\n", options.install_dir);
printf("Using staging directory: %s.\n", options.staging_dir);
printf("Using channels:");
for (i = 0; i < options.num_channels; i++)
printf(" %s", options.channels[i]);
printf(".\n");
}
const struct option LONG_OPTIONS[] = {
{ "help", no_argument, NULL, 'h' },
};
int main(int argc, char *argv[])
{
int opt, longoptidx;
internal_assert(argc > 0);
program_name = argv[0];
init_options();
if (svn_cmdline_init(program_name, stderr) != 0)
internal_error("svn_cmdline_init");
while ((opt = getopt_long(argc, argv, "d:h", LONG_OPTIONS, &longoptidx)) != -1) {
if (opt == 'd') {
options.install_dir = optarg;
} else if (opt == 'h') {
usage(stdout);
exit(0);
} else {
usage_error();
}
}
/* User-specified channels. */
if (optind < argc) {
options.channels = (const char **) argv + optind;
options.num_channels = argc - optind;
}
internal_assert(options.channels != NULL);
internal_assert(options.num_channels > 0);
if (options.verbose)
summarize_options();
read_config_file(options.conf_filename);
if (try_channels(options.channels, options.num_channels) == 0)
return 0;
if (options.username == NULL) {
fprintf(stderr, "\
\n\
Could not stage any channels and don't have authentication credentials.\n\
\n\
Edit the file %s and enter your username and password. For example:\n\
username = user\n\
password = secret\n\
", options.conf_filename);
}
return 1;
}
static int try_channels(const char *channels[], unsigned int num_channels)
{
unsigned int i;
for (i = 0; i < num_channels; i++) {
if (stage_and_install(channels[i]) == 0)
return 0;
}
return -1;
}
static void fatal_err_svn(svn_error_t *err)
{
svn_handle_error2(err, stderr, TRUE, "nmap-update: ");
}
static svn_error_t *simple_auth_callback(svn_auth_cred_simple_t **cred,
void *baton, const char *realm, const char *username,
svn_boolean_t may_save, apr_pool_t *pool)
{
svn_auth_cred_simple_t *ret;
ret = apr_pcalloc(pool, sizeof(*ret));
if (options.verbose) {
if (realm != NULL)
printf("Authenticating to realm %s.\n", realm);
}
if (!(username || options.username) || !options.password) {
return svn_error_create(SVN_ERR_RA_UNKNOWN_AUTH, NULL,
"Don't have authentication credentials");
}
if (options.username != NULL)
ret->username = apr_pstrdup(pool, options.username);
else
ret->username = apr_pstrdup(pool, username);
ret->password = apr_pstrdup(pool, options.password);
*cred = ret;
return SVN_NO_ERROR;
}
static svn_error_t *checkout_svn(const char *url, const char *path)
{
svn_error_t *err;
apr_pool_t *pool;
svn_opt_revision_t peg_revision, revision;
svn_client_ctx_t *ctx;
svn_revnum_t revnum;
svn_auth_provider_object_t *provider;
apr_array_header_t *providers;
peg_revision.kind = svn_opt_revision_unspecified;
revision.kind = svn_opt_revision_head;
pool = svn_pool_create(NULL);
err = svn_client_create_context(&ctx, pool);
if (err != NULL)
fatal_err_svn(err);
providers = apr_array_make(pool, 10, sizeof (svn_auth_provider_object_t *));
svn_auth_get_simple_prompt_provider(&provider,
simple_auth_callback,
NULL, /* baton */
0, /* retry limit */
pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
/* Register the auth providers into the client context's auth_baton. */
svn_auth_open(&ctx->auth_baton, providers, pool);
err = svn_client_checkout3(&revnum, url, path,
&peg_revision, &revision,
svn_depth_infinity,
TRUE, /* ignore_externals */
FALSE, /* allow_unver_obstructions */
ctx, pool);
svn_pool_destroy(pool);
if (err != NULL)
return err;
printf("Checked out r%lu\n", (unsigned long) revnum);
return SVN_NO_ERROR;
}
static int stage_and_install(const char *channel)
{
char *staging_dir, *install_dir;
int rc;
internal_assert(options.staging_dir != NULL);
staging_dir = path_join(options.staging_dir, channel, NULL);
rc = stage_channel(channel, staging_dir);
if (rc == -1) {
free(staging_dir);
return -1;
}
install_dir = path_join(options.install_dir, channel, NULL);
rc = install(staging_dir, install_dir);
free(staging_dir);
free(install_dir);
return rc;
}
static int stage_channel(const char *channel, const char *staging_dir)
{
char *svn_url;
svn_error_t *err;
int rc;
rc = 0;
svn_url = strs_cat(SVN_REPO, SVN_DIR, "/", channel, NULL);
if (options.verbose)
printf("Checking out %s to %s.\n", svn_url, staging_dir);
err = checkout_svn(svn_url, staging_dir);
if (err != NULL) {
svn_handle_error2(err, stderr, FALSE, "nmap-update: ");
fprintf(stderr, "Error checking out %s.\n", svn_url);
rc = -1;
}
free(svn_url);
return rc;
}
static int copy_tree(const char *from_dirname, const char *to_dirname);
static int install(const char *staging_dir, const char *install_dir)
{
if (options.verbose)
printf("Installing from %s to %s.\n", staging_dir, install_dir);
return copy_tree(staging_dir, install_dir);
}
static int copy_file(const char *from_filename, const char *to_filename)
{
char buf[BUFSIZ];
char *tmp_filename;
FILE *from_fd, *tmp_fd;
int rc, from_rc, tmp_rc;
size_t nr, nw;
tmp_filename = NULL;
from_fd = NULL;
tmp_fd = NULL;
errno = 0;
from_fd = fopen(from_filename, "rb");
if (from_fd == NULL) {
fprintf(stderr, "Can't open %s: %s.\n", from_filename, strerror(errno));
goto bail;
}
tmp_filename = strs_cat(to_filename, "-tmp", NULL);
errno = 0;
tmp_fd = fopen(tmp_filename, "wb");
if (tmp_fd == NULL) {
fprintf(stderr, "Can't open %s: %s.\n", tmp_filename, strerror(errno));
goto bail;
}
errno = 0;
while ((nr = fread(buf, 1, sizeof(buf), from_fd)) != 0) {
errno = 0;
nw = fwrite(buf, 1, nr, tmp_fd);
if (nw != nr || errno != 0) {
printf("%lu %lu\n", nw, nr);
fprintf(stderr, "Error writing to %s: %s.\n", tmp_filename, strerror(errno));
goto bail;
}
}
if (errno != 0) {
fprintf(stderr, "Error reading from %s: %s.\n", from_filename, strerror(errno));
goto bail;
}
from_rc = fclose(from_fd);
from_fd = NULL;
if (from_rc == -1) {
fprintf(stderr, "Can't close %s: %s.\n", from_filename, strerror(errno));
goto bail;
}
tmp_rc = fclose(tmp_fd);
tmp_fd = NULL;
if (tmp_rc == -1) {
fprintf(stderr, "Can't close %s: %s.\n", to_filename, strerror(errno));
goto bail;
}
rc = rename(tmp_filename, to_filename);
if (rc == -1) {
fprintf(stderr, "Can't rename %s to %s: %s.\n",
tmp_filename, to_filename, strerror(errno));
goto bail;
}
free(tmp_filename);
tmp_filename = NULL;
return 0;
bail:
if (from_fd != NULL)
fclose(from_fd);
if (tmp_fd != NULL)
fclose(tmp_fd);
if (tmp_filename != NULL)
free(tmp_filename);
return -1;
}
static int is_pathsep(int c)
{
#ifdef WIN32
return c == '/' || c == '\\';
#else
return c == '/';
#endif
}
static char *parent_dir(const char *path)
{
const char *p;
p = path + strlen(path) - 1;
while (p > path && is_pathsep(*p))
p--;
while (p > path && !is_pathsep(*p))
p--;
while (p > path && is_pathsep(*p))
p--;
if (p == path)
return safe_strdup("/");
return string_make(path, p + 1);
}
static int makedir(const char *dirname)
{
return mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
}
static int makedirs(const char *dirname)
{
char *parent;
int rc;
rc = makedir(dirname);
if (rc == 0 || errno == EEXIST)
return 0;
if (errno != ENOENT)
return -1;
parent = parent_dir(dirname);
rc = makedirs(parent);
free(parent);
if (rc == -1)
return -1;
rc = makedir(dirname);
if (rc == -1)
return -1;
return rc;
}
static int copy_tree(const char *from_dirname, const char *to_dirname)
{
DIR *dir;
const struct dirent *ent;
int rc;
rc = makedirs(to_dirname);
if (rc == -1) {
fprintf(stderr, "Can't create the directory %s: %s.\n",
to_dirname, strerror(errno));
return -1;
}
dir = opendir(from_dirname);
if (dir == NULL) {
fprintf(stderr, "Can't open the directory %s: %s.\n",
from_dirname, strerror(errno));
return -1;
}
errno = 0;
while ((ent = readdir(dir)) != NULL) {
char *from_path, *to_path;
int error;
from_path = path_join(from_dirname, ent->d_name, NULL);
to_path = path_join(to_dirname, ent->d_name, NULL);
error = 0;
if (ent->d_type == DT_DIR) {
if (streq(ent->d_name, ".") || streq(ent->d_name, ".."))
continue;
if (streq(ent->d_name, ".svn"))
continue;
rc = makedirs(to_path);
if (rc == 0) {
rc = copy_tree(from_path, to_path);
if (rc == -1)
error = 1;
} else {
error = 1;
}
} else if (ent->d_type == DT_REG) {
rc = copy_file(from_path, to_path);
if (rc == -1)
error = 1;
} else {
fprintf(stderr, "Warning: unknown file type %u of %s.\n",
ent->d_type, ent->d_name);
}
free(from_path);
free(to_path);
if (error)
goto bail;
}
if (errno != 0) {
fprintf(stderr, "Error in readdir: %s.\n", strerror(errno));
goto bail;
}
rc = closedir(dir);
if (rc == -1) {
fprintf(stderr, "Can't close the directory %s: %s.\n",
from_dirname, strerror(errno));
return -1;
}
return 0;
bail:
closedir(dir);
return -1;
}