/* SASL server API implementation * Rob Siemborski * Tim Martin * $Id: server.c,v 1.2 2002/05/22 17:56:56 snsimon Exp $ */ /* * Copyright (c) 2001 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any other legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* local functions/structs don't start with sasl */ #include #include #include #include #include #ifndef macintosh #include #include #endif #include #include #include #include "sasl.h" #include "saslint.h" #include "saslplug.h" #include "saslutil.h" #ifdef sun /* gotta define gethostname ourselves on suns */ extern int gethostname(char *, int); #endif #define DEFAULT_CHECKPASS_MECH "auxprop" #ifndef PATH_MAX # ifdef _POSIX_PATH_MAX # define PATH_MAX _POSIX_PATH_MAX # else # define PATH_MAX 1024 /* arbitrary; probably big enough */ # endif #endif /* Contains functions: * * sasl_server_init * sasl_server_new * sasl_listmech * sasl_server_start * sasl_server_step * sasl_checkpass * sasl_checkapop * sasl_user_exists * sasl_setpass */ /* if we've initialized the server sucessfully */ static int _sasl_server_active = 0; /* For access by other modules */ int _is_sasl_server_active(void) { return _sasl_server_active; } static int _sasl_checkpass(sasl_conn_t *conn, const char *service, const char *user, const char *pass); static mech_list_t *mechlist = NULL; /* global var which holds the list */ static sasl_global_callbacks_t global_callbacks; /* set the password for a user * conn -- SASL connection * user -- user name * pass -- plaintext password, may be NULL to remove user * passlen -- length of password, 0 = strlen(pass) * oldpass -- NULL will sometimes work * oldpasslen -- length of password, 0 = strlen(oldpass) * flags -- see flags below * * returns: * SASL_NOCHANGE -- proper entry already exists * SASL_NOMECH -- no authdb supports password setting as configured * SASL_NOVERIFY -- user exists, but no settable password present * SASL_DISABLED -- account disabled * SASL_PWLOCK -- password locked * SASL_WEAKPASS -- password too weak for security policy * SASL_NOUSERPASS -- user-supplied passwords not permitted * SASL_FAIL -- OS error * SASL_BADPARAM -- password too long * SASL_OK -- successful */ int sasl_setpass(sasl_conn_t *conn, const char *user, const char *pass, unsigned passlen, const char *oldpass, unsigned oldpasslen, unsigned flags) { int result=SASL_OK, tmpresult; sasl_server_conn_t *s_conn = (sasl_server_conn_t *) conn; sasl_server_userdb_setpass_t *setpass_cb = NULL; void *context = NULL; mechanism_t *m; if (!_sasl_server_active || !mechlist) return SASL_NOTINIT; /* check params */ if (!conn) return SASL_BADPARAM; if ((!(flags & SASL_SET_DISABLE) && passlen == 0) || ((flags & SASL_SET_CREATE) && (flags & SASL_SET_DISABLE))) PARAMERROR(conn); /* call userdb callback function */ result = _sasl_getcallback(conn, SASL_CB_SERVER_USERDB_SETPASS, &setpass_cb, &context); if(result == SASL_OK && setpass_cb) { tmpresult = setpass_cb(conn, context, user, pass, passlen, s_conn->sparams->propctx, flags); if(tmpresult != SASL_OK) { _sasl_log(conn, SASL_LOG_ERR, "setpass callback failed for %s: %z", user, tmpresult); } else { _sasl_log(conn, SASL_LOG_NOTE, "setpass callback succeeded for %s", user); } } else { result = SASL_OK; } /* copy info into sparams */ s_conn->sparams->serverFQDN = conn->serverFQDN; s_conn->sparams->service = conn->service; s_conn->sparams->user_realm = s_conn->user_realm; /* now we let the mechanisms set their secrets */ for (m = mechlist->mech_list; m; m = m->next) { if (!m->plug->setpass) { /* can't set pass for this mech */ continue; } tmpresult = m->plug->setpass(m->plug->glob_context, ((sasl_server_conn_t *)conn)->sparams, user, pass, passlen, oldpass, oldpasslen, flags); if (tmpresult == SASL_OK) { _sasl_log(conn, SASL_LOG_NOTE, "%s: set secret for %s", m->plug->mech_name, user); m->condition = SASL_OK; /* if we previously thought the mechanism didn't have any user secrets we now think it does */ } else if (tmpresult == SASL_NOCHANGE) { _sasl_log(conn, SASL_LOG_NOTE, "%s: secret not changed for %s", m->plug->mech_name, user); } else { result = tmpresult; _sasl_log(conn, SASL_LOG_ERR, "%s: failed to set secret for %s: %z (%m)", m->plug->mech_name, user, tmpresult, #ifndef WIN32 errno #else GetLastError() #endif ); } } RETURN(conn, result); } /* local mechanism which disposes of server */ static void server_dispose(sasl_conn_t *pconn) { sasl_server_conn_t *s_conn= (sasl_server_conn_t *) pconn; context_list_t *cur, *cur_next; if (s_conn->mech && s_conn->mech->plug->mech_dispose) { s_conn->mech->plug->mech_dispose(pconn->context, s_conn->sparams->utils); } pconn->context = NULL; for(cur = s_conn->mech_contexts; cur; cur=cur_next) { cur_next = cur->next; if(cur->context) cur->mech->plug->mech_dispose(cur->context, s_conn->sparams->utils); sasl_FREE(cur); } s_conn->mech_contexts = NULL; _sasl_free_utils(&s_conn->sparams->utils); if (s_conn->sparams->propctx) prop_dispose(&s_conn->sparams->propctx); if (s_conn->user_realm) sasl_FREE(s_conn->user_realm); if (s_conn->sparams) sasl_FREE(s_conn->sparams); _sasl_conn_dispose(pconn); } static int init_mechlist(void) { sasl_utils_t *newutils = NULL; mechlist->mutex = sasl_MUTEX_ALLOC(); if(!mechlist->mutex) return SASL_FAIL; /* set util functions - need to do rest */ newutils = _sasl_alloc_utils(NULL, &global_callbacks); if (newutils == NULL) return SASL_NOMEM; newutils->checkpass = &sasl_checkpass; mechlist->utils = newutils; mechlist->mech_list=NULL; mechlist->mech_length=0; return SASL_OK; } /* * parameters: * p - entry point */ int sasl_server_add_plugin(const char *plugname, sasl_server_plug_init_t *p) { int plugcount; sasl_server_plug_t *pluglist; mechanism_t *mech; sasl_server_plug_init_t *entry_point; int result; int version; int lupe; if(!plugname || !p) return SASL_BADPARAM; entry_point = (sasl_server_plug_init_t *)p; /* call into the shared library asking for information about it */ /* version is filled in with the version of the plugin */ result = entry_point(mechlist->utils, SASL_SERVER_PLUG_VERSION, &version, &pluglist, &plugcount); if ((result != SASL_OK) && (result != SASL_NOUSER)) { _sasl_log(NULL, SASL_LOG_WARN, "server add_plugin entry_point error %i\n", result); return result; } /* Make sure plugin is using the same SASL version as us */ if (version != SASL_SERVER_PLUG_VERSION) { _sasl_log(NULL, SASL_LOG_ERR, "version mismatch on plugin"); return SASL_BADVERS; } for (lupe=0;lupe < plugcount ;lupe++) { mech = sasl_ALLOC(sizeof(mechanism_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; /* wheather this mech actually has any users in it's db */ mech->condition = result; /* SASL_OK or SASL_NOUSER */ mech->next = mechlist->mech_list; mechlist->mech_list = mech; mechlist->mech_length++; } return SASL_OK; } static void server_done(void) { mechanism_t *m; mechanism_t *prevm; if (mechlist != NULL) { m=mechlist->mech_list; /* m point to begging of the list */ while (m!=NULL) { prevm=m; m=m->next; if (prevm->plug->mech_free) { prevm->plug->mech_free(prevm->plug->glob_context, mechlist->utils); } sasl_FREE(prevm->plugname); sasl_FREE(prevm); } _sasl_free_utils(&mechlist->utils); sasl_MUTEX_FREE(mechlist->mutex); sasl_FREE(mechlist); mechlist = NULL; } /* Free the auxprop plugins */ _sasl_auxprop_free(); global_callbacks.callbacks = NULL; global_callbacks.appname = NULL; /* no longer active. fail on listmech's etc. */ _sasl_server_active = 0; } static int server_idle(sasl_conn_t *conn) { mechanism_t *m; if (! mechlist) return 0; for (m = mechlist->mech_list; m!=NULL; m = m->next) if (m->plug->idle && m->plug->idle(m->plug->glob_context, conn, conn ? ((sasl_server_conn_t *)conn)->sparams : NULL)) return 1; return 0; } static int load_config(const sasl_callback_t *verifyfile_cb) { int result; const char *path_to_config=NULL; char *c; char *config_filename=NULL; int len; const sasl_callback_t *getpath_cb=NULL; /* get the path to the plugins; for now the config file will reside there */ getpath_cb=_sasl_find_getpath_callback( global_callbacks.callbacks ); if (getpath_cb==NULL) return SASL_BADPARAM; /* getpath_cb->proc MUST be a sasl_getpath_t; if only c had a type system */ result = ((sasl_getpath_t *)(getpath_cb->proc))(getpath_cb->context, &path_to_config); if (result!=SASL_OK) goto done; if (path_to_config == NULL) path_to_config = ""; if ((c = strchr(path_to_config, ':'))) { *c = '\0'; } /* length = length of path + '/' + length of appname + ".conf" + 1 for '\0' */ len = strlen(path_to_config)+2+ strlen(global_callbacks.appname)+5+1; if (len > PATH_MAX ) { result = SASL_FAIL; goto done; } /* construct the filename for the config file */ config_filename = sasl_ALLOC(len); if (! config_filename) { result = SASL_NOMEM; goto done; } snprintf(config_filename, len, "%s/%s.conf", path_to_config, global_callbacks.appname); /* Ask the application if it's safe to use this file */ result = ((sasl_verifyfile_t *)(verifyfile_cb->proc))(verifyfile_cb->context, config_filename, SASL_VRFY_CONF); /* returns continue if this file is to be skipped */ /* returns SASL_CONTINUE if doesn't exist * if doesn't exist we can continue using default behavior */ if (result==SASL_OK) result=sasl_config_init(config_filename); done: if (config_filename) sasl_FREE(config_filename); return result; } /* * Verify that all the callbacks are valid */ static int verify_server_callbacks(const sasl_callback_t *callbacks) { if (callbacks == NULL) return SASL_OK; while (callbacks->id != SASL_CB_LIST_END) { if (callbacks->proc==NULL) return SASL_FAIL; callbacks++; } return SASL_OK; } static char *grab_field(char *line, char **eofield) { int d = 0; char *field; while (isspace((int) *line)) line++; /* find end of field */ while (line[d] && !isspace(((int) line[d]))) d++; field = sasl_ALLOC(d + 1); if (!field) { return NULL; } memcpy(field, line, d); field[d] = '\0'; *eofield = line + d; return field; } struct secflag_map_s { char *name; int value; }; struct secflag_map_s secflag_map[] = { { "noplaintext", SASL_SEC_NOPLAINTEXT }, { "noactive", SASL_SEC_NOACTIVE }, { "nodictionary", SASL_SEC_NODICTIONARY }, { "forward_secrecy", SASL_SEC_FORWARD_SECRECY }, { "noanonymous", SASL_SEC_NOANONYMOUS }, { "pass_credentials", SASL_SEC_PASS_CREDENTIALS }, { "mutual_auth", SASL_SEC_MUTUAL_AUTH }, { NULL, 0x0 } }; static int parse_mechlist_file(const char *mechlistfile) { FILE *f; char buf[1024]; char *t, *ptr; int r = 0; f = fopen(mechlistfile, "r"); if (!f) return SASL_FAIL; r = SASL_OK; while (fgets(buf, sizeof(buf), f) != NULL) { mechanism_t *n = sasl_ALLOC(sizeof(mechanism_t)); sasl_server_plug_t *nplug; if (n == NULL) { r = SASL_NOMEM; break; } n->version = SASL_SERVER_PLUG_VERSION; n->condition = SASL_CONTINUE; nplug = sasl_ALLOC(sizeof(sasl_server_plug_t)); if (nplug == NULL) { r = SASL_NOMEM; break; } memset(nplug, 0, sizeof(sasl_server_plug_t)); /* each line is: plugin-file WS mech_name WS max_ssf *(WS security_flag) RET */ /* grab file */ n->f = grab_field(buf, &ptr); /* grab mech_name */ nplug->mech_name = grab_field(ptr, &ptr); /* grab max_ssf */ nplug->max_ssf = strtol(ptr, &ptr, 10); /* grab security flags */ while (*ptr != '\n') { struct secflag_map_s *map; /* read security flag */ t = grab_field(ptr, &ptr); map = secflag_map; while (map->name) { if (!strcasecmp(t, map->name)) { nplug->security_flags |= map->value; break; } map++; } if (!map->name) { _sasl_log(NULL, SASL_LOG_ERR, "%s: couldn't identify flag '%s'", nplug->mech_name, t); } free(t); } /* insert mechanism into mechlist */ n->plug = nplug; n->next = mechlist->mech_list; mechlist->mech_list = n; mechlist->mech_length++; } fclose(f); return r; } /* initialize server drivers, done once per process * callbacks -- callbacks for all server connections; must include * getopt callback * appname -- name of calling application (for lower level logging) * results: * state -- server state * returns: * SASL_OK -- success * SASL_BADPARAM -- error in config file * SASL_NOMEM -- memory failure * SASL_BADVERS -- Mechanism version mismatch */ int sasl_server_init(const sasl_callback_t *callbacks, const char *appname) { int ret; const sasl_callback_t *vf; const char *pluginfile = NULL; sasl_getopt_t *getopt; void *context; const add_plugin_list_t ep_list[] = { { "sasl_server_plug_init", (void *)&sasl_server_add_plugin }, { "sasl_auxprop_plug_init", (void *)&sasl_auxprop_add_plugin }, { "sasl_canonuser_init", (void *)&sasl_canonuser_add_plugin }, { NULL, NULL } }; /* we require the appname to be non-null */ if (appname==NULL) return SASL_BADPARAM; ret = _sasl_common_init(); if (ret != SASL_OK) return ret; _sasl_server_cleanup_hook = &server_done; /* verify that the callbacks look ok */ ret = verify_server_callbacks(callbacks); if (ret != SASL_OK) return ret; global_callbacks.callbacks = callbacks; global_callbacks.appname = appname; /* allocate mechlist and set it to empty */ mechlist = sasl_ALLOC(sizeof(mech_list_t)); if (mechlist == NULL) return SASL_NOMEM; ret = init_mechlist(); if (ret != SASL_OK) return ret; vf = _sasl_find_verifyfile_callback(callbacks); /* load config file if applicable */ ret = load_config(vf); if ((ret != SASL_OK) && (ret != SASL_CONTINUE)) { return ret; } /* load internal plugins */ sasl_server_add_plugin("EXTERNAL", &external_server_plug_init); /* delayed loading of plugins? (DSO only, as it doesn't * make much [any] sense to delay in the static library case) */ if (!_is_sasl_server_static && _sasl_getcallback(NULL, SASL_CB_GETOPT, &getopt, &context) == SASL_OK) { /* No sasl_conn_t was given to getcallback, so we provide the * global callbacks structure */ ret = getopt(&global_callbacks, NULL, "plugin_list", &pluginfile, NULL); } if (pluginfile != NULL) { /* this file should contain a list of plugins available. we'll load on demand. */ /* Ask the application if it's safe to use this file */ ret = ((sasl_verifyfile_t *)(vf->proc))(vf->context, pluginfile, SASL_VRFY_CONF); if (ret != SASL_OK) { _sasl_log(NULL, SASL_LOG_ERR, "unable to load plugin list %s: %z", pluginfile, ret); } if (ret == SASL_OK) { ret = parse_mechlist_file(pluginfile); } } else { /* load all plugins now */ ret = _sasl_load_plugins(ep_list, _sasl_find_getpath_callback(callbacks), _sasl_find_verifyfile_callback(callbacks)); } if (ret == SASL_OK) { /* _sasl_server_active shows if we're active or not. server_done() sets it back to 0 */ _sasl_server_active = 1; _sasl_server_idle_hook = &server_idle; ret = _sasl_build_mechlist(); } return ret; } /* * Once we have the users plaintext password we * may want to transition them. That is put entries * for them in the passwd database for other * stronger mechanism * * for example PLAIN -> CRAM-MD5 */ static int _sasl_transition(sasl_conn_t * conn, const char * pass, unsigned passlen) { const char *dotrans = "n"; sasl_getopt_t *getopt; int result = SASL_OK; void *context; if (! conn) return SASL_BADPARAM; if (! conn->oparams.authid) PARAMERROR(conn); /* check if this is enabled: default to false */ if (_sasl_getcallback(conn, SASL_CB_GETOPT, &getopt, &context) == SASL_OK) { getopt(context, NULL, "auto_transition", &dotrans, NULL); if (dotrans == NULL) dotrans = "n"; } if (*dotrans == '1' || *dotrans == 'y' || (*dotrans == 'o' && dotrans[1] == 'n') || *dotrans == 't') { /* ok, it's on! */ result = sasl_setpass(conn, conn->oparams.authid, pass, passlen, NULL, 0, 0); } RETURN(conn,result); } /* create context for a single SASL connection * service -- registered name of the service using SASL (e.g. "imap") * serverFQDN -- Fully qualified domain name of server. NULL means use * gethostname() or equivalent. * Useful for multi-homed servers. * user_realm -- permits multiple user realms on server, NULL = default * iplocalport -- server IPv4/IPv6 domain literal string with port * (if NULL, then mechanisms requiring IPaddr are disabled) * ipremoteport -- client IPv4/IPv6 domain literal string with port * (if NULL, then mechanisms requiring IPaddr are disabled) * callbacks -- callbacks (e.g., authorization, lang, new getopt context) * flags -- usage flags (see above) * returns: * pconn -- new connection context * * returns: * SASL_OK -- success * SASL_NOMEM -- not enough memory */ int sasl_server_new(const char *service, const char *serverFQDN, const char *user_realm, const char *iplocalport, const char *ipremoteport, const sasl_callback_t *callbacks, unsigned flags, sasl_conn_t **pconn) { int result; sasl_server_conn_t *serverconn; sasl_utils_t *utils; if (_sasl_server_active==0) return SASL_NOTINIT; if (! pconn) return SASL_FAIL; if (! service) return SASL_FAIL; *pconn=sasl_ALLOC(sizeof(sasl_server_conn_t)); if (*pconn==NULL) return SASL_NOMEM; memset(*pconn, 0, sizeof(sasl_server_conn_t)); serverconn = (sasl_server_conn_t *)*pconn; /* make sparams */ serverconn->sparams=sasl_ALLOC(sizeof(sasl_server_params_t)); if (serverconn->sparams==NULL) MEMERROR(*pconn); memset(serverconn->sparams, 0, sizeof(sasl_server_params_t)); (*pconn)->destroy_conn = &server_dispose; result = _sasl_conn_init(*pconn, service, flags, SASL_CONN_SERVER, &server_idle, serverFQDN, iplocalport, ipremoteport, callbacks, &global_callbacks); if (result != SASL_OK) goto done_error; /* set util functions - need to do rest */ utils=_sasl_alloc_utils(*pconn, &global_callbacks); if (!utils) { result = SASL_NOMEM; goto done_error; } utils->checkpass = &sasl_checkpass; /* Setup the propctx -> We'll assume the default size */ serverconn->sparams->propctx=prop_new(0); if(!serverconn->sparams->propctx) { result = SASL_NOMEM; goto done_error; } serverconn->sparams->utils = utils; serverconn->sparams->transition = &_sasl_transition; serverconn->sparams->canon_user = &_sasl_canon_user; serverconn->sparams->serverFQDN = (*pconn)->serverFQDN; serverconn->sparams->props = serverconn->base.props; serverconn->sparams->flags = flags; /* set some variables */ if (user_realm) { result = _sasl_strdup(user_realm, &serverconn->user_realm, NULL); } else { serverconn->user_realm = NULL; } if(result == SASL_OK) return SASL_OK; done_error: _sasl_conn_dispose(*pconn); sasl_FREE(*pconn); *pconn = NULL; return result; } /* * The rule is: * IF mech strength + external strength < min ssf THEN FAIL * We also have to look at the security properties and make sure * that this mechanism has everything we want */ static int mech_permitted(sasl_conn_t *conn, mechanism_t *mech) { sasl_server_conn_t *s_conn = (sasl_server_conn_t *)conn; const sasl_server_plug_t *plug; int myflags; context_list_t *cur; sasl_getopt_t *getopt; void *context; sasl_ssf_t minssf = 0; if(!conn) return 0; if(! mech || ! mech->plug) { PARAMERROR(conn); return 0; } plug = mech->plug; /* get the list of allowed mechanisms (default = all) */ if (_sasl_getcallback(conn, SASL_CB_GETOPT, &getopt, &context) == SASL_OK) { const char *mlist; getopt(context, NULL, "mech_list", &mlist, NULL); /* if we have a list, check the plugin against it */ if (mlist) { const char *cp; while (*mlist) { for (cp = mlist; *cp && !isspace((int) *cp); cp++); if (((size_t) (cp - mlist) == strlen(plug->mech_name)) && !strncasecmp(mlist, plug->mech_name, strlen(plug->mech_name))) { break; } mlist = cp; while (*mlist && isspace((int) *mlist)) mlist++; } if (!*mlist) return 0; /* reached EOS -> not in our list */ } } /* setup parameters for the call to mech_avail */ s_conn->sparams->serverFQDN=conn->serverFQDN; s_conn->sparams->service=conn->service; s_conn->sparams->user_realm=s_conn->user_realm; s_conn->sparams->props=conn->props; s_conn->sparams->external_ssf=conn->external.ssf; /* Check if we have banished this one already */ for(cur = s_conn->mech_contexts; cur; cur=cur->next) { if(cur->mech == mech) { /* If it's not mech_avail'd, then stop now */ if(!cur->context) return 0; break; } } if (!strcasecmp(plug->mech_name, "EXTERNAL")) { /* Special case for the external mechanism */ if (conn->props.min_ssf > conn->external.ssf || ! conn->external.auth_id) { sasl_seterror(conn, SASL_NOLOG, "External SSF not good enough"); return 0; } } else { if (conn->props.min_ssf < conn->external.ssf) { minssf = 0; } else { minssf = conn->props.min_ssf - conn->external.ssf; } /* Generic mechanism */ if (plug->max_ssf < minssf) { sasl_seterror(conn, SASL_NOLOG, "mech %s is too weak", plug->mech_name); return 0; /* too weak */ } } context = NULL; if(plug->mech_avail && plug->mech_avail(plug->glob_context, s_conn->sparams, (void **)&context) != SASL_OK ) { /* Mark this mech as no good for this connection */ cur = sasl_ALLOC(sizeof(context_list_t)); if(!cur) { MEMERROR(conn); return 0; } cur->context = NULL; cur->mech = mech; cur->next = s_conn->mech_contexts; s_conn->mech_contexts = cur; /* Error should be set by mech_avail call */ return 0; } else if(context) { /* Save this context */ cur = sasl_ALLOC(sizeof(context_list_t)); if(!cur) { MEMERROR(conn); return 0; } cur->context = context; cur->mech = mech; cur->next = s_conn->mech_contexts; s_conn->mech_contexts = cur; } /* Generic mechanism */ if (plug->max_ssf < minssf) { sasl_seterror(conn, SASL_NOLOG, "too weak"); return 0; /* too weak */ } /* if there are no users in the secrets database we can't use this mechanism */ if (mech->condition == SASL_NOUSER) { sasl_seterror(conn, 0, "no users in secrets db"); return 0; } /* security properties---if there are any flags that differ and are in what the connection are requesting, then fail */ /* special case plaintext */ myflags = conn->props.security_flags; /* if there's an external layer this is no longer plaintext */ if ((conn->props.min_ssf <= conn->external.ssf) && (conn->external.ssf > 1)) { myflags &= ~SASL_SEC_NOPLAINTEXT; } /* do we want to special case SASL_SEC_PASS_CREDENTIALS? nah.. */ if (((myflags ^ plug->security_flags) & myflags) != 0) { sasl_seterror(conn, SASL_NOLOG, "security flags do not match required"); return 0; } /* Check Features */ if(plug->features & SASL_FEAT_GETSECRET) { /* We no longer support sasl_server_{get,put}secret */ sasl_seterror(conn, 0, "mech %s requires unprovided secret facility", plug->mech_name); return 0; } return 1; } /* * make the authorization * */ static int do_authorization(sasl_server_conn_t *s_conn) { int ret; sasl_authorize_t *authproc; void *auth_context; /* now let's see if authname is allowed to proxy for username! */ /* check the proxy callback */ if (_sasl_getcallback(&s_conn->base, SASL_CB_PROXY_POLICY, &authproc, &auth_context) != SASL_OK) { INTERROR(&s_conn->base, SASL_NOAUTHZ); } ret = authproc(&(s_conn->base), auth_context, s_conn->base.oparams.user, s_conn->base.oparams.ulen, s_conn->base.oparams.authid, s_conn->base.oparams.alen, s_conn->user_realm, (s_conn->user_realm ? strlen(s_conn->user_realm) : 0), s_conn->sparams->propctx); RETURN(&s_conn->base, ret); } /* start a mechanism exchange within a connection context * mech -- the mechanism name client requested * clientin -- client initial response (NUL terminated), NULL if empty * clientinlen -- length of initial response * serverout -- initial server challenge, NULL if done * (library handles freeing this string) * serveroutlen -- length of initial server challenge * output: * pconn -- the connection negotiation state on success * * Same returns as sasl_server_step() or * SASL_NOMECH if mechanism not available. */ int sasl_server_start(sasl_conn_t *conn, const char *mech, const char *clientin, unsigned clientinlen, const char **serverout, unsigned *serveroutlen) { sasl_server_conn_t *s_conn=(sasl_server_conn_t *) conn; int result; context_list_t *cur, **prev; mechanism_t *m; if (_sasl_server_active==0) return SASL_NOTINIT; /* make sure mech is valid mechanism if not return appropriate error */ m=mechlist->mech_list; /* check parameters */ if(!conn) return SASL_BADPARAM; if (!mech || ((clientin==NULL) && (clientinlen>0))) PARAMERROR(conn); if(serverout) *serverout = NULL; if(serveroutlen) *serveroutlen = 0; while (m!=NULL) { if ( strcasecmp(mech,m->plug->mech_name)==0) { break; } m=m->next; } if (m==NULL) { sasl_seterror(conn, 0, "Couldn't find mech %s", mech); result = SASL_NOMECH; goto done; } /* Make sure that we're willing to use this mech */ if (! mech_permitted(conn, m)) { result = SASL_NOMECH; goto done; } if (m->condition == SASL_CONTINUE) { sasl_server_plug_init_t *entry_point; void *library = NULL; sasl_server_plug_t *pluglist; int version, plugcount; int l = 0; /* need to load this plugin */ result = _sasl_get_plugin(m->f, _sasl_find_verifyfile_callback(global_callbacks.callbacks), &library); if (result == SASL_OK) { result = _sasl_locate_entry(library, "sasl_server_plug_init", (void **)&entry_point); } if (result == SASL_OK) { result = entry_point(mechlist->utils, SASL_SERVER_PLUG_VERSION, &version, &pluglist, &plugcount); } if (result == SASL_OK) { /* find the correct mechanism in this plugin */ for (l = 0; l < plugcount; l++) { if (!strcasecmp(pluglist[l].mech_name, m->plug->mech_name)) break; } if (l == plugcount) { result = SASL_NOMECH; } } if (result == SASL_OK) { /* check that the parameters are the same */ if ((pluglist[l].max_ssf != m->plug->max_ssf) || (pluglist[l].security_flags != m->plug->security_flags)) { _sasl_log(conn, SASL_LOG_ERR, "%s: security parameters don't match mechlist file", pluglist[l].mech_name); result = SASL_NOMECH; } } if (result == SASL_OK) { /* copy mechlist over */ sasl_FREE((sasl_server_plug_t *) m->plug); m->plug = &pluglist[l]; m->condition = SASL_OK; } if (result != SASL_OK) { /* The library will eventually be freed, don't sweat it */ RETURN(conn, result); } } /* We used to setup sparams HERE, but now it's done inside of mech_permitted (which is called above) */ prev = &s_conn->mech_contexts; for(cur = *prev; cur; prev=&cur->next,cur=cur->next) { if(cur->mech == m) { if(!cur->context) { sasl_seterror(conn, 0, "Got past mech_permitted with a disallowed mech!"); return SASL_NOMECH; } /* If we find it, we need to pull cur out of the list so it won't be freed later! */ (*prev)->next = cur->next; conn->context = cur->context; sasl_FREE(cur); } } s_conn->mech = m; if(!conn->context) { /* Note that we don't hand over a new challenge */ result = s_conn->mech->plug->mech_new(s_conn->mech->plug->glob_context, s_conn->sparams, NULL, 0, &(conn->context)); } else { /* the work was already done by mech_avail! */ result = SASL_OK; } if (result == SASL_OK) { if(clientin) { if(s_conn->mech->plug->features & SASL_FEAT_SERVER_FIRST) { /* Remote sent first, but mechanism does not support it. * RFC 2222 says we fail at this point. */ sasl_seterror(conn, 0, "Remote sent first but mech does not allow it."); result = SASL_BADPROT; } else { /* Mech wants client-first, so let them have it */ result = sasl_server_step(conn, clientin, clientinlen, serverout, serveroutlen); } } else { if(s_conn->mech->plug->features & SASL_FEAT_WANT_CLIENT_FIRST) { /* Mech wants client first anyway, so we should do that */ *serverout = ""; *serveroutlen = 0; result = SASL_CONTINUE; } else { /* Mech wants server-first, so let them have it */ result = sasl_server_step(conn, clientin, clientinlen, serverout, serveroutlen); } } } done: if( result != SASL_OK && result != SASL_CONTINUE && result != SASL_INTERACT) { if(conn->context) { s_conn->mech->plug->mech_dispose(conn->context, s_conn->sparams->utils); conn->context = NULL; } } RETURN(conn,result); } /* perform one step of the SASL exchange * inputlen & input -- client data * NULL on first step if no optional client step * outputlen & output -- set to the server data to transmit * to the client in the next step * (library handles freeing this) * * returns: * SASL_OK -- exchange is complete. * SASL_CONTINUE -- indicates another step is necessary. * SASL_TRANS -- entry for user exists, but not for mechanism * and transition is possible * SASL_BADPARAM -- service name needed * SASL_BADPROT -- invalid input from client * ... */ int sasl_server_step(sasl_conn_t *conn, const char *clientin, unsigned clientinlen, const char **serverout, unsigned *serveroutlen) { int ret; sasl_server_conn_t *s_conn = (sasl_server_conn_t *) conn; /* cast */ /* check parameters */ if (_sasl_server_active==0) return SASL_NOTINIT; if (!conn) return SASL_BADPARAM; if ((clientin==NULL) && (clientinlen>0)) PARAMERROR(conn); /* If we've already done the last send, return! */ if(s_conn->sent_last == 1) { return SASL_OK; } /* Don't do another step if the plugin told us that we're done */ if (conn->oparams.doneflag) { _sasl_log(conn, SASL_LOG_ERR, "attempting server step after doneflag"); return SASL_FAIL; } if(serverout) *serverout = NULL; if(serveroutlen) *serveroutlen = 0; ret = s_conn->mech->plug->mech_step(conn->context, s_conn->sparams, clientin, clientinlen, serverout, serveroutlen, &conn->oparams); if (ret == SASL_OK) { ret = do_authorization(s_conn); } if (ret == SASL_OK) { /* if we're done, we need to watch out for the following: * 1. the mech does server-send-last * 2. the protocol does not * * in this case, return SASL_CONTINUE and remember we are done. */ if(*serverout && !(conn->flags & SASL_SUCCESS_DATA)) { s_conn->sent_last = 1; ret = SASL_CONTINUE; } 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"); ret = SASL_BADPROT; } } if( ret != SASL_OK && ret != SASL_CONTINUE && ret != SASL_INTERACT) { if(conn->context) { s_conn->mech->plug->mech_dispose(conn->context, s_conn->sparams->utils); conn->context = NULL; } } RETURN(conn, ret); } /* returns the length of all the mechanisms * added up */ static unsigned mech_names_len() { mechanism_t *listptr; unsigned result = 0; for (listptr = mechlist->mech_list; listptr; listptr = listptr->next) result += strlen(listptr->plug->mech_name); return result; } /* This returns a list of mechanisms in a NUL-terminated string * * The default behavior is to seperate with spaces if sep==NULL */ int _sasl_server_listmech(sasl_conn_t *conn, const char *user __attribute__((unused)), const char *prefix, const char *sep, const char *suffix, const char **result, size_t *plen, int *pcount) { int lup; mechanism_t *listptr; int ret; int resultlen; int flag; const char *mysep; /* if there hasn't been a sasl_sever_init() fail */ if (_sasl_server_active==0) return SASL_NOTINIT; if (!conn) return SASL_BADPARAM; if (conn->type != SASL_CONN_SERVER) PARAMERROR(conn); if (! result) PARAMERROR(conn); if (plen != NULL) *plen = 0; if (pcount != NULL) *pcount = 0; if (sep) { mysep = sep; } else { mysep = " "; } if (! mechlist || mechlist->mech_length <= 0) INTERROR(conn, SASL_NOMECH); resultlen = (prefix ? strlen(prefix) : 0) + (strlen(mysep) * (mechlist->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'; listptr = mechlist->mech_list; flag = 0; /* make list */ for (lup = 0; lup < mechlist->mech_length; lup++) { /* currently, we don't use the "user" parameter for anything */ if (mech_permitted(conn, listptr)) { if (pcount != NULL) (*pcount)++; /* print seperator */ if (flag) { strcat(conn->mechlist_buf, mysep); } else { flag = 1; } /* now print the mechanism name */ strcat(conn->mechlist_buf, listptr->plug->mech_name); } listptr = listptr->next; } if (suffix) strcat(conn->mechlist_buf,suffix); if (plen!=NULL) *plen=strlen(conn->mechlist_buf); *result = conn->mechlist_buf; return SASL_OK; } sasl_string_list_t *_sasl_server_mechs(void) { mechanism_t *listptr; sasl_string_list_t *retval = NULL, *next=NULL; if(!_sasl_server_active) return NULL; /* make list */ for (listptr = mechlist->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; } #define EOSTR(s,n) (((s)[n] == '\0') || ((s)[n] == ' ') || ((s)[n] == '\t')) static int is_mech(const char *t, const char *m) { int sl = strlen(m); return ((!strncasecmp(m, t, sl)) && EOSTR(t, sl)); } /* returns OK if it's valid */ static int _sasl_checkpass(sasl_conn_t *conn, const char *service, const char *user, const char *pass) { sasl_server_conn_t *s_conn = (sasl_server_conn_t *) conn; int result; sasl_getopt_t *getopt; sasl_server_userdb_checkpass_t *checkpass_cb; void *context; const char *mlist, *mech; struct sasl_verify_password_s *v; /* call userdb callback function, if available */ result = _sasl_getcallback(conn, SASL_CB_SERVER_USERDB_CHECKPASS, &checkpass_cb, &context); if(result == SASL_OK && checkpass_cb) { result = checkpass_cb(conn, context, user, pass, strlen(pass), s_conn->sparams->propctx); if(result == SASL_OK) return SASL_OK; } /* figure out how to check (i.e. auxprop or saslauthd or pwcheck) */ if (_sasl_getcallback(conn, SASL_CB_GETOPT, &getopt, &context) == SASL_OK) { getopt(context, NULL, "pwcheck_method", &mlist, NULL); } if(!mlist) mlist = DEFAULT_CHECKPASS_MECH; result = SASL_NOMECH; mech = mlist; while (*mech && result != SASL_OK) { for (v = _sasl_verify_password; v->name; v++) { if(is_mech(mech, v->name)) { result = v->verify(conn, user, pass, service, s_conn->user_realm); break; } } if (result != SASL_OK) { /* skip to next mech in list */ while (*mech && !isspace((int) *mech)) mech++; while (*mech && isspace((int) *mech)) mech++; } } if (result == SASL_NOMECH) { /* no mechanism available ?!? */ _sasl_log(conn, SASL_LOG_ERR, "unknown password verifier %s", mech); } if (result != SASL_OK) sasl_seterror(conn, SASL_NOLOG, "checkpass failed"); RETURN(conn, result); } /* check if a plaintext password is valid * if user is NULL, check if plaintext passwords are enabled * inputs: * user -- user to query in current user_domain * userlen -- length of username, 0 = strlen(user) * pass -- plaintext password to check * passlen -- length of password, 0 = strlen(pass) * returns * SASL_OK -- success * SASL_NOMECH -- mechanism not supported * SASL_NOVERIFY -- user found, but no verifier * SASL_NOUSER -- user not found */ int sasl_checkpass(sasl_conn_t *conn, const char *user, unsigned userlen __attribute__((unused)), const char *pass, unsigned passlen) { int result; if (_sasl_server_active==0) return SASL_NOTINIT; /* check if it's just a query if we are enabled */ if (!user) return SASL_OK; if (!conn) return SASL_BADPARAM; /* check params */ if (pass == NULL) PARAMERROR(conn); result = _sasl_checkpass(conn, conn->service, user, pass); if (result == SASL_OK) { strncpy(conn->authid_buf, user, CANON_BUF_SIZE); conn->oparams.authid = conn->authid_buf; result = _sasl_transition(conn, pass, passlen); } RETURN(conn,result); } /* check if a user exists on server * conn -- connection context (may be NULL, used to hold last error) * service -- registered name of the service using SASL (e.g. "imap") * user_realm -- permits multiple user realms on server, NULL = default * user -- NUL terminated user name * * returns: * SASL_OK -- success * SASL_DISABLED -- account disabled [FIXME: currently not detected] * SASL_NOUSER -- user not found * SASL_NOVERIFY -- user found, but no usable mechanism [FIXME: not supported] * SASL_NOMECH -- no mechanisms enabled */ int sasl_user_exists(sasl_conn_t *conn, const char *service, const char *user_realm, const char *user) { int result=SASL_NOMECH; const char *mlist, *mech; void *context; sasl_getopt_t *getopt; struct sasl_verify_password_s *v; /* check params */ if (_sasl_server_active==0) return SASL_NOTINIT; if (!conn) return SASL_BADPARAM; if (!user || conn->type != SASL_CONN_SERVER) PARAMERROR(conn); if(!service) service = conn->service; /* figure out how to check (i.e. auxprop or saslauthd or pwcheck) */ if (_sasl_getcallback(conn, SASL_CB_GETOPT, &getopt, &context) == SASL_OK) { getopt(context, NULL, "pwcheck_method", &mlist, NULL); } if(!mlist) mlist = DEFAULT_CHECKPASS_MECH; result = SASL_NOMECH; mech = mlist; while (*mech && result != SASL_OK) { for (v = _sasl_verify_password; v->name; v++) { if(is_mech(mech, v->name)) { result = v->verify(conn, user, NULL, service, user_realm); break; } } if (result != SASL_OK) { /* skip to next mech in list */ while (*mech && !isspace((int) *mech)) mech++; while (*mech && isspace((int) *mech)) mech++; } } /* Screen out the SASL_BADPARAM response * we'll get from not giving a password */ if(result == SASL_BADPARAM) { result = SASL_OK; } if (result == SASL_NOMECH) { /* no mechanism available ?!? */ _sasl_log(conn, SASL_LOG_ERR, "no plaintext password verifier?"); sasl_seterror(conn, SASL_NOLOG, "no plaintext password verifier?"); } RETURN(conn, result); } /* check if an apop exchange is valid * (note this is an optional part of the SASL API) * if challenge is NULL, just check if APOP is enabled * inputs: * challenge -- challenge which was sent to client * challen -- length of challenge, 0 = strlen(challenge) * response -- client response, " " (RFC 1939) * resplen -- length of response, 0 = strlen(response) * returns * SASL_OK -- success * SASL_BADAUTH -- authentication failed * SASL_BADPARAM -- missing challenge * SASL_BADPROT -- protocol error (e.g., response in wrong format) * SASL_NOVERIFY -- user found, but no verifier * SASL_NOMECH -- mechanism not supported * SASL_NOUSER -- user not found */ int sasl_checkapop(sasl_conn_t *conn, #ifdef DO_SASL_CHECKAPOP const char *challenge, unsigned challen __attribute__((unused)), const char *response, unsigned resplen __attribute__((unused))) #else const char *challenge __attribute__((unused)), unsigned challen __attribute__((unused)), const char *response __attribute__((unused)), unsigned resplen __attribute__((unused))) #endif { #ifdef DO_SASL_CHECKAPOP sasl_server_conn_t *s_conn = (sasl_server_conn_t *) conn; char *user, *user_end; const char *password_request[] = { SASL_AUX_PASSWORD, NULL }; size_t user_len; int result; if (_sasl_server_active==0) return SASL_NOTINIT; /* check if it's just a query if we are enabled */ if(!challenge) return SASL_OK; /* check params */ if (!conn) return SASL_BADPARAM; if (!response) PARAMERROR(conn); /* Parse out username and digest. * * Per RFC 1939, response must be " ", where * is a 16-octet value which is sent in hexadecimal * format, using lower-case ASCII characters. */ user_end = strrchr(response, ' '); if (!user_end || strspn(user_end + 1, "0123456789abcdef") != 32) { sasl_seterror(conn, 0, "Bad Digest"); RETURN(conn,SASL_BADPROT); } user_len = (size_t)(user_end - response); user = sasl_ALLOC(user_len + 1); memcpy(user, response, user_len); user[user_len] = '\0'; result = prop_request(s_conn->sparams->propctx, password_request); if(result != SASL_OK) RETURN(conn, result); /* Cannonify it */ result = _sasl_canon_user(conn, user, user_len, SASL_CU_AUTHZID, &(conn->oparams)); if(result != SASL_OK) { sasl_FREE(user); RETURN(conn, result); } result = _sasl_canon_user(conn, user, user_len, SASL_CU_AUTHID, &(conn->oparams)); sasl_FREE(user); if(result != SASL_OK) RETURN(conn, result); /* Do APOP verification */ result = _sasl_auxprop_verify_apop(conn, conn->oparams.authid, challenge, user_end + 1, s_conn->user_realm); /* If verification failed, we don't want to encourage getprop to work */ if(result != SASL_OK) { conn->oparams.user = NULL; conn->oparams.authid = NULL; } RETURN(conn, result); #else /* sasl_checkapop was disabled at compile time */ sasl_seterror(conn, SASL_NOLOG, "sasl_checkapop called, but was disabled at compile time"); RETURN(conn, SASL_NOMECH); #endif /* DO_SASL_CHECKAPOP */ }