sml_auth.c
/*
* libsyncml - A syncml protocol implementation
* Copyright (C) 2005 Armin Bauer <armin.bauer@opensync.org>
* Copyright (C) 2008 Michael Bell <michael.bell@opensync.org>
*
* 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.1 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <libsyncml/syncml.h>
#include "sml_auth.h"
#include <libsyncml/syncml_internals.h>
#include "sml_auth_internals.h"
#include <libsyncml/sml_session_internals.h>
#include <libsyncml/sml_elements_internals.h>
#include <libsyncml/sml_command_internals.h>
#include "libsyncml/sml_error_internals.h"
static SmlStatus *_smlAuthHeaderReply(
SmlSession *session,
SmlErrorType code,
SmlAuthType auth,
SmlError **error);
void _status_callback(SmlSession *session, SmlStatus *status, void *userdata)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, userdata);
smlTrace(TRACE_EXIT, "%s", __func__);
}
void _header_callback(SmlSession *session, SmlHeader *header, SmlCred *cred, void *userdata)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, session, header, cred, userdata);
smlAssert(session);
smlAssert(userdata);
SmlStatus *reply = NULL;
SmlAuthenticator *auth = userdata;
SmlError *error = NULL;
if (session->sessionType == SML_SESSION_TYPE_CLIENT) {
/* If this is an OMA DS client then there will be no
* authentication. Only OMA DS servers can request an
* authentication from the remote peer.
*/
g_warning("This is an OMA DS client. An OMA DS client should not use this authentication callback.");
smlTrace(TRACE_INTERNAL, "%s: This is an OMA DS client and so auth is not supported.", __func__);
auth->state = SML_NO_ERROR;
if (auth->enabled)
smlTrace(TRACE_ERROR,
"%s: authentication is enabled but this is an OMA DS client.");
}
else if (!cred)
{
if (!auth->enabled)
{
smlTrace(TRACE_INTERNAL, "%s: Auth is disabled and no cred given", __func__);
auth->state = SML_NO_ERROR;
} else {
/* auth->enabled */
if (auth->state != SML_AUTH_ACCEPTED)
{
/* Ask the remote peer to authenticate.
* This is not an error because it is really dangerous
* for the remote peer to send its password without
* be asked for it. Please note that syncml:auth-basic
* is a clear text password.
*/
smlTrace(TRACE_INTERNAL, "%s: Auth is required", __func__);
auth->state = SML_ERROR_AUTH_REQUIRED;
session->authenticate = TRUE;
}
else
{
/* auth->state == SML_AUTH_ACCEPTED */
smlTrace(TRACE_INTERNAL, "%s: Auth is already accepted.", __func__);
auth->state = SML_AUTH_ACCEPTED;
}
}
} else {
/* cred available */
smlTrace(TRACE_INTERNAL, "%s: Cred is \"%s\"", __func__, VA_STRING(cred->data));
if (!auth->enabled)
{
smlTrace(TRACE_INTERNAL, "%s: Cred received but unwanted", __func__);
auth->state = SML_AUTH_ACCEPTED;
} else {
/* auth->enabled */
if (auth->verifyCallback)
{
/* The callback needs the following stuff:
* Chal
* Cred
* LocName(username)
*/
if (auth->verifyCallback(session->chal, cred,
smlLocationGetName(session->source),
auth->verifyCallbackUserdata, &error))
{
auth->state = SML_AUTH_ACCEPTED;
} else {
smlErrorSet(&error, SML_ERROR_AUTH_REJECTED,
"Auth rejected for username %s",
smlLocationGetName(session->source));
smlSessionDispatchEvent(session, SML_SESSION_EVENT_ERROR, NULL, NULL, NULL, error);
smlErrorDeref(&error);
auth->state = SML_ERROR_AUTH_REJECTED;
}
} else {
smlTrace(TRACE_INTERNAL, "%s: No verify callback set", __func__);
auth->state = SML_ERROR_AUTH_REJECTED;
}
}
}
if (auth->state == SML_ERROR_AUTH_REJECTED) {
smlTrace(TRACE_INTERNAL, "%s: Ending session due to wrong / missing creds", __func__);
session->end = TRUE;
}
reply = _smlAuthHeaderReply(session, auth->state, auth->type, &error);
if (!reply)
goto error;
if (!smlSessionSendReply(session, reply, &error)) {
smlStatusUnref(reply);
goto error;
}
smlStatusUnref(reply);
if (!session->established && !session->end &&
!session->authenticate &&
session->sessionType == SML_SESSION_TYPE_SERVER)
{
session->established = TRUE;
smlSessionDispatchEvent(
session, SML_SESSION_EVENT_ESTABLISHED,
NULL, NULL, NULL, NULL);
}
smlTrace(TRACE_EXIT, "%s", __func__);
return;
error:
smlSessionDispatchEvent(session, SML_SESSION_EVENT_ERROR, NULL, NULL, NULL, error);
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
smlErrorDeref(&error);
return;
}
char *smlAuthGetCredString(
SmlAuthType type,
const char *username,
const char *password,
const char *b64_nonce,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s", __func__);
CHECK_ERROR_REF
char *cred = NULL;
switch (type) {
case SML_AUTH_TYPE_BASIC:
smlTrace(TRACE_INTERNAL, "%s - SML_AUTH_TYPE_BASIC", __func__);
char *plain = g_strjoin(":", username, password, NULL);
cred = g_base64_encode((unsigned char *) plain, strlen(plain));
if (!cred) {
smlErrorSet(error, SML_ERROR_GENERIC,
"The syncml:auth-basic credential cannot be base64 encoded.");
smlSafeCFree(&plain);
goto error;
}
smlSafeCFree(&plain);
break;
case SML_AUTH_TYPE_MD5:
smlTrace(TRACE_INTERNAL, "%s - SML_AUTH_TYPE_MD5", __func__);
/* How does syncml:auth-md5 works?
*
* base64(
* md5(
* base64(
* md5(
* username + ":" + password
* )
* ) +
* ":" + nonce
* )
* )
*/
/* Let's determine the string for the comparison. */
;
char *auth = g_strjoin (":", username, password, NULL);
unsigned char digest[16];
smlMD5GetDigest (auth, strlen(auth), digest);
smlSafeCFree(&auth);
cred = g_base64_encode(digest, 16);
if (!cred) {
smlErrorSet(error, SML_ERROR_GENERIC,
"The username:password part of the syncml:auth-md5 "\
"credential cannot be base64 encoded.");
goto error;
}
auth = g_strjoin (":", cred, b64_nonce, NULL);
smlSafeCFree(&cred);
smlMD5GetDigest (auth, strlen(auth), digest);
smlSafeCFree(&auth);
cred = g_base64_encode(digest, 16);
if (!cred) {
smlErrorSet(error, SML_ERROR_GENERIC,
"The complete syncml:auth-md5 credential cannot be base64 encoded.");
goto error;
}
break;
default:
smlTrace(TRACE_ERROR, "%s - unknown authentication type", __func__);
smlErrorSet(error, SML_ERROR_GENERIC, "Unknown auth format");
goto error;
}
smlTrace(TRACE_EXIT, "%s", __func__);
return cred;
error:
smlTrace(TRACE_EXIT_ERROR, "%s - cannot create credential string");
if (*error == NULL)
smlErrorSet(error, SML_ERROR_GENERIC, "Cannot create credential string for user %s.",
username);
return NULL;
}
SmlBool smlAuthVerify(SmlChal *chal, SmlCred *cred, const char *username, const char *password, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s", __func__);
CHECK_ERROR_REF
/* If no Chal is send to the client but the client offers a Cred
* then we accept it. Theoretically this is not 100% perfect if
* syncml:auth-md5 is used but if the client offers it then we
* accept it because some implementations like UIQ 3 are broken
* and implemented the protocol in a wrong way if we answer with
* error 407 and a fresh nonce.
*
* SECURITY NOTE: This part of the code makes libsyncml vulnerable
* SECURITY NOTE: against replay attacks.
*
*/
if (chal && chal->type != cred->type)
{
if (chal->type == SML_AUTH_TYPE_BASIC &&
cred->type == SML_AUTH_TYPE_MD5)
{
/* This is an upgrade to more security.
* So it is acceptable.
*/
smlTrace(TRACE_INTERNAL, "%s - replace syncml:auth-basic by syncml:auth-md5", __func__);
} else {
/* This is a security event. */
smlErrorSet(error, SML_ERROR_AUTH_REJECTED,
"The type of the authentication was changed to a lower security level.");
goto error;
}
}
smlTrace(TRACE_INTERNAL, "%s - authentication security policy ok", __func__);
char *wanted = NULL;
switch (cred->type) {
case SML_AUTH_TYPE_BASIC:
smlTrace(TRACE_INTERNAL, "%s - SML_AUTH_TYPE_BASIC", __func__);
wanted = smlAuthGetCredString(SML_AUTH_TYPE_BASIC, username, password, NULL, error);
break;
case SML_AUTH_TYPE_MD5:
smlTrace(TRACE_INTERNAL, "%s - SML_AUTH_TYPE_MD5", __func__);
if (chal)
wanted = smlAuthGetCredString(
SML_AUTH_TYPE_MD5,
username, password,
chal->nonce_b64, error);
else
wanted = smlAuthGetCredString(
SML_AUTH_TYPE_MD5,
username, password,
"", error);
break;
default:
smlTrace(TRACE_ERROR, "%s - unknown authentication type", __func__);
smlErrorSet(error, SML_ERROR_GENERIC, "Unknown auth format");
goto error;
}
smlTrace(TRACE_INTERNAL, "%s - credential string calculated", __func__);
/* compare the authentication string */
if (strcmp(wanted, cred->data))
{
smlTrace(TRACE_INTERNAL, "%s - credentials mismatch", __func__);
smlSafeCFree(&wanted);
goto error;
}
smlSafeCFree(&wanted);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s - auth rejected");
if (*error == NULL)
smlErrorSet(error, SML_ERROR_AUTH_REJECTED, "Authentication rejected for username %s.",
username);
return FALSE;
}
SmlAuthenticator *smlAuthNew(SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, error);
CHECK_ERROR_REF
SmlAuthenticator *auth = smlTryMalloc0(sizeof(SmlAuthenticator), error);
if (!auth)
goto error;
auth->enabled = TRUE;
auth->state = SML_ERROR_AUTH_REQUIRED;
smlTrace(TRACE_EXIT, "%s: %p", __func__, auth);
return auth;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return NULL;
}
void smlAuthFree(SmlAuthenticator *auth)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, auth);
smlAssert(auth);
smlSafeFree((gpointer *)&auth);
smlTrace(TRACE_EXIT, "%s", __func__);
}
SmlBool smlAuthRegister(SmlAuthenticator *auth, SmlManager *manager, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, auth, manager, error);
CHECK_ERROR_REF
smlAssert(auth);
smlAssert(manager);
smlManagerRegisterHeaderHandler(manager, _header_callback, _status_callback, auth);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
}
void smlAuthSetState(SmlAuthenticator *auth, SmlErrorType type)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, auth, type);
smlAssert(auth);
auth->state = type;
smlTrace(TRACE_EXIT, "%s", __func__);
}
/* FIXME: DEPRECATED*/
SmlStatus *smlAuthHeaderReply(
SmlSession *session,
SmlAuthType code,
SmlError **error) {
CHECK_ERROR_REF
g_warning("SmlAuthType of smlAuthHeaderReply is used as SmlErrorType.");
return _smlAuthHeaderReply(session, (SmlErrorType) code, SML_AUTH_TYPE_BASIC, error);
}
static SmlStatus *_smlAuthHeaderReply(
SmlSession *session,
SmlErrorType code,
SmlAuthType auth,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i, %i, %p)", __func__, session, code, auth, error);
CHECK_ERROR_REF
// the session structure is from the viewpoint of this machine
// SourceRef and TargetRef of the status MUST use the viewpoint
// of the remote peer
// we have to revert source and target
smlTrace(TRACE_INTERNAL, "%s: SourceRef: %s --> TargetRef: %s",
__func__, VA_STRING(session->target->locURI), VA_STRING(session->source->locURI));
SmlStatus *reply = smlStatusNew(code, 0, session->lastReceivedMessageID, session->target, session->source, SML_COMMAND_TYPE_HEADER, error);
if (!reply)
goto error;
if (code == SML_ERROR_AUTH_REJECTED ||
code == SML_ERROR_AUTH_REQUIRED) {
reply->chal = smlChalNew(auth, error);
if (!reply->chal)
goto error_free_reply;
session->chal = reply->chal;
smlChalRef(session->chal);
}
smlTrace(TRACE_EXIT, "%s: %p", __func__, reply);
return reply;
error_free_reply:
smlStatusUnref(reply);
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return NULL;
}
void smlAuthSetVerifyCallback(SmlAuthenticator *auth, SmlAuthVerifyCb callback, void *userdata)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, auth, callback, userdata);
smlAssert(auth);
auth->verifyCallback = callback;
auth->verifyCallbackUserdata = userdata;
smlTrace(TRACE_EXIT, "%s", __func__);
}
void smlAuthSetEnable(SmlAuthenticator *auth, SmlBool enabled)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, auth, enabled);
smlAssert(auth);
auth->enabled = enabled;
smlTrace(TRACE_EXIT, "%s", __func__);
}
SmlBool smlAuthIsEnabled(SmlAuthenticator *auth)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, auth);
smlAssert(auth);
smlTrace(TRACE_EXIT, "%s - %u", __func__, auth->enabled);
return auth->enabled;
}
void smlAuthSetType(SmlAuthenticator *auth, SmlAuthType type)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, auth, type);
smlAssert(auth);
smlAssert(type != SML_AUTH_TYPE_UNKNOWN);
auth->type = type;
smlTrace(TRACE_EXIT, "%s", __func__);
}
smlSessionSendReply
SmlBool smlSessionSendReply(SmlSession *session, SmlStatus *status, SmlError **error)
Sends a reply to a command.
Definition: sml_session.c:2536
SmlCred
Definition: sml_elements_internals.h:87
smlErrorPrint
const char * smlErrorPrint(SmlError **error)
Returns the message of the error.
Definition: sml_error.c:299
SmlManager
Definition: sml_manager_internals.h:35
SmlStatus
Definition: sml_command_internals.h:94
SmlHeader
Definition: sml_elements_internals.h:73
SmlChal
Definition: sml_elements_internals.h:96
SmlSession
Definition: sml_session_internals.h:27
smlTrace
void smlTrace(SmlTraceType type, const char *message,...)
Used for tracing the application.
Definition: sml_support.c:120
smlTryMalloc0
void * smlTryMalloc0(long n_bytes, SmlError **error)
Safely mallocs.
Definition: sml_support.c:335
SmlAuthenticator
Definition: sml_auth_internals.h:35
smlErrorSet
void smlErrorSet(SmlError **error, SmlErrorType type, const char *format,...)
Sets the error.
Definition: sml_error.c:355
SmlError
Represent an error.
Definition: sml_error_internals.h:29