1 /* This file is part of GNU Dico.
2    Copyright (C) 1998-2020 Sergey Poznyakoff
3 
4    GNU Dico is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    GNU Dico is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with GNU Dico.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include <dicod.h>
18 
19 int sasl_enable = 1;
20 dico_list_t sasl_enabled_mech;
21 dico_list_t sasl_disabled_mech;
22 char *sasl_service;
23 char *sasl_realm;
24 dico_list_t sasl_anon_groups;
25 
26 #ifdef WITH_GSASL
27 #include <gsaslstr.h>
28 
29 static Gsasl *ctx;
30 
31 static int
cmp_names(const void * item,const void * data,void * closure)32 cmp_names(const void *item, const void *data, void *closure)
33 {
34     return c_strcasecmp(item, data);
35 }
36 
37 static int
disabled_mechanism_p(char * name)38 disabled_mechanism_p(char *name)
39 {
40     if (sasl_enabled_mech
41 	&& !_dico_list_locate(sasl_enabled_mech, name, cmp_names, NULL))
42 	return 1;
43     return !!_dico_list_locate(sasl_disabled_mech, name, cmp_names, NULL);
44 }
45 
46 static void
send_challenge(dico_stream_t str,char * data)47 send_challenge(dico_stream_t str, char *data)
48 {
49     if (data[0]) {
50 	 stream_printf(str, "130 challenge follows\n");
51 	 /* FIXME: use dicod_ostream_create */
52 	 stream_writez(str, data);
53 	 stream_writez(str, "\n.\n");
54     }
55     stream_printf(str, "330 send response\n");
56 }
57 
58 #define SASLRESP "SASLRESP"
59 
60 static int
get_sasl_response(dico_stream_t str,char ** pret,char ** pbuf,size_t * psize)61 get_sasl_response(dico_stream_t str, char **pret, char **pbuf, size_t *psize)
62 {
63     char *p;
64     size_t rdbytes;
65 
66     if (get_input_line(str, pbuf, psize, &rdbytes))
67 	return 1;
68     p = *pbuf;
69     rdbytes = dico_trim_nl(p);
70     if (rdbytes >= sizeof(SASLRESP)
71 	&& memcmp(p, SASLRESP, sizeof(SASLRESP) - 1) == 0
72 	&& isspace(p[sizeof(SASLRESP) - 1])) {
73 	for (p += sizeof(SASLRESP), rdbytes -= sizeof(SASLRESP); isspace(*p);
74 	     p++, rdbytes--);
75 	if (*p == '"') {
76 	    /* Simple unquoting */
77 	    p++;
78 	    rdbytes--;
79 	    p[rdbytes-1] = 0;
80 	}
81 	*pret = p;
82 	return 0;
83     }
84     dico_log(L_ERR, 0, _("Unexpected input instead of SASLRESP command: %s"),
85 	     *pbuf);
86     return 1;
87 }
88 
89 #define RC_SUCCESS 0
90 #define RC_FAIL    1
91 #define RC_NOMECH  2
92 
93 struct sasl_data {
94     const char *username;
95     int anon;
96 };
97 
98 static int
_append_item(void * item,void * data)99 _append_item (void *item, void *data)
100 {
101     char *copy = xstrdup (item);
102     dico_list_t list = data;
103     xdico_list_append (list, copy);
104     return 0;
105 }
106 
107 static int
sasl_auth(dico_stream_t str,char * mechanism,char * initresp,Gsasl_session ** psess)108 sasl_auth(dico_stream_t str, char *mechanism, char *initresp,
109 	  Gsasl_session **psess)
110 {
111     int rc;
112     Gsasl_session *sess_ctx;
113     char *input;
114     char *output;
115     char *inbuf;
116     size_t insize;
117     struct sasl_data sdata = { NULL, 0 };
118 
119     if (disabled_mechanism_p(mechanism))
120 	return RC_NOMECH;
121     rc = gsasl_server_start (ctx, mechanism, &sess_ctx);
122     if (rc != GSASL_OK) {
123 	dico_log(L_ERR, 0, _("SASL gsasl_server_start: %s"),
124 		 gsasl_strerror(rc));
125 	return rc == GSASL_UNKNOWN_MECHANISM ? RC_NOMECH : RC_FAIL;
126     }
127 
128     gsasl_callback_hook_set(ctx, &sdata);
129     output = NULL;
130     if (initresp) {
131 	inbuf = xstrdup(initresp);
132 	insize = strlen(initresp) + 1;
133     } else {
134 	inbuf = NULL;
135 	insize = 0;
136     }
137     input = inbuf;
138     while ((rc = gsasl_step64(sess_ctx, input, &output)) == GSASL_NEEDS_MORE) {
139 	send_challenge(str, output);
140 
141 	free(output);
142 	output = NULL;
143 	if (get_sasl_response(str, &input, &inbuf, &insize))
144 	    return RC_FAIL;
145     }
146 
147     if (rc != GSASL_OK) {
148 	dico_log(L_ERR, 0, _("GSASL error: %s"), gsasl_strerror(rc));
149 	free(output);
150 	free(inbuf);
151 	gsasl_finish(sess_ctx);
152 	return RC_FAIL;
153     }
154 
155     /* Some SASL mechanisms output data when GSASL_OK is returned */
156     if (output[0])
157 	send_challenge(str, output);
158 
159     free(output);
160     free(inbuf);
161 
162     if (sdata.username == NULL) {
163 	dico_log(L_ERR, 0, _("GSASL %s: cannot get username"), mechanism);
164 	gsasl_finish(sess_ctx);
165 	return RC_FAIL;
166     }
167 
168     user_name = xstrdup(sdata.username);
169     if (sdata.anon) {
170 	if (sasl_anon_groups) {
171 	    user_groups = xdico_list_create();
172 	    dico_list_iterate (sasl_anon_groups, _append_item, user_groups);
173 	}
174     } else
175 	dico_udb_get_groups(user_db, sdata.username, &user_groups);
176     check_db_visibility();
177 
178     *psess = sess_ctx;
179     return RC_SUCCESS;
180 }
181 
182 static void
dicod_saslauth(dico_stream_t str,int argc,char ** argv)183 dicod_saslauth(dico_stream_t str, int argc, char **argv)
184 {
185     int rc;
186     char *resp;
187     Gsasl_session *sess;
188 
189     if (dico_udb_open(user_db)) {
190 	dico_log(L_ERR, 0, _("failed to open user database"));
191 	stream_writez(str,
192 		      "531 Access denied, "
193 		      "use \"SHOW INFO\" for server information\n");
194 	return;
195     }
196     rc = sasl_auth(str, argv[1], argv[2], &sess);
197     dico_udb_close(user_db);
198     switch (rc) {
199     case RC_SUCCESS:
200 	resp = "230 Authentication successful";
201 	stream_printf(str, "%s\n", resp);
202 	/* FIXME: If insert_gsasl_stream fails, client gets wrong response */
203 	if (insert_gsasl_stream(sess, &str) == 0) {
204 	    replace_io_stream(str);
205 	    dicod_remove_command("SASLAUTH");
206 	}
207 	return;
208 
209     case RC_FAIL:
210 	resp = "531 Access denied, "
211 	       "use \"SHOW INFO\" for server information";
212 	break;
213 
214     case RC_NOMECH:
215 	resp = "532 Access denied, unknown mechanism";
216 	break;
217     }
218     stream_printf(str, "%s\n", resp);
219 }
220 
221 
222 static int
cb_validate(Gsasl * ctx,Gsasl_session * sctx)223 cb_validate(Gsasl *ctx, Gsasl_session *sctx)
224 {
225     int rc;
226     struct sasl_data *pdata = gsasl_callback_hook_get(ctx);
227     const char *authid = gsasl_property_get(sctx, GSASL_AUTHID);
228     const char *pass = gsasl_property_get(sctx, GSASL_PASSWORD);
229     char *dbpass;
230 
231     if (!authid)
232 	return GSASL_NO_AUTHID;
233     if (!pass)
234 	return GSASL_NO_PASSWORD;
235 
236     rc = dico_udb_check_password(user_db, authid, pass);
237     if (rc == ENOSYS) {
238         if (dico_udb_get_password(user_db, authid, &dbpass)) {
239 	    dico_log(L_ERR, 0,
240 		     _("failed to get password for `%s' from the database"),
241 		     authid);
242 	    return GSASL_AUTHENTICATION_ERROR;
243 	}
244 	rc = dicod_check_password(dbpass, pass);
245 	free(dbpass);
246     }
247     if (rc == 0) {
248 	pdata->username = xstrdup(authid);
249 	return GSASL_OK;
250     }
251     return GSASL_AUTHENTICATION_ERROR;
252 }
253 
254 static int
callback(Gsasl * ctx,Gsasl_session * sctx,Gsasl_property prop)255 callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop)
256 {
257     int rc = GSASL_OK;
258     struct sasl_data *pdata;
259     const char *user;
260     char *string;
261 
262     switch (prop) {
263     case GSASL_PASSWORD:
264 	pdata = gsasl_callback_hook_get(ctx);
265 	user = pdata->username;
266 	if (!user) {
267 	    user = gsasl_property_get(sctx, GSASL_AUTHID);
268 	    if (!user) {
269 		dico_log(L_ERR, 0, _("user name not supplied"));
270 		return GSASL_NO_AUTHID;
271 	    }
272 	    pdata->username = user;
273 	}
274 	if (dico_udb_get_password(user_db, user, &string)) {
275 	    dico_log(L_ERR, 0,
276 		     _("failed to get password for `%s' from the database"),
277 		     user);
278 	    return GSASL_NO_PASSWORD;
279 	}
280 	gsasl_property_set(sctx, prop, string);
281 	free(string);
282 	break;
283 
284     case GSASL_SERVICE:
285 	gsasl_property_set(sctx, prop, sasl_service);
286 	break;
287 
288     case GSASL_REALM:
289 	gsasl_property_set(sctx, prop, sasl_realm ? sasl_realm : hostname);
290 	break;
291 
292     case GSASL_HOSTNAME:
293 	gsasl_property_set(sctx, prop, hostname);
294 	break;
295 
296     case GSASL_VALIDATE_SIMPLE:
297 	rc = cb_validate(ctx, sctx);
298 	break;
299 
300 #if 0
301     FIXME:
302     case GSASL_VALIDATE_EXTERNAL:
303     case GSASL_VALIDATE_SECURID:
304 #endif
305 
306     case GSASL_VALIDATE_ANONYMOUS:
307 	pdata = gsasl_callback_hook_get(ctx);
308 	user = gsasl_property_get(sctx, GSASL_ANONYMOUS_TOKEN);
309 	pdata->username = user;
310 	pdata->anon = 1;
311 	break;
312 
313     case GSASL_VALIDATE_GSSAPI:
314 	pdata = gsasl_callback_hook_get(ctx);
315 	user = gsasl_property_get(sctx, GSASL_AUTHZID);
316 	pdata->username = user;
317 	break;
318 
319     default:
320 	rc = GSASL_NO_CALLBACK;
321 	/*dico_log(L_NOTICE, 0, _("Unsupported callback property %d"), prop);*/
322 	break;
323     }
324 
325     return rc;
326 }
327 
328 static int
init_sasl_0(void)329 init_sasl_0(void)
330 {
331     int rc = gsasl_init(&ctx);
332     if (rc != GSASL_OK) {
333 	dico_log(L_ERR, 0, _("cannot initialize libgsasl: %s"),
334 		 gsasl_strerror(rc));
335 	return 1;
336     }
337     gsasl_callback_set(ctx, callback);
338     return 0;
339 }
340 
341 static int
init_sasl_1(void * data)342 init_sasl_1(void *data)
343 {
344     static struct dicod_command cmd =
345 	{ "SASLAUTH", 2, 3, "mechanism [initial-response]",
346 	  "Start SASL authentication",
347 	  dicod_saslauth };
348     static int sasl_initialized;
349 
350     if (!sasl_initialized) {
351 	sasl_initialized = 1;
352 	dicod_add_command(&cmd);
353 	if (!sasl_service)
354 	    sasl_service = xstrdup ("dico");
355     }
356     return 0;
357 }
358 
359 void
register_sasl(void)360 register_sasl(void)
361 {
362   int rc;
363   char *listmech;
364   struct wordsplit ws;
365 
366   if (!sasl_enable || init_sasl_0())
367       return;
368   rc =  gsasl_server_mechlist(ctx, &listmech);
369   if (rc != GSASL_OK) {
370       dico_log(L_ERR, 0, _("cannot get list of available SASL mechanisms: "
371 			   "%s"),
372 	       gsasl_strerror (rc));
373       return;
374   }
375 
376   if (wordsplit(listmech, &ws,
377 		WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_SQUEEZE_DELIMS) == 0) {
378       int i;
379       for (i = 0; i < ws.ws_wordc; i++) {
380 	  if (!disabled_mechanism_p(ws.ws_wordv[i])) {
381 	      char *name = xdico_sasl_mech_to_capa(ws.ws_wordv[i]);
382 	      dicod_capa_register(name, NULL, init_sasl_1, NULL);
383 	      dicod_capa_add(name);
384 	  }
385       }
386       wordsplit_free(&ws);
387   }
388   free(listmech);
389 }
390 
391 #else
392 void
register_sasl(void)393 register_sasl(void)
394 {
395     /* nothing */
396 }
397 #endif
398