#include "c2s.h"
#ifdef STORAGE_MYSQL
#define MYSQL_LU 1024
#define MYSQL_LR 256
#define MYSQL_LP 256
#include <mysql.h>
#define APPLE_ENABLE_OD_AUTH 1
#ifdef APPLE_ENABLE_OD_AUTH
#include <apple_authenticate.h>
#include <apple_authorize.h>
#define APPLE_CHAT_SACL_NAME "chat"
#endif
typedef struct mysqlcontext_st {
MYSQL * conn;
char * sql_create;
char * sql_select;
char * sql_setpassword;
char * sql_setzerok;
char * sql_delete;
char * field_password;
char * field_hash;
char * field_token;
char * field_sequence;
} *mysqlcontext_t;
#ifdef APPLE_ENABLE_OD_AUTH
static int _ar_od_user_exists(authreg_t ar, char *username, char *realm)
{
log_debug( ZONE, "_ar_od_user_exists()." );
if (NULL != username) log_debug( ZONE, "_ar_od_user_exists(): username = %s.", username);
if (NULL != realm) log_debug( ZONE, "_ar_od_user_exists(): realm = %s.", realm);
int iResult = od_auth_check_user_exists((const char *) username);
log_debug( ZONE, "_ar_od_user_exists(): od_auth_check_user_exists returned %d", iResult );
if (0 > iResult)
iResult = 0;
return iResult;
}
static int _ar_od_check_password(authreg_t ar, char *username, char *realm, char password[257])
{
log_debug( ZONE, "_ar_od_check_password()." );
if (NULL != username) log_debug( ZONE, "_ar_od_check_password(): username = %s.", username);
if (NULL != realm) log_debug( ZONE, "_ar_od_check_password(): realm = %s.", realm);
if ((NULL != password) && (0 < strlen(password)))
log_debug( ZONE, "_ar_od_check_password(): password = %s.", password);
int iResult = od_auth_check_plain_password(username, password);
log_debug( ZONE, "_ar_od_check_password(): od_auth_check_plain_password returned %d", iResult );
if (0 != iResult)
iResult = 1;
else {
int iErr = od_auth_check_service_membership(username, APPLE_CHAT_SACL_NAME);
log_debug( ZONE, "_ar_od_check_password(): od_auth_check_service_membership returned %d", iErr );
iResult = (1 == iErr) ? 0 : 1;
}
return iResult;
}
static int _ar_od_create_challenge(authreg_t ar, char *username, char *challenge, int maxlen)
{
log_debug( ZONE, "_ar_od_create_challenge()." );
int iResult = od_auth_supports_cram_md5(username);
log_debug( ZONE, "_ar_od_create_challenge(): od_auth_supports_cram_md5 returned %d", iResult );
if (0 == iResult)
iResult = -1;
iResult = od_auth_create_crammd5_challenge(challenge, maxlen);
log_debug( ZONE, "_ar_od_create_challenge(): od_auth_create_crammd5_challenge returned %d", iResult );
if (0 < iResult)
iResult = 1;
return iResult;
}
static int _ar_od_check_response(authreg_t ar, char *username, char *realm, char *challenge, char *response)
{
log_debug( ZONE, "_ar_od_check_response()." );
if (NULL != username) log_debug( ZONE, "_ar_od_check_response(): username = %s.", username);
if (NULL != realm) log_debug( ZONE, "_ar_od_check_response(): realm = %s.", realm);
if ((NULL != challenge) && (0 < strlen(challenge)))
log_debug( ZONE, "_ar_od_check_response(): challenge = %s.", challenge);
if ((NULL != response) && (0 < strlen(response)))
log_debug( ZONE, "_ar_od_check_response(): response = %s.", response);
int iResult = od_auth_check_crammd5_response(username, challenge, response);
log_debug( ZONE, "_ar_od_check_response(): od_auth_check_crammd5_response returned %d", iResult );
if (0 != iResult)
iResult = 1;
else {
int iErr = od_auth_check_service_membership(username, APPLE_CHAT_SACL_NAME);
log_debug( ZONE, "_ar_od_check_response(): od_auth_check_service_membership returned %d", iErr );
iResult = (1 == iErr) ? 0 : 1;
}
return iResult;
}
#endif
static MYSQL_RES *_ar_mysql_get_user_tuple(authreg_t ar, char *username, char *realm) {
mysqlcontext_t ctx = (mysqlcontext_t) ar->private;
MYSQL *conn = ctx->conn;
char iuser[MYSQL_LU+1], irealm[MYSQL_LR+1];
char euser[MYSQL_LU*2+1], erealm[MYSQL_LR*2+1], sql[1024 + MYSQL_LU*2 + MYSQL_LR*2 + 1];
MYSQL_RES *res;
if(mysql_ping(conn) != 0) {
log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database lost");
return NULL;
}
snprintf(iuser, MYSQL_LU+1, "%s", username);
snprintf(irealm, MYSQL_LR+1, "%s", realm);
mysql_real_escape_string(conn, euser, iuser, strlen(iuser));
mysql_real_escape_string(conn, erealm, irealm, strlen(irealm));
sprintf(sql, ctx->sql_select, euser, erealm);
log_debug(ZONE, "prepared sql: %s", sql);
if(mysql_query(conn, sql) != 0) {
log_write(ar->c2s->log, LOG_ERR, "mysql: sql select failed: %s", mysql_error(conn));
return NULL;
}
res = mysql_store_result(conn);
if(res == NULL) {
log_write(ar->c2s->log, LOG_ERR, "mysql: sql result retrieval failed: %s", mysql_error(conn));
return NULL;
}
if(mysql_num_rows(res) != 1) {
mysql_free_result(res);
return NULL;
}
return res;
}
static int _ar_mysql_user_exists(authreg_t ar, char *username, char *realm) {
MYSQL_RES *res = _ar_mysql_get_user_tuple(ar, username, realm);
if(res != NULL) {
mysql_free_result(res);
return 1;
}
return 0;
}
static int _ar_mysql_get_password(authreg_t ar, char *username, char *realm, char password[257]) {
mysqlcontext_t ctx = (mysqlcontext_t) ar->private;
MYSQL *conn = ctx->conn;
MYSQL_RES *res = _ar_mysql_get_user_tuple(ar, username, realm);
MYSQL_FIELD *field;
MYSQL_ROW tuple;
int i, fpass = 0;
if(res == NULL)
return 1;
for(i = mysql_num_fields(res) - 1; i >= 0; i--) {
field = mysql_fetch_field_direct(res, i);
if(strcmp(field->name, ctx->field_password) == 0) {
fpass = i;
break;
}
}
if((tuple = mysql_fetch_row(res)) == NULL) {
log_write(ar->c2s->log, LOG_ERR, "mysql: sql tuple retrieval failed: %s", mysql_error(conn));
mysql_free_result(res);
return 1;
}
if(tuple[fpass] == NULL) {
mysql_free_result(res);
return 1;
}
strcpy(password, tuple[fpass]);
mysql_free_result(res);
return 0;
}
static int _ar_mysql_set_password(authreg_t ar, char *username, char *realm, char password[257]) {
mysqlcontext_t ctx = (mysqlcontext_t) ar->private;
MYSQL *conn = ctx->conn;
char iuser[MYSQL_LU+1], irealm[MYSQL_LR+1];
char euser[MYSQL_LU*2+1], erealm[MYSQL_LR*2+1], epass[513], sql[1024+MYSQL_LU*2+MYSQL_LR*2+512+1];
if(mysql_ping(conn) != 0) {
log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database lost");
return 1;
}
snprintf(iuser, MYSQL_LU+1, "%s", username);
snprintf(irealm, MYSQL_LR+1, "%s", realm);
password[256]= '\0';
mysql_real_escape_string(conn, euser, iuser, strlen(iuser));
mysql_real_escape_string(conn, erealm, irealm, strlen(irealm));
mysql_real_escape_string(conn, epass, password, strlen(password));
sprintf(sql, ctx->sql_setpassword, epass, euser, erealm);
log_debug(ZONE, "prepared sql: %s", sql);
if(mysql_query(conn, sql) != 0) {
log_write(ar->c2s->log, LOG_ERR, "mysql: sql update failed: %s", mysql_error(conn));
return 1;
}
return 0;
}
static int _ar_mysql_get_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int *sequence) {
mysqlcontext_t ctx = (mysqlcontext_t) ar->private;
MYSQL *conn = ctx->conn;
MYSQL_RES *res = _ar_mysql_get_user_tuple(ar, username, realm);
int i, fhash, ftok, fseq;
MYSQL_FIELD *field;
MYSQL_ROW tuple;
if(res == NULL)
return 1;
fhash = ftok = fseq = 0;
for(i = mysql_num_fields(res) - 1; i >= 0; i--) {
field = mysql_fetch_field_direct(res, i);
if(strcmp(field->name, ctx->field_hash) == 0)
fhash = i;
else if(strcmp(field->name, ctx->field_token) == 0)
ftok = i;
else if(strcmp(field->name, ctx->field_sequence) == 0)
fseq = i;
}
if((tuple = mysql_fetch_row(res)) == NULL) {
log_write(ar->c2s->log, LOG_ERR, "mysql: sql tuple retrieval failed: %s", mysql_error(conn));
mysql_free_result(res);
return 1;
}
if(tuple[fhash] == NULL || tuple[ftok] == NULL || tuple[fseq] == NULL) {
mysql_free_result(res);
return 1;
}
strcpy(hash, tuple[fhash]);
strcpy(token, tuple[ftok]);
*sequence = atoi(tuple[fseq]);
mysql_free_result(res);
return 0;
}
static int _ar_mysql_set_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int sequence) {
mysqlcontext_t ctx = (mysqlcontext_t) ar->private;
MYSQL *conn = ctx->conn;
char iuser[MYSQL_LU+1], irealm[MYSQL_LR+1];
char euser[MYSQL_LU*2+1], erealm[MYSQL_LR*2+1], ehash[81], etoken[21], sql[1024+MYSQL_LU*2+MYSQL_LR*2+80+20+12+1];
if(mysql_ping(conn) != 0) {
log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database lost");
return 1;
}
snprintf(iuser, MYSQL_LU+1, "%s", username);
snprintf(irealm, MYSQL_LR+1, "%s", realm);
mysql_real_escape_string(conn, euser, iuser, strlen(iuser));
mysql_real_escape_string(conn, erealm, irealm, strlen(irealm));
mysql_real_escape_string(conn, ehash, hash, strlen(hash));
mysql_real_escape_string(conn, etoken, token, strlen(token));
sprintf(sql, ctx->sql_setzerok, ehash, etoken, sequence, euser, erealm);
log_debug(ZONE, "prepared sql: %s", sql);
if(mysql_query(conn, sql) != 0) {
log_write(ar->c2s->log, LOG_ERR, "mysql: sql update failed: %s", mysql_error(conn));
return 1;
}
return 0;
}
static int _ar_mysql_create_user(authreg_t ar, char *username, char *realm) {
mysqlcontext_t ctx = (mysqlcontext_t) ar->private;
MYSQL *conn = ctx->conn;
char iuser[MYSQL_LU+1], irealm[MYSQL_LR+1];
char euser[MYSQL_LU*2+1], erealm[MYSQL_LR*2+1], sql[1024+MYSQL_LU*2+MYSQL_LR*2+1];
MYSQL_RES *res = _ar_mysql_get_user_tuple(ar, username, realm);
if(res != NULL) {
mysql_free_result(res);
return 1;
}
mysql_free_result(res);
if(mysql_ping(conn) != 0) {
log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database lost");
return 1;
}
snprintf(iuser, MYSQL_LU+1, "%s", username);
snprintf(irealm, MYSQL_LR+1, "%s", realm);
mysql_real_escape_string(conn, euser, iuser, strlen(iuser));
mysql_real_escape_string(conn, erealm, irealm, strlen(irealm));
sprintf(sql, ctx->sql_create, euser, erealm);
log_debug(ZONE, "prepared sql: %s", sql);
if(mysql_query(conn, sql) != 0) {
log_write(ar->c2s->log, LOG_ERR, "mysql: sql insert failed: %s", mysql_error(conn));
return 1;
}
return 0;
}
static int _ar_mysql_delete_user(authreg_t ar, char *username, char *realm) {
mysqlcontext_t ctx = (mysqlcontext_t) ar->private;
MYSQL *conn = ctx->conn;
char iuser[MYSQL_LU+1], irealm[MYSQL_LR+1];
char euser[MYSQL_LU*2+1], erealm[MYSQL_LR*2+1], sql[1024+MYSQL_LU*2+MYSQL_LR*2+1];
if(mysql_ping(conn) != 0) {
log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database lost");
return 1;
}
snprintf(iuser, MYSQL_LU+1, "%s", username);
snprintf(irealm, MYSQL_LR+1, "%s", realm);
mysql_real_escape_string(conn, euser, iuser, strlen(iuser));
mysql_real_escape_string(conn, erealm, irealm, strlen(irealm));
sprintf(sql, ctx->sql_delete, euser, erealm);
log_debug(ZONE, "prepared sql: %s", sql);
if(mysql_query(conn, sql) != 0) {
log_write(ar->c2s->log, LOG_ERR, "mysql: sql insert failed: %s", mysql_error(conn));
return 1;
}
return 0;
}
static void _ar_mysql_free(authreg_t ar) {
mysqlcontext_t ctx = (mysqlcontext_t) ar->private;
MYSQL *conn = ctx->conn;
if(conn != NULL)
mysql_close(conn);
free(ctx->sql_create);
free(ctx->sql_select);
free(ctx->sql_setpassword);
free(ctx->sql_setzerok);
free(ctx->sql_delete);
free(ctx);
}
char * _ar_mysql_param( config_t c, char * key, char * def ) {
char * value = config_get_one( c, key, 0 );
if( value == NULL )
return def;
else
return value;
}
char * _ar_mysql_check_template( char * template, char * types ) {
int pScan = 0;
int pType = 0;
char c;
if( strlen( template ) > 1024 ) return "longer than 1024 characters";
while( pScan < strlen( template ) )
{
if( template[ pScan++ ] != '%' ) continue;
c = template[ pScan++ ];
if( c == '%' ) continue;
if( c == types[ pType ] )
{
pType++;
continue;
}
return "contained unexpected placeholder type";
}
if( pType < strlen( types ) )
return "contained too few placeholders";
else
return 0;
}
int _ar_mysql_check_sql( authreg_t ar, char * sql, char * types ) {
char * error;
error = _ar_mysql_check_template( sql, types );
if( error == 0 ) return 0;
log_write( ar->c2s->log, LOG_ERR, "mysql: template error: %s - %s", error, sql );
return 1;
}
int ar_mysql_init(authreg_t ar) {
char *host, *port, *dbname, *user, *pass;
char *create, *select, *setpassword, *setzerok, *delete;
char *table, *username, *realm;
char *template;
int strlentur;
MYSQL *conn;
mysqlcontext_t mysqlcontext;
mysqlcontext = (mysqlcontext_t) malloc( sizeof( struct mysqlcontext_st ) );
ar->private = mysqlcontext;
ar->free = _ar_mysql_free;
username = _ar_mysql_param( ar->c2s->config
, "authreg.mysql.field.username"
, "username" );
realm = _ar_mysql_param( ar->c2s->config
, "authreg.mysql.field.realm"
, "realm" );
mysqlcontext->field_password = _ar_mysql_param( ar->c2s->config
, "authreg.mysql.field.password"
, "password" );
mysqlcontext->field_hash = _ar_mysql_param( ar->c2s->config
, "authreg.mysql.field.hash"
, "hash" );
mysqlcontext->field_token = _ar_mysql_param( ar->c2s->config
, "authreg.mysql.field.token"
, "token" );
mysqlcontext->field_sequence = _ar_mysql_param( ar->c2s->config
, "authreg.mysql.field.sequence"
, "sequence" );
table = _ar_mysql_param( ar->c2s->config
, "authreg.mysql.table"
, "authreg" );
strlentur = strlen( table ) + strlen( username) + strlen( realm );
template = "INSERT INTO `%s` ( `%s`, `%s` ) VALUES ( '%%s', '%%s' )";
create = malloc( strlen( template ) + strlentur );
sprintf( create, template, table, username, realm );
template = "SELECT `%s`,`%s`,`%s`,`%s` FROM `%s` WHERE `%s` = '%%s' AND `%s` = '%%s'";
select = malloc( strlen( template )
+ strlen( mysqlcontext->field_password )
+ strlen( mysqlcontext->field_hash ) + strlen( mysqlcontext->field_token )
+ strlen( mysqlcontext->field_sequence )
+ strlentur );
sprintf( select, template
, mysqlcontext->field_password
, mysqlcontext->field_hash, mysqlcontext->field_token, mysqlcontext->field_sequence
, table, username, realm );
template = "UPDATE `%s` SET `%s` = '%%s' WHERE `%s` = '%%s' AND `%s` = '%%s'";
setpassword = malloc( strlen( template ) + strlentur + strlen( mysqlcontext->field_password ) );
sprintf( setpassword, template, table, mysqlcontext->field_password, username, realm );
template = "UPDATE `%s` SET `%s` = '%%s', `%s` = '%%s', `%s` = '%%d' WHERE `%s` = '%%s' AND `%s` = '%%s'";
setzerok = malloc( strlen( template ) + strlentur
+ strlen( mysqlcontext->field_hash ) + strlen( mysqlcontext->field_token )
+ strlen( mysqlcontext->field_sequence ) );
sprintf( setzerok, template, table
, mysqlcontext->field_hash, mysqlcontext->field_token, mysqlcontext->field_sequence
, username, realm );
template = "DELETE FROM `%s` WHERE `%s` = '%%s' AND `%s` = '%%s'";
delete = malloc( strlen( template ) + strlentur );
sprintf( delete, template, table, username, realm );
mysqlcontext->sql_create = strdup(_ar_mysql_param( ar->c2s->config
, "authreg.mysql.sql.create"
, create ));
if( _ar_mysql_check_sql( ar, mysqlcontext->sql_create, "ss" ) != 0 ) return 1;
mysqlcontext->sql_select = strdup(_ar_mysql_param( ar->c2s->config
, "authreg.mysql.sql.select"
, select ));
if( _ar_mysql_check_sql( ar, mysqlcontext->sql_select, "ss" ) != 0 ) return 1;
mysqlcontext->sql_setpassword = strdup(_ar_mysql_param( ar->c2s->config
, "authreg.mysql.sql.setpassword"
, setpassword ));
if( _ar_mysql_check_sql( ar, mysqlcontext->sql_setpassword, "sss" ) != 0 ) return 1;
mysqlcontext->sql_setzerok = strdup(_ar_mysql_param( ar->c2s->config
, "authreg.mysql.sql.setzerok"
, setzerok ));
if( _ar_mysql_check_sql( ar, mysqlcontext->sql_setzerok, "ssdss" ) != 0 ) return 1;
mysqlcontext->sql_delete = strdup(_ar_mysql_param( ar->c2s->config
, "authreg.mysql.sql.delete"
, delete ));
if( _ar_mysql_check_sql( ar, mysqlcontext->sql_delete, "ss" ) != 0 ) return 1;
log_debug( ZONE, "SQL to create account: %s", mysqlcontext->sql_create );
log_debug( ZONE, "SQL to query user information: %s", mysqlcontext->sql_select );
log_debug( ZONE, "SQL to set password: %s", mysqlcontext->sql_setpassword );
log_debug( ZONE, "SQL to set zero K: %s", mysqlcontext->sql_setzerok );
log_debug( ZONE, "SQL to delete account: %s", mysqlcontext->sql_delete );
free(create);
free(select);
free(setpassword);
free(setzerok);
free(delete);
host = config_get_one(ar->c2s->config, "authreg.mysql.host", 0);
port = config_get_one(ar->c2s->config, "authreg.mysql.port", 0);
dbname = config_get_one(ar->c2s->config, "authreg.mysql.dbname", 0);
user = config_get_one(ar->c2s->config, "authreg.mysql.user", 0);
pass = config_get_one(ar->c2s->config, "authreg.mysql.pass", 0);
if(host == NULL || port == NULL || dbname == NULL || user == NULL || pass == NULL) {
log_write(ar->c2s->log, LOG_ERR, "mysql: invalid module config");
return 1;
}
log_debug( ZONE, "mysql connecting as '%s' to database '%s' on %s:%s", user, dbname, host, port );
conn = mysql_init(NULL);
mysqlcontext->conn = conn;
if(conn == NULL) {
log_write(ar->c2s->log, LOG_ERR, "mysql: unable to allocate database connection state");
return 1;
}
#if MYSQL_VERSION_ID > 32349
mysql_options(conn, MYSQL_READ_DEFAULT_GROUP, "jabberd");
#endif
if(mysql_real_connect(conn, host, user, "", dbname, atoi(port), NULL, CLIENT_INTERACTIVE) == NULL) {
log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database failed: %s", mysql_error(conn));
return 1;
}
conn->reconnect = 1;
#ifdef APPLE_ENABLE_OD_AUTH
log_debug( ZONE, "APPLE: initializing OD auth functions." );
ar->user_exists = _ar_od_user_exists;
ar->check_password = _ar_od_check_password;
ar->create_challenge = _ar_od_create_challenge;
ar->check_response = _ar_od_check_response;
#else
ar->user_exists = _ar_mysql_user_exists;
ar->get_password = _ar_mysql_get_password;
ar->set_password = _ar_mysql_set_password;
ar->get_zerok = _ar_mysql_get_zerok;
ar->set_zerok = _ar_mysql_set_zerok;
ar->create_user = _ar_mysql_create_user;
ar->delete_user = _ar_mysql_delete_user;
#endif
return 0;
}
#endif