Line data Source code
1 : /*
2 : SSSD
3 :
4 : Kerberos 5 Backend Module - Serialize the request of a user
5 :
6 : Authors:
7 : Sumit Bose <sbose@redhat.com>
8 :
9 : Copyright (C) 2010 Red Hat
10 :
11 : This program is free software; you can redistribute it and/or modify
12 : it under the terms of the GNU General Public License as published by
13 : the Free Software Foundation; either version 3 of the License, or
14 : (at your option) any later version.
15 :
16 : This program is distributed in the hope that it will be useful,
17 : but WITHOUT ANY WARRANTY; without even the implied warranty of
18 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 : GNU General Public License for more details.
20 :
21 : You should have received a copy of the GNU General Public License
22 : along with this program. If not, see <http://www.gnu.org/licenses/>.
23 : */
24 :
25 : #include <tevent.h>
26 : #include <dhash.h>
27 :
28 : #include <security/pam_modules.h>
29 :
30 : #include "src/providers/krb5/krb5_auth.h"
31 :
32 : #define INIT_HASH_SIZE 5
33 :
34 : struct queue_entry {
35 : struct queue_entry *prev;
36 : struct queue_entry *next;
37 :
38 : struct be_ctx *be_ctx;
39 : struct be_req *be_req;
40 : struct tevent_req *parent_req;
41 : struct pam_data *pd;
42 : struct krb5_ctx *krb5_ctx;
43 : };
44 :
45 : static void wait_queue_auth_done(struct tevent_req *req);
46 :
47 : static void krb5_auth_queue_finish(struct tevent_req *req, errno_t ret,
48 : int pam_status, int dp_err);
49 :
50 1008 : static void wait_queue_auth(struct tevent_context *ev, struct tevent_timer *te,
51 : struct timeval current_time, void *private_data)
52 : {
53 1008 : struct queue_entry *qe = talloc_get_type(private_data, struct queue_entry);
54 : struct tevent_req *req;
55 :
56 1008 : req = krb5_auth_send(qe->parent_req, qe->be_ctx->ev,
57 : qe->be_ctx, qe->pd, qe->krb5_ctx);
58 1008 : if (req == NULL) {
59 0 : DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n");
60 : } else {
61 1008 : tevent_req_set_callback(req, wait_queue_auth_done,
62 1008 : qe->parent_req);
63 : }
64 :
65 1008 : talloc_zfree(qe);
66 1008 : }
67 :
68 1008 : static void wait_queue_auth_done(struct tevent_req *req)
69 : {
70 1008 : struct tevent_req *parent_req = \
71 1008 : tevent_req_callback_data(req, struct tevent_req);
72 : int pam_status;
73 : int dp_err;
74 : errno_t ret;
75 :
76 1008 : ret = krb5_auth_recv(req, &pam_status, &dp_err);
77 1008 : talloc_zfree(req);
78 1008 : if (ret != EOK) {
79 9 : DEBUG(SSSDBG_OP_FAILURE, "krb5_auth_recv failed: %d\n", ret);
80 : }
81 :
82 1008 : krb5_auth_queue_finish(parent_req, ret, pam_status, dp_err);
83 1008 : }
84 :
85 3 : static void wait_queue_del_cb(hash_entry_t *entry, hash_destroy_enum type,
86 : void *pvt)
87 : {
88 : struct queue_entry *head;
89 :
90 3 : if (entry->value.type == HASH_VALUE_PTR) {
91 3 : head = talloc_get_type(entry->value.ptr, struct queue_entry);
92 3 : talloc_zfree(head);
93 3 : return;
94 : }
95 :
96 0 : DEBUG(SSSDBG_CRIT_FAILURE,
97 : "Unexpected value type [%d].\n", entry->value.type);
98 : }
99 :
100 1011 : static errno_t add_to_wait_queue(struct be_ctx *be_ctx,
101 : struct tevent_req *parent_req,
102 : struct pam_data *pd,
103 : struct krb5_ctx *krb5_ctx)
104 : {
105 : int ret;
106 : hash_key_t key;
107 : hash_value_t value;
108 : struct queue_entry *head;
109 : struct queue_entry *queue_entry;
110 :
111 1011 : if (krb5_ctx->wait_queue_hash == NULL) {
112 3 : ret = sss_hash_create_ex(krb5_ctx, INIT_HASH_SIZE,
113 : &krb5_ctx->wait_queue_hash, 0, 0, 0, 0,
114 : wait_queue_del_cb, NULL);
115 3 : if (ret != EOK) {
116 0 : DEBUG(SSSDBG_CRIT_FAILURE, "sss_hash_create failed\n");
117 0 : return ret;
118 : }
119 : }
120 :
121 1011 : key.type = HASH_KEY_STRING;
122 1011 : key.str = pd->user;
123 :
124 1011 : ret = hash_lookup(krb5_ctx->wait_queue_hash, &key, &value);
125 1011 : switch (ret) {
126 : case HASH_SUCCESS:
127 1008 : if (value.type != HASH_VALUE_PTR) {
128 0 : DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected hash value type.\n");
129 0 : return EINVAL;
130 : }
131 :
132 1008 : head = talloc_get_type(value.ptr, struct queue_entry);
133 :
134 1008 : queue_entry = talloc_zero(head, struct queue_entry);
135 1008 : if (queue_entry == NULL) {
136 0 : DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
137 0 : return ENOMEM;
138 : }
139 :
140 1008 : queue_entry->be_ctx = be_ctx;
141 1008 : queue_entry->parent_req = parent_req;
142 1008 : queue_entry->pd = pd;
143 1008 : queue_entry->krb5_ctx = krb5_ctx;
144 :
145 1008 : DLIST_ADD_END(head, queue_entry, struct queue_entry *);
146 :
147 1008 : break;
148 : case HASH_ERROR_KEY_NOT_FOUND:
149 3 : value.type = HASH_VALUE_PTR;
150 3 : head = talloc_zero(krb5_ctx->wait_queue_hash, struct queue_entry);
151 3 : if (head == NULL) {
152 0 : DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
153 0 : return ENOMEM;
154 : }
155 3 : value.ptr = head;
156 :
157 3 : ret = hash_enter(krb5_ctx->wait_queue_hash, &key, &value);
158 3 : if (ret != HASH_SUCCESS) {
159 0 : DEBUG(SSSDBG_CRIT_FAILURE, "hash_enter failed.\n");
160 0 : talloc_free(head);
161 0 : return EIO;
162 : }
163 :
164 3 : break;
165 : default:
166 0 : DEBUG(SSSDBG_CRIT_FAILURE, "hash_lookup failed.\n");
167 0 : return EIO;
168 : }
169 :
170 1011 : if (head->next == NULL) {
171 3 : return ENOENT;
172 : } else {
173 1008 : return EOK;
174 : }
175 : }
176 :
177 1011 : static void check_wait_queue(struct krb5_ctx *krb5_ctx, char *username)
178 : {
179 : int ret;
180 : hash_key_t key;
181 : hash_value_t value;
182 : struct queue_entry *head;
183 : struct queue_entry *queue_entry;
184 : struct tevent_timer *te;
185 :
186 1011 : if (krb5_ctx->wait_queue_hash == NULL) {
187 0 : DEBUG(SSSDBG_CRIT_FAILURE, "No wait queue available.\n");
188 0 : return;
189 : }
190 :
191 1011 : key.type = HASH_KEY_STRING;
192 1011 : key.str = username;
193 :
194 1011 : ret = hash_lookup(krb5_ctx->wait_queue_hash, &key, &value);
195 :
196 1011 : switch (ret) {
197 : case HASH_SUCCESS:
198 1011 : if (value.type != HASH_VALUE_PTR) {
199 0 : DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected hash value type.\n");
200 0 : return;
201 : }
202 :
203 1011 : head = talloc_get_type(value.ptr, struct queue_entry);
204 :
205 1011 : if (head->next == NULL) {
206 3 : DEBUG(SSSDBG_TRACE_LIBS,
207 : "Wait queue for user [%s] is empty.\n", username);
208 : } else {
209 1008 : queue_entry = head->next;
210 :
211 1008 : DLIST_REMOVE(head, queue_entry);
212 :
213 1008 : te = tevent_add_timer(queue_entry->be_ctx->ev, krb5_ctx,
214 : tevent_timeval_current(), wait_queue_auth,
215 : queue_entry);
216 1008 : if (te == NULL) {
217 0 : DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n");
218 : } else {
219 1008 : return;
220 : }
221 : }
222 :
223 3 : ret = hash_delete(krb5_ctx->wait_queue_hash, &key);
224 3 : if (ret != HASH_SUCCESS) {
225 0 : DEBUG(SSSDBG_CRIT_FAILURE,
226 : "Failed to remove wait queue for user [%s].\n",
227 : username);
228 : }
229 :
230 3 : break;
231 : case HASH_ERROR_KEY_NOT_FOUND:
232 0 : DEBUG(SSSDBG_CRIT_FAILURE,
233 : "No wait queue for user [%s] found.\n", username);
234 0 : break;
235 : default:
236 0 : DEBUG(SSSDBG_CRIT_FAILURE, "hash_lookup failed.\n");
237 : }
238 :
239 3 : return;
240 : }
241 :
242 : struct krb5_auth_queue_state {
243 : struct krb5_ctx *krb5_ctx;
244 : struct pam_data *pd;
245 :
246 : int pam_status;
247 : int dp_err;
248 : };
249 :
250 : static void krb5_auth_queue_done(struct tevent_req *subreq);
251 :
252 1011 : struct tevent_req *krb5_auth_queue_send(TALLOC_CTX *mem_ctx,
253 : struct tevent_context *ev,
254 : struct be_ctx *be_ctx,
255 : struct pam_data *pd,
256 : struct krb5_ctx *krb5_ctx)
257 : {
258 : errno_t ret;
259 : struct tevent_req *req;
260 : struct tevent_req *subreq;
261 : struct krb5_auth_queue_state *state;
262 :
263 1011 : req = tevent_req_create(mem_ctx, &state, struct krb5_auth_queue_state);
264 1011 : if (req == NULL) {
265 0 : DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n");
266 0 : return NULL;
267 : }
268 1011 : state->krb5_ctx = krb5_ctx;
269 1011 : state->pd = pd;
270 :
271 1011 : ret = add_to_wait_queue(be_ctx, req, pd, krb5_ctx);
272 1011 : if (ret == EOK) {
273 1008 : DEBUG(SSSDBG_TRACE_LIBS,
274 : "Request [%p] successfully added to wait queue "
275 : "of user [%s].\n", req, pd->user);
276 1008 : ret = EOK;
277 1008 : goto immediate;
278 3 : } else if (ret == ENOENT) {
279 3 : DEBUG(SSSDBG_TRACE_LIBS, "Wait queue of user [%s] is empty, "
280 : "running request [%p] immediately.\n", pd->user, req);
281 : } else {
282 0 : DEBUG(SSSDBG_MINOR_FAILURE,
283 : "Failed to add request to wait queue of user [%s], "
284 : "running request [%p] immediately.\n", pd->user, req);
285 : }
286 :
287 3 : subreq = krb5_auth_send(req, ev, be_ctx, pd, krb5_ctx);
288 3 : if (req == NULL) {
289 0 : DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n");
290 0 : ret = ENOMEM;
291 0 : goto immediate;
292 : }
293 :
294 3 : tevent_req_set_callback(subreq, krb5_auth_queue_done, req);
295 :
296 3 : ret = EOK;
297 :
298 : immediate:
299 1011 : if (ret != EOK) {
300 0 : tevent_req_error(req, ret);
301 0 : tevent_req_post(req, ev);
302 : }
303 1011 : return req;
304 : }
305 :
306 3 : static void krb5_auth_queue_done(struct tevent_req *subreq)
307 : {
308 3 : struct tevent_req *req = \
309 3 : tevent_req_callback_data(subreq, struct tevent_req);
310 3 : struct krb5_auth_queue_state *state = \
311 3 : tevent_req_data(req, struct krb5_auth_queue_state);
312 : errno_t ret;
313 :
314 3 : ret = krb5_auth_recv(subreq, &state->pam_status, &state->dp_err);
315 3 : talloc_zfree(subreq);
316 :
317 3 : check_wait_queue(state->krb5_ctx, state->pd->user);
318 :
319 3 : if (ret != EOK) {
320 1 : DEBUG(SSSDBG_OP_FAILURE, "krb5_auth_recv failed with: %d\n", ret);
321 1 : tevent_req_error(req, ret);
322 1 : return;
323 : }
324 :
325 2 : DEBUG(SSSDBG_TRACE_LIBS, "krb5_auth_queue request [%p] done.\n", req);
326 2 : tevent_req_done(req);
327 : }
328 :
329 : /* This is a violation of the tevent_req style. Ideally, the wait queue would
330 : * be rewritten to the tevent_req style in the future, expose per-request recv
331 : * and not hide the request underneath. But this function allows us to expose
332 : * a tevent_req API for users of this module
333 : */
334 1008 : static void krb5_auth_queue_finish(struct tevent_req *req,
335 : errno_t ret,
336 : int pam_status,
337 : int dp_err)
338 : {
339 1008 : struct krb5_auth_queue_state *state = \
340 1008 : tevent_req_data(req, struct krb5_auth_queue_state);
341 :
342 1008 : check_wait_queue(state->krb5_ctx, state->pd->user);
343 :
344 1008 : state->pam_status = pam_status;
345 1008 : state->dp_err = dp_err;
346 1008 : if (ret != EOK) {
347 9 : tevent_req_error(req, ret);
348 : } else {
349 999 : DEBUG(SSSDBG_TRACE_LIBS, "krb5_auth_queue request [%p] done.\n", req);
350 999 : tevent_req_done(req);
351 : }
352 1008 : }
353 :
354 1011 : int krb5_auth_queue_recv(struct tevent_req *req,
355 : int *_pam_status,
356 : int *_dp_err)
357 : {
358 1011 : struct krb5_auth_queue_state *state = \
359 1011 : tevent_req_data(req, struct krb5_auth_queue_state);
360 :
361 : /* Returning values even on failure is not typical, but IPA password migration
362 : * relies on receiving PAM_CRED_ERR even if the request fails..
363 : */
364 1011 : if (_pam_status) {
365 1011 : *_pam_status = state->pam_status;
366 : }
367 :
368 1011 : if (_dp_err) {
369 1011 : *_dp_err = state->dp_err;
370 : }
371 :
372 1021 : TEVENT_REQ_RETURN_ON_ERROR(req);
373 :
374 1001 : return EOK;
375 : }
|