1/*
2 * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
3 */
4
5/* LOGIN is a PLAIN-like authenticator, but for older deployments. */
6
7/* Login SASL plugin
8 * Rob Siemborski (SASLv2 Conversion)
9 * contributed by Rainer Schoepf <schoepf@uni-mainz.de>
10 * based on PLAIN, by Tim Martin <tmartin@andrew.cmu.edu>
11 * $Id: login.c,v 1.25 2003/02/13 19:56:04 rjs3 Exp $
12 */
13/*
14 * Copyright (c) 1998-2003 Carnegie Mellon University.  All rights reserved.
15 *
16 * Redistribution and use in source and binary forms, with or without
17 * modification, are permitted provided that the following conditions
18 * are met:
19 *
20 * 1. Redistributions of source code must retain the above copyright
21 *    notice, this list of conditions and the following disclaimer.
22 *
23 * 2. Redistributions in binary form must reproduce the above copyright
24 *    notice, this list of conditions and the following disclaimer in
25 *    the documentation and/or other materials provided with the
26 *    distribution.
27 *
28 * 3. The name "Carnegie Mellon University" must not be used to
29 *    endorse or promote products derived from this software without
30 *    prior written permission. For permission or any other legal
31 *    details, please contact
32 *      Office of Technology Transfer
33 *      Carnegie Mellon University
34 *      5000 Forbes Avenue
35 *      Pittsburgh, PA  15213-3890
36 *      (412) 268-4387, fax: (412) 268-7395
37 *      tech-transfer@andrew.cmu.edu
38 *
39 * 4. Redistributions of any form whatsoever must retain the following
40 *    acknowledgment:
41 *    "This product includes software developed by Computing Services
42 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
43 *
44 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
45 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
46 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
47 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
48 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
49 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
50 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
51 */
52
53#include <config.h>
54#include <stdio.h>
55#include <ctype.h>
56#include <sasl.h>
57#include <saslplug.h>
58
59#include "plugin_common.h"
60
61#ifndef _SUN_SDK_
62#ifdef WIN32
63/* This must be after sasl.h */
64# include "saslLOGIN.h"
65#endif /* WIN32 */
66#endif /* !_SUN_SDK_ */
67
68/*****************************  Common Section  *****************************/
69
70#ifndef _SUN_SDK_
71static const char plugin_id[] = "$Id: login.c,v 1.25 2003/02/13 19:56:04 rjs3 Exp $";
72#endif /* !_SUN_SDK_ */
73
74/*****************************  Server Section  *****************************/
75
76typedef struct context {
77    int state;
78
79    char *username;
80    size_t username_len;
81} server_context_t;
82
83static int login_server_mech_new(void *glob_context __attribute__((unused)),
84				 sasl_server_params_t *sparams,
85				 const char *challenge __attribute__((unused)),
86				 unsigned challen __attribute__((unused)),
87				 void **conn_context)
88{
89    server_context_t *text;
90
91    /* holds state are in */
92    text = sparams->utils->malloc(sizeof(server_context_t));
93    if (text == NULL) {
94	MEMERROR( sparams->utils );
95	return SASL_NOMEM;
96    }
97
98    memset(text, 0, sizeof(server_context_t));
99
100    text->state = 1;
101
102    *conn_context = text;
103
104    return SASL_OK;
105}
106
107#define USERNAME_CHALLENGE "Username:"
108#define PASSWORD_CHALLENGE "Password:"
109
110static int login_server_mech_step(void *conn_context,
111				  sasl_server_params_t *params,
112				  const char *clientin,
113				  unsigned clientinlen,
114				  const char **serverout,
115				  unsigned *serveroutlen,
116				  sasl_out_params_t *oparams)
117{
118    server_context_t *text = (server_context_t *) conn_context;
119
120    *serverout = NULL;
121    *serveroutlen = 0;
122
123    switch (text->state) {
124
125    case 1:
126	text->state = 2;
127
128	/* Check inlen, (possibly we have already the user name) */
129	/* In this case fall through to state 2 */
130	if (clientinlen == 0) {
131	    /* demand username */
132
133	    *serveroutlen = strlen(USERNAME_CHALLENGE);
134	    *serverout = USERNAME_CHALLENGE;
135
136	    return SASL_CONTINUE;
137	}
138	/* FALLTHROUGH */
139
140    case 2:
141	/* Catch really long usernames */
142	if (clientinlen > 1024) {
143#ifdef _SUN_SDK_
144	    params->utils->log(params->utils->conn, SASL_LOG_ERR,
145		"username too long (>1024 characters)");
146#else
147	    SETERROR(params->utils, "username too long (>1024 characters)");
148#endif	/* _SUN_SDK_ */
149	    return SASL_BADPROT;
150	}
151
152	/* get username */
153	text->username =
154	    params->utils->malloc(sizeof(sasl_secret_t) + clientinlen + 1);
155	if (!text->username) {
156	    MEMERROR( params->utils );
157	    return SASL_NOMEM;
158	}
159
160	strncpy(text->username, clientin, clientinlen);
161	text->username_len = clientinlen;
162	text->username[clientinlen] = '\0';
163
164	/* demand password */
165	*serveroutlen = strlen(PASSWORD_CHALLENGE);
166	*serverout = PASSWORD_CHALLENGE;
167
168	text->state = 3;
169
170	return SASL_CONTINUE;
171
172
173    case 3: {
174	sasl_secret_t *password;
175	int result;
176
177	/* Catch really long passwords */
178	if (clientinlen > 1024) {
179#ifdef _SUN_SDK_
180	    params->utils->log(params->utils->conn, SASL_LOG_ERR,
181		     "clientinlen is > 1024 characters in LOGIN plugin");
182#else
183	    SETERROR(params->utils,
184		     "clientinlen is > 1024 characters in LOGIN plugin");
185#endif	/* _SUN_SDK_ */
186	    return SASL_BADPROT;
187	}
188
189	/* get password */
190	password =
191	    params->utils->malloc(sizeof(sasl_secret_t) + clientinlen + 1);
192	if (!password) {
193	    MEMERROR(params->utils);
194	    return SASL_NOMEM;
195	}
196
197	strncpy((char *)password->data, clientin, clientinlen);
198	password->data[clientinlen] = '\0';
199	password->len = clientinlen;
200
201	/* canonicalize username first, so that password verification is
202	 * done against the canonical id */
203	result = params->canon_user(params->utils->conn, text->username,
204				    text->username_len,
205				    SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams);
206	if (result != SASL_OK) {
207		_plug_free_secret(params->utils, &password);
208		return result;
209	}
210
211	/* verify_password - return sasl_ok on success */
212	result = params->utils->checkpass(params->utils->conn,
213					oparams->authid, oparams->alen,
214					(char *)password->data, password->len);
215
216	if (result != SASL_OK) {
217	    _plug_free_secret(params->utils, &password);
218	    return result;
219	}
220
221	if (params->transition) {
222	    params->transition(params->utils->conn,
223			       (char *)password->data, password->len);
224	}
225
226	_plug_free_secret(params->utils, &password);
227
228	*serverout = NULL;
229	*serveroutlen = 0;
230
231	oparams->doneflag = 1;
232	oparams->mech_ssf = 0;
233	oparams->maxoutbuf = 0;
234	oparams->encode_context = NULL;
235	oparams->encode = NULL;
236	oparams->decode_context = NULL;
237	oparams->decode = NULL;
238	oparams->param_version = 0;
239
240	return SASL_OK;
241    }
242
243
244    default:
245	params->utils->log(NULL, SASL_LOG_ERR,
246			   "Invalid LOGIN server step %d\n", text->state);
247	return SASL_FAIL;
248    }
249
250    return SASL_FAIL; /* should never get here */
251}
252
253static void login_server_mech_dispose(void *conn_context,
254				      const sasl_utils_t *utils)
255{
256    server_context_t *text = (server_context_t *) conn_context;
257
258    if (!text) return;
259
260    if (text->username) utils->free(text->username);
261
262    utils->free(text);
263}
264
265static sasl_server_plug_t login_server_plugins[] =
266{
267    {
268	"LOGIN",			/* mech_name */
269	0,				/* max_ssf */
270	SASL_SEC_NOANONYMOUS,		/* security_flags */
271	0,				/* features */
272	NULL,				/* glob_context */
273	&login_server_mech_new,		/* mech_new */
274	&login_server_mech_step,	/* mech_step */
275	&login_server_mech_dispose,	/* mech_dispose */
276	NULL,				/* mech_free */
277	NULL,				/* setpass */
278	NULL,				/* user_query */
279	NULL,				/* idle */
280	NULL,				/* mech_avail */
281	NULL				/* spare */
282    }
283};
284
285int login_server_plug_init(sasl_utils_t *utils,
286			   int maxversion,
287			   int *out_version,
288			   sasl_server_plug_t **pluglist,
289			   int *plugcount)
290{
291    if (maxversion < SASL_SERVER_PLUG_VERSION) {
292	SETERROR(utils, "LOGIN version mismatch");
293	return SASL_BADVERS;
294    }
295
296    *out_version = SASL_SERVER_PLUG_VERSION;
297    *pluglist = login_server_plugins;
298    *plugcount = 1;
299
300    return SASL_OK;
301}
302
303/*****************************  Client Section  *****************************/
304
305typedef struct client_context {
306    int state;
307
308#ifdef _INTEGRATED_SOLARIS_
309    void *h;
310#endif /* _INTEGRATED_SOLARIS_ */
311    sasl_secret_t *password;
312    unsigned int free_password; /* set if we need to free password */
313} client_context_t;
314
315static int login_client_mech_new(void *glob_context __attribute__((unused)),
316				 sasl_client_params_t *params,
317				 void **conn_context)
318{
319    client_context_t *text;
320
321    /* holds state are in */
322    text = params->utils->malloc(sizeof(client_context_t));
323    if (text == NULL) {
324	MEMERROR(params->utils);
325	return SASL_NOMEM;
326    }
327
328    memset(text, 0, sizeof(client_context_t));
329
330    text->state = 1;
331
332    *conn_context = text;
333
334    return SASL_OK;
335}
336
337static int login_client_mech_step(void *conn_context,
338				  sasl_client_params_t *params,
339				  const char *serverin __attribute__((unused)),
340				  unsigned serverinlen __attribute__((unused)),
341				  sasl_interact_t **prompt_need,
342				  const char **clientout,
343				  unsigned *clientoutlen,
344				  sasl_out_params_t *oparams)
345{
346    client_context_t *text = (client_context_t *) conn_context;
347
348    *clientout = NULL;
349    *clientoutlen = 0;
350
351    switch (text->state) {
352
353    case 1: {
354	const char *user;
355	int auth_result = SASL_OK;
356	int pass_result = SASL_OK;
357	int result;
358
359	/* check if sec layer strong enough */
360	if (params->props.min_ssf > params->external_ssf) {
361#ifdef _INTEGRATED_SOLARIS_
362	    params->utils->log(params->utils->conn, SASL_LOG_ERR,
363		gettext("SSF requested of LOGIN plugin"));
364#else
365	    SETERROR( params->utils, "SSF requested of LOGIN plugin");
366#endif /* _INTEGRATED_SOLARIS_ */
367	    return SASL_TOOWEAK;
368	}
369
370	/* try to get the userid */
371	/* Note: we want to grab the authname and not the userid, which is
372	 *       who we AUTHORIZE as, and will be the same as the authname
373	 *       for the LOGIN mech.
374	 */
375	if (oparams->user == NULL) {
376	    auth_result = _plug_get_authid(params->utils, &user, prompt_need);
377
378	    if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT))
379		return auth_result;
380	}
381
382	/* try to get the password */
383	if (text->password == NULL) {
384	    pass_result = _plug_get_password(params->utils, &text->password,
385					     &text->free_password, prompt_need);
386
387	    if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT))
388		return pass_result;
389	}
390
391	/* free prompts we got */
392	if (prompt_need && *prompt_need) {
393	    params->utils->free(*prompt_need);
394	    *prompt_need = NULL;
395	}
396
397	/* if there are prompts not filled in */
398	if ((auth_result == SASL_INTERACT) || (pass_result == SASL_INTERACT)) {
399	    /* make the prompt list */
400	    result =
401#ifdef _INTEGRATED_SOLARIS_
402		_plug_make_prompts(params->utils, &text->h, prompt_need,
403		    NULL, NULL,
404		    auth_result == SASL_INTERACT ?
405		    gettext("Please enter your authentication name") : NULL,
406		    NULL,
407		    pass_result == SASL_INTERACT ?
408		    gettext("Please enter your password") : NULL, NULL,
409		    NULL, NULL, NULL,
410		    NULL, NULL, NULL);
411#else
412		_plug_make_prompts(params->utils, prompt_need,
413				   NULL, NULL,
414				   auth_result == SASL_INTERACT ?
415				   "Please enter your authentication name" : NULL,
416				   NULL,
417				   pass_result == SASL_INTERACT ?
418				   "Please enter your password" : NULL, NULL,
419				   NULL, NULL, NULL,
420				   NULL, NULL, NULL);
421#endif /* _INTEGRATED_SOLARIS_ */
422	    if (result != SASL_OK) return result;
423
424	    return SASL_INTERACT;
425	}
426
427	if (!text->password) {
428	    PARAMERROR(params->utils);
429	    return SASL_BADPARAM;
430	}
431
432	result = params->canon_user(params->utils->conn, user, 0,
433				    SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams);
434	if (result != SASL_OK) return result;
435
436	/* server should have sent request for username - we ignore it */
437	if (!serverin) {
438#ifdef _SUN_SDK_
439	    params->utils->log(params->utils->conn, SASL_LOG_ERR,
440		      "Server didn't issue challenge for USERNAME");
441#else
442	    SETERROR( params->utils,
443		      "Server didn't issue challenge for USERNAME");
444#endif /* _SUN_SDK_ */
445	    return SASL_BADPROT;
446	}
447
448	if (!clientout) {
449	    PARAMERROR( params->utils );
450	    return SASL_BADPARAM;
451	}
452
453	if (clientoutlen) *clientoutlen = oparams->alen;
454	*clientout = oparams->authid;
455
456	text->state = 2;
457
458	return SASL_CONTINUE;
459    }
460
461    case 2:
462	/* server should have sent request for password - we ignore it */
463	if (!serverin) {
464#ifdef _SUN_SDK_
465	    params->utils->log(params->utils->conn, SASL_LOG_ERR,
466		      "Server didn't issue challenge for PASSWORD");
467#else
468	    SETERROR( params->utils,
469		      "Server didn't issue challenge for PASSWORD");
470#endif /* _SUN_SDK_ */
471	    return SASL_BADPROT;
472	}
473
474	if (!clientout) {
475	    PARAMERROR(params->utils);
476	    return SASL_BADPARAM;
477	}
478
479	if (clientoutlen) *clientoutlen = text->password->len;
480	*clientout = (char *)text->password->data;
481
482	/* set oparams */
483	oparams->doneflag = 1;
484	oparams->mech_ssf = 0;
485	oparams->maxoutbuf = 0;
486	oparams->encode_context = NULL;
487	oparams->encode = NULL;
488	oparams->decode_context = NULL;
489	oparams->decode = NULL;
490	oparams->param_version = 0;
491
492	return SASL_OK;
493
494    default:
495	params->utils->log(NULL, SASL_LOG_ERR,
496			   "Invalid LOGIN client step %d\n", text->state);
497	return SASL_FAIL;
498    }
499
500    return SASL_FAIL; /* should never get here */
501}
502
503static void login_client_mech_dispose(void *conn_context,
504				      const sasl_utils_t *utils)
505{
506    client_context_t *text = (client_context_t *) conn_context;
507
508    if (!text) return;
509
510    /* free sensitive info */
511    if (text->free_password) _plug_free_secret(utils, &(text->password));
512#ifdef _INTEGRATED_SOLARIS_
513    convert_prompt(utils, &text->h, NULL);
514#endif /* _INTEGRATED_SOLARIS_ */
515
516    utils->free(text);
517}
518
519static sasl_client_plug_t login_client_plugins[] =
520{
521    {
522	"LOGIN",			/* mech_name */
523	0,				/* max_ssf */
524	SASL_SEC_NOANONYMOUS,		/* security_flags */
525	SASL_FEAT_SERVER_FIRST,		/* features */
526	NULL,				/* required_prompts */
527	NULL,				/* glob_context */
528	&login_client_mech_new,		/* mech_new */
529	&login_client_mech_step,	/* mech_step */
530	&login_client_mech_dispose,	/* mech_dispose */
531	NULL,				/* mech_free */
532	NULL,				/* idle */
533	NULL,				/* spare */
534	NULL				/* spare */
535    }
536};
537
538int login_client_plug_init(sasl_utils_t *utils,
539			   int maxversion,
540			   int *out_version,
541			   sasl_client_plug_t **pluglist,
542			   int *plugcount)
543{
544    if (maxversion < SASL_CLIENT_PLUG_VERSION) {
545	SETERROR(utils, "Version mismatch in LOGIN");
546	return SASL_BADVERS;
547    }
548
549    *out_version = SASL_CLIENT_PLUG_VERSION;
550    *pluglist = login_client_plugins;
551    *plugcount = 1;
552
553    return SASL_OK;
554}
555