e473806984875079573db3f519bb7be7d6220bdc
[awesomized/libmemcached] / libmemcached / sasl.cc
1 /* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
2 *
3 * Libmemcached library
4 *
5 * Copyright (C) 2011-2012 Data Differential, http://datadifferential.com/
6 * Copyright (C) 2006-2009 Brian Aker All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are
10 * met:
11 *
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * * Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following disclaimer
17 * in the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * * The names of its contributors may not be used to endorse or
21 * promote products derived from this software without specific prior
22 * written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 *
36 */
37
38 #include "libmemcached/common.h"
39 #include <cassert>
40
41 #if defined(LIBMEMCACHED_WITH_SASL_SUPPORT) && LIBMEMCACHED_WITH_SASL_SUPPORT
42
43 #if defined(HAVE_LIBSASL) && HAVE_LIBSASL
44 #include <sasl/sasl.h>
45 #endif
46
47 #include <pthread.h>
48
49 void memcached_set_sasl_callbacks(memcached_st *ptr,
50 const sasl_callback_t *callbacks)
51 {
52 ptr->sasl.callbacks= const_cast<sasl_callback_t *>(callbacks);
53 ptr->sasl.is_allocated= false;
54 }
55
56 sasl_callback_t *memcached_get_sasl_callbacks(memcached_st *ptr)
57 {
58 return ptr->sasl.callbacks;
59 }
60
61 /**
62 * Resolve the names for both ends of a connection
63 * @param fd socket to check
64 * @param laddr local address (out)
65 * @param raddr remote address (out)
66 * @return true on success false otherwise (errno contains more info)
67 */
68 static memcached_return_t resolve_names(org::libmemcached::Instance& server, char *laddr, size_t laddr_length, char *raddr, size_t raddr_length)
69 {
70 char host[MEMCACHED_NI_MAXHOST];
71 char port[MEMCACHED_NI_MAXSERV];
72 struct sockaddr_storage saddr;
73 socklen_t salen= sizeof(saddr);
74
75 if (getsockname(server.fd, (struct sockaddr *)&saddr, &salen) < 0)
76 {
77 return memcached_set_error(server, MEMCACHED_HOST_LOOKUP_FAILURE, MEMCACHED_AT);
78 }
79
80 if (getnameinfo((struct sockaddr *)&saddr, salen, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) < 0)
81 {
82 return memcached_set_error(server, MEMCACHED_HOST_LOOKUP_FAILURE, MEMCACHED_AT);
83 }
84
85 (void)snprintf(laddr, laddr_length, "%s;%s", host, port);
86 salen= sizeof(saddr);
87
88 if (getpeername(server.fd, (struct sockaddr *)&saddr, &salen) < 0)
89 {
90 return memcached_set_error(server, MEMCACHED_HOST_LOOKUP_FAILURE, MEMCACHED_AT);
91 }
92
93 if (getnameinfo((struct sockaddr *)&saddr, salen, host, sizeof(host),
94 port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) < 0)
95 {
96 return memcached_set_error(server, MEMCACHED_HOST_LOOKUP_FAILURE, MEMCACHED_AT);
97 }
98
99 (void)snprintf(raddr, raddr_length, "%s;%s", host, port);
100
101 return MEMCACHED_SUCCESS;
102 }
103
104 extern "C" {
105
106 static void sasl_shutdown_function()
107 {
108 sasl_done();
109 }
110
111 static volatile int sasl_startup_state= SASL_OK;
112 pthread_mutex_t sasl_startup_state_LOCK= PTHREAD_MUTEX_INITIALIZER;
113 static pthread_once_t sasl_startup_once= PTHREAD_ONCE_INIT;
114 static void sasl_startup_function(void)
115 {
116 sasl_startup_state= sasl_client_init(NULL);
117
118 if (sasl_startup_state == SASL_OK)
119 {
120 (void)atexit(sasl_shutdown_function);
121 }
122 }
123
124 } // extern "C"
125
126 memcached_return_t memcached_sasl_authenticate_connection(org::libmemcached::Instance* server)
127 {
128 if (LIBMEMCACHED_WITH_SASL_SUPPORT == 0)
129 {
130 return MEMCACHED_NOT_SUPPORTED;
131 }
132
133 if (server == NULL)
134 {
135 return MEMCACHED_INVALID_ARGUMENTS;
136 }
137
138 /* SANITY CHECK: SASL can only be used with the binary protocol */
139 if (memcached_is_binary(server->root) == false)
140 {
141 return memcached_set_error(*server, MEMCACHED_INVALID_ARGUMENTS, MEMCACHED_AT,
142 memcached_literal_param("memcached_sasl_authenticate_connection() is not supported via the ASCII protocol"));
143 }
144
145 /* Try to get the supported mech from the server. Servers without SASL
146 * support will return UNKNOWN COMMAND, so we can just treat that
147 * as authenticated
148 */
149 protocol_binary_request_no_extras request= { };
150
151 initialize_binary_request(server, request.message.header);
152
153 request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_LIST_MECHS;
154
155 if (memcached_io_write(server, request.bytes, sizeof(request.bytes), true) != sizeof(request.bytes))
156 {
157 return MEMCACHED_WRITE_FAILURE;
158 }
159 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
160
161 memcached_server_response_increment(server);
162
163 char mech[MEMCACHED_MAX_BUFFER];
164 memcached_return_t rc= memcached_response(server, mech, sizeof(mech), NULL);
165 if (memcached_failed(rc))
166 {
167 if (rc == MEMCACHED_PROTOCOL_ERROR)
168 {
169 /* If the server doesn't support SASL it will return PROTOCOL_ERROR.
170 * This error may also be returned for other errors, but let's assume
171 * that the server don't support SASL and treat it as success and
172 * let the client fail with the next operation if the error was
173 * caused by another problem....
174 */
175 rc= MEMCACHED_SUCCESS;
176 }
177
178 return rc;
179 }
180 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
181
182 /* set ip addresses */
183 char laddr[MEMCACHED_NI_MAXHOST + MEMCACHED_NI_MAXSERV];
184 char raddr[MEMCACHED_NI_MAXHOST + MEMCACHED_NI_MAXSERV];
185
186 if (memcached_failed(rc= resolve_names(*server, laddr, sizeof(laddr), raddr, sizeof(raddr))))
187 {
188 return rc;
189 }
190
191 int pthread_error;
192 if ((pthread_error= pthread_once(&sasl_startup_once, sasl_startup_function)) != 0)
193 {
194 return memcached_set_errno(*server, pthread_error, MEMCACHED_AT);
195 }
196
197 (void)pthread_mutex_lock(&sasl_startup_state_LOCK);
198 if (sasl_startup_state != SASL_OK)
199 {
200 const char *sasl_error_msg= sasl_errstring(sasl_startup_state, NULL, NULL);
201 return memcached_set_error(*server, MEMCACHED_AUTH_PROBLEM, MEMCACHED_AT,
202 memcached_string_make_from_cstr(sasl_error_msg));
203 }
204 (void)pthread_mutex_unlock(&sasl_startup_state_LOCK);
205
206 sasl_conn_t *conn;
207 int ret;
208 if ((ret= sasl_client_new("memcached", server->hostname, laddr, raddr, server->root->sasl.callbacks, 0, &conn) ) != SASL_OK)
209 {
210 const char *sasl_error_msg= sasl_errstring(ret, NULL, NULL);
211
212 sasl_dispose(&conn);
213
214 return memcached_set_error(*server, MEMCACHED_AUTH_PROBLEM, MEMCACHED_AT,
215 memcached_string_make_from_cstr(sasl_error_msg));
216 }
217
218 const char *data;
219 const char *chosenmech;
220 unsigned int len;
221 ret= sasl_client_start(conn, mech, NULL, &data, &len, &chosenmech);
222 if (ret != SASL_OK and ret != SASL_CONTINUE)
223 {
224 const char *sasl_error_msg= sasl_errstring(ret, NULL, NULL);
225
226 sasl_dispose(&conn);
227
228 return memcached_set_error(*server, MEMCACHED_AUTH_PROBLEM, MEMCACHED_AT,
229 memcached_string_make_from_cstr(sasl_error_msg));
230 }
231 uint16_t keylen= (uint16_t)strlen(chosenmech);
232 request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_AUTH;
233 request.message.header.request.keylen= htons(keylen);
234 request.message.header.request.bodylen= htonl(len + keylen);
235
236 do {
237 /* send the packet */
238
239 libmemcached_io_vector_st vector[]=
240 {
241 { request.bytes, sizeof(request.bytes) },
242 { chosenmech, keylen },
243 { data, len }
244 };
245
246 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
247 if (memcached_io_writev(server, vector, 3, true) == false)
248 {
249 rc= MEMCACHED_WRITE_FAILURE;
250 break;
251 }
252 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
253 memcached_server_response_increment(server);
254
255 /* read the response */
256 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
257 rc= memcached_response(server, NULL, 0, NULL);
258 if (rc != MEMCACHED_AUTH_CONTINUE)
259 {
260 break;
261 }
262 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
263
264 ret= sasl_client_step(conn, memcached_result_value(&server->root->result),
265 (unsigned int)memcached_result_length(&server->root->result),
266 NULL, &data, &len);
267
268 if (ret != SASL_OK && ret != SASL_CONTINUE)
269 {
270 rc= MEMCACHED_AUTH_PROBLEM;
271 break;
272 }
273
274 request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_STEP;
275 request.message.header.request.bodylen= htonl(len + keylen);
276 } while (true);
277
278 /* Release resources */
279 sasl_dispose(&conn);
280
281 return memcached_set_error(*server, rc, MEMCACHED_AT);
282 }
283
284 static int get_username(void *context, int id, const char **result, unsigned int *len)
285 {
286 if (!context || !result || (id != SASL_CB_USER && id != SASL_CB_AUTHNAME))
287 {
288 return SASL_BADPARAM;
289 }
290
291 *result= (char *)context;
292 if (len)
293 {
294 *len= (unsigned int)strlen(*result);
295 }
296
297 return SASL_OK;
298 }
299
300 static int get_password(sasl_conn_t *conn, void *context, int id,
301 sasl_secret_t **psecret)
302 {
303 if (!conn || ! psecret || id != SASL_CB_PASS)
304 {
305 return SASL_BADPARAM;
306 }
307
308 *psecret= (sasl_secret_t *)context;
309
310 return SASL_OK;
311 }
312
313 memcached_return_t memcached_set_sasl_auth_data(memcached_st *ptr,
314 const char *username,
315 const char *password)
316 {
317 if (LIBMEMCACHED_WITH_SASL_SUPPORT == 0)
318 {
319 return MEMCACHED_NOT_SUPPORTED;
320 }
321
322 if (ptr == NULL or username == NULL or password == NULL)
323 {
324 return MEMCACHED_INVALID_ARGUMENTS;
325 }
326
327 memcached_return_t ret;
328 if (memcached_failed(ret= memcached_behavior_set(ptr, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1)))
329 {
330 return memcached_set_error(*ptr, ret, MEMCACHED_AT, memcached_literal_param("Unable change to binary protocol which is required for SASL."));
331 }
332
333 memcached_destroy_sasl_auth_data(ptr);
334
335 sasl_callback_t *callbacks= libmemcached_xcalloc(ptr, 4, sasl_callback_t);
336 size_t password_length= strlen(password);
337 size_t username_length= strlen(username);
338 char *name= (char *)libmemcached_malloc(ptr, username_length +1);
339 sasl_secret_t *secret= (sasl_secret_t*)libmemcached_malloc(ptr, password_length +1 + sizeof(sasl_secret_t));
340
341 if (callbacks == NULL or name == NULL or secret == NULL)
342 {
343 libmemcached_free(ptr, callbacks);
344 libmemcached_free(ptr, name);
345 libmemcached_free(ptr, secret);
346 return memcached_set_error(*ptr, MEMCACHED_MEMORY_ALLOCATION_FAILURE, MEMCACHED_AT);
347 }
348
349 secret->len= password_length;
350 memcpy(secret->data, password, password_length);
351 secret->data[password_length]= 0;
352
353 callbacks[0].id= SASL_CB_USER;
354 callbacks[0].proc= (int (*)())get_username;
355 callbacks[0].context= strncpy(name, username, username_length +1);
356 callbacks[1].id= SASL_CB_AUTHNAME;
357 callbacks[1].proc= (int (*)())get_username;
358 callbacks[1].context= name;
359 callbacks[2].id= SASL_CB_PASS;
360 callbacks[2].proc= (int (*)())get_password;
361 callbacks[2].context= secret;
362 callbacks[3].id= SASL_CB_LIST_END;
363
364 ptr->sasl.callbacks= callbacks;
365 ptr->sasl.is_allocated= true;
366
367 return MEMCACHED_SUCCESS;
368 }
369
370 memcached_return_t memcached_destroy_sasl_auth_data(memcached_st *ptr)
371 {
372 if (LIBMEMCACHED_WITH_SASL_SUPPORT == 0)
373 {
374 return MEMCACHED_NOT_SUPPORTED;
375 }
376
377 if (ptr == NULL)
378 {
379 return MEMCACHED_INVALID_ARGUMENTS;
380 }
381
382 if (ptr->sasl.callbacks == NULL)
383 {
384 return MEMCACHED_SUCCESS;
385 }
386
387 if (ptr->sasl.is_allocated)
388 {
389 libmemcached_free(ptr, ptr->sasl.callbacks[0].context);
390 libmemcached_free(ptr, ptr->sasl.callbacks[2].context);
391 libmemcached_free(ptr, (void*)ptr->sasl.callbacks);
392 ptr->sasl.is_allocated= false;
393 }
394
395 ptr->sasl.callbacks= NULL;
396
397 return MEMCACHED_SUCCESS;
398 }
399
400 memcached_return_t memcached_clone_sasl(memcached_st *clone, const memcached_st *source)
401 {
402 if (LIBMEMCACHED_WITH_SASL_SUPPORT == 0)
403 {
404 return MEMCACHED_NOT_SUPPORTED;
405 }
406
407 if (clone == NULL or source == NULL)
408 {
409 return MEMCACHED_INVALID_ARGUMENTS;
410 }
411
412 if (source->sasl.callbacks == NULL)
413 {
414 return MEMCACHED_SUCCESS;
415 }
416
417 /* Hopefully we are using our own callback mechanisms.. */
418 if (source->sasl.callbacks[0].id == SASL_CB_USER &&
419 source->sasl.callbacks[0].proc == (int (*)())get_username &&
420 source->sasl.callbacks[1].id == SASL_CB_AUTHNAME &&
421 source->sasl.callbacks[1].proc == (int (*)())get_username &&
422 source->sasl.callbacks[2].id == SASL_CB_PASS &&
423 source->sasl.callbacks[2].proc == (int (*)())get_password &&
424 source->sasl.callbacks[3].id == SASL_CB_LIST_END)
425 {
426 sasl_secret_t *secret= (sasl_secret_t *)source->sasl.callbacks[2].context;
427 return memcached_set_sasl_auth_data(clone,
428 (const char*)source->sasl.callbacks[0].context,
429 (const char*)secret->data);
430 }
431
432 /*
433 * But we're not. It may work if we know what the user tries to pass
434 * into the list, but if we don't know the ID we don't know how to handle
435 * the context...
436 */
437 ptrdiff_t total= 0;
438
439 while (source->sasl.callbacks[total].id != SASL_CB_LIST_END)
440 {
441 switch (source->sasl.callbacks[total].id)
442 {
443 case SASL_CB_USER:
444 case SASL_CB_AUTHNAME:
445 case SASL_CB_PASS:
446 break;
447 default:
448 /* I don't know how to deal with this... */
449 return MEMCACHED_NOT_SUPPORTED;
450 }
451
452 ++total;
453 }
454
455 sasl_callback_t *callbacks= libmemcached_xcalloc(clone, total +1, sasl_callback_t);
456 if (callbacks == NULL)
457 {
458 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
459 }
460 memcpy(callbacks, source->sasl.callbacks, (total + 1) * sizeof(sasl_callback_t));
461
462 /* Now update the context... */
463 for (ptrdiff_t x= 0; x < total; ++x)
464 {
465 if (callbacks[x].id == SASL_CB_USER || callbacks[x].id == SASL_CB_AUTHNAME)
466 {
467 callbacks[x].context= (sasl_callback_t*)libmemcached_malloc(clone, strlen((const char*)source->sasl.callbacks[x].context));
468
469 if (callbacks[x].context == NULL)
470 {
471 /* Failed to allocate memory, clean up previously allocated memory */
472 for (ptrdiff_t y= 0; y < x; ++y)
473 {
474 libmemcached_free(clone, clone->sasl.callbacks[y].context);
475 }
476
477 libmemcached_free(clone, callbacks);
478 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
479 }
480 strncpy((char*)callbacks[x].context, (const char*)source->sasl.callbacks[x].context, sizeof(callbacks[x].context));
481 }
482 else
483 {
484 sasl_secret_t *src= (sasl_secret_t *)source->sasl.callbacks[x].context;
485 sasl_secret_t *n= (sasl_secret_t*)libmemcached_malloc(clone, src->len + 1 + sizeof(*n));
486 if (n == NULL)
487 {
488 /* Failed to allocate memory, clean up previously allocated memory */
489 for (ptrdiff_t y= 0; y < x; ++y)
490 {
491 libmemcached_free(clone, clone->sasl.callbacks[y].context);
492 }
493
494 libmemcached_free(clone, callbacks);
495 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
496 }
497 memcpy(n, src, src->len + 1 + sizeof(*n));
498 callbacks[x].context= n;
499 }
500 }
501
502 clone->sasl.callbacks= callbacks;
503 clone->sasl.is_allocated= true;
504
505 return MEMCACHED_SUCCESS;
506 }
507
508 #else
509
510 void memcached_set_sasl_callbacks(memcached_st *, const sasl_callback_t *)
511 {
512 }
513
514 sasl_callback_t *memcached_get_sasl_callbacks(memcached_st *)
515 {
516 return NULL;
517 }
518
519 memcached_return_t memcached_set_sasl_auth_data(memcached_st *, const char *, const char *)
520 {
521 return MEMCACHED_NOT_SUPPORTED;
522 }
523
524 memcached_return_t memcached_clone_sasl(memcached_st *, const memcached_st *)
525 {
526 return MEMCACHED_NOT_SUPPORTED;
527 }
528
529 #endif