/* This file is part of Smap.
Copyright (C) 2006-2021 Sergey Poznyakoff
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, 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 . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MDB_OPEN 0x01
#define MDB_DEFDB 0x02
struct mod_mysql_db {
int flags;
unsigned refcnt;
MYSQL mysql;
const char *name;
char *config_file;
char *config_group;
char *ssl_ca;
char *host;
char *user;
char *password;
char *database;
long port;
char *socket;
char *template;
char *positive_reply;
char *negative_reply;
char *onerror_reply;
};
static size_t dbgid;
static struct mod_mysql_db def_db;
static MYSQL *
moddb_handle(struct mod_mysql_db *db)
{
if (db->flags & MDB_DEFDB)
return &def_db.mysql;
return &db->mysql;
}
static const char *
moddb_positive_reply(struct mod_mysql_db *db)
{
return db->positive_reply ?
db->positive_reply :
(def_db.positive_reply ? def_db.positive_reply : "OK");
}
static const char *
moddb_negative_reply(struct mod_mysql_db *db)
{
return db->negative_reply ?
db->negative_reply :
(def_db.negative_reply ? def_db.negative_reply : "NOTFOUND");
}
static const char *
moddb_onerror_reply(struct mod_mysql_db *db)
{
return db->onerror_reply ?
db->onerror_reply :
(def_db.onerror_reply ? def_db.onerror_reply : "NOTFOUND");
}
static int
opendb(struct mod_mysql_db *db)
{
if (db->flags & MDB_OPEN) {
db->refcnt++;
return 0;
}
if (!mysql_init(&db->mysql)) {
smap_error("%s: not enough memory", db->name);
return 1;
}
if (db->config_file)
mysql_options(&db->mysql, MYSQL_READ_DEFAULT_FILE,
db->config_file);
if (db->config_group)
mysql_options (&db->mysql, MYSQL_READ_DEFAULT_GROUP,
db->config_group);
if (db->ssl_ca)
mysql_ssl_set (&db->mysql, NULL, NULL, db->ssl_ca,
NULL, NULL);
if (!mysql_real_connect(&db->mysql,
db->host,
db->user,
db->password,
db->database,
db->port,
db->socket, CLIENT_MULTI_RESULTS)) {
smap_error("%s: failed to connect to database, error: %s",
db->name, mysql_error(&db->mysql));
mysql_close(&db->mysql);
return 1;
}
db->refcnt++;
db->flags |= MDB_OPEN;
return 0;
}
static void
closedb(struct mod_mysql_db *db)
{
if ((db->flags & MDB_OPEN) && --db->refcnt == 0) {
mysql_close(&db->mysql);
db->flags &= ~MDB_OPEN;
}
}
static void
freedb(struct mod_mysql_db *db)
{
free(db->config_file);
free(db->config_group);
free(db->ssl_ca);
free(db->host);
free(db->user);
free(db->password);
free(db->database);
free(db->socket);
free(db->template);
free(db->positive_reply);
free(db->negative_reply);
free(db->onerror_reply);
}
static int
dbdeclared(struct mod_mysql_db *db)
{
return db->config_file ||
db->config_group ||
db->ssl_ca ||
db->host ||
db->user ||
db->password ||
db->database ||
db->port ||
db->socket;
}
static int
mod_init(int argc, char **argv)
{
int rc;
struct smap_option init_option[] = {
{ SMAP_OPTSTR(config-file), smap_opt_string,
&def_db.config_file },
{ SMAP_OPTSTR(config-group), smap_opt_string,
&def_db.config_group },
{ SMAP_OPTSTR(ssl-ca), smap_opt_string,
&def_db.ssl_ca },
{ SMAP_OPTSTR(host), smap_opt_string,
&def_db.host },
{ SMAP_OPTSTR(user), smap_opt_string,
&def_db.user },
{ SMAP_OPTSTR(password), smap_opt_string,
&def_db.password },
{ SMAP_OPTSTR(database), smap_opt_string,
&def_db.database },
{ SMAP_OPTSTR(port), smap_opt_long,
&def_db.port },
{ SMAP_OPTSTR(socket), smap_opt_string,
&def_db.socket },
{ SMAP_OPTSTR(query), smap_opt_string,
&def_db.template },
{ SMAP_OPTSTR(positive-reply), smap_opt_string,
&def_db.positive_reply },
{ SMAP_OPTSTR(negative-reply), smap_opt_string,
&def_db.negative_reply },
{ SMAP_OPTSTR(onerror-reply), smap_opt_string,
&def_db.onerror_reply },
{ NULL }
};
dbgid = smap_debug_alloc("mysql");
rc = smap_parseopt(init_option, argc, argv, 0, NULL);
if (rc)
return rc;
def_db.flags = 0;
return 0;
}
static smap_database_t
mod_init_db(const char *dbid, int argc, char **argv)
{
struct mod_mysql_db *db;
char *positive_reply = NULL;
char *negative_reply = NULL;
char *onerror_reply = NULL;
char *query = NULL;
char *config_file = NULL;
char *config_group = NULL;
char *ssl_ca = NULL;
char *host = NULL;
char *user = NULL;
char *password = NULL;
char *database = NULL;
long port = 0;
char *socket = NULL;
int flags = 0;
struct smap_option init_option[] = {
{ SMAP_OPTSTR(defaultdb), smap_opt_bitmask,
&flags, { MDB_DEFDB } },
{ SMAP_OPTSTR(config-file), smap_opt_string,
&config_file },
{ SMAP_OPTSTR(config-group), smap_opt_string,
&config_group },
{ SMAP_OPTSTR(ssl-ca), smap_opt_string,
&ssl_ca },
{ SMAP_OPTSTR(host), smap_opt_string,
&host },
{ SMAP_OPTSTR(user), smap_opt_string,
&user },
{ SMAP_OPTSTR(password), smap_opt_string,
&password },
{ SMAP_OPTSTR(database), smap_opt_string,
&database },
{ SMAP_OPTSTR(port), smap_opt_long,
&port },
{ SMAP_OPTSTR(socket), smap_opt_string,
&socket },
{ SMAP_OPTSTR(query), smap_opt_string,
&query },
{ SMAP_OPTSTR(positive-reply), smap_opt_string,
&positive_reply },
{ SMAP_OPTSTR(negative-reply), smap_opt_string,
&negative_reply },
{ SMAP_OPTSTR(onerror-reply), smap_opt_string,
&onerror_reply },
{ NULL }
};
if (smap_parseopt(init_option, argc, argv, 0, NULL))
return NULL;
db = calloc(1, sizeof(*db));
if (!db) {
smap_error("%s: not enough memory", dbid);
return NULL;
}
db->flags = flags;
db->name = dbid;
db->config_file = config_file;
db->config_group = config_group;
db->ssl_ca = ssl_ca;
db->host = host;
db->user = user;
db->password = password;
db->database = database;
db->port = port;
db->socket = socket;
db->template = query;
db->positive_reply = positive_reply;
db->negative_reply = negative_reply;
db->onerror_reply = onerror_reply;
if (!dbdeclared(db))
db->flags |= MDB_DEFDB;
return (smap_database_t) db;
}
static int
mod_free_db(smap_database_t dbp)
{
struct mod_mysql_db *db = (struct mod_mysql_db *)dbp;
freedb(db);
free(db);
return 0;
}
static int
mod_open(smap_database_t dbp)
{
struct mod_mysql_db *db = (struct mod_mysql_db *)dbp;
if (db->flags & MDB_DEFDB)
return opendb(&def_db);
return opendb(db);
}
static int
mod_close(smap_database_t dbp)
{
struct mod_mysql_db *db = (struct mod_mysql_db *)dbp;
if (db->flags & MDB_DEFDB)
closedb(&def_db);
closedb(db);
return 0;
}
static char *
format_envar(const char *var, const char *val)
{
char *p = malloc(strlen(var) + strlen(val) + 2);
if (!p) {
smap_error("not enough memory");
return NULL;
}
strcpy(p, var);
strcat(p, "=");
strcat(p, val);
return p;
}
static void
free_env(char **env)
{
int i;
for (i = 0; env[i]; i++)
free(env[i]);
free(env);
}
#define INIT_ENV_SIZE 4
static int
fill_env(MYSQL *mysql, char **vartab, const char **intab, char ***penv)
{
int rc = 0;
int i;
char **env;
char *buf = NULL;
size_t size = 0;
env = calloc(INIT_ENV_SIZE + 1, sizeof(*env));
if (!env) {
smap_error("not enough memory");
return 1;
}
for (i = 0; i < INIT_ENV_SIZE; i++) {
const char *val = intab[i];
if (!val)
continue;
if (mysql) {
size_t ilen = strlen(val);
size_t olen = 2 * ilen + 1;
if (!buf) {
buf = malloc(olen);
if (!buf) {
rc = 1;
break;
}
size = olen;
} else if (olen > size) {
char *np = realloc(buf, olen);
if (!np) {
rc = 1;
break;
}
buf = np;
size = olen;
}
mysql_real_escape_string(mysql, buf, val, ilen);
val = buf;
}
env[i] = format_envar(vartab[i], val);
if (!env[i]) {
rc = 1;
break;
}
}
if (rc) {
free_env(env);
free(buf);
smap_error("not enough memory");
} else
*penv = env;
return rc;
}
static int
create_query_env(struct mod_mysql_db *db,
const char *map, const char *key,
struct smap_conninfo const *conninfo,
char ***penv, char ***pqenv)
{
static char *vartab[] = { "map", "key", "src", "dst" };
static const char *intab[INIT_ENV_SIZE];
struct sockaddr_in *s_in;
intab[0] = map;
intab[1] = key;
if (conninfo && conninfo->src->sa_family == AF_INET) {
s_in = (struct sockaddr_in *)conninfo->src;
intab[2] = inet_ntoa(s_in->sin_addr);
} else
intab[2] = NULL;
if (conninfo && conninfo->dst->sa_family == AF_INET) {
s_in = (struct sockaddr_in *)conninfo->dst;
intab[3] = inet_ntoa(s_in->sin_addr);
} else
intab[3] = NULL;
if (fill_env(NULL, vartab, intab, penv))
return 1;
if (fill_env(moddb_handle(db), vartab, intab, pqenv)) {
free_env(*penv);
return 1;
}
return 0;
}
static int
do_query(struct mod_mysql_db *db, char **env, MYSQL_RES **pres)
{
struct wordsplit ws;
int rc;
MYSQL *mysql = moddb_handle(db);
ws.ws_env = (const char **) env;
ws.ws_error = smap_error;
rc = wordsplit(db->template, &ws,
WRDSF_NOSPLIT |
WRDSF_NOCMD |
WRDSF_ENV |
WRDSF_ERROR |
WRDSF_SHOWERR);
if (rc)
return 1;
smap_debug(dbgid, 1,
("running query: %s", ws.ws_wordv[0]));
rc = mysql_query(mysql, ws.ws_wordv[0]);
if (rc) {
smap_error("%s: query failed: %s",
db->name, mysql_error(&db->mysql));
smap_error("%s: failed query: %s",
db->name, ws.ws_wordv[0]);
}
wordsplit_free(&ws);
if (rc)
return 1;
*pres = mysql_store_result(mysql);
return 0;
}
static int
send_reply(smap_stream_t ostr, const char *template, char **env)
{
struct wordsplit ws;
int rc;
ws.ws_env = (const char **) env;
ws.ws_error = smap_error;
rc = wordsplit(template, &ws,
WRDSF_NOSPLIT |
WRDSF_NOCMD |
WRDSF_ENV |
WRDSF_ERROR |
WRDSF_SHOWERR);
if (rc) {
smap_error("cannot format reply");
wordsplit_free(&ws);
return 1;
}
smap_debug(dbgid, 1, ("reply: %s", ws.ws_wordv[0]));
smap_stream_printf(ostr, "%s\n", ws.ws_wordv[0]);
wordsplit_free(&ws);
return 0;
}
static int
do_positive_reply(struct mod_mysql_db *db,
smap_stream_t ostr,
char ***penv,
MYSQL_RES *result)
{
unsigned nfld;
char **env;
size_t i, j;
MYSQL_FIELD *fields;
MYSQL_ROW row;
row = mysql_fetch_row(result);
nfld = mysql_num_fields(result);
env = *penv;
for (i = 0; env[i]; i++)
;
env = realloc(env, (nfld + i + 1) * sizeof(env[0]));
if (!env) {
smap_error("not enough memory");
return 1;
}
*penv = env;
fields = mysql_fetch_fields(result);
for (j = 0; j < nfld; j++) {
char *p = format_envar(fields[j].name, row[j]);
env[i + j] = p;
if (!p) {
smap_error("not enough memory");
return 1;
}
}
env[i + j] = NULL;
return send_reply(ostr, moddb_positive_reply(db), env);
}
/* Consume additional result sets */
static void
flush_result(struct mod_mysql_db *db)
{
MYSQL *mysql = moddb_handle(db);
while (mysql_next_result(mysql) == 0) {
MYSQL_RES *result = mysql_store_result(mysql);
if (!result)
break;
if (mysql_field_count(mysql))
while (mysql_fetch_row(result))
;
mysql_free_result(result);
}
}
static int
mod_query(smap_database_t dbp,
smap_stream_t ostr,
const char *map, const char *key,
struct smap_conninfo const *conninfo)
{
struct mod_mysql_db *db = (struct mod_mysql_db *)dbp;
MYSQL_RES *res;
int rc;
char **env, **qenv;
if (create_query_env(db, map, key, conninfo, &env, &qenv))
return 1;
rc = do_query(db, qenv, &res);
free_env(qenv);
if (rc) {
rc = send_reply(ostr, moddb_onerror_reply(db), env);
} else if (res) {
unsigned nrow = mysql_num_rows(res);
unsigned ncol = mysql_num_fields(res);
smap_debug(dbgid, 1,
("query returned %u columns in %u rows",
ncol, nrow));
if (nrow > 0)
rc = do_positive_reply(db, ostr, &env, res);
else
rc = send_reply(ostr, moddb_negative_reply(db),
env);
mysql_free_result(res);
flush_result(db);
} else
rc = send_reply(ostr, moddb_negative_reply(db), env);
free_env(env);
return rc;
}
struct smap_module smap_module = {
SMAP_MODULE_VERSION,
SMAP_CAPA_QUERY,
mod_init,
mod_init_db,
mod_free_db,
mod_open,
mod_close,
mod_query,
NULL /* smap_xform */
};