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