/* * _build_error: constructs a modret_t filled with error information; * mod_sql_postgres calls this function and returns the resulting mod_ret_t * whenever a call to the database results in an error. */ static modret_t *_build_error(cmd_rec *cmd, db_conn_t *conn) { if (!conn) return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, PQerrorMessage(conn->postgres)); }
/* * 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_checkauth: some backend databases may provide backend-specific * methods to check passwords. This function takes a cleartext password * and a hashed password and checks to see if they are the same. * * Inputs: * cmd->argv[0]: connection name * cmd->argv[1]: cleartext string * cmd->argv[2]: hashed string * * Returns: * PR_HANDLED(cmd) -- passwords match * PR_ERROR_INT(cmd, PR_AUTH_NOPWD) -- missing password * PR_ERROR_INT(cmd, PR_AUTH_BADPWD) -- passwords don't match * PR_ERROR_INT(cmd, PR_AUTH_DISABLEPWD) -- password is disabled * PR_ERROR_INT(cmd, PR_AUTH_AGEPWD) -- password is aged * PR_ERROR(cmd) -- unknown error * * Notes: * If this backend does not provide this functionality, this cmd *must* * return ERROR. */ MODRET cmd_checkauth(cmd_rec * cmd) { conn_entry_t *entry = NULL; db_conn_t *conn = NULL; sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_checkauth"); _sql_check_cmd(cmd, "cmd_checkauth"); if (cmd->argc != 3) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_checkauth"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); } /* get the named connection -- not used in this case, but for consistency */ entry = _sql_get_connection(cmd->argv[0]); if (!entry) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_checkauth"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "unknown named connection"); } conn = (db_conn_t *) entry->data; sql_log(DEBUG_WARN, MOD_SQL_POSTGRES_VERSION ": Postgres does not support the 'Backend' SQLAuthType"); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_checkauth"); /* PostgreSQL doesn't provide this functionality */ return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "Postgres does not support the 'Backend' SQLAuthType"); }
/* * cmd_close: attempts to close the named connection. * * Inputs: * cmd->argv[0]: connection name * Optional: * cmd->argv[1]: close immediately * * Returns: * either a properly filled error modret_t if a connection could not be * closed, or a simple non-error modret_t. For the case of mod_sql_postgres, * there are no error codes returned by the close call; other backends * may be able to return a useful error message. * * 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 should not need to be re-opened for counts > 1. * * If argv[1] exists and is not NULL, the connection should be immediately * closed and the connection count should be reset to 0. */ MODRET cmd_close(cmd_rec *cmd) { conn_entry_t *entry = NULL; db_conn_t *conn = NULL; sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_close"); _sql_check_cmd(cmd, "cmd_close"); if ((cmd->argc < 1) || (cmd->argc > 2)) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_close"); 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_close"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "unknown named connection"); } conn = (db_conn_t *) entry->data; /* if we're closed already (connections == 0) return HANDLED */ if (entry->connections == 0) { sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_close"); return PR_HANDLED(cmd); } /* decrement connections. If our count is 0 or we received a second arg * close the connection, explicitly set the counter to 0, and remove any * timers. */ if (((--entry->connections) == 0 ) || ((cmd->argc == 2) && (cmd->argv[1]))) { PQfinish(conn->postgres); conn->postgres = NULL; entry->connections = 0; if (entry->timer) { pr_timer_remove(entry->timer, &sql_postgres_module); entry->timer = 0; sql_log(DEBUG_INFO, "connection '%s' - timer stopped", entry->name); } sql_log(DEBUG_INFO, "connection '%s' closed", entry->name); } sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_close"); return PR_HANDLED(cmd); }
/* * _build_data: both cmd_select and cmd_procedure potentially * return data to mod_sql; this function builds a modret to return * that data. This is Postgres specific; other backends may choose * to do things differently. */ static modret_t *_build_data(cmd_rec *cmd, db_conn_t *conn) { PGresult *result = NULL; sql_data_t *sd = NULL; char **data = NULL; int index = 0; int field = 0; int row =0; if (!conn) return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); result = conn->result; sd = (sql_data_t *) pcalloc(cmd->tmp_pool, sizeof(sql_data_t)); sd->rnum = (unsigned long) PQntuples(result); sd->fnum = (unsigned long) PQnfields(result); data = (char **) pcalloc(cmd->tmp_pool, sizeof(char *) * ((sd->rnum * sd->fnum) + 1)); for (row = 0; row < sd->rnum; row++) { for (field = 0; field < sd->fnum; field++) { data[index++] = pstrdup(cmd->tmp_pool, PQgetvalue(result, row, field)); } } data[index] = NULL; sd->data = data; return mod_create_data( cmd, (void *) sd ); }
/* * cmd_procedure: executes a stored procedure. * * Inputs: * cmd->argv[0]: connection name * cmd->argv[1]: procedure name * cmd->argv[2]: procedure string * * Returns: * either a properly filled error modret_t if the procedure failed in * some way, or a modret_t with the result data. If a procedure * returns data, it should be returned in the same way as cmd_select. * * Notes: * not every backend will support stored procedures. Backends which do * not support stored procedures should return an error with a descriptive * error message (something like 'backend does not support procedures'). */ MODRET cmd_procedure(cmd_rec *cmd) { sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_procedure"); _sql_check_cmd(cmd, "cmd_procedure"); if (cmd->argc != 3) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_procedure"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); } /* PostgreSQL supports procedures, but the backend doesn't. */ sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_procedure"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "backend does not support procedures"); }
/* * 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; char *unescaped = NULL; char *escaped = NULL; 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; /* Note: the PQescapeString() function appeared in the C API as of * Postgres-7.2. */ unescaped = cmd->argv[1]; escaped = (char *) pcalloc(cmd->tmp_pool, sizeof(char) * (strlen(unescaped) * 2) + 1); PQescapeString(escaped, unescaped, strlen(unescaped)); 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_defineconnection: takes all information about a database * connection and stores it for later use. * * Inputs: * cmd->argv[0]: connection name * cmd->argv[1]: username portion of the SQLConnectInfo directive * cmd->argv[2]: password portion of the SQLConnectInfo directive * cmd->argv[3]: info portion of the SQLConnectInfo directive * Optional: * cmd->argv[4]: time-to-live in seconds * * Returns: * either a properly filled error modret_t if the connection could not * defined, or a simple non-error modret_t. * * Notes: * time-to-live is the length of time to allow a connection to remain unused; * once that amount of time has passed, a connection should be closed and * it's connection count should be reduced to 0. If ttl is 0, or ttl is not * a number or ttl is negative, the connection will be assumed to have no * associated timer. */ MODRET cmd_defineconnection(cmd_rec *cmd) { char *info = NULL; char *name = NULL; char *db = NULL; char *host = NULL; char *port = NULL; char *havehost = NULL; char *haveport = NULL; char *connectstring = NULL; conn_entry_t *entry = NULL; db_conn_t *conn = NULL; sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_defineconnection"); _sql_check_cmd(cmd, "cmd_defineconnection"); if ((cmd->argc < 4) || (cmd->argc > 5) || (!cmd->argv[0])) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_defineconnection"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); } if (!conn_pool) { pr_log_pri(PR_LOG_WARNING, "warning: the mod_sql_postgres module has not " "been properly initialized. Please make sure your --with-modules " "configure option lists mod_sql *before* mod_sql_postgres, and " "recompile."); sql_log(DEBUG_FUNC, "%s", "The mod_sql_postgres module has not been " "properly initialized. Please make sure your --with-modules configure " "option lists mod_sql *before* mod_sql_postgres, and recompile."); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_defineconnection"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "uninitialized module"); } conn = (db_conn_t *) pcalloc(conn_pool, sizeof(db_conn_t)); name = pstrdup(conn_pool, cmd->argv[0]); conn->user = pstrdup(conn_pool, cmd->argv[1]); conn->pass = pstrdup(conn_pool, cmd->argv[2]); info = cmd->argv[3]; db = pstrdup(cmd->tmp_pool, info); havehost = strchr(db, '@'); haveport = strchr(db, ':'); /* * if haveport, parse it, otherwise default it. * if haveport, set it to '\0' * * if havehost, parse it, otherwise default it. * if havehost, set it to '\0' */ if (haveport) { port = haveport + 1; *haveport = '\0'; } else { port = _POSTGRES_PORT; } if (havehost) { host = havehost + 1; *havehost = '\0'; } else { host = "localhost"; } conn->host = pstrdup(conn_pool, host); conn->db = pstrdup(conn_pool, db); conn->port = pstrdup(conn_pool, port); /* setup the connect string the way postgres likes it */ connectstring = pstrcat(cmd->tmp_pool, "host='", conn->host, "' port='", conn->port,"' dbname='", conn->db, "' user='******' password='******'", NULL); conn->connectstring = pstrdup(conn_pool, connectstring); /* insert the new conn_info into the connection hash */ if (!(entry = _sql_add_connection(conn_pool, name, (void *) conn))) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_defineconnection"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "named connection already exists"); } if (cmd->argc == 5) { entry->ttl = (int) strtol(cmd->argv[4], (char **) NULL, 10); if (entry->ttl >= 1) { pr_sql_conn_policy = SQL_CONN_POLICY_TIMER; } else { entry->ttl = 0; } } entry->timer = 0; entry->connections = 0; sql_log(DEBUG_INFO, " name: '%s'", entry->name); sql_log(DEBUG_INFO, " user: '******'", conn->user); sql_log(DEBUG_INFO, " host: '%s'", conn->host); sql_log(DEBUG_INFO, " db: '%s'", conn->db); sql_log(DEBUG_INFO, " port: '%s'", conn->port); sql_log(DEBUG_INFO, " ttl: '%d'", entry->ttl); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_defineconnection"); return PR_HANDLED(cmd); }
/* * 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_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 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) && (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); } /* 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 PR_HANDLED(cmd); }
MODRET wrap_handle_request(cmd_rec *cmd) { /* these variables are names expected to be set by the TCP wrapper code */ struct request_info request; char *user = NULL; config_rec *conf = NULL, *access_conf = NULL, *syslog_conf = NULL; hosts_allow_table = NULL; hosts_deny_table = NULL; /* hide passwords */ session.hide_password = TRUE; /* Sneaky...found in mod_auth.c's cmd_pass() function. Need to find the * login UID in order to resolve the possibly-login-dependent filename. */ user = (char *) get_param_ptr(cmd->server->conf, C_USER, FALSE); /* It's possible that a PASS command came before USER. This is a PRE_CMD * handler, so it won't be protected from this case; we'll need to do * it manually. */ if (!user) return PR_DECLINED(cmd); /* Use mod_auth's _auth_resolve_user() [imported for use here] to get the * right configuration set, since the user may be logging in anonymously, * and the session struct hasn't yet been set for that yet (thus short- * circuiting the easiest way to get the right context...the macros. */ conf = wrap_resolve_user(cmd->pool, &user); /* Search first for user-specific access files. Multiple TCPUserAccessFiles * directives are allowed. */ if ((access_conf = find_config(conf ? conf->subset : CURRENT_CONF, CONF_PARAM, "TCPUserAccessFiles", FALSE)) != NULL) { int matched = FALSE; array_header *user_array = NULL; while (access_conf) { user_array = make_array(cmd->tmp_pool, 0, sizeof(char *)); *((char **) push_array(user_array)) = pstrdup(cmd->tmp_pool, user); /* Check the user expression -- don't forget the offset, to skip * the access file name strings in argv */ if (wrap_eval_expression(((char **) access_conf->argv) + 2, user_array)) { pr_log_debug(DEBUG4, MOD_WRAP_VERSION ": matched TCPUserAccessFiles expression"); matched = TRUE; break; } access_conf = find_config_next(access_conf, access_conf->next, CONF_PARAM, "TCPUserAccessFiles", FALSE); } if (!matched) access_conf = NULL; } /* Next, search for group-specific access files. Multiple * TCPGroupAccessFiles directives are allowed. */ if (!access_conf && (access_conf = find_config(conf ? conf->subset : CURRENT_CONF, CONF_PARAM, "TCPGroupAccessFiles", FALSE)) != NULL) { unsigned char matched = FALSE; /* NOTE: this gid_array is only necessary until Bug#1461 is fixed */ array_header *gid_array = make_array(cmd->pool, 0, sizeof(gid_t)); array_header *group_array = make_array(cmd->pool, 0, sizeof(char *)); while (access_conf) { if (pr_auth_getgroups(cmd->pool, user, &gid_array, &group_array) < 1) { pr_log_debug(DEBUG3, MOD_WRAP_VERSION ": no supplemental groups found for user '%s'", user); } else { /* Check the group expression -- don't forget the offset, to skip * the access file names strings in argv */ if (wrap_eval_expression(((char **) access_conf->argv) + 2, group_array)) { pr_log_debug(DEBUG4, MOD_WRAP_VERSION ": matched TCPGroupAccessFiles expression"); matched = TRUE; break; } } access_conf = find_config_next(access_conf, access_conf->next, CONF_PARAM, "TCPGroupAccessFiles", FALSE); } if (!matched) access_conf = NULL; } /* Finally for globally-applicable access files. Only one such directive * is allowed. */ if (!access_conf) { access_conf = find_config(conf ? conf->subset : CURRENT_CONF, CONF_PARAM, "TCPAccessFiles", FALSE); } if (access_conf) { hosts_allow_table = (char *) access_conf->argv[0]; hosts_deny_table = (char *) access_conf->argv[1]; } /* Now, check the retrieved filename, and see if it requires a login-time * file. */ if (hosts_allow_table != NULL && hosts_allow_table[0] == '~' && hosts_allow_table[1] == '/') { char *allow_real_table = NULL; allow_real_table = wrap_get_user_table(cmd, user, hosts_allow_table); if (!wrap_is_usable_file(allow_real_table)) { pr_log_pri(PR_LOG_WARNING, MOD_WRAP_VERSION ": configured TCPAllowFile %s is unusable", hosts_allow_table); hosts_allow_table = NULL; } else hosts_allow_table = allow_real_table; } if (hosts_deny_table != NULL && hosts_deny_table[0] == '~' && hosts_deny_table[1] == '/') { char *deny_real_table = NULL; deny_real_table = dir_realpath(cmd->pool, hosts_deny_table); if (!wrap_is_usable_file(deny_real_table)) { pr_log_pri(PR_LOG_WARNING, MOD_WRAP_VERSION ": configured TCPDenyFile %s is unusable", hosts_deny_table); hosts_deny_table = NULL; } else hosts_deny_table = deny_real_table; } /* Make sure that _both_ allow and deny TCPAccessFiles are present. * If not, log the missing file, and by default allow request to succeed. */ if (hosts_allow_table != NULL && hosts_deny_table != NULL) { /* Most common case...nothing more necessary */ } else if (hosts_allow_table == NULL && hosts_deny_table != NULL) { /* Log the missing file */ pr_log_pri(PR_LOG_INFO, MOD_WRAP_VERSION ": no usable allow access file -- " "allowing connection"); return PR_DECLINED(cmd); } else if (hosts_allow_table != NULL && hosts_deny_table == NULL) { /* log the missing file */ pr_log_pri(PR_LOG_INFO, MOD_WRAP_VERSION ": no usable deny access file -- " "allowing connection"); return PR_DECLINED(cmd); } else { /* Neither set -- assume the admin hasn't configured these directives * at all. */ return PR_DECLINED(cmd); } /* Log the names of the allow/deny files being used. */ pr_log_pri(PR_LOG_DEBUG, MOD_WRAP_VERSION ": using access files: %s, %s", hosts_allow_table, hosts_deny_table); /* retrieve the user-defined syslog priorities, if any. Fall back to the * defaults as seen in tcpd.h if not defined. */ syslog_conf = find_config(main_server->conf, CONF_PARAM, "TCPAccessSyslogLevels", FALSE); if (syslog_conf) { allow_severity = (int) syslog_conf->argv[1]; deny_severity = (int) syslog_conf->argv[2]; } else { allow_severity = PR_LOG_INFO; deny_severity = PR_LOG_WARNING; } pr_log_debug(DEBUG4, MOD_WRAP_VERSION ": checking under service name '%s'", wrap_service_name); request_init(&request, RQ_DAEMON, wrap_service_name, RQ_FILE, session.c->rfd, 0); fromhost(&request); if (STR_EQ(eval_hostname(request.client), paranoid) || !hosts_access(&request)) { char *denymsg = NULL; /* log the denied connection */ wrap_log_request_denied(deny_severity, &request); /* check for AccessDenyMsg */ if ((denymsg = (char *) get_param_ptr(TOPLEVEL_CONF, "AccessDenyMsg", FALSE)) != NULL) denymsg = sreplace(cmd->tmp_pool, denymsg, "%u", user, NULL); if (denymsg) return PR_ERROR_MSG(cmd, R_530, denymsg); else return PR_ERROR_MSG(cmd, R_530, "Access denied."); } /* If request is allowable, return DECLINED (for engine to act as if this * handler was never called, else ERROR (for engine to abort processing and * deny request. */ wrap_log_request_allowed(allow_severity, &request); return PR_DECLINED(cmd); }
MODRET add_tcpuseraccessfiles(cmd_rec *cmd) { int user_argc = 1; char **user_argv = NULL; array_header *user_acl = NULL; config_rec *c = NULL; /* assume use of the standard TCP wrappers installation locations */ char *allow_filename = NULL, *deny_filename = NULL; CHECK_ARGS(cmd, 3); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); /* use the user-given files, checking to make sure that they exist and * are readable. */ allow_filename = cmd->argv[2]; deny_filename = cmd->argv[3]; /* if the filenames begin with a '~', AND this is not immediately followed * by a '/' (ie '~/'), expand it out for checking and storing for later * lookups. If the filenames DO begin with '~/', do the expansion later, * after authenication. In other words, do checking of static filenames * now, and checking of dynamic (user-authentication-based) filenames * later. */ if (allow_filename[0] == '/') { /* it's an absolute path, so the filename will be checked as is */ if (!wrap_is_usable_file(allow_filename)) return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", allow_filename, "' must be a usable file", NULL)); } else if (allow_filename[0] == '~' && allow_filename[1] != '/') { char *allow_real_file = NULL; allow_real_file = dir_realpath(cmd->pool, allow_filename); if (allow_real_file == NULL || !wrap_is_usable_file(allow_real_file)) return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", allow_filename, "' must be a usable file", NULL)); allow_filename = allow_real_file; } else if (allow_filename[0] != '~' && allow_filename[0] != '/') { /* no relative paths allowed */ return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", allow_filename, "' must start with \"/\" or \"~\"", NULL)); } else { /* it's a determine-at-login-time filename -- check it later */ ; } if (deny_filename[0] == '/') { /* it's an absolute path, so the filename will be checked as is */ if (!wrap_is_usable_file(deny_filename)) return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", deny_filename, "' must be a usable file", NULL)); } else if (deny_filename[0] == '~' && deny_filename[1] != '/') { char *deny_real_file = NULL; deny_real_file = dir_realpath(cmd->pool, deny_filename); if (deny_real_file == NULL || !wrap_is_usable_file(deny_real_file)) return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", deny_filename, "' must be a usable file", NULL)); deny_filename = deny_real_file; } else if (deny_filename[0] != '~' && deny_filename[0] != '/') { /* no relative paths allowed */ return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", deny_filename, "' must start with \"/\" or \"~\"", NULL)); } else { /* it's a determine-at-login-time filename -- check it later */ ; } c = add_config_param_str(cmd->argv[0], 0); user_acl = pr_expr_create(cmd->tmp_pool, &user_argc, &cmd->argv[0]); /* build the desired config_rec manually */ c->argc = user_argc + 2; c->argv = pcalloc(c->pool, (user_argc + 3) * sizeof(char *)); user_argv = (char **) c->argv; /* the access files are the first two arguments */ *user_argv++ = pstrdup(c->pool, allow_filename); *user_argv++ = pstrdup(c->pool, deny_filename); /* and the user names follow */ if (user_argc && user_acl) while (user_argc--) { *user_argv++ = pstrdup(c->pool, *((char **) user_acl->elts)); user_acl->elts = ((char **) user_acl->elts) + 1; } /* don't forget to NULL-terminate */ *user_argv = NULL; c->flags |= CF_MERGEDOWN; /* done */ return PR_HANDLED(cmd); }
MODRET add_tcpaccessfiles(cmd_rec *cmd) { config_rec *c = NULL; /* assume use of the standard TCP wrappers installation locations */ char *allow_filename = "/etc/hosts.allow"; char *deny_filename = "/etc/hosts.deny"; CHECK_ARGS(cmd, 2); CHECK_CONF(cmd, CONF_ROOT|CONF_ANON|CONF_VIRTUAL|CONF_GLOBAL); /* use the user-given files, checking to make sure that they exist and * are readable. */ allow_filename = cmd->argv[1]; deny_filename = cmd->argv[2]; /* if the filenames begin with a '~', AND this is not immediately followed * by a '/' (ie '~/'), expand it out for checking and storing for later * lookups. If the filenames DO begin with '~/', do the expansion later, * after authenication. In other words, do checking of static filenames * now, and checking of dynamic (user-authentication-based) filenames * later. */ if (allow_filename[0] == '/') { /* it's an absolute path, so the filename will be checked as is */ if (!wrap_is_usable_file(allow_filename)) return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", allow_filename, "' must be a usable file", NULL)); } else if (allow_filename[0] == '~' && allow_filename[1] != '/') { char *allow_real_file = NULL; allow_real_file = dir_realpath(cmd->pool, allow_filename); if (allow_real_file == NULL || !wrap_is_usable_file(allow_real_file)) return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", allow_filename, "' must be a usable file", NULL)); allow_filename = allow_real_file; } else if (allow_filename[0] != '~' && allow_filename[0] != '/') { /* no relative paths allowed */ return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", allow_filename, "' must start with \"/\" or \"~\"", NULL)); } else { /* it's a determine-at-login-time filename -- check it later */ ; } if (deny_filename[0] == '/') { /* it's an absolute path, so the filename will be checked as is */ if (!wrap_is_usable_file(deny_filename)) return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", deny_filename, "' must be a usable file", NULL)); } else if (deny_filename[0] == '~' && deny_filename[1] != '/') { char *deny_real_file = NULL; deny_real_file = dir_realpath(cmd->pool, deny_filename); if (deny_real_file == NULL || !wrap_is_usable_file(deny_real_file)) return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", deny_filename, "' must be a usable file", NULL)); deny_filename = deny_real_file; } else if (deny_filename[0] != '~' && deny_filename[0] != '/') { /* no relative paths allowed */ return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0], ": '", deny_filename, "' must start with \"/\" or \"~\"", NULL)); } else { /* it's a determine-at-login-time filename -- check it later */ ; } c = add_config_param_str(cmd->argv[0], 2, (void *) allow_filename, (void *) deny_filename); c->flags |= CF_MERGEDOWN; /* done */ return PR_HANDLED(cmd); }
static modret_t *dispatch_auth(cmd_rec *cmd, char *match, module **m) { authtable *start_tab = NULL, *iter_tab = NULL; modret_t *mr = NULL; start_tab = pr_stash_get_symbol(PR_SYM_AUTH, match, NULL, &cmd->stash_index); if (start_tab == NULL) { int xerrno = errno; pr_trace_msg(trace_channel, 1, "error finding start symbol for '%s': %s", match, strerror(xerrno)); return PR_ERROR_MSG(cmd, NULL, strerror(xerrno)); } iter_tab = start_tab; while (iter_tab) { pr_signals_handle(); if (m && *m && *m != iter_tab->m) { goto next; } pr_trace_msg(trace_channel, 6, "dispatching auth request \"%s\" to module mod_%s", match, iter_tab->m->name); mr = pr_module_call(iter_tab->m, iter_tab->handler, cmd); /* Return a pointer, if requested, to the module which answered the * auth request. This is used, for example, by auth_getpwnam() for * associating the answering auth module with the data looked up. */ if (iter_tab->auth_flags & PR_AUTH_FL_REQUIRED) { pr_trace_msg(trace_channel, 6, "\"%s\" response from module mod_%s is authoritative", match, iter_tab->m->name); if (m) { *m = iter_tab->m; } break; } if (MODRET_ISHANDLED(mr) || MODRET_ISERROR(mr)) { if (m) { *m = iter_tab->m; } break; } next: iter_tab = pr_stash_get_symbol(PR_SYM_AUTH, match, iter_tab, &cmd->stash_index); if (iter_tab == start_tab) { /* We have looped back to the start. Break out now and do not loop * around again (and again, and again...) */ pr_trace_msg(trace_channel, 15, "reached end of symbols for '%s'", match); mr = PR_DECLINED(cmd); break; } } return mr; }