Commit cd4ef84c authored by pierre-luc's avatar pierre-luc
Browse files

[#1959] This adds the ability to store password as an MD5 Hash in the

configuration file. The option is available from the "general" tab under
"preferences" in sflphone-client-gnome.
parent 97222b7a
......@@ -149,24 +149,32 @@ edit_account(GtkWidget *widget UNUSED, gpointer data UNUSED)
/**
* Add an account
*/
static void
static void
add_account(GtkWidget *widget UNUSED, gpointer data UNUSED)
{
show_account_window(NULL);
}
void
static void
set_md5_hash_cb(GtkWidget *widget UNUSED, gpointer data UNUSED)
{
gboolean enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
dbus_set_md5_credential_hashing(enabled);
}
static void
start_hidden( void )
{
dbus_start_hidden();
}
void
static void
set_popup_mode( void )
{
dbus_switch_popup_mode();
}
void
set_notif_level( )
{
......@@ -573,7 +581,7 @@ create_general_settings ()
GtkWidget *mutewidget;
GtkWidget *trayItem;
GtkWidget *frame;
GtkWidget *history_w;
GtkWidget *checkBoxWidget;
GtkWidget *label;
GtkWidget *entryPort;
GtkWidget *table;
......@@ -629,20 +637,28 @@ create_general_settings ()
gnome_main_section_new_with_table (_("Calls History"), &frame, &table, 3, 1);
gtk_box_pack_start(GTK_BOX(ret), frame, FALSE, FALSE, 0);
history_w = gtk_check_button_new_with_mnemonic(_("_Keep my history for at least"));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (history_w), history_enabled);
g_signal_connect (G_OBJECT (history_w) , "clicked" , G_CALLBACK (history_enabled_cb) , NULL);
gtk_table_attach( GTK_TABLE(table), history_w, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 5);
checkBoxWidget = gtk_check_button_new_with_mnemonic(_("_Keep my history for at least"));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkBoxWidget), history_enabled);
g_signal_connect (G_OBJECT (checkBoxWidget) , "clicked" , G_CALLBACK (history_enabled_cb) , NULL);
gtk_table_attach( GTK_TABLE(table), checkBoxWidget, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 5);
history_value = gtk_spin_button_new_with_range(1, 99, 1);
gtk_spin_button_set_value (GTK_SPIN_BUTTON(history_value), history_limit);
g_signal_connect( G_OBJECT (history_value) , "value-changed" , G_CALLBACK (history_limit_cb) , history_value);
gtk_widget_set_sensitive (GTK_WIDGET (history_value), gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (history_w)));
gtk_widget_set_sensitive (GTK_WIDGET (history_value), gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkBoxWidget)));
gtk_table_attach( GTK_TABLE(table), history_value, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 5);
label = gtk_label_new(_("days"));
gtk_table_attach( GTK_TABLE(table), label, 2, 3, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 5);
// Configuration File
gnome_main_section_new_with_table (_("Configuration File"), &frame, &table, 1, 1);
gtk_box_pack_start(GTK_BOX(ret), frame, FALSE, FALSE, 0);
checkBoxWidget = gtk_check_button_new_with_mnemonic(_("Store SIP credentials as MD5 hash"));
gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(checkBoxWidget), dbus_is_md5_credential_hashing() );
g_signal_connect(G_OBJECT( checkBoxWidget ) , "clicked" , G_CALLBACK(set_md5_hash_cb) , NULL);
gtk_table_attach( GTK_TABLE(table), checkBoxWidget, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 5);
/** PULSEAUDIO CONFIGURATION */
gnome_main_section_new_with_table (_("PulseAudio sound server"), &frame, &table, 1, 1);
gtk_box_pack_start(GTK_BOX(ret), frame, FALSE, FALSE, 0);
......
......@@ -203,7 +203,15 @@
</method>
<!-- General Settings Panel -->
<method name="isMd5CredentialHashing">
<arg type="b" name="res" direction="out"/>
</method>
<method name="setMd5CredentialHashing">
<arg type="b" name="enabled" direction="in"/>
</method>
<method name="isIax2Enabled">
<arg type="i" name="res" direction="out"/>
</method>
......
......@@ -1211,6 +1211,36 @@ dbus_ringtone_enabled()
}
}
gboolean
dbus_is_md5_credential_hashing()
{
int res;
GError* error = NULL;
org_sflphone_SFLphone_ConfigurationManager_is_md5_credential_hashing(
configurationManagerProxy,
&res,
&error);
if(error)
{
g_error_free(error);
}
return res;
}
void
dbus_set_md5_credential_hashing(gboolean enabled)
{
GError* error = NULL;
org_sflphone_SFLphone_ConfigurationManager_set_md5_credential_hashing(
configurationManagerProxy,
&enabled,
&error);
if(error)
{
g_error_free(error);
}
}
int
dbus_is_iax2_enabled()
{
......
......@@ -299,6 +299,22 @@ int dbus_get_audio_device_index(const gchar* name);
*/
gchar* dbus_get_current_audio_output_plugin();
/**
* ConfigurationManager - Query to server to
* know if MD5 credential hashing is enabled.
* @return True if enabled, false otherwise
*
*/
gboolean dbus_is_md5_credential_hashing();
/**
* ConfigurationManager - Set whether or not
* the server should store credential as
* a md5 hash.
* @param enabled
*/
void dbus_set_md5_credential_hashing(gboolean enabled);
/**
* ConfigurationManager - Tells the GUI if IAX2 support is enabled
* @return int 1 if IAX2 is enabled
......
......@@ -203,7 +203,15 @@
</method>
<!-- General Settings Panel -->
<method name="isMd5CredentialHashing">
<arg type="b" name="res" direction="out"/>
</method>
<method name="setMd5CredentialHashing">
<arg type="b" name="enabled" direction="in"/>
</method>
<method name="isIax2Enabled">
<arg type="i" name="res" direction="out"/>
</method>
......
......@@ -84,45 +84,7 @@ ConfigurationManager::setCredential (const std::string& accountID, const int32_t
{
_debug ("ConfigurationManager::setCredential received\n");
std::map<std::string, std::string>::iterator it;
std::map<std::string, std::string> credentialInformation = details;
std::string credentialIndex;
std::stringstream streamOut;
streamOut << index;
credentialIndex = streamOut.str();
std::string section = "Credential" + std::string(":") + accountID + std::string(":") + credentialIndex;
_debug("Setting credential in section %s\n", section.c_str());
it = credentialInformation.find(USERNAME);
if(it == credentialInformation.end()) {
Manager::instance().setConfig (section, USERNAME, EMPTY_FIELD);
} else {
Manager::instance().setConfig (section, USERNAME, it->second);
}
_debug("Username: %s\n", it->second.c_str());
it = credentialInformation.find(PASSWORD);
if(it == credentialInformation.end()) {
Manager::instance().setConfig (section, PASSWORD, EMPTY_FIELD);
} else {
Manager::instance().setConfig (section, PASSWORD, it->second);
}
_debug("Password: %s\n", it->second.c_str());
it = credentialInformation.find(REALM);
if(it == credentialInformation.end()) {
Manager::instance().setConfig (section, REALM, EMPTY_FIELD);
} else {
Manager::instance().setConfig (section, REALM, it->second);
}
_debug("Realm: %s\n", it->second.c_str());
Manager::instance().setCredential(accountID, index, details);
}
void
......@@ -322,6 +284,18 @@ ConfigurationManager::getRecordDeviceList()
}
bool
ConfigurationManager::isMd5CredentialHashing(void)
{
return Manager::instance().getMd5CredentialHashing();
}
void
ConfigurationManager::setMd5CredentialHashing(const bool& enabled)
{
Manager::instance().setMd5CredentialHashing(enabled);
}
int32_t
ConfigurationManager::isIax2Enabled (void)
{
......
......@@ -78,6 +78,8 @@ public:
int32_t getAudioManager( void );
void setAudioManager( const int32_t& api );
bool isMd5CredentialHashing (void);
void setMd5CredentialHashing (const bool& enabled);
int32_t isIax2Enabled( void );
int32_t isRingtoneEnabled( void );
void ringtoneEnabled( void );
......
......@@ -53,6 +53,8 @@
#define fill_config_int(name, value) \
(_config.addConfigTreeItem(section, Conf::ConfigTreeItem(std::string(name), std::string(value), type_int)))
#define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, len)
ManagerImpl::ManagerImpl (void)
: _hasTriedToRegister (false)
, _config()
......@@ -280,8 +282,6 @@ ManagerImpl::outgoingCall (const std::string& accountid, const CallID& id, const
bool
ManagerImpl::answerCall (const CallID& id)
{
bool isActive = false;
stopTone (true);
AccountID currentAccountId;
......@@ -1359,6 +1359,7 @@ ManagerImpl::initConfigFile (bool load_user_value, std::string alternate)
fill_config_int (CONFIG_PA_VOLUME_CTRL , YES_STR);
fill_config_int (CONFIG_SIP_PORT, DFT_SIP_PORT);
fill_config_str (CONFIG_ACCOUNTS_ORDER, "");
fill_config_int (CONFIG_MD5HASH, NO_STR);
section = ADDRESSBOOK;
fill_config_int (ADDRESSBOOK_ENABLE, YES_STR);
......@@ -1782,6 +1783,22 @@ ManagerImpl::setRecordPath (const std::string& recPath)
setConfig (AUDIO, RECORD_PATH, recPath);
}
bool
ManagerImpl::getMd5CredentialHashing(void)
{
return getConfigInt(PREFERENCES, CONFIG_MD5HASH);
}
void
ManagerImpl::setMd5CredentialHashing(bool enabled)
{
if (enabled) {
setConfig(PREFERENCES, CONFIG_MD5HASH, YES_STR);
} else {
setConfig(PREFERENCES, CONFIG_MD5HASH, NO_STR);
}
}
int
ManagerImpl::getDialpad (void)
{
......@@ -2490,6 +2507,113 @@ std::map< std::string, std::string > ManagerImpl::getAccountDetails (const Accou
return a;
}
/* Transform digest to string.
* output must be at least PJSIP_MD5STRLEN+1 bytes.
* Helper function taken from sip_auth_client.c in
* pjproject-1.0.3.
*
* NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
*/
void ManagerImpl::digest2str(const unsigned char digest[], char *output)
{
int i;
for (i = 0; i<16; ++i) {
pj_val_to_hex_digit(digest[i], output);
output += 2;
}
}
std::string ManagerImpl::computeMd5HashFromCredential(const std::string& username, const std::string& password, const std::string& realm)
{
pj_md5_context pms;
unsigned char digest[16];
char ha1[PJSIP_MD5STRLEN];
pj_str_t usernamePjFormat = pj_str(strdup(username.c_str()));
pj_str_t passwordPjFormat = pj_str(strdup(password.c_str()));
pj_str_t realmPjFormat = pj_str(strdup(realm.c_str()));
/* Compute md5 hash = MD5(username ":" realm ":" password) */
pj_md5_init(&pms);
MD5_APPEND( &pms, usernamePjFormat.ptr, usernamePjFormat.slen);
MD5_APPEND( &pms, ":", 1);
MD5_APPEND( &pms, realmPjFormat.ptr, realmPjFormat.slen);
MD5_APPEND( &pms, ":", 1);
MD5_APPEND( &pms, passwordPjFormat.ptr, passwordPjFormat.slen);
pj_md5_final(&pms, digest);
digest2str(digest, ha1);
char ha1_null_terminated[PJSIP_MD5STRLEN+1];
memcpy(ha1_null_terminated, ha1, sizeof(char)*PJSIP_MD5STRLEN);
ha1_null_terminated[PJSIP_MD5STRLEN] = '\0';
std::string hashedDigest = ha1_null_terminated;
return hashedDigest;
}
void ManagerImpl::setCredential (const std::string& accountID, const int32_t& index, const std::map< std::string, std::string >& details)
{
std::map<std::string, std::string>::iterator it;
std::map<std::string, std::string> credentialInformation = details;
std::string credentialIndex;
std::stringstream streamOut;
streamOut << index;
credentialIndex = streamOut.str();
std::string section = "Credential" + std::string(":") + accountID + std::string(":") + credentialIndex;
_debug("Setting credential in section %s\n", section.c_str());
it = credentialInformation.find(USERNAME);
std::string username;
if (it == credentialInformation.end()) {
username = EMPTY_FIELD;
} else {
username = it->second;
}
Manager::instance().setConfig (section, USERNAME, username);
it = credentialInformation.find(REALM);
std::string realm;
if (it == credentialInformation.end()) {
realm = EMPTY_FIELD;
} else {
realm = it->second;
}
Manager::instance().setConfig (section, REALM, realm);
it = credentialInformation.find(PASSWORD);
std::string password;
if (it == credentialInformation.end()) {
password = EMPTY_FIELD;
} else {
password = it->second;
}
if(getMd5CredentialHashing()) {
// TODO: Fix this.
// This is an extremly weak test in order to check
// if the password is a hashed value. This is done
// because deleteCredential() is called before this
// method. Therefore, we cannot check if the value
// is different from the one previously stored in
// the configuration file.
if(password.length() != 32) {
password = computeMd5HashFromCredential(username, password, realm);
}
}
Manager::instance().setConfig (section, PASSWORD, password);
}
//TODO: tidy this up. Make a macro or inline
// method to reduce the if/else mess.
void ManagerImpl::setAccountDetails (const std::string& accountID, const std::map< std::string, std::string >& details)
{
......@@ -2512,22 +2636,61 @@ void ManagerImpl::setAccountDetails (const std::string& accountID, const std::ma
( (iter = map_cpy.find (CONFIG_ACCOUNT_ENABLE)) == map_cpy.end ()) ? setConfig (accountID, CONFIG_ACCOUNT_ENABLE, "0")
: setConfig (accountID, CONFIG_ACCOUNT_ENABLE, iter->second == "TRUE" ? "1"
: "0");
if(!getMd5CredentialHashing()) {
if((iter = map_cpy.find(AUTHENTICATION_USERNAME)) == map_cpy.end())
{ setConfig (accountID, AUTHENTICATION_USERNAME, EMPTY_FIELD); }
else
{ setConfig (accountID, AUTHENTICATION_USERNAME, iter->second); }
if((iter = map_cpy.find(USERNAME)) == map_cpy.end())
{ setConfig (accountID, USERNAME, EMPTY_FIELD); }
else
{ setConfig (accountID, USERNAME, iter->second); }
if((iter = map_cpy.find(PASSWORD)) == map_cpy.end())
{ setConfig (accountID, PASSWORD, EMPTY_FIELD); }
else
{ setConfig (accountID, PASSWORD, iter->second); }
if((iter = map_cpy.find(REALM)) == map_cpy.end())
{ setConfig (accountID, REALM, EMPTY_FIELD); }
else
{ setConfig (accountID, REALM, iter->second); }
} else {
std::string username;
std::string authenticationName;
std::string password;
std::string realm;
if((iter = map_cpy.find(AUTHENTICATION_USERNAME)) == map_cpy.end()) { authenticationName = EMPTY_FIELD; } else { authenticationName = iter->second; }
if((iter = map_cpy.find(USERNAME)) == map_cpy.end()) { username = EMPTY_FIELD; } else { username = iter->second; }
if((iter = map_cpy.find(PASSWORD)) == map_cpy.end()) { password = EMPTY_FIELD; } else { password = iter->second; }
if((iter = map_cpy.find(REALM)) == map_cpy.end()) { realm = EMPTY_FIELD; } else { realm = iter->second; }
// Make sure not to re-hash the password field if
// it is already saved as a MD5 Hash.
// TODO: This test is weak. Fix this.
if ((password.compare(getConfigString(accountID, PASSWORD)) != 0) && (password.length() != 32)) {
_debug("Password sent and password from config are different. Re-hashing\n");
std::string hash;
if(authenticationName.empty()) {
hash = computeMd5HashFromCredential(username, password, realm);
} else {
hash = computeMd5HashFromCredential(authenticationName, password, realm);
}
setConfig(accountID, PASSWORD, hash);
}
}
( (iter = map_cpy.find (CONFIG_ACCOUNT_RESOLVE_ONCE)) == map_cpy.end ()) ? setConfig (accountID, CONFIG_ACCOUNT_RESOLVE_ONCE, DFT_RESOLVE_ONCE)
: setConfig (accountID, CONFIG_ACCOUNT_RESOLVE_ONCE, iter->second == "TRUE" ? "1"
: "0");
( (iter = map_cpy.find (USERNAME)) == map_cpy.end ()) ? setConfig (accountID, USERNAME, EMPTY_FIELD)
: setConfig (accountID, USERNAME, iter->second);
( (iter = map_cpy.find (PASSWORD)) == map_cpy.end ()) ? setConfig (accountID, PASSWORD, EMPTY_FIELD)
: setConfig (accountID, PASSWORD, iter->second);
( (iter = map_cpy.find (HOSTNAME)) == map_cpy.end ()) ? setConfig (accountID, HOSTNAME, EMPTY_FIELD)
: setConfig (accountID, HOSTNAME, iter->second);
( (iter = map_cpy.find (REALM)) == map_cpy.end ()) ? setConfig (accountID, REALM, std::string("*"))
: setConfig (accountID, REALM, iter->second);
( (iter = map_cpy.find (CONFIG_ACCOUNT_MAILBOX)) == map_cpy.end ()) ? setConfig (accountID, CONFIG_ACCOUNT_MAILBOX, EMPTY_FIELD)
: setConfig (accountID, CONFIG_ACCOUNT_MAILBOX, iter->second);
......@@ -2535,8 +2698,6 @@ void ManagerImpl::setAccountDetails (const std::string& accountID, const std::ma
( (iter = map_cpy.find (CONFIG_ACCOUNT_REGISTRATION_EXPIRE)) == map_cpy.end ()) ? setConfig (accountID, CONFIG_ACCOUNT_REGISTRATION_EXPIRE, DFT_EXPIRE_VALUE)
: setConfig (accountID, CONFIG_ACCOUNT_REGISTRATION_EXPIRE, iter->second);
( (iter = map_cpy.find (AUTHENTICATION_USERNAME)) == map_cpy.end ()) ? setConfig (accountID, AUTHENTICATION_USERNAME, EMPTY_FIELD)
: setConfig (accountID, AUTHENTICATION_USERNAME, iter->second);
saveConfig();
acc = getAccount (accountID);
......@@ -2545,8 +2706,9 @@ void ManagerImpl::setAccountDetails (const std::string& accountID, const std::ma
if (acc->isEnabled()) {
acc->unregisterVoIPLink();
acc->registerVoIPLink();
} else
} else {
acc->unregisterVoIPLink();
}
// Update account details to the client side
if (_dbus) _dbus->getConfigurationManager()->accountsChanged();
......
......@@ -491,6 +491,26 @@ class ManagerImpl {
*/
void setRecordPath( const std::string& recPath);
/**
* Set a credential for a given account. If it
* does not exist yet, it will be created.
*/
void setCredential (const std::string& accountID, const int32_t& index, const std::map< std::string, std::string >& details);
/**
* Set whether we should pre-hash the credentials
* in config file.
*
* @param enabled True if hashing should be used, false otherwise.
*/
void setMd5CredentialHashing(bool enabled);
/**
* Retreive the value set in the configuration file.
* @return True if credentials hashing is enabled.
*/
bool getMd5CredentialHashing(void);
/**
* Tells if the user wants to display the dialpad or not
* @return int 1 if dialpad has to be displayed
......@@ -906,6 +926,22 @@ class ManagerImpl {
bool initAudioDriver(void);
private:
/* Transform digest to string.
* output must be at least PJSIP_MD5STRLEN+1 bytes.
* Helper function taken from sip_auth_client.c in
* pjproject-1.0.3.
*
* NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
*/
void digest2str(const unsigned char digest[], char *output);
/**
* Helper function that creates an MD5 Hash from the credential
* information provided as parameters. The hash is computed as
* MD5(username ":" realm ":" password).
*
*/
std::string computeMd5HashFromCredential(const std::string& username, const std::string& password, const std::string& realm);
/**
* Check if a process is running with the system command
......
......@@ -71,7 +71,17 @@ int SIPAccount::registerVoIPLink()
int credentialCount = 0;
credentialCount = Manager::instance().getConfigInt (_accountID, CONFIG_CREDENTIAL_NUMBER);
credentialCount += 1;
int md5HashingEnabled = 0;
int dataType = 0;
md5HashingEnabled = Manager::instance().getConfigInt(PREFERENCES, CONFIG_MD5HASH);
std::string digest;
if (md5HashingEnabled) {
dataType = PJSIP_CRED_DATA_DIGEST;
} else {
dataType = PJSIP_CRED_DATA_PLAIN_PASSWD;
}
pjsip_cred_info * cred_info = (pjsip_cred_info *) malloc(sizeof(pjsip_cred_info)*(credentialCount));
if (cred_info == NULL) {
_debug("Failed to set cred_info for account %s\n", _accountID.c_str());
......@@ -86,7 +96,7 @@ int SIPAccount::registerVoIPLink()
}
cred_info[0].data = pj_str(strdup(_password.c_str()));
cred_info[0].realm = pj_str(strdup(_realm.c_str()));
cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
cred_info[0].data_type = dataType;
cred_info[0].scheme = pj_str("digest");
int i;
......@@ -105,7 +115,7 @@ int SIPAccount::registerVoIPLink()
cred_info[i].username = pj_str(strdup(username.c_str()));
cred_info[i].data = pj_str(strdup(password.c_str()));
cred_info[i].realm = pj_str(strdup(realm.c_str()));
cred_info[i].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
cred_info[i].data_type = dataType;
cred_info[i].scheme = pj_str("digest");
_debug("Setting credential %d realm = %s passwd = %s username = %s data_type = %d\n", i, realm.c_str(), password.c_str(), username.c_str(), cred_info[i].data_type);
......
......@@ -46,6 +46,7 @@
#define RECORD_PATH "Record.path" /** Recording path */