1/* SASL server API implementation
2 * Rob Siemborski
3 * Tim Martin
4 * $Id: sasldb.c,v 1.17 2009/03/10 14:37:03 mel Exp $
5 */
6/*
7 * Copyright (c) 1998-2003 Carnegie Mellon University.  All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in
18 *    the documentation and/or other materials provided with the
19 *    distribution.
20 *
21 * 3. The name "Carnegie Mellon University" must not be used to
22 *    endorse or promote products derived from this software without
23 *    prior written permission. For permission or any other legal
24 *    details, please contact
25 *      Office of Technology Transfer
26 *      Carnegie Mellon University
27 *      5000 Forbes Avenue
28 *      Pittsburgh, PA  15213-3890
29 *      (412) 268-4387, fax: (412) 268-7395
30 *      tech-transfer@andrew.cmu.edu
31 *
32 * 4. Redistributions of any form whatsoever must retain the following
33 *    acknowledgment:
34 *    "This product includes software developed by Computing Services
35 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
36 *
37 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
38 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
39 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
40 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
41 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
42 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
43 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
44 */
45
46#include <config.h>
47
48/* sasldb stuff */
49
50#include <stdio.h>
51
52#include "sasl.h"
53#include "saslutil.h"
54#include "saslplug.h"
55#include "../sasldb/sasldb.h"
56
57#include "plugin_common.h"
58
59static int sasldb_auxprop_lookup(void *glob_context __attribute__((unused)),
60				  sasl_server_params_t *sparams,
61				  unsigned flags,
62				  const char *user,
63				  unsigned ulen)
64{
65    char *userid = NULL;
66    char *realm = NULL;
67    const char *user_realm = NULL;
68    int ret;
69    const struct propval *to_fetch, *cur;
70    char value[8192];
71    size_t value_len;
72    char *user_buf;
73    int verify_against_hashed_password;
74    int saw_user_password = 0;
75
76    if (!sparams || !user) return SASL_BADPARAM;
77
78    user_buf = sparams->utils->malloc(ulen + 1);
79    if(!user_buf) {
80	ret = SASL_NOMEM;
81	goto done;
82    }
83
84    memcpy(user_buf, user, ulen);
85    user_buf[ulen] = '\0';
86
87    if(sparams->user_realm) {
88	user_realm = sparams->user_realm;
89    } else {
90	user_realm = sparams->serverFQDN;
91    }
92
93    ret = _plug_parseuser(sparams->utils, &userid, &realm, user_realm,
94			  sparams->serverFQDN, user_buf);
95    if(ret != SASL_OK) goto done;
96
97    to_fetch = sparams->utils->prop_get(sparams->propctx);
98    if (!to_fetch) {
99	ret = SASL_NOMEM;
100	goto done;
101    }
102
103    verify_against_hashed_password = flags & SASL_AUXPROP_VERIFY_AGAINST_HASH;
104
105    /* Use a fake value to signal that we have no property to lookup */
106    ret = SASL_CONTINUE;
107    for(cur = to_fetch; cur->name; cur++) {
108	int cur_ret;
109	const char *realname = cur->name;
110
111	/* Only look up properties that apply to this lookup! */
112	if(cur->name[0] == '*' && (flags & SASL_AUXPROP_AUTHZID)) continue;
113	if(!(flags & SASL_AUXPROP_AUTHZID)) {
114	    if(cur->name[0] != '*') continue;
115	    else realname = cur->name + 1;
116	}
117
118	/* If it's there already, we want to see if it needs to be
119	 * overridden. userPassword is a special case, because it's value
120	   is always present if SASL_AUXPROP_VERIFY_AGAINST_HASH is specified.
121	   When SASL_AUXPROP_VERIFY_AGAINST_HASH is set, we just clear userPassword. */
122	if (cur->values && !(flags & SASL_AUXPROP_OVERRIDE) &&
123	    (verify_against_hashed_password == 0 ||
124	     strcasecmp(realname, SASL_AUX_PASSWORD_PROP) != 0)) {
125	    continue;
126	} else if (cur->values) {
127	    sparams->utils->prop_erase(sparams->propctx, cur->name);
128	}
129
130	if (strcasecmp(realname, SASL_AUX_PASSWORD_PROP) == 0) {
131	    saw_user_password = 1;
132	}
133
134	cur_ret = _sasldb_getdata(sparams->utils,
135			      sparams->utils->conn, userid, realm,
136			      realname, value, sizeof(value), &value_len);
137
138	/* Assumption: cur_ret is never SASL_CONTINUE */
139
140	/* If this is the first property we've tried to fetch ==>
141	   always set the global error code.
142	   If we had SASL_NOUSER ==> any other error code overrides it
143	   (including SASL_NOUSER). */
144	if (ret == SASL_CONTINUE || ret == SASL_NOUSER) {
145	    ret = cur_ret;
146	} else if (ret == SASL_OK) {
147	    /* Any error code other than SASL_NOUSER overrides SASL_OK.
148	       (And SASL_OK overrides SASL_OK as well) */
149	    if (cur_ret != SASL_NOUSER) {
150		ret = cur_ret;
151	    }
152	}
153	/* Any other global error code is left as is */
154
155	if (cur_ret != SASL_OK) {
156	    if (cur_ret != SASL_NOUSER) {
157		/* No point in continuing if we hit any serious error */
158		break;
159	    }
160	    /* We didn't find it, leave it as not found */
161	    continue;
162	}
163
164	sparams->utils->prop_set(sparams->propctx, cur->name,
165				 value, (unsigned) value_len);
166    }
167
168    /* [Keep in sync with LDAPDB, SQL]
169       If ret is SASL_CONTINUE, it means that no properties were requested
170       (or maybe some were requested, but they already have values and
171       SASL_AUXPROP_OVERRIDE flag is not set).
172       Always return SASL_OK in this case. */
173    if (ret == SASL_CONTINUE) {
174        ret = SASL_OK;
175    }
176
177    if (flags & SASL_AUXPROP_AUTHZID) {
178	/* This is a lie, but the caller can't handle
179	   when we return SASL_NOUSER for authorization identity lookup. */
180	if (ret == SASL_NOUSER) {
181	    ret = SASL_OK;
182	}
183    } else {
184	if (ret == SASL_NOUSER && saw_user_password == 0) {
185	    /* Verify user existence by checking presence of
186	       the userPassword attribute */
187	    ret = _sasldb_getdata(sparams->utils,
188				  sparams->utils->conn,
189				  userid,
190				  realm,
191				  SASL_AUX_PASSWORD_PROP,
192				  value,
193				  sizeof(value),
194				  &value_len);
195	}
196    }
197
198 done:
199    if (userid) sparams->utils->free(userid);
200    if (realm)  sparams->utils->free(realm);
201    if (user_buf) sparams->utils->free(user_buf);
202
203    return ret;
204}
205
206static int sasldb_auxprop_store(void *glob_context __attribute__((unused)),
207				sasl_server_params_t *sparams,
208				struct propctx *ctx,
209				const char *user,
210				unsigned ulen)
211{
212    char *userid = NULL;
213    char *realm = NULL;
214    const char *user_realm = NULL;
215    int ret = SASL_FAIL;
216    const struct propval *to_store, *cur;
217    char *user_buf;
218
219    /* just checking if we are enabled */
220    if(!ctx) return SASL_OK;
221
222    if(!sparams || !user) return SASL_BADPARAM;
223
224    user_buf = sparams->utils->malloc(ulen + 1);
225    if(!user_buf) {
226	ret = SASL_NOMEM;
227	goto done;
228    }
229
230    memcpy(user_buf, user, ulen);
231    user_buf[ulen] = '\0';
232
233    if(sparams->user_realm) {
234	user_realm = sparams->user_realm;
235    } else {
236	user_realm = sparams->serverFQDN;
237    }
238
239    ret = _plug_parseuser(sparams->utils, &userid, &realm, user_realm,
240			  sparams->serverFQDN, user_buf);
241    if(ret != SASL_OK) goto done;
242
243    to_store = sparams->utils->prop_get(ctx);
244    if(!to_store) {
245	ret = SASL_BADPARAM;
246	goto done;
247    }
248
249    ret = SASL_OK;
250    for (cur = to_store; cur->name; cur++) {
251	char * value = (char *)((cur->values && cur->values[0]) ? cur->values[0] : NULL); /* APPLE: cast */
252
253	if (cur->name[0] == '*') {
254	    continue;
255	}
256
257	/* WARN: We only support one value right now. */
258	ret = _sasldb_putdata(sparams->utils,
259			      sparams->utils->conn,
260			      userid,
261			      realm,
262			      cur->name,
263			      value,
264			      value ? strlen(value) : 0);
265
266	if (value == NULL && ret == SASL_NOUSER) {
267	    /* Deleting something which is not there is not an error */
268	    ret = SASL_OK;
269	}
270
271	if (ret != SASL_OK) {
272	    /* We've already failed, no point in continuing */
273	    break;
274	}
275    }
276
277 done:
278    if (userid) sparams->utils->free(userid);
279    if (realm)  sparams->utils->free(realm);
280    if (user_buf) sparams->utils->free(user_buf);
281
282    return ret;
283}
284
285static sasl_auxprop_plug_t sasldb_auxprop_plugin = {
286    0,           		/* Features */
287    0,           		/* spare */
288    NULL,        		/* glob_context */
289    sasldb_auxprop_free,        /* auxprop_free */
290    sasldb_auxprop_lookup,	/* auxprop_lookup */
291    "sasldb",			/* name */
292    sasldb_auxprop_store	/* auxprop_store */
293};
294
295int sasldb_auxprop_plug_init(const sasl_utils_t *utils,
296                             int max_version,
297                             int *out_version,
298                             sasl_auxprop_plug_t **plug,
299                             const char *plugname __attribute__((unused)))
300{
301    if(!out_version || !plug) return SASL_BADPARAM;
302
303    /* Do we have database support? */
304    /* Note that we can use a NULL sasl_conn_t because our
305     * sasl_utils_t is "blessed" with the global callbacks */
306    if(_sasl_check_db(utils, NULL) != SASL_OK)
307	return SASL_NOMECH;
308
309    /* Check if libsasl API is older than ours. If it is, fail */
310    if(max_version < SASL_AUXPROP_PLUG_VERSION) return SASL_BADVERS;
311
312    *out_version = SASL_AUXPROP_PLUG_VERSION;
313
314    *plug = &sasldb_auxprop_plugin;
315
316    return SASL_OK;
317}
318