Hi there guys, I am building a XMPP service around jabberd2 and I am very impressed with the current architecture. However, as I am getting further into our implementation, a couple of problems have become apparent.
Problems: - We need to maintain per user session data in c2s. For example: authentication token and unique user ID that is system and session specific. authreg_t has a private member to hold authreg related data but this is module wide and not tied to an individual user. - For CRAM-MD5, to generate the proper challenge, we need more than the username. Here, realm would be good to have and for verifying the response, the associated resource would be needed to generate the unique login ID in our system. For us, the two are intertwined and the current APIs and data structures do not provide a solution to the above problems, to the best of my knowledge. If I am mistaken, please do let me know :) Solutions: There needs to be a way for a 3rd party to plug in and provide the necessary information to respond to the initial auth request and also have all the relevant information to properly authenticate the user and be able to retain user & resource specific tokens throughout the lifetime of the user's session. - Build a hash table of relevant data and store it in the authreg_t private data member. Pros: Fits nicely into the existing architecture Cons: Its up to the authreg provider to maintain proper state and moves session related data away from the session and into a component which outlives the span of an individual session. Doesn¹t help much With problem (1). - Retrofit existing interfaces with the necessary data. a. Introduce void *sess_private in sess_t. b. APIs to add pointer to sess_private along with realm and resource arguments when available. struct sess_st { ... void* sess_private; /* Data per session per user */ } int (*create_challenge)(authreg_t ar, sess_private *data, const char *username, const char *realm, const char *challenge, int maxlen); int (*check_response)(authreg_t ar, sess_private *data, const char *username, const char *realm, const char *resource, const char *challenge, const char *response); Pros: Maintain same methods but new parameters, faster approach. Cons: (BIG)breaks everyone out there. In some cases, other 3rd parties may want similar mechanism for plain text login as well and this approach wouldn't work for them. - Introduce the private data in sess_t as above and also new functions in authreg_t that allows for generic authentication handling by third parties. (see sess_private above) /* Extension for custom authentication providers */ int (*custom_auth_get)(authreg_t ar, authdata_t data); int (*custom_auth_set)(authreg_t ar, authdata_t data); where /* Custom authentication data object */ struct authdata_st { void **sess_private; const char *username; const char *resource; const char *realm; const char *password; const char *digest; char *challenge; int challenge_maxlen; const char *response; }; The authdata_st can be expanded as needed without changing the APIs. The APIs mimic authreg_auth_[get/set] functions and for get, in the case of CRAM-MD5, returns a challenge based on the contextual data. In the case of set, it has all the relevant pieces of information to serve the request as necessary and return true or false for success or failure. Pros: A very, very generic way to leave it to the authreg implementor to authenticate the user, as long as the right results are returned. Cons: New APIs, potentially very generic and open. I chose the third route, because others could implement their own authentication schemes now that all the relevant and required pieces of information is available. I put together a quick implementation and the patch is added below. I coded it up in a way that if the custom authentication approach fails, we fall back on the existing methods so the impact would be inconsequential if an auth provider doesn't implement the change. This applies to both get & set functionality. See the patch for more details. I am trying to come up with a solution that would be accepted by the current maintainers and that I can submit back to the community. This patch by no means is the final version and I open to all feedback, be it naming, implementation, or the approach in general, as long as it solves the two problems stated above. Looking forward to the feedback, thanks. Shawn --- diff --git a/c2s/authreg.c b/c2s/authreg.c index 627d22e..ace320c 100644 --- a/c2s/authreg.c +++ b/c2s/authreg.c @@ -135,6 +135,8 @@ static void _authreg_auth_get(c2s_t c2s, sess_t sess, nad_t nad) { int ns, elem, attr, err; char username[1024], id[128]; int ar_mechs; + authdata_t ad; + int processed = 0; /* can't auth if they're active */ if(sess->active) { @@ -211,16 +213,50 @@ static void _authreg_auth_get(c2s_t c2s, sess_t sess, nad_t nad) { nad_append_elem(nad, ns, "digest", 2); if (ar_mechs & AR_MECH_TRAD_CRAMMD5 && c2s->ar->create_challenge != NULL) { - err = (c2s->ar->create_challenge)(c2s->ar, (char *) username, (char *) sess->auth_challenge, sizeof(sess->auth_challenge)); - if (0 == err) { /* operation failed */ - sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_INTERNAL_SERVER_ERROR), 0)); - return; + /* + * Check if the authreg provider has the custom auth get/set + * available, if so, call the custom_auth_get function. + * + * If any errors are encountered, fall back to traditional + * functions, otherwise bypass the call to create_challenge. + */ + if (c2s->ar->custom_auth_get != NULL) { + ad = (authdata_t)calloc(1, sizeof(struct authdata_st)); + if (ad != NULL) { + ad->sess_private = &(sess->sess_private); + ad->username = username; + ad->resource = NULL; /* not reqd for get */ + ad->realm = sess->host->realm; + ad->password = NULL; /* not reqd for get */ + ad->digest = NULL; /* not reqd for get */ + ad->challenge = sess->auth_challenge; + ad->challenge_maxlen = sizeof(sess->auth_challenge); + ad->response = NULL; /* not reqd for get */ + + err = (c2s->ar->custom_auth_get)(c2s->ar, ad); + if (1 == err) /* operation succeeded */ + processed = 1; + + free(ad); + } } - else if (1 == err) { /* operation succeeded */ - nad_append_elem(nad, ns, "crammd5", 2); - nad_append_attr(nad, -1, "challenge", sess->auth_challenge); + + /* + * Call create_challenge only if custom_auth_get is not + * available or the call to custom_auth_get failed. + */ + if (!processed) { + err = (c2s->ar->create_challenge)(c2s->ar, (char *) username, (char *) sess->auth_challenge, sizeof(sess->auth_challenge)); + if (0 == err) { /* operation failed */ + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_INTERNAL_SERVER_ERROR), 0)); + return; + } + else if (1 == err) { /* operation succeeded */ + nad_append_elem(nad, ns, "crammd5", 2); + nad_append_attr(nad, -1, "challenge", sess->auth_challenge); + } + else ; /* auth method unsupported for user */ } - else ; /* auth method unsupported for user */ } /* give it back to the client */ @@ -233,7 +269,9 @@ static void _authreg_auth_get(c2s_t c2s, sess_t sess, nad_t nad) { static void _authreg_auth_set(c2s_t c2s, sess_t sess, nad_t nad) { int ns, elem, attr, authd = 0; char username[1024], resource[1024], str[1024], hash[280]; + char response[1024]; int ar_mechs; + authdata_t ad; /* can't auth if they're active */ if(sess->active) { @@ -294,15 +332,56 @@ static void _authreg_auth_set(c2s_t c2s, sess_t sess, nad_t nad) { sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_OLD_UNAUTH), 0)); return; } - + + /* handle custom authentication */ + if (!authd && c2s->ar->custom_auth_set != NULL) { + ad = (authdata_t)calloc(1, sizeof(struct authdata_st)); + if (ad != NULL) { + ad->sess_private = &(sess->sess_private); + ad->username = username; + ad->resource = resource; + ad->realm = sess->host->realm; + ad->challenge = sess->auth_challenge; + ad->challenge_maxlen = sizeof(sess->auth_challenge); + + elem = nad_find_elem(nad, 1, ns, "crammd5", 1); + if(elem >= 0) { + snprintf(response, 1024, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); + ad->response = response; + } + + elem = nad_find_elem(nad, 1, ns, "digest", 1); + if(elem >= 0) { + snprintf(hash, 280, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); + ad->digest = hash; + } + + elem = nad_find_elem(nad, 1, ns, "password", 1); + if(elem >= 0) { + snprintf(str, 1024, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); + ad->password = str; + } + + if((c2s->ar->custom_auth_set)(c2s->ar, ad) == 0) { + authd = 1; + log_debug(ZONE, "custom auth (check) succeded"); + _authreg_auth_log(c2s, sess, "custom auth", username, resource, TRUE); + } else { + _authreg_auth_log(c2s, sess, "custom auth", username, resource, FALSE); + } + + free(ad); + } + } + /* handle CRAM-MD5 response */ if(!authd && ar_mechs & AR_MECH_TRAD_CRAMMD5 && c2s->ar->check_response != NULL) { elem = nad_find_elem(nad, 1, ns, "crammd5", 1); if(elem >= 0) { - snprintf(str, 1024, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); - if((c2s->ar->check_response)(c2s->ar, username, sess->host->realm, sess->auth_challenge, str) == 0) + snprintf(response, 1024, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); + if((c2s->ar->check_response)(c2s->ar, username, sess->host->realm, sess->auth_challenge, response) == 0) { log_debug(ZONE, "crammd5 auth (check) succeded"); authd = 1; diff --git a/c2s/c2s.h b/c2s/c2s.h index f1cfc79..55efbfd 100644 --- a/c2s/c2s.h +++ b/c2s/c2s.h @@ -54,6 +54,7 @@ typedef struct c2s_st *c2s_t; typedef struct bres_st *bres_t; typedef struct sess_st *sess_t; typedef struct authreg_st *authreg_t; +typedef struct authdata_st *authdata_t; /** list of resources bound to session */ struct bres_st { @@ -111,6 +112,9 @@ struct sess_st { /** Apple: session challenge for challenge-response authentication */ char auth_challenge[65]; + + /* Storage of per user session authreg data */ + void *sess_private; }; /* allowed mechanisms */ @@ -347,6 +351,10 @@ struct authreg_st /** Apple extensions for challenge/response authentication methods */ int (*create_challenge)(authreg_t ar, const char *username, const char *challenge, int maxlen); int (*check_response)(authreg_t ar, const char *username, const char *realm, const char *challenge, const char *response); + + /* Extension for custom authentication providers */ + int (*custom_auth_get)(authreg_t ar, authdata_t data); + int (*custom_auth_set)(authreg_t ar, authdata_t data); }; /** get a handle for a single module */ @@ -386,3 +394,15 @@ typedef struct stream_redirect_st const char *to_port; } *stream_redirect_t; +/* Custom authentication data object */ +struct authdata_st { + void **sess_private; + const char *username; + const char *resource; + const char *realm; + const char *password; + const char *digest; + char *challenge; + int challenge_maxlen; + const char *response; +};
c2s.patch.gz
Description: c2s.patch.gz