pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Fix some GWarnings during finch's startup
21 months ago, Gary Kramlich
01b94846bb60
Fix some GWarnings during finch's startup
Testing Done:
Ran via `G_DEBUG=fatal-warnings gdb --ex run finch3` and verified I was able to make it to the contact list.
Reviewed at https://reviews.imfreedom.org/r/1592/
/*
* Purple - Internet Messaging Library
* Copyright (C) Pidgin Developers <devel@pidgin.im>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <https://www.gnu.org/licenses/>.
*/
#include
<glib/gi18n-lib.h>
#include
<gplugin.h>
#include
<gplugin-native.h>
#include
<purple.h>
#include
<CoreFoundation/CoreFoundation.h>
#include
<Security/Security.h>
#define KEYCHAIN_ACCESS_ID "purple/keychain-access"
#define KEYCHAIN_ACCESS_NAME N_("Keychain Access")
#define KEYCHAIN_ACCESS_SUMMARY N_("Keychain Access credential provider")
#define KEYCHAIN_ACCESS_DESCRIPTION N_("This plugin will store passwords in " \
"Keychain Access on macOS.")
#define KEYCHAIN_ACCESS_DOMAIN (g_quark_from_static_string("keychain-access"))
#define PURPLE_TYPE_KEYCHAIN_ACCESS (purple_keychain_access_get_type())
G_DECLARE_FINAL_TYPE
(
PurpleKeychainAccess
,
purple_keychain_access
,
PURPLE
,
KEYCHAIN_ACCESS
,
PurpleCredentialProvider
)
struct
_PurpleKeychainAccess
{
PurpleCredentialProvider
parent
;
};
G_DEFINE_DYNAMIC_TYPE
(
PurpleKeychainAccess
,
purple_keychain_access
,
PURPLE_TYPE_CREDENTIAL_PROVIDER
)
/* Most of this work is heavily based off of
* https://stackoverflow.com/a/58850099.
*/
/******************************************************************************
* Globals
*****************************************************************************/
static
PurpleCredentialProvider
*
instance
=
NULL
;
/******************************************************************************
* Helpers
*****************************************************************************/
static
void
purple_keychain_access_set_task_error_from_osstatus
(
GTask
*
task
,
OSStatus
result
)
{
CFStringRef
error_msg
;
gchar
buff
[
255
];
gboolean
converted
=
FALSE
;
error_msg
=
SecCopyErrorMessageString
(
result
,
NULL
);
converted
=
CFStringGetCString
(
error_msg
,
buff
,
sizeof
(
buff
),
kCFStringEncodingUTF8
);
if
(
converted
)
{
g_task_return_new_error
(
task
,
KEYCHAIN_ACCESS_DOMAIN
,
result
,
"%s"
,
buff
);
}
else
{
g_task_return_new_error
(
task
,
KEYCHAIN_ACCESS_DOMAIN
,
result
,
"unknown error"
);
}
CFRelease
(
error_msg
);
}
/******************************************************************************
* PurpleCredentialProvider Implementation
*****************************************************************************/
static
void
purple_keychain_access_read_password_async
(
PurpleCredentialProvider
*
provider
,
PurpleAccount
*
account
,
GCancellable
*
cancellable
,
GAsyncReadyCallback
callback
,
gpointer
data
)
{
GTask
*
task
=
NULL
;
const
gchar
*
account_name
=
NULL
;
CFStringRef
keys
[
4
];
CFStringRef
cf_account_name
;
CFTypeRef
values
[
4
];
CFTypeRef
item
;
CFDictionaryRef
query
;
OSStatus
result
;
g_return_if_fail
(
PURPLE_IS_CREDENTIAL_PROVIDER
(
provider
));
g_return_if_fail
(
PURPLE_IS_ACCOUNT
(
account
));
account_name
=
purple_account_get_username
(
account
);
cf_account_name
=
CFStringCreateWithCString
(
kCFAllocatorDefault
,
account_name
,
kCFStringEncodingUTF8
);
/* Set our security class. */
keys
[
0
]
=
kSecClass
;
values
[
0
]
=
kSecClassGenericPassword
;
/* Set the username. */
keys
[
1
]
=
kSecAttrAccount
;
values
[
1
]
=
cf_account_name
;
/* Make sure the password is decrypted. */
keys
[
2
]
=
kSecReturnData
;
values
[
2
]
=
kCFBooleanTrue
;
/* Limit to a single result. */
keys
[
3
]
=
kSecMatchLimit
;
values
[
3
]
=
kSecMatchLimitOne
;
/* Build our query. */
query
=
CFDictionaryCreate
(
kCFAllocatorDefault
,
(
const
void
**
)
keys
,
(
const
void
**
)
values
,
4
,
NULL
,
NULL
);
/* Finally query the item. */
result
=
SecItemCopyMatching
(
query
,
&
item
);
/* Cleanup */
CFRelease
(
cf_account_name
);
CFRelease
(
query
);
/* Create the task and return our results. */
task
=
g_task_new
(
provider
,
cancellable
,
callback
,
data
);
if
(
result
==
errSecSuccess
)
{
if
(
CFGetTypeID
(
item
)
!=
CFDataGetTypeID
())
{
g_task_return_new_error
(
task
,
KEYCHAIN_ACCESS_DOMAIN
,
0
,
"unexpected item found"
);
}
else
{
CFStringRef
cf_password
;
gchar
buff
[
255
];
cf_password
=
CFStringCreateFromExternalRepresentation
(
kCFAllocatorDefault
,
(
CFDataRef
)
item
,
kCFStringEncodingUTF8
);
if
(
CFStringGetCString
(
cf_password
,
buff
,
sizeof
(
buff
),
kCFStringEncodingUTF8
))
{
g_task_return_pointer
(
task
,
g_strdup
(
buff
),
g_free
);
}
else
{
g_task_return_new_error
(
task
,
KEYCHAIN_ACCESS_DOMAIN
,
0
,
"failed to read password"
);
}
CFRelease
(
cf_password
);
}
}
else
{
purple_keychain_access_set_task_error_from_osstatus
(
task
,
result
);
}
g_object_unref
(
task
);
}
static
gchar
*
purple_keychain_access_read_password_finish
(
PurpleCredentialProvider
*
provider
,
GAsyncResult
*
result
,
GError
**
error
)
{
g_return_val_if_fail
(
PURPLE_IS_CREDENTIAL_PROVIDER
(
provider
),
NULL
);
g_return_val_if_fail
(
G_IS_ASYNC_RESULT
(
result
),
NULL
);
return
g_task_propagate_pointer
(
G_TASK
(
result
),
error
);
}
static
void
purple_keychain_access_write_password_async
(
PurpleCredentialProvider
*
provider
,
PurpleAccount
*
account
,
const
gchar
*
password
,
GCancellable
*
cancellable
,
GAsyncReadyCallback
callback
,
gpointer
data
)
{
GTask
*
task
=
NULL
;
const
gchar
*
account_name
=
NULL
;
CFStringRef
keys
[
3
];
CFTypeRef
values
[
3
];
CFDictionaryRef
query
;
CFStringRef
cf_account_name
,
cf_password
;
OSStatus
result
;
g_return_if_fail
(
PURPLE_IS_CREDENTIAL_PROVIDER
(
provider
));
g_return_if_fail
(
PURPLE_IS_ACCOUNT
(
account
));
g_return_if_fail
(
password
!=
NULL
);
account_name
=
purple_account_get_username
(
account
);
cf_account_name
=
CFStringCreateWithCString
(
kCFAllocatorDefault
,
account_name
,
kCFStringEncodingUTF8
);
cf_password
=
CFStringCreateWithCString
(
kCFAllocatorDefault
,
password
,
kCFStringEncodingUTF8
);
/* set our security class */
keys
[
0
]
=
kSecClass
;
values
[
0
]
=
kSecClassGenericPassword
;
/* set the username */
keys
[
1
]
=
kSecAttrAccount
;
values
[
1
]
=
cf_account_name
;
/* set the password */
keys
[
2
]
=
kSecValueData
;
values
[
2
]
=
cf_password
;
/* build our query */
query
=
CFDictionaryCreate
(
kCFAllocatorDefault
,
(
const
void
**
)
keys
,
(
const
void
**
)
values
,
3
,
NULL
,
NULL
);
/* Finally add the item */
result
=
SecItemAdd
(
query
,
NULL
);
/* Cleanup */
CFRelease
(
cf_account_name
);
CFRelease
(
cf_password
);
CFRelease
(
query
);
/* Now create the GTask and set the result. */
task
=
g_task_new
(
provider
,
cancellable
,
callback
,
data
);
if
(
result
==
errSecSuccess
)
{
g_task_return_boolean
(
task
,
TRUE
);
}
else
{
purple_keychain_access_set_task_error_from_osstatus
(
task
,
result
);
}
g_object_unref
(
task
);
}
static
gboolean
purple_keychain_access_write_password_finish
(
PurpleCredentialProvider
*
provider
,
GAsyncResult
*
result
,
GError
**
error
)
{
g_return_val_if_fail
(
PURPLE_IS_CREDENTIAL_PROVIDER
(
provider
),
FALSE
);
g_return_val_if_fail
(
G_IS_ASYNC_RESULT
(
result
),
FALSE
);
return
g_task_propagate_boolean
(
G_TASK
(
result
),
error
);
}
static
void
purple_keychain_access_clear_password_async
(
PurpleCredentialProvider
*
provider
,
PurpleAccount
*
account
,
GCancellable
*
cancellable
,
GAsyncReadyCallback
callback
,
gpointer
data
)
{
GTask
*
task
=
NULL
;
const
gchar
*
account_name
=
NULL
;
CFStringRef
keys
[
2
];
CFTypeRef
values
[
2
];
CFDictionaryRef
query
;
CFStringRef
cf_account_name
;
OSStatus
result
;
g_return_if_fail
(
PURPLE_IS_CREDENTIAL_PROVIDER
(
provider
));
g_return_if_fail
(
PURPLE_IS_ACCOUNT
(
account
));
account_name
=
purple_account_get_username
(
account
);
cf_account_name
=
CFStringCreateWithCString
(
kCFAllocatorDefault
,
account_name
,
kCFStringEncodingUTF8
);
/* set our security class */
keys
[
0
]
=
kSecClass
;
values
[
0
]
=
kSecClassGenericPassword
;
/* set the username */
keys
[
1
]
=
kSecAttrAccount
;
values
[
1
]
=
cf_account_name
;
/* build our query */
query
=
CFDictionaryCreate
(
kCFAllocatorDefault
,
(
const
void
**
)
keys
,
(
const
void
**
)
values
,
2
,
NULL
,
NULL
);
/* Finally add the item */
result
=
SecItemDelete
(
query
);
/* Cleanup */
CFRelease
(
cf_account_name
);
CFRelease
(
query
);
/* Now create the GTask and set the result. */
task
=
g_task_new
(
provider
,
cancellable
,
callback
,
data
);
if
(
result
==
errSecSuccess
)
{
g_task_return_boolean
(
task
,
TRUE
);
}
else
{
purple_keychain_access_set_task_error_from_osstatus
(
task
,
result
);
}
g_object_unref
(
task
);
}
static
gboolean
purple_keychain_access_clear_password_finish
(
PurpleCredentialProvider
*
provider
,
GAsyncResult
*
result
,
GError
**
error
)
{
g_return_val_if_fail
(
PURPLE_IS_CREDENTIAL_PROVIDER
(
provider
),
FALSE
);
g_return_val_if_fail
(
G_IS_ASYNC_RESULT
(
result
),
FALSE
);
return
g_task_propagate_boolean
(
G_TASK
(
result
),
error
);
}
/******************************************************************************
* GObject Implementation
*****************************************************************************/
static
void
purple_keychain_access_init
(
PurpleKeychainAccess
*
keychain_access
)
{
}
static
void
purple_keychain_access_class_init
(
PurpleKeychainAccessClass
*
klass
)
{
PurpleCredentialProviderClass
*
provider_class
=
NULL
;
provider_class
=
PURPLE_CREDENTIAL_PROVIDER_CLASS
(
klass
);
provider_class
->
read_password_async
=
purple_keychain_access_read_password_async
;
provider_class
->
read_password_finish
=
purple_keychain_access_read_password_finish
;
provider_class
->
write_password_async
=
purple_keychain_access_write_password_async
;
provider_class
->
write_password_finish
=
purple_keychain_access_write_password_finish
;
provider_class
->
clear_password_async
=
purple_keychain_access_clear_password_async
;
provider_class
->
clear_password_finish
=
purple_keychain_access_clear_password_finish
;
}
static
void
purple_keychain_access_class_finalize
(
PurpleKeychainAccessClass
*
klass
)
{
}
/******************************************************************************
* API
*****************************************************************************/
static
PurpleCredentialProvider
*
purple_keychain_access_new
(
void
)
{
return
g_object_new
(
PURPLE_TYPE_KEYCHAIN_ACCESS
,
"id"
,
KEYCHAIN_ACCESS_ID
,
"name"
,
KEYCHAIN_ACCESS_NAME
,
"description"
,
_
(
KEYCHAIN_ACCESS_DESCRIPTION
),
NULL
);
}
/******************************************************************************
* Plugin Exports
*****************************************************************************/
static
GPluginPluginInfo
*
keychain_access_query
(
G_GNUC_UNUSED
GError
**
error
)
{
const
gchar
*
const
authors
[]
=
{
"Pidgin Developers <devel@pidgin.im>"
,
NULL
};
return
GPLUGIN_PLUGIN_INFO
(
purple_plugin_info_new
(
"id"
,
KEYCHAIN_ACCESS_ID
,
"name"
,
KEYCHAIN_ACCESS_NAME
,
"version"
,
DISPLAY_VERSION
,
"category"
,
N_
(
"Credentials"
),
"summary"
,
KEYCHAIN_ACCESS_SUMMARY
,
"description"
,
KEYCHAIN_ACCESS_DESCRIPTION
,
"authors"
,
authors
,
"website"
,
PURPLE_WEBSITE
,
"abi-version"
,
PURPLE_ABI_VERSION
,
"flags"
,
PURPLE_PLUGIN_INFO_FLAGS_INTERNAL
|
PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD
,
NULL
));
}
static
gboolean
keychain_access_load
(
GPluginPlugin
*
plugin
,
GError
**
error
)
{
PurpleCredentialManager
*
manager
=
NULL
;
gboolean
ret
=
FALSE
;
purple_keychain_access_register_type
(
G_TYPE_MODULE
(
plugin
));
manager
=
purple_credential_manager_get_default
();
instance
=
purple_keychain_access_new
();
ret
=
purple_credential_manager_register
(
manager
,
instance
,
error
);
if
(
!
ret
)
{
g_clear_object
(
&
instance
);
}
return
ret
;
}
static
gboolean
keychain_access_unload
(
G_GNUC_UNUSED
GPluginPlugin
*
plugin
,
G_GNUC_UNUSED
gboolean
shutdown
,
GError
**
error
)
{
PurpleCredentialManager
*
manager
=
NULL
;
gboolean
ret
=
FALSE
;
manager
=
purple_credential_manager_get_default
();
ret
=
purple_credential_manager_unregister
(
manager
,
instance
,
error
);
if
(
!
ret
)
{
return
ret
;
}
g_clear_object
(
&
instance
);
return
TRUE
;
}
GPLUGIN_NATIVE_PLUGIN_DECLARE
(
keychain_access
)