uhub/src/tools/uhub-passwd.c
Jan Vidar Krey df7bbc094f Command arguments handling + cleanups
Fix bug #185 - Args of !commands lost/damaged.
All string arguments were incorrectly freed after being added to the argument list for a command.
Instead this fix makes sure it is properly copied into a new string, and by doing so this requires
a new API for dealing with hub command arguments in a type safe manner, and also allows for each
argument to be cleaned up properly when the command is no longer needed.

This also fixes issues with parse errors for certain types, and optional arguments (previously it was impossible
to tell the difference for an integer with value 0 or if no integer was given).

All arguments can now be accessed through the new functions
hub_command_arg_reset() and hub_command_arg_next().

These functions are also exposed to plug-ins.

The argument type notations for 'n' has changed to mean nick (string),
and 'u' is used for a user (struct hub_user - must be online).
2012-04-18 23:03:05 +02:00

396 lines
8.1 KiB
C

/*
* uhub - A tiny ADC p2p connection hub
* Copyright (C) 2007-2012, 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 <http://www.gnu.org/licenses/>.
*
*/
#include "uhub.h"
#include "util/misc.h"
#include <sqlite3.h>
// #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;
char* buf;
for (n = 0, size = strlen(str); n < strlen(str); n++)
if (str[n] == '\'')
size++;
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 const char* validate_username(const char* username)
{
const char* tmp;
// verify length
if (strlen(username) > MAX_NICK_LEN)
{
fprintf(stderr, "User name is too long.\n");
exit(1);
}
/* Nick must not start with a space */
if (is_white_space(username[0]))
{
fprintf(stderr, "User name cannot start with white space.\n");
exit(1);
}
/* Check for ASCII values below 32 */
for (tmp = username; *tmp; tmp++)
if ((*tmp < 32) && (*tmp > 0))
{
fprintf(stderr, "User name contains illegal characters.\n");
exit(1);
}
if (!is_valid_utf8(username))
{
fprintf(stderr, "User name must be utf-8 encoded.\n");
exit(1);
}
return username;
}
static const char* validate_password(const char* password)
{
// verify length
if (strlen(password) > MAX_PASS_LEN)
{
fprintf(stderr, "Password is too long.\n");
exit(1);
}
if (!is_valid_utf8(password))
{
fprintf(stderr, "Password must be utf-8 encoded.\n");
exit(1);
}
return password;
}
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(validate_username(argv[0]));
pass = sql_escape_string(validate_password(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(validate_password(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 < 1)
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 %i bytes)\n"
" 'password' is a password (UTF-8, up to %i bytes)\n"
" 'credentials' is one of 'admin', 'super', 'op', 'user'\n"
"\n"
, binary, MAX_NICK_LEN, MAX_PASS_LEN);
}
int main(int argc, char** argv)
{
size_t n = 0;
binary = argv[0];
filename = argv[1];
command = argv[2];
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 - 3, (const char**) &argv[3]);
}
// Unknown command!
main_usage(argv[0]);
return 1;
}