1/* Plain SASL plugin
2 * Rob Siemborski
3 * Tim Martin
4 * $Id: plain.c,v 1.67 2009/06/10 16:05:19 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#include <stdio.h>
48#include <string.h>
49#include <sasl.h>
50#include <saslplug.h>
51
52#include "plugin_common.h"
53
54#ifdef macintosh
55#include <sasl_plain_plugin_decl.h>
56#endif
57
58/*****************************  Common Section  *****************************/
59
60static const char plugin_id[] = "$Id: plain.c,v 1.67 2009/06/10 16:05:19 mel Exp $";
61
62/*****************************  Server Section  *****************************/
63
64static int plain_server_mech_new(void *glob_context __attribute__((unused)),
65				 sasl_server_params_t *sparams,
66				 const char *challenge __attribute__((unused)),
67				 unsigned challen __attribute__((unused)),
68				 void **conn_context)
69{
70    /* holds state are in */
71    if (!conn_context) {
72	PARAMERROR( sparams->utils );
73	return SASL_BADPARAM;
74    }
75
76    *conn_context = NULL;
77
78    return SASL_OK;
79}
80
81static int plain_server_mech_step(void *conn_context __attribute__((unused)),
82				  sasl_server_params_t *params,
83				  const char *clientin,
84				  unsigned clientinlen,
85				  const char **serverout,
86				  unsigned *serveroutlen,
87				  sasl_out_params_t *oparams)
88{
89    const char *author;
90    const char *authen;
91    const char *password;
92    unsigned password_len;
93    unsigned lup = 0;
94    int result;
95    char *passcopy;
96    unsigned canon_flags = 0;
97
98    *serverout = NULL;
99    *serveroutlen = 0;
100
101    /* should have received author-id NUL authen-id NUL password */
102
103    /* get author */
104    author = clientin;
105    while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup;
106
107    if (lup >= clientinlen) {
108	SETERROR(params->utils, "Can only find author (no password)");
109	return SASL_BADPROT;
110    }
111
112    /* get authen */
113    ++lup;
114    authen = clientin + lup;
115    while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup;
116
117    if (lup >= clientinlen) {
118	params->utils->seterror(params->utils->conn, 0,
119				"Can only find author/en (no password)");
120	return SASL_BADPROT;
121    }
122
123    /* get password */
124    lup++;
125    password = clientin + lup;
126    while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup;
127
128    password_len = (unsigned) (clientin + lup - password);
129
130    if (lup != clientinlen) {
131	SETERROR(params->utils,
132		 "Got more data than we were expecting in the PLAIN plugin\n");
133	return SASL_BADPROT;
134    }
135
136    /* this kinda sucks. we need password to be null terminated
137       but we can't assume there is an allocated byte at the end
138       of password so we have to copy it */
139    passcopy = params->utils->malloc(password_len + 1);
140    if (passcopy == NULL) {
141	MEMERROR(params->utils);
142	return SASL_NOMEM;
143    }
144
145    strncpy(passcopy, password, password_len);
146    passcopy[password_len] = '\0';
147
148    /* Canonicalize userid first, so that password verification is only
149     * against the canonical id */
150    if (!author || !*author) {
151	author = authen;
152	canon_flags = SASL_CU_AUTHZID;
153    } else if (strcmp(author, authen) == 0) {
154	/* While this isn't going to find out that <user> and <user>@<defaultdomain>
155	   are the same thing, this is good enough for many cases */
156	canon_flags = SASL_CU_AUTHZID;
157    }
158
159    result = params->canon_user(params->utils->conn,
160				authen,
161				0,
162				SASL_CU_AUTHID | canon_flags | SASL_CU_EXTERNALLY_VERIFIED,
163				oparams);
164    if (result != SASL_OK) {
165	_plug_free_string(params->utils, &passcopy);
166	return result;
167    }
168
169    /* verify password (and possibly fetch both authentication and
170       authorization identity related properties) - return SASL_OK
171       on success */
172    result = params->utils->checkpass(params->utils->conn,
173				      oparams->authid,
174				      oparams->alen,
175				      passcopy,
176				      password_len);
177
178    _plug_free_string(params->utils, &passcopy);
179
180    if (result != SASL_OK) {
181	params->utils->seterror(params->utils->conn, 0,
182				"Password verification failed");
183	return result;
184    }
185
186    /* Canonicalize and store the authorization ID */
187    /* We need to do this after calling verify_user just in case verify_user
188     * needed to get auxprops itself */
189    if (canon_flags == 0) {
190	const struct propval *pr;
191	int i;
192
193	pr = params->utils->prop_get(params->propctx);
194	if (!pr) {
195	    return SASL_FAIL;
196	}
197
198	/* params->utils->checkpass() might have fetched authorization identity related properties
199	   for the wrong user name. Free these values. */
200	for (i = 0; pr[i].name; i++) {
201	    if (pr[i].name[0] == '*') {
202		continue;
203	    }
204
205            if (pr[i].values) {
206	    	params->utils->prop_erase(params->propctx, pr[i].name);
207            }
208	}
209
210	result = params->canon_user(params->utils->conn,
211				    author,
212				    0,
213				    SASL_CU_AUTHZID,
214				    oparams);
215	if (result != SASL_OK) {
216	    return result;
217	}
218    }
219
220    /* set oparams */
221    oparams->doneflag = 1;
222    oparams->mech_ssf = 0;
223    oparams->maxoutbuf = 0;
224    oparams->encode_context = NULL;
225    oparams->encode = NULL;
226    oparams->decode_context = NULL;
227    oparams->decode = NULL;
228    oparams->param_version = 0;
229
230    return SASL_OK;
231}
232
233static sasl_server_plug_t plain_server_plugins[] =
234{
235    {
236	"PLAIN",			/* mech_name */
237	0,				/* max_ssf */
238	SASL_SEC_NOANONYMOUS
239	| SASL_SEC_PASS_CREDENTIALS,	/* security_flags */
240	SASL_FEAT_WANT_CLIENT_FIRST
241	| SASL_FEAT_ALLOWS_PROXY,	/* features */
242	NULL,				/* glob_context */
243	&plain_server_mech_new,		/* mech_new */
244	&plain_server_mech_step,	/* mech_step */
245	NULL,				/* mech_dispose */
246	NULL,				/* mech_free */
247	NULL,				/* setpass */
248	NULL,				/* user_query */
249	NULL,				/* idle */
250	NULL,				/* mech_avail */
251	NULL				/* spare */
252    }
253};
254
255int plain_server_plug_init(const sasl_utils_t *utils,
256			   int maxversion,
257			   int *out_version,
258			   sasl_server_plug_t **pluglist,
259			   int *plugcount)
260{
261    if (maxversion < SASL_SERVER_PLUG_VERSION) {
262	SETERROR(utils, "PLAIN version mismatch");
263	return SASL_BADVERS;
264    }
265
266    *out_version = SASL_SERVER_PLUG_VERSION;
267    *pluglist = plain_server_plugins;
268    *plugcount = 1;
269
270    return SASL_OK;
271}
272
273/*****************************  Client Section  *****************************/
274
275typedef struct client_context {
276    char *out_buf;
277    unsigned out_buf_len;
278} client_context_t;
279
280static int plain_client_mech_new(void *glob_context __attribute__((unused)),
281				 sasl_client_params_t *params,
282				 void **conn_context)
283{
284    client_context_t *text;
285
286    /* holds state are in */
287    text = params->utils->malloc(sizeof(client_context_t));
288    if (text == NULL) {
289	MEMERROR( params->utils );
290	return SASL_NOMEM;
291    }
292
293    memset(text, 0, sizeof(client_context_t));
294
295    *conn_context = text;
296
297    return SASL_OK;
298}
299
300static int plain_client_mech_step(void *conn_context,
301				  sasl_client_params_t *params,
302				  const char *serverin __attribute__((unused)),
303				  unsigned serverinlen __attribute__((unused)),
304				  sasl_interact_t **prompt_need,
305				  const char **clientout,
306				  unsigned *clientoutlen,
307				  sasl_out_params_t *oparams)
308{
309    client_context_t *text = (client_context_t *) conn_context;
310    const char *user = NULL, *authid = NULL;
311    sasl_secret_t *password = NULL;
312    unsigned int free_password = 0; /* set if we need to free password */
313    int user_result = SASL_OK;
314    int auth_result = SASL_OK;
315    int pass_result = SASL_OK;
316    int result;
317    char *p;
318
319    *clientout = NULL;
320    *clientoutlen = 0;
321
322    /* doesn't really matter how the server responds */
323
324    /* check if sec layer strong enough */
325    if (params->props.min_ssf > params->external_ssf) {
326	SETERROR( params->utils, "SSF requested of PLAIN plugin");
327	return SASL_TOOWEAK;
328    }
329
330    /* try to get the authid */
331    if (oparams->authid == NULL) {
332	auth_result = _plug_get_authid(params->utils, &authid, prompt_need);
333
334	if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT))
335	    return auth_result;
336    }
337
338    /* try to get the userid */
339    if (oparams->user == NULL) {
340	user_result = _plug_get_userid(params->utils, &user, prompt_need);
341
342	if ((user_result != SASL_OK) && (user_result != SASL_INTERACT))
343	    return user_result;
344    }
345
346    /* try to get the password */
347    if (password == NULL) {
348	pass_result = _plug_get_password(params->utils, &password,
349					 &free_password, prompt_need);
350
351	if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT))
352	    return pass_result;
353    }
354
355    /* free prompts we got */
356    if (prompt_need && *prompt_need) {
357	params->utils->free(*prompt_need);
358	*prompt_need = NULL;
359    }
360
361    /* if there are prompts not filled in */
362    if ((user_result == SASL_INTERACT) || (auth_result == SASL_INTERACT) ||
363	(pass_result == SASL_INTERACT)) {
364	/* make the prompt list */
365	result =
366	    _plug_make_prompts(params->utils, prompt_need,
367			       user_result == SASL_INTERACT ?
368			       "Please enter your authorization name" : NULL,
369			       NULL,
370			       auth_result == SASL_INTERACT ?
371			       "Please enter your authentication name" : NULL,
372			       NULL,
373			       pass_result == SASL_INTERACT ?
374			       "Please enter your password" : NULL, NULL,
375			       NULL, NULL, NULL,
376			       NULL, NULL, NULL);
377	if (result != SASL_OK) goto cleanup;
378
379	return SASL_INTERACT;
380    }
381
382    if (!password) {
383	PARAMERROR(params->utils);
384	return SASL_BADPARAM;
385    }
386
387    if (!user || !*user) {
388	result = params->canon_user(params->utils->conn, authid, 0,
389				    SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams);
390    }
391    else {
392	result = params->canon_user(params->utils->conn, user, 0,
393				    SASL_CU_AUTHZID, oparams);
394	if (result != SASL_OK) goto cleanup;
395
396	result = params->canon_user(params->utils->conn, authid, 0,
397				    SASL_CU_AUTHID, oparams);
398    }
399    if (result != SASL_OK) goto cleanup;
400
401    /* send authorized id NUL authentication id NUL password */
402    *clientoutlen = ((user && *user ? oparams->ulen : 0) +
403		     1 + oparams->alen +
404		     1 + password->len);
405
406    /* remember the extra NUL on the end for stupid clients */
407    result = _plug_buf_alloc(params->utils, &(text->out_buf),
408			     &(text->out_buf_len), *clientoutlen + 1);
409    if (result != SASL_OK) goto cleanup;
410
411    memset(text->out_buf, 0, *clientoutlen + 1);
412    p = text->out_buf;
413    if (user && *user) {
414	memcpy(p, oparams->user, oparams->ulen);
415	p += oparams->ulen;
416    }
417    memcpy(++p, oparams->authid, oparams->alen);
418    p += oparams->alen;
419    memcpy(++p, password->data, password->len);
420
421    *clientout = text->out_buf;
422
423    /* set oparams */
424    oparams->doneflag = 1;
425    oparams->mech_ssf = 0;
426    oparams->maxoutbuf = 0;
427    oparams->encode_context = NULL;
428    oparams->encode = NULL;
429    oparams->decode_context = NULL;
430    oparams->decode = NULL;
431    oparams->param_version = 0;
432
433    result = SASL_OK;
434
435  cleanup:
436    /* free sensitive info */
437    if (free_password) _plug_free_secret(params->utils, &password);
438
439    return result;
440}
441
442static void plain_client_mech_dispose(void *conn_context,
443				      const sasl_utils_t *utils)
444{
445    client_context_t *text = (client_context_t *) conn_context;
446
447    if (!text) return;
448
449    if (text->out_buf) utils->free(text->out_buf);
450
451    utils->free(text);
452}
453
454static sasl_client_plug_t plain_client_plugins[] =
455{
456    {
457	"PLAIN",			/* mech_name */
458	0,				/* max_ssf */
459	SASL_SEC_NOANONYMOUS
460	| SASL_SEC_NOLEGACY // APPLE: prefer PLAIN over LOGIN
461	| SASL_SEC_PASS_CREDENTIALS,	/* security_flags */
462	SASL_FEAT_WANT_CLIENT_FIRST
463	| SASL_FEAT_ALLOWS_PROXY,	/* features */
464	NULL,				/* required_prompts */
465	NULL,				/* glob_context */
466	&plain_client_mech_new,		/* mech_new */
467	&plain_client_mech_step,	/* mech_step */
468	&plain_client_mech_dispose,	/* mech_dispose */
469	NULL,				/* mech_free */
470	NULL,				/* idle */
471	NULL,				/* spare */
472	NULL				/* spare */
473    }
474};
475
476int plain_client_plug_init(sasl_utils_t *utils,
477			   int maxversion,
478			   int *out_version,
479			   sasl_client_plug_t **pluglist,
480			   int *plugcount)
481{
482    if (maxversion < SASL_CLIENT_PLUG_VERSION) {
483	SETERROR(utils, "PLAIN version mismatch");
484	return SASL_BADVERS;
485    }
486
487    *out_version = SASL_CLIENT_PLUG_VERSION;
488    *pluglist = plain_client_plugins;
489    *plugcount = 1;
490
491    return SASL_OK;
492}
493