/*
* ProFTPD: mod_sql -- SQL frontend
* Copyright (c) 1998-1999 Johnie Ingram.
* Copyright (c) 2001 Andrew Houghton.
* Copyright (c) 2004-2005 TJ Saunders
*
* 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 of the License, 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* As a special exemption, Andrew Houghton and other respective copyright
* holders give permission to link this program with OpenSSL, and distribute
* the resulting executable, without including the source code for OpenSSL in
* the source distribution.
*
* $Id: mod_sql.c,v 1.101 2005/12/13 17:54:53 castaglia Exp $
*/
#include "conf.h"
#include "privs.h"
#include "mod_sql.h"
#define MOD_SQL_VERSION "mod_sql/4.2.1"
#if defined(HAVE_CRYPT_H) && !defined(AIX4) && !defined(AIX5)
# include <crypt.h>
#endif
/* Uncomment the following define to allow OpenSSL hashed password checking;
* you'll also need to link with OpenSSL's crypto library ( -lcrypto )
*/
/* #define HAVE_OPENSSL */
#ifdef HAVE_OPENSSL
# include <openssl/evp.h>
#endif
/* default information for tables and fields */
#define MOD_SQL_DEF_USERTABLE "users"
#define MOD_SQL_DEF_USERNAMEFIELD "userid"
#define MOD_SQL_DEF_USERUIDFIELD "uid"
#define MOD_SQL_DEF_USERGIDFIELD "gid"
#define MOD_SQL_DEF_USERPASSWORDFIELD "passwd"
#define MOD_SQL_DEF_USERSHELLFIELD "shell"
#define MOD_SQL_DEF_USERHOMEDIRFIELD "homedir"
#define MOD_SQL_DEF_GROUPTABLE "groups"
#define MOD_SQL_DEF_GROUPNAMEFIELD "groupname"
#define MOD_SQL_DEF_GROUPGIDFIELD "gid"
#define MOD_SQL_DEF_GROUPMEMBERSFIELD "members"
/* default minimum id / default uid / default gid info.
* uids and gids less than MOD_SQL_MIN_USER_UID and
* MOD_SQL_MIN_USER_GID, respectively, get automatically
* mapped to the defaults, below. These can be
* overridden using directives
*/
#define MOD_SQL_MIN_USER_UID 999
#define MOD_SQL_MIN_USER_GID 999
#define MOD_SQL_DEF_UID 65533
#define MOD_SQL_DEF_GID 65533
#define MOD_SQL_BUFSIZE 32
/* Named Query defines */
#define SQL_SELECT_C "SELECT"
#define SQL_INSERT_C "INSERT"
#define SQL_UPDATE_C "UPDATE"
#define SQL_FREEFORM_C "FREEFORM"
/* SQLEngine flags */
#define SQL_ENGINE_FL_AUTH 0x001
#define SQL_ENGINE_FL_LOG 0x002
/* authmask defines */
#define SQL_AUTH_USERS (1<<0)
#define SQL_AUTH_GROUPS (1<<1)
#define SQL_AUTH_USERSET (1<<4)
#define SQL_AUTH_GROUPSET (1<<5)
#define SQL_FAST_USERSET (1<<6)
#define SQL_FAST_GROUPSET (1<<7)
#define SQL_GROUPS (cmap.authmask & SQL_AUTH_GROUPS)
#define SQL_USERS (cmap.authmask & SQL_AUTH_USERS)
#define SQL_GROUPSET (cmap.authmask & SQL_AUTH_GROUPSET)
#define SQL_USERSET (cmap.authmask & SQL_AUTH_USERSET)
#define SQL_FASTGROUPS (cmap.authmask & SQL_FAST_GROUPSET)
#define SQL_FASTUSERS (cmap.authmask & SQL_FAST_USERSET)
/*
* externs, function signatures.. whatever necessary to make
* the compiler happy..
*/
extern pr_response_t *resp_list,*resp_err_list;
module sql_module;
static char *_sql_where(pool *p, int cnt, ...);
MODRET cmd_getgrent(cmd_rec *);
MODRET cmd_setgrent(cmd_rec *);
MODRET sql_lookup(cmd_rec *);
static pool *sql_pool = NULL;
/*
* cache typedefs
*/
#define CACHE_SIZE 13
typedef struct cache_entry {
struct cache_entry *list_next;
struct cache_entry *bucket_next;
void *data;
} cache_entry_t;
/* this struct holds invariant information for the current session */
static struct {
/*
* info valid after getpwnam
*/
char *authuser; /* current authorized user */
struct passwd *authpasswd; /* and their passwd struct */
/*
* generic status information
*/
int engine; /* is mod_sql on? */
int authmask; /* authentication mask.
* see set_sqlauthenticate for info */
/*
* user table and field information
*/
char *usrtable; /* user info table name */
char *usrfield; /* user name field */
char *pwdfield; /* user password field */
char *uidfield; /* user uid field */
char *gidfield; /* user gid field */
char *homedirfield; /* user homedir field */
char *shellfield; /* user login shell field */
char *userwhere; /* users where clause */
char *usercustom; /* custom users query */
/*
* group table and field information
*/
char *grptable; /* group info table name */
char *grpfield; /* group name field */
char *grpgidfield; /* group gid field */
char *grpmembersfield; /* group members field */
char *groupwhere; /* groups where clause */
char *groupcustom; /* custom groups query */
/*
* other information
*/
array_header *authlist; /* auth handler list */
char *defaulthomedir; /* default homedir if no field specified */
int buildhomedir; /* create homedir if it doesn't exist? */
uid_t minid; /* users UID must be this or greater */
uid_t minuseruid; /* users UID must be this or greater */
gid_t minusergid; /* users UID must be this or greater */
uid_t defaultuid; /* default UID if none in database */
gid_t defaultgid; /* default GID if none in database */
cache_entry_t *curr_group; /* next group in group array for getgrent */
cache_entry_t *curr_passwd; /* next passwd in passwd array for getpwent */
int group_cache_filled;
int passwd_cache_filled;
/* Cache negative, as well as positive, lookups */
unsigned char negative_cache;
/*
* mod_ratio data -- someday this needs to be removed from mod_sql
*/
char *sql_fstor; /* fstor int(11) NOT NULL DEFAULT '0', */
char *sql_fretr; /* fretr int(11) NOT NULL DEFAULT '0', */
char *sql_bstor; /* bstor int(11) NOT NULL DEFAULT '0', */
char *sql_bretr; /* bretr int(11) NOT NULL DEFAULT '0', */
char *sql_frate; /* frate int(11) NOT NULL DEFAULT '5', */
char *sql_fcred; /* fcred int(2) NOT NULL DEFAULT '15', */
char *sql_brate; /* brate int(11) NOT NULL DEFAULT '5', */
char *sql_bcred; /* bcred int(2) NOT NULL DEFAULT '150000', */
/*
* precomputed strings
*/
char *usrfields;
char *grpfields;
}
cmap;
struct sql_backend {
struct sql_backend *next, *prev;
const char *backend;
cmdtable *cmdtab;
};
static struct sql_backend *sql_backends = NULL;
static unsigned int sql_nbackends = 0;
static cmdtable *sql_cmdtable = NULL;
/*
* cache functions
*/
typedef unsigned int ( * val_func ) ( const void * );
typedef int ( * cmp_func ) ( const void *, const void * );
typedef struct {
/* memory pool for this object */
pool *pool;
/* cache buckets */
cache_entry_t *buckets[ CACHE_SIZE ];
/* cache functions */
val_func hash_val;
cmp_func cmp;
/* list pointers */
cache_entry_t *head;
/* list size */
unsigned int nelts;
} cache_t;
cache_t *group_name_cache;
cache_t *group_gid_cache;
cache_t *passwd_name_cache;
cache_t *passwd_uid_cache;
static cache_t *make_cache( pool *p, val_func hash_val, cmp_func cmp )
{
cache_t *res;
if ( ( p == NULL ) || ( hash_val == NULL ) ||
( cmp == NULL ) )
return NULL;
res = ( cache_t * ) pcalloc( p, sizeof( cache_t ) );
res->pool = p;
res->hash_val = hash_val;
res->cmp = cmp;
res->head = NULL;
res->nelts = 0;
return res;
}
static cache_entry_t *cache_addentry( cache_t *cache, void *data )
{
cache_entry_t *entry;
int hashval;
if ( ( cache == NULL ) || ( data == NULL ) )
return NULL;
/* create the entry */
entry = ( cache_entry_t * ) pcalloc( cache->pool,
sizeof( cache_entry_t ) );
entry->data = data;
/* deal with the list */
if ( cache->head == NULL ) {
cache->head = entry;
} else {
entry->list_next = cache->head;
cache->head = entry;
}
/* deal with the buckets */
hashval = cache->hash_val( data ) % CACHE_SIZE;
if ( cache->buckets[ hashval ] == NULL ) {
cache->buckets[ hashval ] = entry;
} else {
entry->bucket_next = cache->buckets[ hashval ];
cache->buckets[ hashval ] = entry;
}
cache->nelts++;
return entry;
}
static void *cache_findvalue( cache_t *cache, void *data )
{
cache_entry_t *entry;
int hashval;
if ( ( cache == NULL ) || ( data == NULL ) ) return NULL;
hashval = cache->hash_val( data ) % CACHE_SIZE;
entry = cache->buckets[ hashval ];
while ( entry != NULL ) {
if ( cache->cmp( data, entry->data ) )
break;
else
entry = entry->bucket_next;
}
return ( ( entry == NULL ) ? NULL : entry->data );
}
cmd_rec *_sql_make_cmd(pool *p, int argc, ...) {
register unsigned int i = 0;
pool *newpool = NULL;
cmd_rec *cmd = NULL;
va_list args;
newpool = make_sub_pool(p);
cmd = pcalloc(newpool, sizeof(cmd_rec));
cmd->argc = argc;
cmd->stash_index = -1;
cmd->pool = newpool;
cmd->argv = pcalloc(newpool, sizeof(void *) * (argc + 1));
cmd->tmp_pool = newpool;
cmd->server = main_server;
va_start(args, argc);
for (i = 0; i < argc; i++)
cmd->argv[i] = (void *) va_arg(args, char *);
va_end(args);
cmd->argv[argc] = NULL;
return cmd;
}
static modret_t *_sql_check_response(modret_t *mr) {
if (!MODRET_ISERROR(mr))
return mr;
sql_log(DEBUG_WARN, "%s", "unrecoverable backend error");
sql_log(DEBUG_WARN, "error: '%s'", mr->mr_numeric);
sql_log(DEBUG_WARN, "message: '%s'", mr->mr_message);
end_login(1);
/* make the compiler happy */
return NULL;
}
static modret_t *_sql_dispatch(cmd_rec *cmd, char *cmdname) {
modret_t *mr = NULL;
register unsigned int i = 0;
for (i = 0; sql_cmdtable[i].command; i++) {
if (strcmp(cmdname, sql_cmdtable[i].command) == 0) {
pr_signals_block();
mr = sql_cmdtable[i].handler(cmd);
pr_signals_unblock();
return mr;
}
}
sql_log(DEBUG_WARN, "unknown backend handler '%s'", cmdname);
return ERROR(cmd);
}
static struct sql_backend *sql_get_backend(const char *backend) {
struct sql_backend *sb;
if (!sql_backends)
return NULL;
for (sb = sql_backends; sb; sb = sb->next) {
if (strcasecmp(sb->backend, backend) == 0)
return sb;
}
return NULL;
}
/* This function is used by mod_sql backends, to register their
* individual backend command tables with the main mod_sql module.
*/
int sql_register_backend(const char *backend, cmdtable *cmdtab) {
struct sql_backend *sb;
if (!backend || !cmdtab) {
errno = EINVAL;
return -1;
}
if (!sql_pool) {
sql_pool = make_sub_pool(permanent_pool);
pr_pool_tag(sql_pool, MOD_SQL_VERSION);
}
/* Check to see if this backend has already been registered. */
sb = sql_get_backend(backend);
if (sb) {
errno = EEXIST;
return -1;
}
sb = pcalloc(sql_pool, sizeof(struct sql_backend));
sb->backend = backend;
sb->cmdtab = cmdtab;
if (sql_backends)
sb->next = sql_backends;
else
sb->next = NULL;
sql_backends = sb;
sql_nbackends++;
return 0;
}
/* Used by mod_sql backends to unregister their backend command tables
* from the main mod_sql module.
*/
int sql_unregister_backend(const char *backend) {
struct sql_backend *sb;
if (!backend) {
errno = EINVAL;
return -1;
}
/* Check to see if this backend has been registered. */
sb = sql_get_backend(backend);
if (!sb) {
errno = ENOENT;
return -1;
}
#if !defined(PR_SHARED_MODULE)
/* If there is only one registered backend, it cannot be removed.
*/
if (sql_nbackends == 1) {
errno = EPERM;
return -1;
}
/* Be sure to handle the case where this is the currently active backend. */
if (sql_cmdtable &&
sb->cmdtab == sql_cmdtable) {
errno = EACCES;
return -1;
}
#endif
/* Remove this backend from the linked list. */
if (sb->prev)
sb->prev->next = sb->next;
else
/* This backend is the start of the sql_backends list (prev is NULL),
* so we need to update the list head pointer as well.
*/
sql_backends = sb->next;
if (sb->next)
sb->next->prev = sb->prev;
sb->prev = sb->next = NULL;
sql_nbackends--;
/* NOTE: a counter should be kept of the number of unregistrations,
* as the memory for a registration is not freed on unregistration.
*/
return 0;
}
/*****************************************************************
*
* AUTHENTICATION FUNCTIONS
*
*****************************************************************/
static modret_t *check_auth_crypt(cmd_rec *cmd, const char *c_clear,
const char *c_hash) {
int success = 0;
if (*c_hash == '\0')
return ERROR_INT(cmd, PR_AUTH_BADPWD);
success = !strcmp((char *) crypt(c_clear, c_hash), c_hash);
return success ? HANDLED(cmd) : ERROR_INT(cmd, PR_AUTH_BADPWD);
}
static modret_t *check_auth_plaintext(cmd_rec *cmd, const char *c_clear,
const char *c_hash) {
int success = 0;
if (*c_hash == '\0')
return ERROR_INT(cmd, PR_AUTH_BADPWD);
success = !strcmp(c_clear, c_hash);
return success ? HANDLED(cmd) : ERROR_INT(cmd, PR_AUTH_BADPWD);
}
static modret_t *check_auth_empty(cmd_rec *cmd, const char *c_clear,
const char *c_hash) {
int success = 0;
success = !strcmp(c_hash, "");
return success ? HANDLED(cmd) : ERROR_INT(cmd, PR_AUTH_BADPWD);
}
static modret_t *check_auth_backend(cmd_rec *cmd, const char *c_clear,
const char *c_hash) {
modret_t *mr = NULL;
if (*c_hash == '\0')
return ERROR_INT(cmd, PR_AUTH_BADPWD);
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 3, "default", c_clear,
c_hash), "sql_checkauth");
return mr;
}
#ifdef HAVE_OPENSSL
static modret_t *check_auth_openssl(cmd_rec *cmd, const char *c_clear,
const char *c_hash) {
/*
* c_clear : plaintext password provided by user
* c_hash : combination digest name and hashed
* value, of the form {digest}hash
*/
EVP_MD_CTX md_ctxt;
EVP_ENCODE_CTX base64_ctxt;
const EVP_MD *md;
unsigned char mdval[EVP_MAX_MD_SIZE];
int mdlen, res;
char buf[EVP_MAX_KEY_LENGTH];
char *digestname; /* ptr to name of the digest function */
char *hashvalue; /* ptr to hashed value we're comparing to */
char *copyhash; /* temporary copy of the c_hash string */
if (c_hash[0] != '{')
return ERROR_INT(cmd, PR_AUTH_BADPWD);
/* We need a copy of c_hash. */
copyhash = pstrdup(cmd->tmp_pool, c_hash);
digestname = copyhash + 1;
hashvalue = (char *) strchr(copyhash, '}');
if (hashvalue == NULL)
return ERROR_INT(cmd, PR_AUTH_BADPWD);
*hashvalue = '\0';
hashvalue++;
OpenSSL_add_all_digests();
md = EVP_get_digestbyname(digestname);
if (!md)
return ERROR_INT(cmd, PR_AUTH_BADPWD);
EVP_DigestInit(&md_ctxt, md);
EVP_DigestUpdate(&md_ctxt, c_clear, strlen(c_clear));
EVP_DigestFinal(&md_ctxt, mdval, &mdlen);
EVP_EncodeInit(&base64_ctxt);
EVP_EncodeBlock(buf, mdval, mdlen);
res = strcmp(buf, hashvalue);
return res ? ERROR_INT(cmd, PR_AUTH_BADPWD) : HANDLED(cmd);
}
#endif
/*
* support for general-purpose authentication schemes
*/
#define PLAINTEXT_AUTH_FLAG 1<<0
#define CRYPT_AUTH_FLAG 1<<1
#define BACKEND_AUTH_FLAG 1<<2
#define EMPTY_AUTH_FLAG 1<<3
#ifdef HAVE_OPENSSL
#define OPENSSL_AUTH_FLAG 1<<4
#endif
typedef modret_t *(*auth_func_ptr) (cmd_rec *, const char *, const char *);
typedef struct {
char *name;
auth_func_ptr check_function;
int flag;
}
auth_type_entry;
static auth_type_entry supported_auth_types[] = {
{"Plaintext", check_auth_plaintext, PLAINTEXT_AUTH_FLAG},
{"Crypt", check_auth_crypt, CRYPT_AUTH_FLAG},
{"Backend", check_auth_backend, BACKEND_AUTH_FLAG},
{"Empty", check_auth_empty, EMPTY_AUTH_FLAG},
#ifdef HAVE_OPENSSL
{"OpenSSL", check_auth_openssl, OPENSSL_AUTH_FLAG},
#endif
/*
* add additional encryption types below
*/
{NULL, NULL, 0}
};
static auth_type_entry *get_auth_entry(char *name) {
auth_type_entry *ate = supported_auth_types;
while (ate->name) {
if (strcasecmp(ate->name, name) == 0)
return ate;
ate++;
}
return NULL;
}
/*****************************************************************
*
* INTERNAL HELPER FUNCTIONS
*
*****************************************************************/
/* find who core thinks is the user, and return a (backend-escaped)
* version of that name */
static char *_sql_realuser(cmd_rec *cmd) {
modret_t *mr = NULL;
char *user = NULL;
/* this is the userid given by the user */
user = (char *) get_param_ptr(main_server->conf, C_USER, FALSE);
/* do we need to check for useralias?
* see mod_time.c, get_user_cmd_times() */
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 2, "default", user),
"sql_escapestring");
_sql_check_response(mr);
return mr ? (char *) mr->data : NULL;
}
static char *_sql_where(pool *p, int cnt, ...) {
int tcnt;
int flag;
int len;
char *res, *tchar;
va_list dummy;
flag = 0;
len = 0;
va_start(dummy, cnt);
for (tcnt = 0; tcnt < cnt; tcnt++) {
res = va_arg(dummy, char *);
if (res != NULL && *res != '\0') {
if (flag++)
len += 5;
len += strlen(res);
len += 2;
}
}
va_end(dummy);
if (!len)
return NULL;
res = pcalloc(p, sizeof(char) * (len+1));
flag = 0;
va_start(dummy, cnt);
for (tcnt = 0; tcnt < cnt; tcnt++) {
tchar = va_arg(dummy, char *);
if (tchar != NULL && *tchar != '\0') {
if (flag++)
sstrcat(res, " and ", len+1);
sstrcat(res, "(", len+1);
sstrcat(res, tchar, len+1);
sstrcat(res, ")", len+1);
}
}
va_end(dummy);
return res;
}
static int _sql_strcmp(const char *s1, const char *s2) {
if ((s1 == NULL) || (s2 == NULL))
return 1;
return strcmp(s1, s2);
}
static unsigned int _group_gid(const void *val) {
if (val == NULL)
return 0;
return ((struct group *) val)->gr_gid;
}
static unsigned int _group_name(const void *val) {
char *name;
int cnt;
unsigned int nameval = 0;
if (val == NULL)
return 0;
name = ((struct group *) val)->gr_name;
if (name == NULL)
return 0;
for (cnt = 0; cnt < strlen(name); cnt++) {
nameval += name[cnt];
}
return nameval;
}
static int _groupcmp(const void *val1, const void *val2) {
if ((val1 == NULL) || (val2 == NULL))
return 0;
/* either the groupnames match or the gids match */
if (_sql_strcmp(((struct group *) val1)->gr_name,
((struct group *) val2)->gr_name) == 0)
return 1;
if (((struct group *) val1)->gr_gid == ((struct group *) val2)->gr_gid)
return 1;
return 0;
}
static unsigned int _passwd_uid(const void *val) {
if (val == NULL)
return 0;
return ((struct passwd *) val)->pw_uid;
}
static unsigned int _passwd_name(const void *val) {
char *name;
int cnt;
unsigned int nameval = 0;
if (val == NULL)
return 0;
name = ((struct passwd *) val)->pw_name;
if (name == NULL)
return 0;
for (cnt = 0; cnt < strlen(name); cnt++) {
nameval += name[cnt];
}
return nameval;
}
static int _passwdcmp(const void *val1, const void *val2) {
if ((val1 == NULL) || (val2 == NULL))
return 0;
/* either the usernames match or the uids match */
if (_sql_strcmp(((struct passwd *) val1)->pw_name,
((struct passwd *) val2)->pw_name) == 0)
return 1;
if (((struct passwd *) val1)->pw_uid == ((struct passwd *) val2)->pw_uid)
return 1;
return 0;
}
static void show_group(pool *p, struct group *g) {
char **member = NULL, *members = "";
if (g == NULL) {
sql_log(DEBUG_INFO, "%s", "NULL group to show_group()");
return;
}
member = g->gr_mem;
while (*member != NULL) {
members = pstrcat(p, members, *members ? ", " : "", *member, NULL);
member++;
}
sql_log(DEBUG_INFO, "+ grp.gr_name : %s", g->gr_name);
sql_log(DEBUG_INFO, "+ grp.gr_gid : %lu", (unsigned long) g->gr_gid);
sql_log(DEBUG_INFO, "+ grp.gr_mem : %s", members);
return;
}
static void show_passwd(struct passwd *p) {
if (p == NULL) {
sql_log(DEBUG_INFO, "%s", "NULL passwd to show_passwd()");
return;
}
sql_log(DEBUG_INFO, "+ pwd.pw_name : %s", p->pw_name);
sql_log(DEBUG_INFO, "+ pwd.pw_uid : %lu", (unsigned long) p->pw_uid);
sql_log(DEBUG_INFO, "+ pwd.pw_gid : %lu", (unsigned long) p->pw_gid);
sql_log(DEBUG_INFO, "+ pwd.pw_dir : %s", p->pw_dir);
sql_log(DEBUG_INFO, "+ pwd.pw_shell : %s", p->pw_shell);
return;
}
static int build_homedir(cmd_rec *cmd, char *path, mode_t omode, uid_t uid,
gid_t gid) {
struct stat st;
mode_t old_umask;
int retval = 0;
char *local_ptr;
char *local_path;
int userdir_flag = 0;
gid_t p_gid;
uid_t p_uid;
sql_log(DEBUG_FUNC, ">>> build_homedir(%s,omode,%i,%i)", path, uid, gid);
/* we assume we're handed a null-terminated string defining the
* user's home directory. we walk it, directory by directory,
* creating it if it doesn't exist. path must start with '/'
*/
if (path[0] != '/') {
sql_log(DEBUG_WARN, "%s", "no '/' at start of user's homedir");
sql_log(DEBUG_FUNC, "%s", "<<< build_homedir");
return -1;
}
/* sanity check -- make sure the path doesn't exist */
if (!pr_fsio_stat(path, &st)) {
sql_log(DEBUG_WARN, "%s", "user's homedir already exists");
sql_log(DEBUG_FUNC, "%s", "<<< build_homedir");
return 0;
} else if (errno != ENOENT) {
sql_log(DEBUG_WARN, "problem with stat of user's homedir: %s",
strerror(errno));
sql_log(DEBUG_FUNC, "%s", "<<< build_homedir");
return -1;
}
/* make our local copy of path, adding a '/' if necessary..
* after this call, we're *guaranteed* a terminating '/'. We use
* this info later. */
if ( path[(strlen(path) - 1)] == '/' )
local_path = pstrdup(cmd->tmp_pool, path);
else
local_path = pstrcat(cmd->tmp_pool, path, "/", NULL);
/* gain root for dir creation process */
p_gid = getegid();
p_uid = geteuid();
PRIVS_ROOT
/* skip the leading '/' */
local_ptr = local_path + 1;
while ( ( local_ptr = strchr( local_ptr, '/' ) ) != NULL ) {
*local_ptr = '\0';
if ( *(local_ptr + 1) == '\0' )
userdir_flag = 1;
if ( pr_fsio_stat( local_path, &st ) ) {
/* if the stat failed.. */
if (errno == ENOENT) {
/* and it's 'cause the directory doesn't exist */
if ( !userdir_flag ) {
/* if it's an intermediate dir */
if ( pr_fsio_mkdir(local_path, S_IRWXU | S_IRWXG | S_IRWXO ) ) {
PRIVS_RELINQUISH
return -1;
} else {
pr_fsio_chown(local_path, p_uid, p_gid );
}
} else {
/* this is the user's homedir, and the final directory */
old_umask = umask(0);
umask( old_umask & ~(S_IWUSR | S_IXUSR | S_IRUSR) );
if ( pr_fsio_mkdir(local_path, omode) ) {
umask( old_umask );
PRIVS_RELINQUISH
return -1;
} else {
pr_fsio_chown(local_path, uid, gid);
}
umask( old_umask );
}
} else {
/* we failed for a reason other than no such
* directory, so we return an error */
PRIVS_RELINQUISH
return -1;
}
}
/* fix local_ptr, and bump it */
*local_ptr = '/';
local_ptr++;
}
/* relinquish root privileges */
PRIVS_RELINQUISH
sql_log(DEBUG_FUNC, "%s", "<<< build_homedir");
return (retval);
}
/* _sql_addpasswd: creates a passwd and adds it to the passwd struct
* cache if it doesn't already exist. Returns the created passwd
* struct, or the pre-existing struct if there was one.
*
* DOES NOT CHECK ARGUMENTS. CALLING FUNCTIONS NEED TO MAKE SURE
* THEY PASS VALID DATA
*/
static struct passwd *_sql_addpasswd(cmd_rec *cmd, char *username,
char *password, uid_t uid, gid_t gid, char *shell, char *dir) {
struct passwd *cached = NULL;
struct passwd *pwd = NULL;
pwd = pcalloc(cmd->tmp_pool, sizeof(struct passwd));
pwd->pw_uid = uid;
pwd->pw_name = username;
/* check to make sure the entry doesn't exist in the cache */
if ( ((cached = (struct passwd *)
cache_findvalue(passwd_name_cache, pwd)) != NULL)) {
pwd = cached;
sql_log(DEBUG_INFO, "cache hit for user '%s'", pwd->pw_name);
} else {
pwd = pcalloc(sql_pool, sizeof(struct passwd));
if (username)
pwd->pw_name = pstrdup(sql_pool, username);
if (password)
pwd->pw_passwd = pstrdup(sql_pool, password);
pwd->pw_uid = uid;
pwd->pw_gid = gid;
if (shell)
pwd->pw_shell = pstrdup(sql_pool, shell);
if (dir)
pwd->pw_dir = pstrdup(sql_pool, dir);
cache_addentry(passwd_name_cache, pwd);
cache_addentry(passwd_uid_cache, pwd);
sql_log(DEBUG_INFO, "cache miss for user '%s'", pwd->pw_name);
sql_log(DEBUG_INFO, "user '%s' cached", pwd->pw_name);
show_passwd(pwd);
}
return pwd;
}
static struct passwd *_sql_getpasswd(cmd_rec *cmd, struct passwd *p) {
sql_data_t *sd = NULL;
modret_t *mr = NULL;
struct passwd *pwd = NULL;
char uidstr[MOD_SQL_BUFSIZE] = {'\0'};
char *usrwhere, *where;
char *realname = NULL;
int i = 0;
char *username = NULL;
char *password = NULL;
char *shell = NULL;
char *dir = NULL;
uid_t uid = 0;
gid_t gid = 0;
if (p == NULL) {
sql_log(DEBUG_WARN, "%s", "_sql_getpasswd called with NULL passwd struct");
sql_log(DEBUG_WARN, "%s", "THIS SHOULD NEVER HAPPEN");
return NULL;
}
if (!cmap.homedirfield &&
!cmap.defaulthomedir)
return NULL;
/* check to see if the passwd already exists in one of the passwd caches */
if (((pwd = (struct passwd *)
cache_findvalue(passwd_name_cache, p)) != NULL) ||
((pwd = (struct passwd *)
cache_findvalue(passwd_uid_cache, p)) != NULL)) {
sql_log(DEBUG_AUTH, "cache hit for user '%s'", pwd->pw_name);
/* Check for negatively cached passwds, which will have NULL
* passwd/home/shell.
*/
if (!pwd->pw_dir) {
sql_log(DEBUG_AUTH, "negative cache entry for user '%s'", pwd->pw_name);
return NULL;
}
return pwd;
}
if (p->pw_name != NULL) {
realname = p->pw_name;
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 2, "default", realname),
"sql_escapestring" );
_sql_check_response(mr);
username = (char *) mr->data;
usrwhere = pstrcat(cmd->tmp_pool, cmap.usrfield, "='", username, "'", NULL);
sql_log(DEBUG_WARN, "cache miss for user '%s'", realname);
} else {
/* Assume we have a uid */
snprintf(uidstr, MOD_SQL_BUFSIZE, "%lu", (unsigned long) p->pw_uid);
sql_log(DEBUG_WARN, "cache miss for uid '%s'", uidstr);
if (cmap.uidfield)
usrwhere = pstrcat(cmd->tmp_pool, cmap.uidfield, " = ", uidstr, NULL);
else {
sql_log(DEBUG_WARN, "no user uid field configured, declining to "
"lookup uid '%s'", uidstr);
/* If no uid field has been configured, return now and let other
* modules possibly have a chance at resolving this UID to a name.
*/
return NULL;
}
}
if (!cmap.usercustom) {
where = _sql_where(cmd->tmp_pool, 2, usrwhere, cmap.userwhere );
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 5, "default",
cmap.usrtable, cmap.usrfields, where, "1"), "sql_select");
_sql_check_response(mr);
if (MODRET_HASDATA(mr))
sd = (sql_data_t *) mr->data;
} else {
mr = sql_lookup(_sql_make_cmd(cmd->tmp_pool, 3, "default", cmap.usercustom,
realname ? realname : "NULL"));
_sql_check_response(mr);
if (MODRET_HASDATA(mr)) {
array_header *ah = (array_header *) mr->data;
sd = pcalloc(cmd->tmp_pool, sizeof(sql_data_t));
/* Assume the query only returned 1 row. */
sd->fnum = ah->nelts;
if (sd->fnum) {
sd->rnum = 1;
sd->data = (char **) ah->elts;
} else {
sd->rnum = 0;
sd->data = NULL;
}
}
}
/* if we have no data.. */
if (sd == NULL ||
sd->rnum == 0) {
if (!cmap.negative_cache) {
return NULL;
} else {
/* If doing caching of negative lookups, cache this failed lookup.
* Use the default UID and GID.
*/
return _sql_addpasswd(cmd, username, NULL, p->pw_uid, p->pw_gid,
NULL, NULL);
}
}
i = 0;
username = sd->data[i++];
password = sd->data[i++];
uid = cmap.defaultuid;
if (cmap.uidfield) {
if (sd->data[i]) {
uid = atoi(sd->data[i++]);
} else {
i++;
}
}
gid = cmap.defaultgid;
if (cmap.gidfield) {
if (sd->data[i]) {
gid = atoi(sd->data[i++]);
} else {
i++;
}
}
dir = cmap.defaulthomedir;
if (sd->data[i]) {
if (strcmp(sd->data[i], "") == 0 ||
strcmp(sd->data[i], "NULL") == 0)
/* Leave dir pointing to the SQLDefaultHomedir, if any. */
i++;
else
dir = sd->data[i++];
}
if (cmap.shellfield) {
if (sd->fnum < i || !sd->data[i]) {
/* Make sure that, if configured, the shell value is valid, and scream
* if it is not.
*/
sql_log(DEBUG_WARN, "NULL shell column value, setting to \"\"");
shell = "";
} else {
shell = sd->data[i];
}
} else
shell = "";
if (uid < cmap.minuseruid)
uid = cmap.defaultuid;
if (gid < cmap.minusergid)
gid = cmap.defaultgid;
return _sql_addpasswd(cmd, username, password, uid, gid, shell, dir);
}
/* _sql_addgroup: creates a group and adds it to the group struct
* cache if it doesn't already exist. Returns the created group
* struct, or the pre-existing struct if there was one.
*
* DOES NOT CHECK ARGUMENTS. CALLING FUNCTIONS NEED TO MAKE SURE
* THEY PASS VALID DATA
*/
static struct group *_sql_addgroup(cmd_rec *cmd, char *groupname, gid_t gid,
array_header *ah) {
struct group *cached = NULL;
struct group *grp = NULL;
int cnt = 0;
grp = pcalloc(cmd->tmp_pool, sizeof(struct group));
grp->gr_gid = gid;
grp->gr_name = groupname;
/* check to make sure the entry doesn't exist in the cache */
if ((cached = (struct group *) cache_findvalue(group_name_cache, grp)) != NULL) {
grp = cached;
sql_log(DEBUG_INFO, "cache hit for group '%s'", grp->gr_name);
} else {
grp = pcalloc(sql_pool, sizeof(struct group));
if (groupname)
grp->gr_name = pstrdup(sql_pool, groupname);
grp->gr_gid = gid;
/* finish filling in the group */
grp->gr_mem = (char **) pcalloc(sql_pool, sizeof(char *) * (ah->nelts + 1));
for (cnt = 0; cnt < ah->nelts; cnt++) {
grp->gr_mem[cnt] = pstrdup(sql_pool, ((char **) ah->elts)[cnt]);
}
grp->gr_mem[ah->nelts] = '\0';
cache_addentry(group_name_cache, grp);
cache_addentry(group_gid_cache, grp);
sql_log(DEBUG_INFO, "cache miss for group '%s'", grp->gr_name);
sql_log(DEBUG_INFO, "group '%s' cached", grp->gr_name);
show_group(cmd->tmp_pool, grp);
}
return grp;
}
static struct group *_sql_getgroup(cmd_rec *cmd, struct group *g) {
struct group *grp = NULL;
modret_t *mr = NULL;
int cnt = 0;
sql_data_t *sd = NULL;
char *groupname = NULL;
char gidstr[MOD_SQL_BUFSIZE] = {'\0'};
char **rows = NULL;
int numrows = 0;
array_header *ah = NULL;
char *members = NULL;
char *member = NULL;
char *grpwhere;
char *where;
char *iterator;
gid_t gid = 0;
if (g == NULL) {
sql_log(DEBUG_WARN, "%s", "_sql_getgroup called with NULL group struct");
sql_log(DEBUG_WARN, "%s", "THIS SHOULD NEVER HAPPEN");
return NULL;
}
/* check to see if the group already exists in one of the group caches */
if (((grp = (struct group *) cache_findvalue(group_name_cache, g)) != NULL) ||
((grp = (struct group *) cache_findvalue(group_gid_cache, g)) != NULL)) {
sql_log(DEBUG_AUTH, "cache hit for group '%s'", grp->gr_name);
/* Check for negatively cached groups, which will have NULL gr_mem. */
if (!grp->gr_mem) {
sql_log(DEBUG_AUTH, "negative cache entry for group '%s'", grp->gr_name);
return NULL;
}
return grp;
}
if (g->gr_name != NULL) {
groupname = g->gr_name;
sql_log(DEBUG_WARN, "cache miss for group '%s'", groupname);
} else {
/* Get groupname from gid */
snprintf(gidstr, MOD_SQL_BUFSIZE, "%lu", (unsigned long) g->gr_gid);
sql_log(DEBUG_WARN, "cache miss for gid '%s'", gidstr);
if (cmap.grpgidfield)
grpwhere = pstrcat(cmd->tmp_pool, cmap.grpgidfield, " = ", gidstr, NULL);
else {
sql_log(DEBUG_WARN, "no group gid field configured, declining to lookup "
"gid '%s'", gidstr);
/* If no gid field has been configured, return now and let other
* modules possibly have a chance at resolving this GID to a name.
*/
return NULL;
}
where = _sql_where(cmd->tmp_pool, 2, grpwhere, cmap.groupwhere);
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 5, "default",
cmap.grptable, cmap.grpfield, where, "1"), "sql_select");
_sql_check_response(mr);
sd = (sql_data_t *) mr->data;
/* If we have no data.. */
if (sd->rnum == 0)
return NULL;
groupname = sd->data[0];
}
grpwhere = pstrcat(cmd->tmp_pool, cmap.grpfield, " = '", groupname, "'",
NULL);
where = _sql_where(cmd->tmp_pool, 2, grpwhere, cmap.groupwhere);
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 4, "default",
cmap.grptable, cmap.grpfields, where), "sql_select");
_sql_check_response(mr);
sd = (sql_data_t *) mr->data;
/* if we have no data.. */
if (sd->rnum == 0) {
if (!cmap.negative_cache) {
return NULL;
} else {
/* If doing caching of negative lookups, cache this failed lookup. */
return _sql_addgroup(cmd, groupname, g->gr_gid, NULL);
}
}
rows = sd->data;
numrows = sd->rnum;
gid = (gid_t) strtoul(rows[1], NULL, 10);
/*
* painful.. we need to walk through the returned rows and fill in our
* members. Every third element in a row is a member field, and every
* member field can have multiple members.
*/
ah = make_array(cmd->tmp_pool, 10, sizeof(char *));
for (cnt = 0; cnt < numrows; cnt++) {
members = rows[(cnt * 3) + 2];
iterator = members;
/* If the row is null, continue.. */
if (members == NULL)
continue;
/* For each member in the list, toss 'em into the array. no
* need to copy the string -- _sql_addgroup will do it for us
*/
for (member = strsep(&iterator, ","); member;
member = strsep(&iterator, ",")) {
if (*member == '\0')
continue;
*((char **) push_array(ah)) = member;
}
}
return _sql_addgroup(cmd, groupname, gid, ah);
}
static void _setstats(cmd_rec *cmd, int fstor, int fretr, int bstor,
int bretr) {
/*
* if anyone has a better way of doing this, let me know..
*/
char query[256] = { '\0' };
char *usrwhere, *where;
modret_t *mr = NULL;
snprintf(query, sizeof(query),
"%s = %s + %i, %s = %s + %i, %s = %s + %i, %s = %s + %i",
cmap.sql_fstor, cmap.sql_fstor, fstor,
cmap.sql_fretr, cmap.sql_fretr, fretr,
cmap.sql_bstor, cmap.sql_bstor, bstor,
cmap.sql_bretr, cmap.sql_bretr, bretr);
usrwhere = pstrcat(cmd->tmp_pool, cmap.usrfield, " = '", _sql_realuser(cmd), "'", NULL);
where = _sql_where(cmd->tmp_pool, 2, usrwhere, cmap.userwhere );
mr = _sql_dispatch( _sql_make_cmd( cmd->tmp_pool, 4, "default", cmap.usrtable,
query, where ), "sql_update" );
_sql_check_response(mr);
}
static int _sql_getgroups(cmd_rec *cmd) {
struct passwd *pw = NULL, lpw;
struct group *grp, lgr;
char *grpwhere = NULL, *where = NULL, **rows = NULL;
sql_data_t *sd = NULL;
modret_t *mr = NULL;
array_header *gids = NULL, *groups = NULL;
char *name = cmd->argv[0], *username = NULL;
int numrows = 0;
register unsigned int i = 0;
/* Check for NULL values */
if (cmd->argv[1])
gids = (array_header *) cmd->argv[1];
if (cmd->argv[2])
groups = (array_header *) cmd->argv[2];
lpw.pw_uid = -1;
lpw.pw_name = name;
/* Retrieve the necessary info */
if (!name ||
!(pw = _sql_getpasswd(cmd, &lpw)))
return -1;
/* Populate the first group ID and name */
if (gids)
*((gid_t *) push_array(gids)) = pw->pw_gid;
lgr.gr_gid = pw->pw_gid;
lgr.gr_name = NULL;
if (groups &&
(grp = _sql_getgroup(cmd, &lgr)) != NULL)
*((char **) push_array(groups)) = pstrdup(permanent_pool, grp->gr_name);
/* Use a single SELECT:
*
* SELECT groupname,gid,members FROM groups
* WHERE members LIKE '%,<user>,%' OR LIKE '<user>,%' OR LIKE '%,<user>';
*/
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 2, "default", name),
"sql_escapestring");
_sql_check_response(mr);
username = (char *) mr->data;
grpwhere = pstrcat(cmd->tmp_pool,
cmap.grpmembersfield, " = '", username, "' OR ",
cmap.grpmembersfield, " LIKE '", username, ",%' OR ",
cmap.grpmembersfield, " LIKE '%,", username, "' OR ",
cmap.grpmembersfield, " LIKE '%,", username, ",%'", NULL);
where = _sql_where(cmd->tmp_pool, 2, grpwhere, cmap.groupwhere);
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 4, "default",
cmap.grptable, cmap.grpfields, where), "sql_select");
_sql_check_response(mr);
sd = (sql_data_t *) mr->data;
/* If we have no data... */
if (sd->rnum == 0)
return -1;
rows = sd->data;
numrows = sd->rnum;
for (i = 0; i < numrows; i++) {
char *groupname = sd->data[(i * 3)];
gid_t gid = (gid_t) atoi(sd->data[(i * 3) +1]);
char *memberstr = sd->data[(i * 3) + 2], *member = NULL;
array_header *members = make_array(cmd->tmp_pool, 2, sizeof(char *));
*((gid_t *) push_array(gids)) = gid;
*((char **) push_array(groups)) = pstrdup(permanent_pool, groupname);
/* For each member in the list, toss 'em into the array. no
* need to copy the string -- _sql_addgroup will do it for us
*/
for (member = strsep(&memberstr, ","); member;
member = strsep(&memberstr, ",")) {
if (*member == '\0')
continue;
*((char **) push_array(members)) = member;
}
/* Add this group data to the group cache. */
_sql_addgroup(cmd, groupname, gid, members);
}
if (gids && gids->nelts > 0)
return gids->nelts;
else if (groups && groups->nelts)
return groups->nelts;
/* Default */
return -1;
}
/* Command handlers
*/
MODRET sql_pre_pass(cmd_rec *cmd) {
config_rec *c = NULL, *anon_config = NULL;
char *user = NULL;
if (cmap.engine == 0)
return DECLINED(cmd);
sql_log(DEBUG_FUNC, "%s", ">>> sql_pre_pass");
user = get_param_ptr(cmd->server->conf, C_USER, FALSE);
if (!user) {
sql_log(DEBUG_FUNC, "%s", "Missing user name, skipping");
sql_log(DEBUG_FUNC, "%s", "<<< sql_pre_pass");
return DECLINED(cmd);
}
/* Use the looked-up user name to determine whether this is to be
* an anonymous session.
*/
anon_config = pr_auth_get_anon_config(cmd->pool, &user, NULL, NULL);
c = find_config(anon_config ? anon_config->subset : main_server->conf,
CONF_PARAM, "SQLEngine", FALSE);
if (c)
cmap.engine = *((int *) c->argv[0]);
sql_log(DEBUG_FUNC, "%s", "<<< sql_pre_pass");
return DECLINED(cmd);
}
MODRET sql_post_stor(cmd_rec *cmd) {
if (cmap.engine == 0)
return DECLINED(cmd);
sql_log(DEBUG_FUNC, "%s", ">>> sql_post_stor");
if (cmap.sql_fstor)
_setstats(cmd, 1, 0, session.xfer.total_bytes, 0);
sql_log(DEBUG_FUNC, "%s", "<<< sql_post_stor");
return DECLINED(cmd);
}
MODRET sql_post_retr(cmd_rec *cmd) {
if (cmap.engine == 0)
return DECLINED(cmd);
sql_log(DEBUG_FUNC, "%s", ">>> sql_post_retr");
if (cmap.sql_fretr)
_setstats(cmd, 0, 1, 0, session.xfer.total_bytes);
sql_log(DEBUG_FUNC, "%s", "<<< sql_post_retr");
return DECLINED(cmd);
}
static char *resolve_tag(cmd_rec *cmd, char tag) {
char arg[256] = {'\0'}, *argp;
switch(tag) {
case 'A': {
char *pass;
argp = arg;
pass = get_param_ptr(main_server->conf, C_PASS, FALSE);
if (!pass)
pass = "UNKNOWN";
sstrncpy( argp, pass, sizeof(arg));
}
break;
case 'a':
argp = arg;
sstrncpy(argp, pr_netaddr_get_ipstr(pr_netaddr_get_sess_remote_addr()),
sizeof(arg));
break;
case 'b':
argp = arg;
if (session.xfer.p)
snprintf(argp, sizeof(arg), "%" PR_LU, session.xfer.total_bytes);
else
sstrncpy( argp, "0", sizeof(arg));
break;
case 'c':
argp = arg;
sstrncpy(argp, session.class ? session.class->cls_name : "-", sizeof(arg));
break;
case 'd':
argp = arg;
if (strcmp(cmd->argv[0], C_CDUP) == 0 ||
strcmp(cmd->argv[0], C_CWD) == 0 ||
strcmp(cmd->argv[0], C_MKD) == 0 ||
strcmp(cmd->argv[0], C_RMD) == 0 ||
strcmp(cmd->argv[0], C_XCWD) == 0 ||
strcmp(cmd->argv[0], C_XCUP) == 0 ||
strcmp(cmd->argv[0], C_XMKD) == 0 ||
strcmp(cmd->argv[0], C_XRMD) == 0) {
char *tmp = strrchr(cmd->arg, '/');
sstrncpy(argp, tmp ? tmp : cmd->arg, sizeof(arg));
} else
sstrncpy(argp, "", sizeof(arg));
break;
case 'D':
argp = arg;
if (strcmp(cmd->argv[0], C_CDUP) == 0 ||
strcmp(cmd->argv[0], C_MKD) == 0 ||
strcmp(cmd->argv[0], C_RMD) == 0 ||
strcmp(cmd->argv[0], C_XCUP) == 0 ||
strcmp(cmd->argv[0], C_XMKD) == 0 ||
strcmp(cmd->argv[0], C_XRMD) == 0) {
sstrncpy(argp, dir_abs_path(cmd->tmp_pool, cmd->arg, TRUE), sizeof(arg));
} else if (strcmp(cmd->argv[0], C_CWD) == 0 ||
strcmp(cmd->argv[0], C_XCWD) == 0) {
/* Note: by this point in the dispatch cycle, the current working
* directory has already been changed. For the CWD/XCWD commands,
* this means that dir_abs_path() may return an improper path,
* with the target directory being reported twice. To deal with this,
* don't use dir_abs_path(), and use pr_fs_getvwd()/pr_fs_getcwd()
* instead.
*/
if (session.chroot_path) {
/* Chrooted session. */
sstrncpy(arg, strcmp(pr_fs_getvwd(), "/") ?
pdircat(cmd->tmp_pool, session.chroot_path, pr_fs_getvwd(), NULL) :
session.chroot_path, sizeof(arg));
} else
/* Non-chrooted session. */
sstrncpy(arg, pr_fs_getcwd(), sizeof(arg));
} else
sstrncpy(argp, "", sizeof(arg));
break;
case 'f':
argp = arg;
if (strcmp(cmd->argv[0], C_RNTO) == 0) {
sstrncpy(argp, dir_abs_path(cmd->tmp_pool, cmd->arg, TRUE), sizeof(arg));
} else if (session.xfer.p &&
session.xfer.path) {
sstrncpy(argp, dir_abs_path(cmd->tmp_pool, session.xfer.path, TRUE),
sizeof(arg));
} else {
/* Some commands (i.e. DELE, MKD, RMD, XMKD, and XRMD) have associated
* filenames that are not stored in the session.xfer structure; these
* should be expanded properly as well.
*/
if (strcmp(cmd->argv[0], C_DELE) == 0 ||
strcmp(cmd->argv[0], C_MKD) == 0 ||
strcmp(cmd->argv[0], C_RMD) == 0 ||
strcmp(cmd->argv[0], C_XMKD) == 0 ||
strcmp(cmd->argv[0], C_XRMD) == 0)
sstrncpy(arg, dir_abs_path(cmd->tmp_pool, cmd->arg, TRUE), sizeof(arg));
else
/* All other situations get a "-". */
sstrncpy(argp, "-", sizeof(arg));
}
break;
case 'F':
argp = arg;
if (session.xfer.p && session.xfer.path) {
sstrncpy(argp, session.xfer.path, sizeof(arg));
} else {
/* Some commands (i.e. DELE) have associated filenames that are not
* stored in the session.xfer structure; these should be expanded
* properly as well.
*/
if (strcmp(cmd->argv[0], C_DELE) == 0)
sstrncpy(arg, cmd->arg, sizeof(arg));
else
sstrncpy(argp, "-", sizeof(arg));
}
break;
case 'J':
argp = arg;
if (strcasecmp(cmd->argv[0], C_PASS) == 0 &&
session.hide_password) {
sstrncpy(argp, "(hidden)", sizeof(arg));
} else {
sstrncpy(argp, cmd->arg, sizeof(arg));
}
break;
case 'h':
argp = arg;
sstrncpy(argp, pr_netaddr_get_sess_remote_name(), sizeof(arg));
break;
case 'L':
argp = arg;
sstrncpy(argp, pr_netaddr_get_ipstr(pr_netaddr_get_sess_local_addr()),
sizeof(arg));
break;
case 'l':
argp = arg;
sstrncpy(argp, session.ident_user, sizeof(arg));
break;
case 'm':
argp = arg;
sstrncpy(argp, cmd->argv[0], sizeof(arg));
break;
case 'P':
argp = arg;
snprintf(argp, sizeof(arg), "%u",(unsigned int)getpid());
break;
case 'p':
argp = arg;
snprintf(argp, sizeof(arg), "%d", cmd->server->ServerPort);
break;
case 'r':
argp = arg;
if(!strcasecmp(cmd->argv[0], C_PASS) && session.hide_password)
sstrncpy(argp, C_PASS " (hidden)", sizeof(arg));
else
sstrncpy(argp, get_full_cmd(cmd), sizeof(arg));
break;
case 's': {
pr_response_t *r;
argp = arg;
r = (resp_list ? resp_list : resp_err_list);
for (; r && !r->num; r=r->next);
if (r && r->num)
sstrncpy(argp, r->num, sizeof(arg));
else
sstrncpy(argp, "-", sizeof(arg));
}
break;
case 'T':
argp = arg;
if (session.xfer.p) {
struct timeval end_time;
gettimeofday(&end_time, NULL);
end_time.tv_sec -= session.xfer.start_time.tv_sec;
if (end_time.tv_usec >= session.xfer.start_time.tv_usec)
end_time.tv_usec -= session.xfer.start_time.tv_usec;
else {
end_time.tv_usec = 1000000L - (session.xfer.start_time.tv_usec -
end_time.tv_usec);
end_time.tv_sec--;
}
snprintf(argp, sizeof(arg), "%lu.%03lu", (unsigned long) end_time.tv_sec,
(unsigned long) (end_time.tv_usec / 1000));
} else
sstrncpy(argp, "0.0", sizeof(arg));
break;
case 'U':
argp = arg;
{
char *login_user = get_param_ptr(main_server->conf, C_USER, FALSE);
if (!login_user)
login_user = "root";
sstrncpy(argp, login_user, sizeof(arg));
}
break;
case 'u':
argp = arg;
if (!session.user) {
char *u;
u = get_param_ptr(main_server->conf, "UserName", FALSE);
if (!u)
u = "root";
sstrncpy(argp, u, sizeof(arg));
} else
sstrncpy(argp, session.user, sizeof(arg));
break;
case 'V':
argp = arg;
sstrncpy(argp, cmd->server->ServerFQDN, sizeof(arg));
break;
case 'v':
argp = arg;
sstrncpy(argp, cmd->server->ServerName, sizeof(arg));
break;
case '%':
argp = "%";
break;
default:
argp = "{UNKNOWN TAG}";
break;
}
return pstrdup(cmd->tmp_pool, argp);
}
static char *_named_query_type(cmd_rec *cmd, char *name) {
config_rec *c = NULL;
char *query = NULL;
query = pstrcat(cmd->tmp_pool, "SQLNamedQuery_", name, NULL);
c = find_config(main_server->conf, CONF_PARAM, query, FALSE);
if (c)
return c->argv[0];
return NULL;
}
static modret_t *_process_named_query(cmd_rec *cmd, char *name) {
config_rec *c;
char *query, *tmp, *argp;
char outs[4096] = {'\0'}, *outsp;
char *esc_arg = NULL;
modret_t *mr = NULL;
int num = 0;
char *argc = 0;
char *endptr = NULL;
sql_log(DEBUG_FUNC, "%s", ">>> _process_named_query");
/* Check for a query by that name */
query = pstrcat(cmd->tmp_pool, "SQLNamedQuery_", name, NULL);
c = find_config(main_server->conf, CONF_PARAM, query, FALSE);
if (c) {
/* Select string fixup */
memset(outs, '\0', sizeof(outs));
outsp = outs;
for (tmp = c->argv[1]; *tmp; ) {
if (*tmp == '%') {
if (*(++tmp) == '{') {
char *tmp_query;
if (*tmp != '\0')
tmp_query = ++tmp;
/* Find the argument number to use */
while (*tmp && *tmp != '}')
tmp++;
argc = pstrndup(cmd->tmp_pool, tmp_query, (tmp - tmp_query));
if (argc) {
num = strtol(argc, &endptr, 10);
if (*endptr != '\0' ||
num < 0 ||
(cmd->argc - 3) < num) {
return ERROR_MSG(cmd, MOD_SQL_VERSION,
"reference out-of-bounds in query");
}
} else {
return ERROR_MSG(cmd, MOD_SQL_VERSION,
"malformed reference %{?} in query");
}
esc_arg = cmd->argv[num+2];
} else {
argp = resolve_tag(cmd, *tmp);
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 2, "default",
argp), "sql_escapestring");
_sql_check_response(mr);
esc_arg = (char *) mr->data;
}
/* XXX Should be sstrcat(). */
strcat(outs, esc_arg);
outsp += strlen(esc_arg);
if (*tmp != '\0')
tmp++;
} else {
*outsp++ = *tmp++;
}
}
*outsp++ = 0;
/* Construct our return data based on the type of query */
if (strcasecmp(c->argv[0], SQL_UPDATE_C) == 0) {
query = pstrcat(cmd->tmp_pool, c->argv[2], " SET ", outs, NULL);
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 2, "default", query),
"sql_update");
} else if (strcasecmp(c->argv[0], SQL_INSERT_C) == 0) {
query = pstrcat(cmd->tmp_pool, "INTO ", c->argv[2], " VALUES (",
outs, ")", NULL);
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 2, "default", query),
"sql_insert");
} else if (strcasecmp(c->argv[0], SQL_FREEFORM_C) == 0) {
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 2, "default", outs),
"sql_query");
} else if (strcasecmp(c->argv[0], SQL_SELECT_C) == 0) {
mr = _sql_dispatch(_sql_make_cmd(cmd->tmp_pool, 2, "default", outs),
"sql_select");
} else {
mr = ERROR_MSG(cmd, MOD_SQL_VERSION, "unknown NamedQuery type");
}
} else {
mr = ERROR(cmd);
}
sql_log(DEBUG_FUNC, "%s", "<<< _process_named_query");
return mr;
}
MODRET log_master(cmd_rec *cmd) {
char *name = NULL;
char *qname = NULL;
char *type = NULL;
config_rec *c = NULL;
modret_t *mr = NULL;
if (!(cmap.engine & SQL_ENGINE_FL_LOG))
return DECLINED(cmd);
/* handle explicit queries */
name = pstrcat(cmd->tmp_pool, "SQLLog_", cmd->argv[0], NULL);
c = find_config(main_server->conf, CONF_PARAM, name, FALSE);
if (c) {
do {
sql_log(DEBUG_FUNC, "%s", ">>> log_master");
qname = c->argv[0];
type = _named_query_type(cmd, qname);
if (type) {
if ((!strcasecmp(type, SQL_UPDATE_C)) ||
(!strcasecmp(type, SQL_FREEFORM_C)) ||
(!strcasecmp(type, SQL_INSERT_C))) {
mr = _process_named_query( cmd, qname );
if (c->argc == 2) _sql_check_response(mr);
} else {
sql_log(DEBUG_WARN, "named query '%s' is not an INSERT, UPDATE, or "
"FREEFORM query", qname);
}
} else {
sql_log(DEBUG_WARN, "named query '%s' cannot be found", qname);
}
sql_log(DEBUG_FUNC, "%s", "<<< log_master");
} while((c = find_config_next(c, c->next,
CONF_PARAM, name, FALSE)) != NULL);
}
/* handle implit queries */
name = pstrcat(cmd->tmp_pool, "SQLLog_*", NULL);
c = find_config(main_server->conf, CONF_PARAM, name, FALSE);
if (c) {
do {
sql_log(DEBUG_FUNC, "%s", ">>> log_master");
qname = c->argv[0];
type = _named_query_type(cmd, qname);
if (type) {
if ((!strcasecmp(type, SQL_UPDATE_C)) ||
(!strcasecmp(type, SQL_FREEFORM_C)) ||
(!strcasecmp(type, SQL_INSERT_C))) {
mr = _process_named_query( cmd, qname );
if (c->argc == 2) _sql_check_response(mr);
} else {
sql_log(DEBUG_WARN, "named query '%s' is not an INSERT, UPDATE, or "
"FREEFORM query", qname);
}
} else {
sql_log(DEBUG_WARN, "named query '%s' cannot be found", qname);
}
sql_log(DEBUG_FUNC, "%s", "<<< log_master");
} while((c = find_config_next(c, c->next,
CONF_PARAM, name, FALSE)) != NULL);
}
return DECLINED(cmd);
}
MODRET err_master(cmd_rec *cmd) {
char *name = NULL;
char *qname = NULL;
char *type = NULL;
config_rec *c = NULL;
modret_t *mr = NULL;
if (!(cmap.engine & SQL_ENGINE_FL_LOG))
return DECLINED(cmd);
/* handle explicit errors */
name = pstrcat(cmd->tmp_pool, "SQLLog_ERR_", cmd->argv[0], NULL);
c = find_config(main_server->conf, CONF_PARAM, name, FALSE);
if (c) {
do {
sql_log(DEBUG_FUNC, "%s", ">>> err_master");
qname = c->argv[0];
type = _named_query_type(cmd, qname);
if (type) {
if ((!strcasecmp(type, SQL_UPDATE_C)) ||
(!strcasecmp(type, SQL_FREEFORM_C)) ||
(!strcasecmp(type, SQL_INSERT_C))) {
mr = _process_named_query( cmd, qname );
if (c->argc == 2) _sql_check_response(mr);
} else {
sql_log(DEBUG_WARN, "named query '%s' is not an INSERT, UPDATE, or "
"FREEFORM query", qname);
}
} else {
sql_log(DEBUG_WARN, "named query '%s' cannot be found", qname);
}
sql_log(DEBUG_FUNC, "%s", "<<< err_master");
} while((c = find_config_next(c, c->next,
CONF_PARAM, name, FALSE)) != NULL);
}
/* handle implicit errors */
name = pstrcat(cmd->tmp_pool, "SQLLog_ERR_*", NULL);
c = find_config(main_server->conf, CONF_PARAM, name, FALSE);
if (c) {
do {
sql_log(DEBUG_FUNC, "%s", ">>> err_master");
qname = c->argv[0];
type = _named_query_type(cmd, qname);
if (type) {
if ((!strcasecmp(type, SQL_UPDATE_C)) ||
(!strcasecmp(type, SQL_FREEFORM_C)) ||
(!strcasecmp(type, SQL_INSERT_C))) {
mr = _process_named_query( cmd, qname );
if (c->argc == 2) _sql_check_response(mr);
} else {
sql_log(DEBUG_WARN, "named query '%s' is not an INSERT, UPDATE, or "
"FREEFORM query", qname);
}
} else {
sql_log(DEBUG_WARN, "named query '%s' cannot be found", qname);
}
sql_log(DEBUG_FUNC, "%s", "<<< err_master");
} while((c = find_config_next(c, c->next,
CONF_PARAM, name, FALSE)) != NULL);
}
return DECLINED(cmd);
}
MODRET info_master(cmd_rec *cmd) {
char *type = NULL;
char *name = NULL;
config_rec *c = NULL;
char outs[4096] = {'\0'}, *outsp;
char *argp = NULL;
char *tmp = NULL;
modret_t *mr = NULL;
sql_data_t *sd = NULL;
if (!(cmap.engine & SQL_ENGINE_FL_LOG))
return DECLINED(cmd);
/* process explicit handlers */
name = pstrcat(cmd->tmp_pool, "SQLShowInfo_", cmd->argv[0], NULL);
c = find_config(main_server->conf, CONF_PARAM, name, FALSE);
if (c) {
sql_log(DEBUG_FUNC, "%s", ">>> info_master");
/* we now have at least one config_rec. Take the output string from
* each, and process it -- resolve tags, and when we find a named
* query, run it and get info from it.
*/
do {
memset(outs, '\0', sizeof(outs));
outsp = outs;
for (tmp = c->argv[1]; *tmp; ) {
if(*tmp == '%') {
/* is the tag a named_query reference? If so, process the
* named query, otherwise process it as a normal tag..
*/
if (*(++tmp) == '{') {
char *query;
if (*tmp!='\0') query = ++tmp;
/* get the name of the query */
while ( *tmp && *tmp!='}' ) tmp++;
query = pstrndup(cmd->tmp_pool, query, (tmp - query));
/* make sure it's a SELECT query */
type = _named_query_type(cmd, query);
if (type && ((!strcasecmp(type, SQL_SELECT_C )) ||
(!strcasecmp(type, SQL_FREEFORM_C )))) {
mr = _process_named_query(cmd, query);
if (MODRET_ISERROR(mr)) {
argp = "{null}";
} else {
sd = (sql_data_t *) mr->data;
if ((sd->rnum == 0) || (!sd->data[0]))
argp = "{null}";
else
argp = sd->data[0];
}
} else {
argp = "{null}";
}
} else {
argp=resolve_tag( cmd, *tmp);
}
sstrcat(outs, argp, sizeof(outs));
outsp += strlen(argp);
if (*tmp!='\0') tmp++;
} else {
*outsp++ = *tmp++;
}
}
*outsp++ = 0;
/* add the response */
pr_response_add(c->argv[0], "%s", outs);
} while((c = find_config_next(c, c->next, CONF_PARAM, name, FALSE)) != NULL);
sql_log(DEBUG_FUNC, "%s", "<<< info_master");
}
/* process implicit handlers */
name = pstrcat(cmd->tmp_pool, "SQLShowInfo_*", NULL);
c = find_config(main_server->conf, CONF_PARAM, name, FALSE);
if (c) {
sql_log(DEBUG_FUNC, "%s", ">>> info_master");
/* we now have at least one config_rec. Take the output string from
* each, and process it -- resolve tags, and when we find a named
* query, run it and get info from it.
*/
do {
memset(outs, '\0', sizeof(outs));
outsp = outs;
for (tmp = c->argv[1]; *tmp; ) {
if(*tmp == '%') {
/* is the tag a named_query reference? If so, process the
* named query, otherwise process it as a normal tag..
*/
if (*(++tmp) == '{') {
char *query;
if (*tmp!='\0') query = ++tmp;
/* get the name of the query */
while ( *tmp && *tmp!='}' ) tmp++;
query = pstrndup(cmd->tmp_pool, query, (tmp - query));
/* make sure it's a SELECT query */
type = _named_query_type(cmd, query);
if (type && ((!strcasecmp(type, SQL_SELECT_C )) ||
(!strcasecmp(type, SQL_FREEFORM_C )))) {
mr = _process_named_query(cmd, query);
if (MODRET_ISERROR(mr)) {
argp = "{null}";
} else {
sd = (sql_data_t *) mr->data;
if ((sd->rnum == 0) || (!sd->data[0]))
argp = "{null}";
else
argp = sd->data[0];
}
} else {
argp = "{null}";
}
} else {
argp=resolve_tag( cmd, *tmp);
}
sstrcat(outs, argp, sizeof(outs));
outsp += strlen(argp);
if (*tmp!='\0') tmp++;
} else {
*outsp++ = *tmp++;
}
}
*outsp++ = 0;
/* add the response */
pr_response_add(c->argv[0], "%s", outs);
} while((c = find_config_next(c, c->next, CONF_PARAM, name, FALSE)) != NULL);
sql_log(DEBUG_FUNC, "%s", "<<< info_master");
}
return DECLINED(cmd);
}