/* * cmd_escapestring: certain strings sent to a database should be properly * escaped -- for instance, quotes need to be escaped to insure that * a query string is properly formatted. cmd_escapestring does whatever * is necessary to escape the special characters in a string. * * Inputs: * cmd->argv[0]: connection name * cmd->argv[1]: string to escape * * Returns: * this command CANNOT fail. The return string is null-terminated and * stored in the data field of the modret_t structure. * * Notes: * Different languages may escape different characters in different ways. * A backend should handle this correctly, where possible. If there is * no client library function to do the string conversion, it is strongly * recommended that the backend module writer do whatever is necessry (read * the database documentation and figure it out) to do the conversion * themselves in this function. * * A backend MUST supply a working escapestring implementation. Simply * copying the data from argv[0] into the data field of the modret allows * for possible SQL injection attacks when this backend is used. */ MODRET cmd_escapestring(cmd_rec * cmd) { conn_entry_t *entry = NULL; db_conn_t *conn = NULL; modret_t *cmr = NULL; char *unescaped = NULL; char *escaped = NULL; cmd_rec *close_cmd; size_t unescaped_len = 0; #ifdef HAVE_POSTGRES_PQESCAPESTRINGCONN int pgerr = 0; #endif sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_escapestring"); _sql_check_cmd(cmd, "cmd_escapestring"); if (cmd->argc != 2) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_escapestring"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); } /* get the named connection */ entry = _sql_get_connection(cmd->argv[0]); if (!entry) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_escapestring"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "unknown named connection"); } conn = (db_conn_t *) entry->data; /* Make sure the connection is open. */ cmr = cmd_open(cmd); if (MODRET_ERROR(cmr)) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_escapestring"); return cmr; } unescaped = cmd->argv[1]; unescaped_len = strlen(unescaped); escaped = (char *) pcalloc(cmd->tmp_pool, sizeof(char) * (unescaped_len * 2) + 1); #ifdef HAVE_POSTGRES_PQESCAPESTRINGCONN PQescapeStringConn(conn->postgres, escaped, unescaped, unescaped_len, &pgerr); if (pgerr != 0) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_escapestring"); return _build_error(cmd, conn); } #else PQescapeString(escaped, unescaped, unescaped_len); #endif close_cmd = _sql_make_cmd(cmd->tmp_pool, 1, entry->name); cmd_close(close_cmd); SQL_FREE_CMD(close_cmd); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_escapestring"); return mod_create_data(cmd, (void *) escaped); }
/* * cmd_insert: executes an INSERT query, properly constructing the query * based on the inputs. * * cmd_insert takes either exactly two inputs, or exactly four. If only * two inputs are given, the second is a monolithic query string. See * the examples below. * * Inputs: * cmd->argv[0]: connection name * cmd->argv[1]: table * cmd->argv[2]: field string * cmd->argv[3]: value string * * Returns: * either a properly filled error modret_t if the insert failed, or a * simple non-error modret_t. * * Example: * These are example queries that would be executed for Postgres; other * backends will have different SQL syntax. * * argv[] = "default","log","userid, date, count", "'aah', now(), 2" * query = "INSERT INTO log (userid, date, count) VALUES ('aah', now(), 2)" * * argv[] = "default"," INTO foo VALUES ('do','re','mi','fa')" * query = "INSERT INTO foo VALUES ('do','re','mi','fa')" * * Notes: * none */ MODRET cmd_insert(cmd_rec *cmd) { conn_entry_t *entry = NULL; db_conn_t *conn = NULL; modret_t *cmr = NULL; modret_t *dmr = NULL; char *query = NULL; cmd_rec *close_cmd; sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_insert"); _sql_check_cmd(cmd, "cmd_insert"); if ((cmd->argc != 2) && (cmd->argc != 4)) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_insert"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); } /* get the named connection */ entry = _sql_get_connection(cmd->argv[0]); if (!entry) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_insert"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "unknown named connection"); } conn = (db_conn_t *) entry->data; cmr = cmd_open(cmd); if (MODRET_ERROR(cmr)) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_insert"); return cmr; } /* construct the query string */ if (cmd->argc == 2) { query = pstrcat(cmd->tmp_pool, "INSERT ", cmd->argv[1], NULL); } else { query = pstrcat( cmd->tmp_pool, "INSERT INTO ", cmd->argv[1], " (", cmd->argv[2], ") VALUES (", cmd->argv[3], ")", NULL ); } /* log the query string */ sql_log(DEBUG_INFO, "query \"%s\"", query); /* perform the query. if it doesn't work, log the error, close the * connection then return the error from the query processing. */ if (!(conn->result = PQexec(conn->postgres, query)) || (PQresultStatus(conn->result) != PGRES_COMMAND_OK)) { dmr = _build_error( cmd, conn ); if (conn->result) PQclear(conn->result); close_cmd = _sql_make_cmd( cmd->tmp_pool, 1, entry->name ); cmd_close(close_cmd); SQL_FREE_CMD(close_cmd); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_insert"); return dmr; } PQclear(conn->result); /* close the connection and return HANDLED. */ close_cmd = _sql_make_cmd( cmd->tmp_pool, 1, entry->name ); cmd_close(close_cmd); SQL_FREE_CMD(close_cmd); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_insert"); return PR_HANDLED(cmd); }
/* * cmd_select: executes a SELECT query. properly constructing the query * based on the inputs. See mod_sql.h for the definition of the _sql_data * structure which is used to return the result data. * * cmd_select takes either exactly two inputs, or more than two. If only * two inputs are given, the second is a monolithic query string. See * the examples below. * * Inputs: * cmd->argv[0]: connection name * cmd->argv[1]: table * cmd->argv[2]: select string * Optional: * cmd->argv[3]: where clause * cmd->argv[4]: requested number of return rows (LIMIT) * * etc. : other options, such as "GROUP BY", "ORDER BY", * and "DISTINCT" will start at cmd->arg[5]. All * backends MUST support 'DISTINCT', the other * arguments are optional (but encouraged). * * Returns: * either a properly filled error modret_t if the select failed, or a * modret_t with the result data filled in. * * Example: * These are example queries that would be executed for Postgres; other * backends will have different SQL syntax. * * argv[] = "default","user","userid, count", "userid='aah'","2" * query = "SELECT userid, count FROM user WHERE userid='aah' LIMIT 2" * * argv[] = "default","usr1, usr2","usr1.foo, usr2.bar" * query = "SELECT usr1.foo, usr2.bar FROM usr1, usr2" * * argv[] = "default","usr1","foo",,,"DISTINCT" * query = "SELECT DISTINCT foo FROM usr1" * * argv[] = "default","bar FROM usr1 WHERE tmp=1 ORDER BY bar" * query = "SELECT bar FROM usr1 WHERE tmp=1 ORDER BY bar" * * Notes: * certain selects could return huge amounts of data. do whatever is * possible to minimize the amount of data copying here. */ MODRET cmd_select(cmd_rec *cmd) { conn_entry_t *entry = NULL; db_conn_t *conn = NULL; modret_t *cmr = NULL; modret_t *dmr = NULL; char *query = NULL; int cnt = 0; cmd_rec *close_cmd; sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_select"); _sql_check_cmd(cmd, "cmd_select"); if (cmd->argc < 2) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_select"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); } /* get the named connection */ entry = _sql_get_connection(cmd->argv[0]); if (!entry) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_select"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "unknown named connection"); } conn = (db_conn_t *) entry->data; cmr = cmd_open(cmd); if (MODRET_ERROR(cmr)) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_select"); return cmr; } /* construct the query string */ if (cmd->argc == 2) { query = pstrcat(cmd->tmp_pool, "SELECT ", cmd->argv[1], NULL); } else { query = pstrcat( cmd->tmp_pool, cmd->argv[2], " FROM ", cmd->argv[1], NULL ); if ((cmd->argc > 3) && (cmd->argv[3])) query = pstrcat( cmd->tmp_pool, query, " WHERE ", cmd->argv[3], NULL ); if ((cmd->argc > 4) && (cmd->argv[4])) query = pstrcat( cmd->tmp_pool, query, " LIMIT ", cmd->argv[4], NULL ); if (cmd->argc > 5) { /* handle the optional arguments -- they're rare, so in this case * we'll play with the already constructed query string, but in * general we should probably take optional arguments into account * and put the query string together later once we know what they are. */ for (cnt=5; cnt < cmd->argc; cnt++) { if ((cmd->argv[cnt]) && !strcasecmp("DISTINCT",cmd->argv[cnt])) { query = pstrcat( cmd->tmp_pool, "DISTINCT ", query, NULL); } } } query = pstrcat( cmd->tmp_pool, "SELECT ", query, NULL); } /* log the query string */ sql_log(DEBUG_INFO, "query \"%s\"", query); /* perform the query. if it doesn't work, log the error, close the * connection then return the error from the query processing. */ if (!(conn->result = PQexec(conn->postgres, query)) || (PQresultStatus(conn->result) != PGRES_TUPLES_OK)) { dmr = _build_error( cmd, conn ); if (conn->result) PQclear(conn->result); close_cmd = _sql_make_cmd( cmd->tmp_pool, 1, entry->name ); cmd_close(close_cmd); SQL_FREE_CMD(close_cmd); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_select"); return dmr; } /* get the data. if it doesn't work, log the error, close the * connection then return the error from the data processing. */ dmr = _build_data( cmd, conn ); PQclear(conn->result); if (MODRET_ERROR(dmr)) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_select"); close_cmd = _sql_make_cmd( cmd->tmp_pool, 1, entry->name ); cmd_close(close_cmd); SQL_FREE_CMD(close_cmd); return dmr; } /* close the connection, return the data. */ close_cmd = _sql_make_cmd( cmd->tmp_pool, 1, entry->name ); cmd_close(close_cmd); SQL_FREE_CMD(close_cmd); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_select"); return dmr; }
/* * cmd_open: attempts to open a named connection to the database. * * Inputs: * cmd->argv[0]: connection name * * Returns: * either a properly filled error modret_t if a connection could not be * opened, or a simple non-error modret_t. * * Notes: * mod_sql depends on these semantics -- a backend should not open * a connection unless mod_sql requests it, nor close one unless * mod_sql requests it. Connection counting is *REQUIRED* for complete * compatibility; a connection should not be closed unless the count * reaches 0, and ideally will not need to be re-opened for counts > 1. */ MODRET cmd_open(cmd_rec *cmd) { conn_entry_t *entry = NULL; db_conn_t *conn = NULL; const char *server_version = NULL; sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_open"); _sql_check_cmd(cmd, "cmd_open" ); if (cmd->argc < 1) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); } /* get the named connection */ if (!(entry = _sql_get_connection(cmd->argv[0]))) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "unknown named connection"); } conn = (db_conn_t *) entry->data; /* if we're already open (connections > 0) increment connections * reset our timer if we have one, and return HANDLED */ if (entry->connections > 0) { if (PQstatus(conn->postgres) == CONNECTION_OK) { entry->connections++; if (entry->timer) { pr_timer_reset(entry->timer, &sql_postgres_module); } sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_HANDLED(cmd); } else { char *reason; size_t reason_len; /* Unless we've been told not to reconnect, try to reconnect now. * We only try once; if it fails, we return an error. */ if (!(pr_sql_opts & SQL_OPT_NO_RECONNECT)) { PQreset(conn->postgres); if (PQstatus(conn->postgres) == CONNECTION_OK) { entry->connections++; if (entry->timer) { pr_timer_reset(entry->timer, &sql_postgres_module); } sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_HANDLED(cmd); } } reason = PQerrorMessage(conn->postgres); reason_len = strlen(reason); /* Postgres might give us an empty string as the reason; not helpful. */ if (reason_len == 0) { reason = "(unknown)"; reason_len = strlen(reason); } /* The error message returned by Postgres is usually appended with * a newline. Let's prettify it by removing the newline. Note * that yes, we are overwriting the pointer given to us by Postgres, * but it's OK. The Postgres docs say that we're not supposed to * free the memory associated with the returned string anyway. */ reason = pstrdup(session.pool, reason); if (reason[reason_len-1] == '\n') { reason[reason_len-1] = '\0'; reason_len--; } sql_log(DEBUG_INFO, "lost connection to database: %s", reason); entry->connections = 0; if (entry->timer) { pr_timer_remove(entry->timer, &sql_postgres_module); entry->timer = 0; } sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "lost connection to database"); } } /* make sure we have a new conn struct */ conn->postgres = PQconnectdb(conn->connectstring); if (PQstatus(conn->postgres) == CONNECTION_BAD) { /* if it didn't work, return an error */ sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return _build_error( cmd, conn ); } #if defined(PG_VERSION_STR) sql_log(DEBUG_FUNC, "Postgres client: %s", PG_VERSION_STR); #endif server_version = PQparameterStatus(conn->postgres, "server_version"); if (server_version != NULL) { sql_log(DEBUG_FUNC, "Postgres server version: %s", server_version); } #ifdef PR_USE_NLS if (pr_encode_get_encoding() != NULL) { const char *encoding; encoding = get_postgres_encoding(pr_encode_get_encoding()); /* Configure the connection for the current local character set. */ if (PQsetClientEncoding(conn->postgres, encoding) < 0) { /* if it didn't work, return an error */ sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return _build_error(cmd, conn); } sql_log(DEBUG_FUNC, "Postgres connection character set now '%s' " "(from '%s')", pg_encoding_to_char(PQclientEncoding(conn->postgres)), pr_encode_get_encoding()); } #endif /* !PR_USE_NLS */ /* bump connections */ entry->connections++; if (pr_sql_conn_policy == SQL_CONN_POLICY_PERSESSION) { /* If the connection policy is PERSESSION... */ if (entry->connections == 1) { /* ...and we are actually opening the first connection to the database; * we want to make sure this connection stays open, after this first use * (as per Bug#3290). To do this, we re-bump the connection count. */ entry->connections++; } } else if (entry->ttl > 0) { /* Set up our timer if necessary */ entry->timer = pr_timer_add(entry->ttl, -1, &sql_postgres_module, sql_timer_cb, "postgres connection ttl"); sql_log(DEBUG_INFO, "connection '%s' - %d second timer started", entry->name, entry->ttl); /* Timed connections get re-bumped so they don't go away when cmd_close * is called. */ entry->connections++; } /* return HANDLED */ sql_log(DEBUG_INFO, "connection '%s' opened", entry->name); sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_HANDLED(cmd); }
/* * cmd_query: executes a freeform query string, with no syntax checking. * * cmd_query takes exactly two inputs, the connection and the query string. * * Inputs: * cmd->argv[0]: connection name * cmd->argv[1]: query string * * Returns: * depending on the query type, returns a modret_t with data, a non-error * modret_t, or a properly filled error modret_t if the query failed. * * Example: * None. The query should be passed directly to the backend database. * * Notes: * None. */ MODRET cmd_query(cmd_rec *cmd) { conn_entry_t *entry = NULL; db_conn_t *conn = NULL; modret_t *cmr = NULL; modret_t *dmr = NULL; char *query = NULL; cmd_rec *close_cmd; sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_query"); _sql_check_cmd(cmd, "cmd_query"); if (cmd->argc != 2) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_query"); return ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); } /* get the named connection */ entry = _sql_get_connection(cmd->argv[0]); if (!entry) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_query"); return ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "unknown named connection"); } conn = (db_conn_t *) entry->data; cmr = cmd_open(cmd); if (MODRET_ERROR(cmr)) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_query"); return cmr; } query = pstrcat(cmd->tmp_pool, cmd->argv[1], NULL); /* log the query string */ sql_log( DEBUG_INFO, "query \"%s\"", query); /* perform the query. if it doesn't work, log the error, close the * connection then return the error from the query processing. */ if (!(conn->result = PQexec(conn->postgres, query)) || ((PQresultStatus(conn->result) != PGRES_TUPLES_OK) && (PQresultStatus(conn->result) != PGRES_COMMAND_OK))) { dmr = _build_error( cmd, conn ); if (conn->result) PQclear(conn->result); close_cmd = _sql_make_cmd( cmd->tmp_pool, 1, entry->name ); cmd_close(close_cmd); SQL_FREE_CMD(close_cmd); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_select"); return dmr; } /* get data if necessary. if it doesn't work, log the error, close the * connection then return the error from the data processing. */ if ( PQresultStatus( conn->result ) == PGRES_TUPLES_OK ) { dmr = _build_data( cmd, conn ); PQclear(conn->result); if (MODRET_ERROR(dmr)) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_query"); } } else { dmr = HANDLED(cmd); } /* close the connection, return the data. */ close_cmd = _sql_make_cmd( cmd->tmp_pool, 1, entry->name ); cmd_close(close_cmd); SQL_FREE_CMD(close_cmd); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_query"); return dmr; }
/* * cmd_open: attempts to open a named connection to the database. * * Inputs: * cmd->argv[0]: connection name * * Returns: * either a properly filled error modret_t if a connection could not be * opened, or a simple non-error modret_t. * * Notes: * mod_sql depends on these semantics -- a backend should not open * a connection unless mod_sql requests it, nor close one unless * mod_sql requests it. Connection counting is *REQUIRED* for complete * compatibility; a connection should not be closed unless the count * reaches 0, and ideally will not need to be re-opened for counts > 1. */ MODRET cmd_open(cmd_rec *cmd) { conn_entry_t *entry = NULL; db_conn_t *conn = NULL; sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_open"); _sql_check_cmd(cmd, "cmd_open" ); if (cmd->argc < 1) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); } /* get the named connection */ if (!(entry = _sql_get_connection(cmd->argv[0]))) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "unknown named connection"); } conn = (db_conn_t *) entry->data; /* if we're already open (connections > 0) increment connections * reset our timer if we have one, and return HANDLED */ if ((entry->connections > 0) && (PQstatus(conn->postgres) == CONNECTION_OK)) { entry->connections++; if (entry->timer) pr_timer_reset(entry->timer, &sql_postgres_module); sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return HANDLED(cmd); } /* make sure we have a new conn struct */ conn->postgres = PQconnectdb(conn->connectstring); if (PQstatus(conn->postgres) == CONNECTION_BAD) { /* if it didn't work, return an error */ sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return _build_error( cmd, conn ); } /* bump connections */ entry->connections++; /* set up our timer if necessary */ if (entry->ttl > 0) { entry->timer = pr_timer_add(entry->ttl, -1, &sql_postgres_module, _sql_timer_callback); sql_log(DEBUG_INFO, "connection '%s' - %d second timer started", entry->name, entry->ttl); /* timed connections get re-bumped so they don't go away when cmd_close * is called. */ entry->connections++; } /* return HANDLED */ sql_log(DEBUG_INFO, "connection '%s' opened", entry->name); sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return HANDLED(cmd); }