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_
71 static 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 
76 typedef struct context {
77     int state;
78 
79     char *username;
80     size_t username_len;
81 } server_context_t;
82 
login_server_mech_new(void * glob_context,sasl_server_params_t * sparams,const char * challenge,unsigned challen,void ** conn_context)83 static 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 
login_server_mech_step(void * conn_context,sasl_server_params_t * params,const char * clientin,unsigned clientinlen,const char ** serverout,unsigned * serveroutlen,sasl_out_params_t * oparams)110 static 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 
login_server_mech_dispose(void * conn_context,const sasl_utils_t * utils)253 static 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 
265 static 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 
login_server_plug_init(sasl_utils_t * utils,int maxversion,int * out_version,sasl_server_plug_t ** pluglist,int * plugcount)285 int 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 
305 typedef 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 
login_client_mech_new(void * glob_context,sasl_client_params_t * params,void ** conn_context)315 static 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 
login_client_mech_step(void * conn_context,sasl_client_params_t * params,const char * serverin,unsigned serverinlen,sasl_interact_t ** prompt_need,const char ** clientout,unsigned * clientoutlen,sasl_out_params_t * oparams)337 static 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 
login_client_mech_dispose(void * conn_context,const sasl_utils_t * utils)503 static 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 
519 static 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 
login_client_plug_init(sasl_utils_t * utils,int maxversion,int * out_version,sasl_client_plug_t ** pluglist,int * plugcount)538 int 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