1 -- Freeciv - Copyright (C) 2011 - The Freeciv Project
2 -- This program is free software; you can redistribute it and/or modify
3 -- it under the terms of the GNU General Public License as published by
4 -- the Free Software Foundation; either version 2, or (at your option)
7 -- This program is distributed in the hope that it will be useful,
8 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
9 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 -- GNU General Public License for more details.
12 -- This file is the Freeciv server`s interface to the database backend
13 -- when authentication is enabled. See doc/README.fcdb.
17 -- Machinery for debug logging of options
19 local function options_init()
22 local function option_log(name
, val
, is_sensitive
, source
)
23 if not seen_options
[name
] then
24 seen_options
[name
] = true
26 log.debug('Database option \'%s\': %s', name
, source
)
28 log.debug('Database option \'%s\': %s: value \'%s\'', name
, source
, val
)
33 -- Get an option from configuration file, falling back to sensible
34 -- defaults where they exist
35 local function get_option(name
, is_sensitive
)
38 table_user
= "fcdb_auth",
39 table_log
= "fcdb_log"
41 local val
= fcdb
.option(name
)
43 option_log(name
, val
, is_sensitive
, 'read from file')
47 option_log(name
, val
, is_sensitive
, 'using default')
51 log.error('Database option \'%s\' not specified in configuration file',
57 -- connect to a MySQL database (or stop with an error)
58 local function mysql_connect()
59 local err
-- error message
65 local sql
= luasql
.mysql()
67 log.verbose('MySQL database version is %s.', luasql
._MYSQLVERSION
)
69 -- Load the database parameters.
70 local database
= get_option("database")
71 local user
= get_option("user")
72 local password
= get_option("password", true)
73 local host
= get_option("host")
74 local port
= get_option("port")
76 dbh
, err
= sql
:connect(database
, user
, password
, host
, port
)
78 log.error('[mysql:connect]: %s', err
)
79 return fcdb
.status
.ERROR
81 return fcdb
.status
.TRUE
85 -- open a SQLite database (or stop with an error)
86 local function sqlite_connect()
87 local err
-- error message
93 local sql
= luasql
.sqlite3()
95 -- Load the database parameters.
96 local database
= get_option("database")
98 dbh
, err
= sql
:connect(database
)
100 log.error('[sqlite:connect]: %s', err
)
101 return fcdb
.status
.ERROR
103 return fcdb
.status
.TRUE
107 -- execute a sql query
108 local function execute(query
)
109 local res
-- result handle
110 local err
-- error message
113 return fcdb
.status
.ERROR
, "[execute] Invalid database handle."
116 -- log.verbose("Database query: %s", query)
118 res
, err
= dbh
:execute(query
)
120 log.error("[luasql:execute]: %s", err
)
121 return fcdb
.status
.ERROR
, err
123 return fcdb
.status
.TRUE
, res
127 -- DIRTY: return a string to put in a database query which gets the
128 -- current time (in seconds since the epoch, UTC).
129 -- (This should be replaced with Lua os.time() once the script has access
130 -- to this, see <http://gna.org/bugs/?19729>.)
132 local backend
= get_option("backend")
133 if backend
== 'mysql' then
134 return 'UNIX_TIMESTAMP()'
135 elseif backend
== 'sqlite' then
136 return 'strftime(\'%s\',\'now\')'
138 log.error('Don\'t know how to do timestamps for database backend \'%s\'', backend
)
143 -- Set up tables for an SQLite database.
144 -- (Since there`s no concept of user rights, we can do this directly from Lua,
145 -- without needing a separate script like MySQL. The server operator can do
146 -- "/fcdb lua sqlite_createdb()" from the server prompt.)
147 function sqlite_createdb()
151 if get_option("backend") ~= 'sqlite' then
152 log.error("'backend' in configuration file must be 'sqlite'")
153 return fcdb
.status
.ERROR
156 local table_user
= get_option("table_user")
157 local table_log
= get_option("table_log")
160 log.error("Missing database connection...")
161 return fcdb
.status
.ERROR
164 query
= string.format([[
166 id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
167 name VARCHAR(48) default NULL UNIQUE,
168 password VARCHAR(32) default NULL,
169 email VARCHAR default NULL,
170 createtime INTEGER default NULL,
171 accesstime INTEGER default NULL,
172 address VARCHAR default NULL,
173 createaddress VARCHAR default NULL,
174 logincount INTEGER default '0'
177 status
, res
= execute(query
)
178 if status
== fcdb
.status
.TRUE
then
179 log.normal("Successfully created user table '%s'", table_user
)
181 log.error("Error creating user table '%s'", table_user
)
182 return fcdb
.status
.ERROR
185 query
= string.format([[
187 id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
188 name VARCHAR(48) default NULL,
189 logintime INTEGER default NULL,
190 address VARCHAR default NULL,
191 succeed TEXT default 'S'
193 status
, res
= execute(query
)
194 if status
== fcdb
.status
.TRUE
then
195 log.normal("Successfully created log table '%s'", table_log
)
197 log.error("Error creating log table '%s'", table_log
)
198 return fcdb
.status
.ERROR
201 return fcdb
.status
.TRUE
205 -- **************************************************************************
206 -- For MySQL, the following shapes of tables are expected
207 -- (scripts/setup_auth_server.sh automates this):
209 -- CREATE TABLE fcdb_auth (
210 -- id int(11) NOT NULL auto_increment,
211 -- name varchar(48) default NULL,
212 -- password varchar(32) default NULL,
213 -- email varchar(128) default NULL,
214 -- createtime int(11) default NULL,
215 -- accesstime int(11) default NULL,
216 -- address varchar(255) default NULL,
217 -- createaddress varchar(255) default NULL,
218 -- logincount int(11) default '0',
220 -- UNIQUE KEY name (name)
223 -- CREATE TABLE fcdb_log (
224 -- id int(11) NOT NULL auto_increment,
225 -- name varchar(48) default NULL,
226 -- logintime int(11) default NULL,
227 -- address varchar(255) default NULL,
228 -- succeed enum('S','F') default 'S',
232 -- N.B. if the tables are not of this format, then the select, insert,
233 -- and update syntax in the following functions must be changed.
234 -- **************************************************************************
236 -- **************************************************************************
237 -- freeciv user auth functions
238 -- **************************************************************************
241 function user_load(conn
)
242 local status
-- return value (status of the request)
243 local res
-- result handle
244 local row
-- one row of the sql result
245 local query
-- sql query
247 local fields
= 'password'
249 local table_user
= get_option("table_user")
250 local table_log
= get_option("table_log")
253 log.error("Missing database connection...")
254 return fcdb
.status
.ERROR
257 local username
= dbh
:escape(auth
.get_username(conn
))
258 local ipaddr
= dbh
:escape(auth
.get_ipaddr(conn
))
260 -- get the password for this user
261 query
= string.format([[SELECT %s FROM %s WHERE name = '%s']],
262 fields
, table_user
, username
)
263 status
, res
= execute(query
)
264 if status
~= fcdb
.status
.TRUE
then
265 return fcdb
.status
.ERROR
268 row
= res
:fetch({}, 'a')
272 return fcdb
.status
.FALSE
275 -- There should be only one result
277 log.error('[user_load]: multiple entries (%d) for user: %s',
280 return fcdb
.status
.FALSE
283 auth
.set_password(conn
, row
.password
)
287 return fcdb
.status
.TRUE
290 -- save a user to the database
291 function user_save(conn
)
292 local status
-- return value (status of the request)
293 local res
-- result handle
294 local query
-- sql query
296 local table_user
= get_option("table_user")
299 log.error("Missing database connection...")
300 return fcdb
.status
.ERROR
303 local username
= dbh
:escape(auth
.get_username(conn
))
304 local password
= dbh
:escape(auth
.get_password(conn
))
305 local ipaddr
= auth
.get_ipaddr(conn
)
308 --local now = os.time()
309 query
= string.format([[INSERT INTO %s VALUES (NULL, '%s', '%s',
310 NULL, %s, %s, '%s', '%s', 0)]],
311 table_user
, username
, password
,
312 sql_time(), sql_time(),
314 status
, res
= execute(query
)
315 if status
~= fcdb
.status
.TRUE
then
316 return fcdb
.status
.ERROR
320 return user_log(conn
, true)
324 function user_log(conn
, success
)
325 local status
-- return value (status of the request)
326 local res
-- result handle
327 local query
-- sql query
330 log.error("Missing database connection...")
331 return fcdb
.status
.ERROR
334 local table_user
= get_option("table_user")
335 local table_log
= get_option("table_log")
337 local username
= dbh
:escape(auth
.get_username(conn
))
338 local ipaddr
= auth
.get_ipaddr(conn
)
339 local success_str
= success
and 'S' or 'F'
342 --local now = os.time()
343 query
= string.format([[UPDATE %s SET accesstime = %s, address = '%s',
344 logincount = logincount + 1
345 WHERE name = '%s']], table_user
, sql_time(),
347 status
, res
= execute(query
)
348 if status
~= fcdb
.status
.TRUE
then
349 return fcdb
.status
.ERROR
352 -- insert the log row for this user
353 query
= string.format([[INSERT INTO %s (name, logintime, address, succeed)
354 VALUES ('%s', %s, '%s', '%s')]],
355 table_log
, username
, sql_time(), ipaddr
, success_str
)
356 status
, res
= execute(query
)
357 if status
~= fcdb
.status
.TRUE
then
358 return fcdb
.status
.ERROR
361 return fcdb
.status
.TRUE
364 -- **************************************************************************
365 -- freeciv database entry functions
366 -- **************************************************************************
368 -- test and initialise the database connection
369 function database_init()
370 local status
-- return value (status of the request)
374 local backend
= get_option("backend")
375 if backend
== 'mysql' then
376 log.verbose('Opening MySQL database connection...')
377 status
= mysql_connect()
378 elseif backend
== 'sqlite' then
379 log.verbose('Opening SQLite database connection...')
380 status
= sqlite_connect()
382 log.error('Database backend \'%s\' not supported by database.lua', backend
)
383 return fcdb
.status
.ERROR
386 if status
== fcdb
.status
.TRUE
then
387 log.verbose('Database connection successful.')
393 -- free the database connection
394 function database_free()
395 log.verbose('Closing database connection...')
401 return fcdb
.status
.TRUE
;