<?php
// $Id: sql_auth.module,v 1.13 2006/02/23 19:34:19 ber Exp $

/**
 * @file
 * Lets users log in using an external database.
 */

/**
 * Implementation of hook_help().
 */
function sql_auth_help($section) {
  switch ($section) {
    case 'user/help#sql_auth':
      return variable_get('sql_auth_aff_desc', _sql_auth_help());
    case 'admin/modules#description':
      return t('Lets users in another database log in.');
    case 'admin/settings/drupal':
      return NULL;
  }
}

/**
 * Helper for hook_help().
 */
function _sql_auth_help() {
  return t('This function lets you log in with your details from Another Database. If your username in Another Database is Joe, then you should log in with Joe@<strong>%server</strong>.', array('%server' => variable_get('sql_auth_aff_server', 'database_name')));
}

/**
 * Implementation of hook_settings().
 */
function sql_auth_settings() {
  $group = form_textfield(t('Affiliate name'), 'sql_auth_aff_name', variable_get('sql_auth_aff_name', 'Another database'), 55, 128, t('The name of the affiliate, listed no the login page'));
  $group .= form_textarea(t('Affiliate explanation'), 'sql_auth_aff_desc', variable_get('sql_auth_aff_desc', _sql_auth_help()), 55, 15, t('The name of the affiliate, listed no the login page'));
  $group .= form_textfield(t('Affiliate extension'), 'sql_auth_aff_server', variable_get('sql_auth_aff_server', 'database_name'), 55, 128, t('The part after the @. So a user \'foo\' users logs in with foo@database_name'));
  $group .= form_checkbox(t('Use full username string'), 'sql_auth_aff_server_include', 1, variable_get('sql_auth_aff_server_include', 0), t('Wether to send the full login string (including the @database_name) as a username to the database server when authenticating.'));
  $output = form_group(t('User help'), $group);

  $group = NULL;
  $group = form_textfield(t('Database string'), 'sql_auth_string', variable_get('sql_auth_string', 'mysql://username:password@localhost/database'), 55, 128, t('The database string, in the form of mysql://username:password@localhost/database. <strong>Note:!</strong> storing your information, with unecrypted passwords like this is unsafe. In that case, please add a few lines to settings.php:
  <pre>
    $db_url[\'default\'] = \'mysql://drupal:drupal@localhost/drupal\';
    $db_url[\'mydb\'] = \'mysql://user:pwd@localhost/anotherdb\';
  </pre>
  and remove your default $db_url = \'mysql://drupal:drupal@localhost/drupal\';<br/>
  You can then give \'mydb\' in this textfield.<br/>
  <a href="http://drupal.org/node/18429">more info here</a>
  '));
  $group .= form_textfield(t('Database table'), 'sql_auth_table', variable_get('sql_auth_table', 'users'),  55, 128, t('The table where the users are stored.'));
  $group .= form_textfield(t('User column'), 'sql_auth_user_col', variable_get('sql_auth_user_col', 'name'), 55, 128, t('The table column where the username is stored'));
  $group .= form_textfield(t('Password column'), 'sql_auth_pass_col', variable_get('sql_auth_pass_col', 'pass'), 55, 128, t('The table column where the password is stored'));
  $group .= form_select(t('Password scheme'), 'sql_auth_pass_scheme', variable_get('sql_auth_pass_scheme', 'MD5'), _sql_auth_supported_schemes(), t('The password algorithm that is used to store (and check) the password. Drupal uses MD5 authentication by default, but some other systems might use other encryption algorithm. Those are passed to the database backend verbatim, as a function call so support might vary from a database engine to another. See %url for details.', array('url' => 'http://dev.mysql.com/doc/refman/5.0/en/encryption-functions.html')));
  $group .= form_checkbox(t('Encrypt with salt'), 'sql_auth_pass_salt', 1, variable_get('sql_auth_pass_salt', 0), t('Wether to encrypt the password with a salt when checking. Usually only relevant with the ENCRYPT() function.'));
  $output .= form_group(t('Database settings'), $group);
  return $output;
}


/**
 * Implementation of hook_info().
 */
function sql_auth_info($field = 0) {
  $info['name'] = variable_get('sql_auth_aff_name', 'Another database');
  $info['protocol'] = '';//I am unsure what this is for :)

  if ($field) {
    return $info[$field];
  }
  else {
    return $info;
  }
}

/**
 * Implementation of hook_menu().
 */
function sql_auth_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array('path' => 'sql_auth', 'title' => t('Database authentication'),
      'callback' => 'sql_auth_page_help', 'access' => TRUE,
      'type' => MENU_SUGGESTED_ITEM);
  }
  return $items;
}

/**
 * Menu callback; print Drupal-authentication-specific information from user/help.
 */
function sql_auth_page_help() {
  print theme('page', drupal_help('user/help#drupal'));
}

/**
 * Implementation of hook_auth().
 */
function sql_auth_auth($username, $password, $server) {
  if (variable_get('sql_auth_aff_server', 'database_name') == $server) {
    if (variable_get('sql_auth_aff_server_include', 0)) {
      $auth_user = $username . '@' . $server;
    } else {
      $auth_user = $username;
    }
    $result = sql_auth_query($auth_user, $password);
    if ($result === FALSE) {
      drupal_set_message(t('Mysql authentication failed for %user@%server', array('%user' =>  theme('placeholder', $username), '%server' =>  theme('placeholder', $server))), 'error');
      return FALSE;
    }
    else {
      return TRUE;
    }
  }
  else {
    return FALSE;
  }
}

/**
 * Callback function for authenticating clients.
 * Helper for sql_auth_auth
 */
function sql_auth_query($username, $password) {
  global $db_url, $active_db;

  $behold_db_url = $db_url;
  if (!is_array($db_url)) {
    $db_url = array();
    $db_url['default'] = $behold_db_url;
  }

  //add a database to the available ones.
  if (strstr(variable_get('sql_auth_string', 'mysql://username:password@localhost/database'), '://')) {
    //in case we have an url, we should use that.
    $db_url['sql_auth'] = variable_get('sql_auth_string', 'mysql://username:password@localhost/database');
    db_set_active('sql_auth'); //set our remote DB active!
  }
  else {
    //but if we have no url, we can assume that we have a name. use that name!
    db_set_active(variable_get('sql_auth_string', 'mysql://username:password@localhost/database'));
  }

  if (variable_get('sql_auth_pass_salt', 0)) {
    $salt = ', '. variable_get('sql_auth_pass_col', 'pass');
  }
  $res = db_fetch_array(db_query("SELECT %s AS name FROM %s WHERE %s = '%s' AND %s = %s('%s'%s)",
    variable_get('sql_auth_user_col', 'name'),
    variable_get('sql_auth_table', 'users'),
    variable_get('sql_auth_user_col', 'name'),
    $username,
    variable_get('sql_auth_pass_col', 'pass'),
    _sql_auth_current_scheme(),
    $password,
    $salt
    ));

  db_set_active(); //set the connection back

  unset($db_url);
  $db_url = $behold_db_url;

  if (!empty($res['name'])) { //we have a winner!
    return $res['name'];
  }

  return FALSE;
}

/**
 * helper function that lists the supported encryption schemes with a description
 *
 * @returns array associative array of function_name => description pairs
 */
function _sql_auth_supported_schemes() {
  return array('MD5' => 'MD5() authentication', 'ENCRYPT' => 'builtin ENCRYPT() function', 'DES_ENCRYPT' => 'triple-DES encryption', 'SHA' => 'SHA1 encryption', 'PASSWORD' => 'MySQL password encryption', 'OLD_PASSWORD' => 'Older MySQL password encryption');
}

/**
 * helper function to fetch safely the current scheme to encrypt passwords with
 *
 * this is to avoid giving the permission to admins to call an arbitrary SQL function
 */
function _sql_auth_current_scheme() {
  $scheme = variable_get('sql_auth_pass_scheme', 'MD5');
  /* security: allow only certain functions to be called */
  if (!array_key_exists($scheme, _sql_auth_supported_schemes())) {
    $scheme = 'MD5';
  }
  return $scheme;
}
