diff --git a/freeciv/._cfg0001_database.lua b/freeciv/._cfg0001_database.lua new file mode 100644 index 0000000..cfcd27d --- /dev/null +++ b/freeciv/._cfg0001_database.lua @@ -0,0 +1,402 @@ +-- Freeciv - Copyright (C) 2011 - The Freeciv Project +-- 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 2, 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. + +-- This file is the Freeciv server`s interface to the database backend +-- when authentication is enabled. See doc/README.fcdb. + +local dbh = nil + +-- Machinery for debug logging of options +local seen_options +local function options_init() + seen_options = {} +end +local function option_log(name, val, is_sensitive, source) + if not seen_options[name] then + seen_options[name] = true + if is_sensitive then + log.debug('Database option \'%s\': %s', name, source) + else + log.debug('Database option \'%s\': %s: value \'%s\'', name, source, val) + end + end +end + +-- Get an option from configuration file, falling back to sensible +-- defaults where they exist +local function get_option(name, is_sensitive) + local defaults = { + backend = "sqlite", + table_user = "fcdb_auth", + table_log = "fcdb_log" + } + local val = fcdb.option(name) + if val then + option_log(name, val, is_sensitive, 'read from file') + else + val = defaults[name] + if val then + option_log(name, val, is_sensitive, 'using default') + end + end + if not val then + log.error('Database option \'%s\' not specified in configuration file', + name) + end + return val +end + +-- connect to a MySQL database (or stop with an error) +local function mysql_connect() + local err -- error message + + if dbh then + dbh:close() + end + + local sql = ls_mysql.mysql() + + log.verbose('MySQL database version is %s.', ls_mysql._MYSQLVERSION) + + -- Load the database parameters. + local database = get_option("database") + local user = get_option("user") + local password = get_option("password", true) + local host = get_option("host") + local port = get_option("port") + + dbh, err = sql:connect(database, user, password, host, port) + if not dbh then + log.error('[mysql:connect]: %s', err) + return fcdb.status.ERROR + else + return fcdb.status.TRUE + end +end + +-- open a SQLite database (or stop with an error) +local function sqlite_connect() + local err -- error message + + if dbh then + dbh:close() + end + + local sql = ls_sqlite3.sqlite3() + + -- Load the database parameters. + local database = get_option("database") + + dbh, err = sql:connect(database) + if not dbh then + log.error('[sqlite:connect]: %s', err) + return fcdb.status.ERROR + else + return fcdb.status.TRUE + end +end + +-- execute a sql query +local function execute(query) + local res -- result handle + local err -- error message + + if not dbh then + return fcdb.status.ERROR, "[execute] Invalid database handle." + end + + -- log.verbose("Database query: %s", query) + + res, err = dbh:execute(query) + if not res then + log.error("[luasql:execute]: %s", err) + return fcdb.status.ERROR, err + else + return fcdb.status.TRUE, res + end +end + +-- DIRTY: return a string to put in a database query which gets the +-- current time (in seconds since the epoch, UTC). +-- (This should be replaced with Lua os.time() once the script has access +-- to this, see .) +function sql_time() + local backend = get_option("backend") + if backend == 'mysql' then + return 'UNIX_TIMESTAMP()' + elseif backend == 'sqlite' then + return 'strftime(\'%s\',\'now\')' + else + log.error('Don\'t know how to do timestamps for database backend \'%s\'', backend) + return 'ERROR' + end +end + +-- Set up tables for an SQLite database. +-- (Since there`s no concept of user rights, we can do this directly from Lua, +-- without needing a separate script like MySQL. The server operator can do +-- "/fcdb lua sqlite_createdb()" from the server prompt.) +function sqlite_createdb() + local query + local res + + if get_option("backend") ~= 'sqlite' then + log.error("'backend' in configuration file must be 'sqlite'") + return fcdb.status.ERROR + end + + local table_user = get_option("table_user") + local table_log = get_option("table_log") + + if not dbh then + log.error("Missing database connection...") + return fcdb.status.ERROR + end + + query = string.format([[ +CREATE TABLE %s ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name VARCHAR(48) default NULL UNIQUE, + password VARCHAR(32) default NULL, + email VARCHAR default NULL, + createtime INTEGER default NULL, + accesstime INTEGER default NULL, + address VARCHAR default NULL, + createaddress VARCHAR default NULL, + logincount INTEGER default '0' +); +]], table_user) + status, res = execute(query) + if status == fcdb.status.TRUE then + log.normal("Successfully created user table '%s'", table_user) + else + log.error("Error creating user table '%s'", table_user) + return fcdb.status.ERROR + end + + query = string.format([[ +CREATE TABLE %s ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name VARCHAR(48) default NULL, + logintime INTEGER default NULL, + address VARCHAR default NULL, + succeed TEXT default 'S' +);]], table_log) + status, res = execute(query) + if status == fcdb.status.TRUE then + log.normal("Successfully created log table '%s'", table_log) + else + log.error("Error creating log table '%s'", table_log) + return fcdb.status.ERROR + end + + return fcdb.status.TRUE + +end + +-- ************************************************************************** +-- For MySQL, the following shapes of tables are expected +-- (scripts/setup_auth_server.sh automates this): +-- +-- CREATE TABLE fcdb_auth ( +-- id int(11) NOT NULL auto_increment, +-- name varchar(48) default NULL, +-- password varchar(32) default NULL, +-- email varchar(128) default NULL, +-- createtime int(11) default NULL, +-- accesstime int(11) default NULL, +-- address varchar(255) default NULL, +-- createaddress varchar(255) default NULL, +-- logincount int(11) default '0', +-- PRIMARY KEY (id), +-- UNIQUE KEY name (name) +-- ); +-- +-- CREATE TABLE fcdb_log ( +-- id int(11) NOT NULL auto_increment, +-- name varchar(48) default NULL, +-- logintime int(11) default NULL, +-- address varchar(255) default NULL, +-- succeed enum('S','F') default 'S', +-- PRIMARY KEY (id) +-- ); +-- +-- N.B. if the tables are not of this format, then the select, insert, +-- and update syntax in the following functions must be changed. +-- ************************************************************************** + +-- ************************************************************************** +-- freeciv user auth functions +-- ************************************************************************** + +-- load user data +function user_load(conn) + local status -- return value (status of the request) + local res -- result handle + local row -- one row of the sql result + local query -- sql query + + local fields = 'password' + + local table_user = get_option("table_user") + local table_log = get_option("table_log") + + if not dbh then + log.error("Missing database connection...") + return fcdb.status.ERROR + end + + local username = dbh:escape(auth.get_username(conn)) + local ipaddr = dbh:escape(auth.get_ipaddr(conn)) + + -- get the password for this user + query = string.format([[SELECT %s FROM %s WHERE name = '%s']], + fields, table_user, username) + status, res = execute(query) + if status ~= fcdb.status.TRUE then + return fcdb.status.ERROR + end + + row = res:fetch({}, 'a') + if not row then + -- No match + res:close() + return fcdb.status.FALSE + end + + -- There should be only one result + if res:fetch() then + log.error('[user_load]: multiple entries (%d) for user: %s', + numrows, username) + res:close() + return fcdb.status.FALSE + end + + auth.set_password(conn, row.password) + + res:close() + + return fcdb.status.TRUE +end + +-- save a user to the database +function user_save(conn) + local status -- return value (status of the request) + local res -- result handle + local query -- sql query + + local table_user = get_option("table_user") + + if not dbh then + log.error("Missing database connection...") + return fcdb.status.ERROR + end + + local username = dbh:escape(auth.get_username(conn)) + local password = dbh:escape(auth.get_password(conn)) + local ipaddr = auth.get_ipaddr(conn) + + -- insert the user + --local now = os.time() + query = string.format([[INSERT INTO %s VALUES (NULL, '%s', '%s', + NULL, %s, %s, '%s', '%s', 0)]], + table_user, username, password, + sql_time(), sql_time(), + ipaddr, ipaddr) + status, res = execute(query) + if status ~= fcdb.status.TRUE then + return fcdb.status.ERROR + end + + -- log this session + return user_log(conn, true) +end + +-- log the session +function user_log(conn, success) + local status -- return value (status of the request) + local res -- result handle + local query -- sql query + + if not dbh then + log.error("Missing database connection...") + return fcdb.status.ERROR + end + + local table_user = get_option("table_user") + local table_log = get_option("table_log") + + local username = dbh:escape(auth.get_username(conn)) + local ipaddr = auth.get_ipaddr(conn) + local success_str = success and 'S' or 'F' + + -- update user data + --local now = os.time() + query = string.format([[UPDATE %s SET accesstime = %s, address = '%s', + logincount = logincount + 1 + WHERE name = '%s']], table_user, sql_time(), + ipaddr, username) + status, res = execute(query) + if status ~= fcdb.status.TRUE then + return fcdb.status.ERROR + end + + -- insert the log row for this user + query = string.format([[INSERT INTO %s (name, logintime, address, succeed) + VALUES ('%s', %s, '%s', '%s')]], + table_log, username, sql_time(), ipaddr, success_str) + status, res = execute(query) + if status ~= fcdb.status.TRUE then + return fcdb.status.ERROR + end + + return fcdb.status.TRUE +end + +-- ************************************************************************** +-- freeciv database entry functions +-- ************************************************************************** + +-- test and initialise the database connection +function database_init() + local status -- return value (status of the request) + + options_init() + + local backend = get_option("backend") + if backend == 'mysql' then + log.verbose('Opening MySQL database connection...') + status = mysql_connect() + elseif backend == 'sqlite' then + log.verbose('Opening SQLite database connection...') + status = sqlite_connect() + else + log.error('Database backend \'%s\' not supported by database.lua', backend) + return fcdb.status.ERROR + end + + if status == fcdb.status.TRUE then + log.verbose('Database connection successful.') + end + + return status +end + +-- free the database connection +function database_free() + log.verbose('Closing database connection...') + + if dbh then + dbh:close() + end + + return fcdb.status.TRUE; +end