diff --git a/GNUmakefile b/GNUmakefile
index 19e4fe6..40dfa67 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -182,6 +182,9 @@ libadc_client_SOURCES := \
uhub_SOURCES := src/core/main.c
+uhub-passwd_SOURCES := src/tools/uhub-passwd.c
+uhub-passwd_LIBS := -lsqlite3
+
adcrush_SOURCES := src/tools/adcrush.c
admin_SOURCES := src/tools/admin.c
@@ -229,6 +232,7 @@ libadc_client_OBJECTS := $(libadc_client_SOURCES:.c=.o)
libadc_common_OBJECTS := $(libadc_common_SOURCES:.c=.o)
uhub_OBJECTS := $(uhub_SOURCES:.c=.o)
+uhub-passwd_OBJECTS := $(uhub-passwd_SOURCES:.c=.o)
adcrush_OBJECTS := $(adcrush_SOURCES:.c=.o)
admin_OBJECTS := $(admin_SOURCES:.c=.o)
@@ -236,6 +240,7 @@ all_OBJECTS := $(libuhub_OBJECTS) $(uhub_OBJECTS) $(libutils_OBJECTS) $(adcr
all_plugins := $(plugin_example_TARGET) $(plugin_logging_TARGET) $(plugin_auth_TARGET) $(plugin_auth_sqlite_TARGET) $(plugin_chat_history_TARGET)
uhub_BINARY=uhub$(BIN_EXT)
+uhub-passwd_BINARY=uhub-passwd$(BIN_EXT)
adcrush_BINARY=adcrush$(BIN_EXT)
admin_BINARY=uhub-admin$(BIN_EXT)
autotest_BINARY=autotest/test$(BIN_EXT)
@@ -250,7 +255,7 @@ endif
%.o: %.c version.h revision.h
$(MSG_CC) $(CC) -fPIC -c $(CFLAGS) -o $@ $<
-all: $(uhub_BINARY) plugins
+all: $(uhub_BINARY) $(uhub-passwd_BINARY) plugins
plugins: $(uhub_BINARY) $(all_plugins)
@@ -278,6 +283,10 @@ $(admin_BINARY): $(admin_OBJECTS) $(libuhub_OBJECTS) $(libutils_OBJECTS) $(libad
$(uhub_BINARY): $(uhub_OBJECTS) $(libuhub_OBJECTS) $(libutils_OBJECTS) $(libadc_common_OBJECTS)
$(MSG_LD) $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
+$(uhub-passwd_BINARY): $(uhub-passwd_OBJECTS)
+ $(MSG_LD) $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS) $(uhub-passwd_LIBS)
+
+
autotest.c: $(autotest_SOURCES)
$(shell exotic --standalone $(autotest_SOURCES) > $@)
diff --git a/src/tools/uhub-passwd.c b/src/tools/uhub-passwd.c
new file mode 100644
index 0000000..79d3587
--- /dev/null
+++ b/src/tools/uhub-passwd.c
@@ -0,0 +1,340 @@
+/*
+ * uhub - A tiny ADC p2p connection hub
+ * Copyright (C) 2007-2011, Jan Vidar Krey
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "uhub.h"
+#include
+
+// #define DEBUG_SQL
+
+static sqlite3* db = NULL;
+static const char* command = NULL;
+static const char* filename = NULL;
+static const char* binary = NULL;
+
+typedef int (*command_func_t)(size_t, const char**);
+
+static int create(size_t argc, const char** argv);
+static int list(size_t argc, const char** argv);
+static int pass(size_t argc, const char** argv);
+static int add(size_t argc, const char** argv);
+static int del(size_t argc, const char** argv);
+static int mod(size_t argc, const char** argv);
+
+static struct commands
+{
+ command_func_t handle;
+ const char* command;
+ const char* usage;
+} COMMANDS[6] = {
+ { &create, "create", "" },
+ { &list, "list", "" },
+ { &add, "add", "username password [credentials = user]" },
+ { &del, "del", "username" },
+ { &mod, "mod", "username credentials" },
+ { &pass, "pass", "username password" },
+};
+
+static void print_usage(const char* str)
+{
+ fprintf(stderr, "Usage: %s filename %s %s\n", binary, command, str);
+ exit(1);
+}
+
+
+/**
+ * Escape an SQL statement and return a pointer to the string.
+ * NOTE: The returned value needs to be free'd.
+ *
+ * @return an escaped string.
+ */
+static char* sql_escape_string(const char* str)
+{
+ size_t i, n, size;
+
+ for (n = 0, size = strlen(str); n < strlen(str); n++)
+ if (str[n] == '\'')
+ size++;
+
+ char* buf = malloc(size+1);
+ for (n = 0, i = 0; n < strlen(str); n++)
+ {
+ if (str[n] == '\'')
+ buf[i++] = '\'';
+ buf[i++] = str[n];
+ }
+ buf[i++] = '\0';
+ return buf;
+}
+
+/**
+ * Validate credentials.
+ */
+static const char* validate_cred(const char* cred_str)
+{
+ if (!strcmp(cred_str, "admin"))
+ return "admin";
+
+ if (!strcmp(cred_str, "super"))
+ return "super";
+
+ if (!strcmp(cred_str, "op"))
+ return "op";
+
+ if (!strcmp(cred_str, "user"))
+ return "user";
+
+ fprintf(stderr, "Invalid user credentials. Must be one of: 'admin', 'super', 'op' or 'user'\n");
+ exit(1);
+}
+
+
+static void open_database()
+{
+ int res = sqlite3_open(filename, &db);
+
+ if (res)
+ {
+ fprintf(stderr, "Unable to open database: %s (result=%d)\n", filename, res);
+ exit(1);
+ }
+}
+
+static int sql_callback(void* ptr, int argc, char **argv, char **colName) { return 0; }
+
+static int sql_execute(const char* sql, ...)
+{
+ va_list args;
+ char query[1024];
+ char* errMsg;
+ int rc;
+
+ va_start(args, sql);
+ vsnprintf(query, sizeof(query), sql, args);
+
+#ifdef DEBUG_SQL
+ printf("SQL: %s\n", query);
+#endif
+
+ open_database();
+
+ rc = sqlite3_exec(db, query, sql_callback, NULL, &errMsg);
+ if (rc != SQLITE_OK) {
+ fprintf(stderr, "ERROR: %s\n", errMsg);
+ sqlite3_free(errMsg);
+ }
+
+ rc = sqlite3_changes(db);
+ sqlite3_close(db);
+ return rc;
+}
+
+static int create(size_t argc, const char** argv)
+{
+ const char* sql = "CREATE TABLE users"
+ "("
+ "nickname CHAR NOT NULL UNIQUE,"
+ "password CHAR NOT NULL,"
+ "credentials CHAR NOT NULL DEFAULT 'user',"
+ "created TIMESTAMP DEFAULT (DATETIME('NOW')),"
+ "activity TIMESTAMP DEFAULT (DATETIME('NOW'))"
+ ");";
+
+ sql_execute(sql);
+ return 0;
+}
+
+
+static int sql_callback_list(void* ptr, int argc, char **argv, char **colName)
+{
+ int* found = (int*) ptr;
+ uhub_assert(strcmp(colName[0], "nickname") == 0 && strcmp(colName[2], "credentials") == 0);
+ printf("%s\t%s\n", argv[2], argv[0]);
+ (*found)++;
+ return 0;
+}
+
+static int list(size_t argc, const char** argv)
+{
+ char* errMsg;
+ int found = 0;
+ int rc;
+
+ open_database();
+
+ rc = sqlite3_exec(db, "SELECT * FROM users;", sql_callback_list, &found, &errMsg);
+ if (rc != SQLITE_OK) {
+#ifdef DEBUG_SQL
+ fprintf(stderr, "SQL: ERROR: %s (%d)\n", errMsg, rc);
+#endif
+ sqlite3_free(errMsg);
+ exit(1);
+ }
+
+ sqlite3_close(db);
+ return 0;
+}
+
+
+static int add(size_t argc, const char** argv)
+{
+ char* user = NULL;
+ char* pass = NULL;
+ const char* cred = NULL;
+ int rc;
+
+ if (argc < 2)
+ print_usage("username password [credentials = user]");
+
+ user = sql_escape_string(argv[0]);
+ pass = sql_escape_string(argv[1]);
+ cred = validate_cred(argv[2] ? argv[2] : "user");
+
+ rc = sql_execute("INSERT INTO users (nickname, password, credentials) VALUES('%s', '%s', '%s');", user, pass, cred);
+
+ free(user);
+ free(pass);
+
+ if (rc != 1)
+ {
+ fprintf(stderr, "Unable to add user \"%s\"\n", argv[0]);
+ return 1;
+ }
+ return 0;
+}
+
+static int mod(size_t argc, const char** argv)
+{
+ char* user = NULL;
+ const char* cred = NULL;
+ int rc;
+
+ if (argc < 2)
+ print_usage("username credentials");
+
+ user = sql_escape_string(argv[0]);
+ cred = validate_cred(argv[1]);
+
+ rc = sql_execute("UPDATE users SET credentials = '%s' WHERE nickname = '%s';", cred, user);
+
+ free(user);
+
+ if (rc != 1)
+ {
+ fprintf(stderr, "Unable to set credentials for user \"%s\"\n", argv[0]);
+ return 1;
+ }
+ return 0;
+}
+
+static int pass(size_t argc, const char** argv)
+{
+ char* user = NULL;
+ char* pass = NULL;
+ int rc;
+
+ if (argc < 2)
+ print_usage("username password");
+
+ user = sql_escape_string(argv[0]);
+ pass = sql_escape_string(argv[1]);
+
+ rc = sql_execute("UPDATE users SET password = '%s' WHERE nickname = '%s';", pass, user);
+
+ free(user);
+ free(pass);
+
+ if (rc != 1)
+ {
+ fprintf(stderr, "Unable to change password for user \"%s\"\n", argv[0]);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static int del(size_t argc, const char** argv)
+{
+ char* user = NULL;
+ int rc;
+
+ if (argc < 2)
+ print_usage("username");
+
+ user = sql_escape_string(argv[0]);
+
+ rc = sql_execute("DELETE FROM users WHERE nickname = '%s';", user);
+ free(user);
+
+ if (rc != 1)
+ {
+ fprintf(stderr, "Unable to delete user \"%s\".\n", argv[0]);
+ return 1;
+ }
+
+ return 0;
+}
+
+void main_usage(const char* binary)
+{
+ printf(
+ "Usage: %s filename command [...]\n"
+ "\n"
+ "Command syntax:\n"
+ " create\n"
+ " add username password [credentials = user]\n"
+ " del username\n"
+ " mod username credentials\n"
+ " pass username password\n"
+ " list\n"
+ "\n"
+ "Parameters:\n"
+ " 'filename' is a database file\n"
+ " 'username' is a nickname (UTF-8, up to 64 bytes)\n"
+ " 'password' is a password (UTF-8, up to 64 bytes)\n"
+ " 'credentials' is one of 'admin', 'super', 'op', 'user'\n"
+ "\n"
+ , binary);
+}
+
+int main(int argc, char** argv)
+{
+ binary = argv[0];
+ filename = argv[1];
+ command = argv[2];
+ size_t n = 0;
+
+ if (argc < 3)
+ {
+ main_usage(argv[0]);
+ return 1;
+ }
+
+ for (; n < sizeof(COMMANDS) / sizeof(COMMANDS[0]); n++)
+ {
+ if (!strcmp(command, COMMANDS[n].command))
+ return COMMANDS[n].handle(argc - 2, (const char**) &argv[3]);
+ }
+
+ // Unknown command!
+ main_usage(argv[0]);
+ return 1;
+}
+
+