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