#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "sasl.h"
#include "saslplug.h"
#include "saslutil.h"
#include "saslint.h"
static cmech_list_t *cmechlist;
static sasl_global_callbacks_t global_callbacks;
static int _sasl_client_active = 0;
static int init_mechlist()
{
cmechlist->mutex = sasl_MUTEX_ALLOC();
if(!cmechlist->mutex) return SASL_FAIL;
cmechlist->utils=_sasl_alloc_utils(NULL, &global_callbacks);
if (cmechlist->utils==NULL)
return SASL_NOMEM;
cmechlist->mech_list=NULL;
cmechlist->mech_length=0;
return SASL_OK;
}
static int client_done(void) {
cmechanism_t *cm;
cmechanism_t *cprevm;
if(!_sasl_client_active)
return SASL_NOTINIT;
else
_sasl_client_active--;
if(_sasl_client_active) {
return SASL_CONTINUE;
}
cm=cmechlist->mech_list;
while (cm!=NULL)
{
cprevm=cm;
cm=cm->next;
if (cprevm->plug->mech_free) {
cprevm->plug->mech_free(cprevm->plug->glob_context,
cmechlist->utils);
}
sasl_FREE(cprevm->plugname);
sasl_FREE(cprevm);
}
sasl_MUTEX_FREE(cmechlist->mutex);
_sasl_free_utils(&cmechlist->utils);
sasl_FREE(cmechlist);
cmechlist = NULL;
return SASL_OK;
}
int sasl_client_add_plugin(const char *plugname,
sasl_client_plug_init_t *entry_point)
{
int plugcount;
sasl_client_plug_t *pluglist;
cmechanism_t *mech;
int result;
int version;
int lupe;
if(!plugname || !entry_point) return SASL_BADPARAM;
result = entry_point(cmechlist->utils, SASL_CLIENT_PLUG_VERSION, &version,
&pluglist, &plugcount);
if (result != SASL_OK)
{
_sasl_log(NULL, SASL_LOG_WARN,
"entry_point failed in sasl_client_add_plugin for %s",
plugname);
return result;
}
if (version != SASL_CLIENT_PLUG_VERSION)
{
_sasl_log(NULL, SASL_LOG_WARN,
"version conflict in sasl_client_add_plugin for %s", plugname);
return SASL_BADVERS;
}
for (lupe=0;lupe< plugcount ;lupe++)
{
mech = sasl_ALLOC(sizeof(cmechanism_t));
if (! mech) return SASL_NOMEM;
mech->plug=pluglist++;
if(_sasl_strdup(plugname, &mech->plugname, NULL) != SASL_OK) {
sasl_FREE(mech);
return SASL_NOMEM;
}
mech->version = version;
mech->next = cmechlist->mech_list;
cmechlist->mech_list = mech;
cmechlist->mech_length++;
}
return SASL_OK;
}
static int
client_idle(sasl_conn_t *conn)
{
cmechanism_t *m;
if (! cmechlist)
return 0;
for (m = cmechlist->mech_list;
m;
m = m->next)
if (m->plug->idle
&& m->plug->idle(m->plug->glob_context,
conn,
conn ? ((sasl_client_conn_t *)conn)->cparams : NULL))
return 1;
return 0;
}
int sasl_client_init(const sasl_callback_t *callbacks)
{
int ret;
const add_plugin_list_t ep_list[] = {
{ "sasl_client_plug_init", (add_plugin_t *)sasl_client_add_plugin },
{ "sasl_canonuser_init", (add_plugin_t *)sasl_canonuser_add_plugin },
{ NULL, NULL }
};
if(_sasl_client_active) {
_sasl_client_active++;
return SASL_OK;
}
global_callbacks.callbacks = callbacks;
global_callbacks.appname = NULL;
cmechlist=sasl_ALLOC(sizeof(cmech_list_t));
if (cmechlist==NULL) return SASL_NOMEM;
_sasl_client_active = 1;
ret=init_mechlist();
if (ret!=SASL_OK) {
client_done();
return ret;
}
sasl_client_add_plugin("EXTERNAL", &external_client_plug_init);
ret = _sasl_common_init(&global_callbacks);
if (ret == SASL_OK)
ret = _sasl_load_plugins(ep_list,
_sasl_find_getpath_callback(callbacks),
_sasl_find_verifyfile_callback(callbacks));
if (ret == SASL_OK) {
_sasl_client_cleanup_hook = &client_done;
_sasl_client_idle_hook = &client_idle;
ret = _sasl_build_mechlist();
} else {
client_done();
}
return ret;
}
static void client_dispose(sasl_conn_t *pconn)
{
sasl_client_conn_t *c_conn=(sasl_client_conn_t *) pconn;
if (c_conn->mech && c_conn->mech->plug->mech_dispose) {
c_conn->mech->plug->mech_dispose(pconn->context,
c_conn->cparams->utils);
}
pconn->context = NULL;
if (c_conn->clientFQDN)
sasl_FREE(c_conn->clientFQDN);
if (c_conn->cparams) {
_sasl_free_utils(&(c_conn->cparams->utils));
sasl_FREE(c_conn->cparams);
}
_sasl_conn_dispose(pconn);
}
int sasl_client_new(const char *service,
const char *serverFQDN,
const char *iplocalport,
const char *ipremoteport,
const sasl_callback_t *prompt_supp,
unsigned flags,
sasl_conn_t **pconn)
{
int result;
char name[MAXHOSTNAMELEN];
sasl_client_conn_t *conn;
sasl_utils_t *utils;
if(_sasl_client_active==0) return SASL_NOTINIT;
if (!pconn || !service)
return SASL_BADPARAM;
*pconn=sasl_ALLOC(sizeof(sasl_client_conn_t));
if (*pconn==NULL) {
_sasl_log(NULL, SASL_LOG_ERR,
"Out of memory allocating connection context");
return SASL_NOMEM;
}
memset(*pconn, 0, sizeof(sasl_client_conn_t));
(*pconn)->destroy_conn = &client_dispose;
conn = (sasl_client_conn_t *)*pconn;
conn->mech = NULL;
conn->cparams=sasl_ALLOC(sizeof(sasl_client_params_t));
if (conn->cparams==NULL)
MEMERROR(*pconn);
memset(conn->cparams,0,sizeof(sasl_client_params_t));
result = _sasl_conn_init(*pconn, service, flags, SASL_CONN_CLIENT,
&client_idle, serverFQDN,
iplocalport, ipremoteport,
prompt_supp, &global_callbacks);
if (result != SASL_OK) RETURN(*pconn, result);
utils=_sasl_alloc_utils(*pconn, &global_callbacks);
if (utils==NULL)
MEMERROR(*pconn);
utils->conn= *pconn;
conn->cparams->utils = utils;
conn->cparams->canon_user = &_sasl_canon_user;
conn->cparams->flags = flags;
conn->cparams->prompt_supp = (*pconn)->callbacks;
memset(name, 0, sizeof(name));
gethostname(name, MAXHOSTNAMELEN);
result = _sasl_strdup(name, &conn->clientFQDN, NULL);
if(result == SASL_OK) return SASL_OK;
_sasl_conn_dispose(*pconn);
sasl_FREE(*pconn);
*pconn = NULL;
_sasl_log(NULL, SASL_LOG_ERR, "Out of memory in sasl_client_new");
return result;
}
static int have_prompts(sasl_conn_t *conn,
const sasl_client_plug_t *mech)
{
static const unsigned long default_prompts[] = {
SASL_CB_AUTHNAME,
SASL_CB_PASS,
SASL_CB_LIST_END
};
const unsigned long *prompt;
int (*pproc)();
void *pcontext;
int result;
for (prompt = (mech->required_prompts
? mech->required_prompts :
default_prompts);
*prompt != SASL_CB_LIST_END;
prompt++) {
result = _sasl_getcallback(conn, *prompt, &pproc, &pcontext);
if (result != SASL_OK && result != SASL_INTERACT)
return 0;
}
return 1;
}
int sasl_client_start(sasl_conn_t *conn,
const char *mechlist,
sasl_interact_t **prompt_need,
const char **clientout,
unsigned *clientoutlen,
const char **mech)
{
sasl_client_conn_t *c_conn= (sasl_client_conn_t *) conn;
char name[SASL_MECHNAMEMAX + 1];
cmechanism_t *m=NULL,*bestm=NULL;
size_t pos=0,place;
size_t list_len;
sasl_ssf_t bestssf = 0, minssf = 0;
int result;
if(_sasl_client_active==0) return SASL_NOTINIT;
if (!conn) return SASL_BADPARAM;
if (mechlist == NULL)
PARAMERROR(conn);
if (prompt_need && *prompt_need != NULL) {
goto dostep;
}
if(conn->props.min_ssf < conn->external.ssf) {
minssf = 0;
} else {
minssf = conn->props.min_ssf - conn->external.ssf;
}
list_len = strlen(mechlist);
while (pos<list_len)
{
place=0;
while ((pos<list_len) && (isalnum((unsigned char)mechlist[pos])
|| mechlist[pos] == '_'
|| mechlist[pos] == '-')) {
name[place]=mechlist[pos];
pos++;
place++;
if (SASL_MECHNAMEMAX < place) {
place--;
while(pos<list_len && (isalnum((unsigned char)mechlist[pos])
|| mechlist[pos] == '_'
|| mechlist[pos] == '-'))
pos++;
}
}
pos++;
name[place]=0;
if (! place) continue;
for (m = cmechlist->mech_list; m != NULL; m = m->next) {
int myflags;
if (strcasecmp(m->plug->mech_name, name))
continue;
if (!have_prompts(conn, m->plug))
break;
if (minssf > m->plug->max_ssf)
break;
myflags = conn->props.security_flags;
if ((conn->props.min_ssf <= conn->external.ssf) &&
(conn->external.ssf > 1)) {
myflags &= ~SASL_SEC_NOPLAINTEXT;
}
if (((myflags ^ m->plug->security_flags) & myflags) != 0) {
break;
}
if ((m->plug->features & SASL_FEAT_NEEDSERVERFQDN)
&& !conn->serverFQDN) {
break;
}
if ((conn->flags & SASL_NEED_PROXY) &&
!(m->plug->features & SASL_FEAT_ALLOWS_PROXY)) {
break;
}
#ifdef PREFER_MECH
if (strcasecmp(m->plug->mech_name, PREFER_MECH) &&
bestm && m->plug->max_ssf <= bestssf) {
break;
}
#else
if (bestm && m->plug->max_ssf <= bestssf) {
break;
}
#endif
if (bestm &&
((m->plug->security_flags ^ bestm->plug->security_flags) &
bestm->plug->security_flags)) {
break;
}
if (mech) {
*mech = m->plug->mech_name;
}
bestssf = m->plug->max_ssf;
bestm = m;
break;
}
}
if (bestm == NULL) {
sasl_seterror(conn, 0, "No worthy mechs found");
result = SASL_NOMECH;
goto done;
}
c_conn->cparams->service = conn->service;
c_conn->cparams->servicelen = (unsigned) strlen(conn->service);
if (conn->serverFQDN) {
c_conn->cparams->serverFQDN = conn->serverFQDN;
c_conn->cparams->slen = (unsigned) strlen(conn->serverFQDN);
}
c_conn->cparams->clientFQDN = c_conn->clientFQDN;
c_conn->cparams->clen = (unsigned) strlen(c_conn->clientFQDN);
c_conn->cparams->external_ssf = conn->external.ssf;
c_conn->cparams->props = conn->props;
c_conn->mech = bestm;
result = c_conn->mech->plug->mech_new(c_conn->mech->plug->glob_context,
c_conn->cparams,
&(conn->context));
if(result != SASL_OK) goto done;
dostep:
if(clientout) {
if(c_conn->mech->plug->features & SASL_FEAT_SERVER_FIRST) {
*clientout = NULL;
*clientoutlen = 0;
result = SASL_CONTINUE;
} else {
result = sasl_client_step(conn, NULL, 0, prompt_need,
clientout, clientoutlen);
}
}
else
result = SASL_CONTINUE;
done:
RETURN(conn, result);
}
int sasl_client_step(sasl_conn_t *conn,
const char *serverin,
unsigned serverinlen,
sasl_interact_t **prompt_need,
const char **clientout,
unsigned *clientoutlen)
{
sasl_client_conn_t *c_conn= (sasl_client_conn_t *) conn;
int result;
if(_sasl_client_active==0) return SASL_NOTINIT;
if(!conn) return SASL_BADPARAM;
if ((serverin==NULL) && (serverinlen>0))
PARAMERROR(conn);
if (conn->oparams.doneflag) {
_sasl_log(conn, SASL_LOG_ERR, "attempting client step after doneflag");
return SASL_FAIL;
}
if(clientout) *clientout = NULL;
if(clientoutlen) *clientoutlen = 0;
result = c_conn->mech->plug->mech_step(conn->context,
c_conn->cparams,
serverin,
serverinlen,
prompt_need,
clientout, clientoutlen,
&conn->oparams);
if (result == SASL_OK) {
if(!*clientout && !(conn->flags & SASL_SUCCESS_DATA)) {
*clientout = "";
*clientoutlen = 0;
}
if(!conn->oparams.maxoutbuf) {
conn->oparams.maxoutbuf = conn->props.maxbufsize;
}
if(conn->oparams.user == NULL || conn->oparams.authid == NULL) {
sasl_seterror(conn, 0,
"mech did not call canon_user for both authzid and authid");
result = SASL_BADPROT;
}
}
RETURN(conn,result);
}
static unsigned mech_names_len()
{
cmechanism_t *listptr;
unsigned result = 0;
for (listptr = cmechlist->mech_list;
listptr;
listptr = listptr->next)
result += (unsigned) strlen(listptr->plug->mech_name);
return result;
}
int _sasl_client_listmech(sasl_conn_t *conn,
const char *prefix,
const char *sep,
const char *suffix,
const char **result,
unsigned *plen,
int *pcount)
{
cmechanism_t *m=NULL;
sasl_ssf_t minssf = 0;
int ret;
size_t resultlen;
int flag;
const char *mysep;
if(_sasl_client_active == 0) return SASL_NOTINIT;
if (!conn) return SASL_BADPARAM;
if(conn->type != SASL_CONN_CLIENT) PARAMERROR(conn);
if (! result)
PARAMERROR(conn);
if (plen != NULL)
*plen = 0;
if (pcount != NULL)
*pcount = 0;
if (sep) {
mysep = sep;
} else {
mysep = " ";
}
if(conn->props.min_ssf < conn->external.ssf) {
minssf = 0;
} else {
minssf = conn->props.min_ssf - conn->external.ssf;
}
if (! cmechlist || cmechlist->mech_length <= 0)
INTERROR(conn, SASL_NOMECH);
resultlen = (prefix ? strlen(prefix) : 0)
+ (strlen(mysep) * (cmechlist->mech_length - 1))
+ mech_names_len()
+ (suffix ? strlen(suffix) : 0)
+ 1;
ret = _buf_alloc(&conn->mechlist_buf,
&conn->mechlist_buf_len, resultlen);
if(ret != SASL_OK) MEMERROR(conn);
if (prefix)
strcpy (conn->mechlist_buf,prefix);
else
*(conn->mechlist_buf) = '\0';
flag = 0;
for (m = cmechlist->mech_list; m != NULL; m = m->next) {
if (!have_prompts(conn, m->plug))
continue;
if (minssf > m->plug->max_ssf)
continue;
if (((conn->props.security_flags ^ m->plug->security_flags)
& conn->props.security_flags) != 0) {
continue;
}
if ((m->plug->features & SASL_FEAT_NEEDSERVERFQDN)
&& !conn->serverFQDN) {
continue;
}
if ((conn->flags & SASL_NEED_PROXY) &&
!(m->plug->features & SASL_FEAT_ALLOWS_PROXY)) {
break;
}
if (pcount != NULL)
(*pcount)++;
if (flag) {
strcat(conn->mechlist_buf, mysep);
} else {
flag = 1;
}
strcat(conn->mechlist_buf, m->plug->mech_name);
}
if (suffix)
strcat(conn->mechlist_buf,suffix);
if (plen!=NULL)
*plen = (unsigned) strlen(conn->mechlist_buf);
*result = conn->mechlist_buf;
return SASL_OK;
}
sasl_string_list_t *_sasl_client_mechs(void)
{
cmechanism_t *listptr;
sasl_string_list_t *retval = NULL, *next=NULL;
if(!_sasl_client_active) return NULL;
for (listptr = cmechlist->mech_list; listptr; listptr = listptr->next) {
next = sasl_ALLOC(sizeof(sasl_string_list_t));
if(!next && !retval) return NULL;
else if(!next) {
next = retval->next;
do {
sasl_FREE(retval);
retval = next;
next = retval->next;
} while(next);
return NULL;
}
next->d = listptr->plug->mech_name;
if(!retval) {
next->next = NULL;
retval = next;
} else {
next->next = retval;
retval = next;
}
}
return retval;
}