Merge branch 'catch' into v1.x
[awesomized/libmemcached] / src / 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 #include <atomic>
41
42 #if defined(LIBMEMCACHED_WITH_SASL_SUPPORT) && LIBMEMCACHED_WITH_SASL_SUPPORT
43
44 #if defined(HAVE_LIBSASL) && HAVE_LIBSASL
45 #include <sasl/sasl.h>
46 #endif
47
48 #define CAST_SASL_CB(cb) reinterpret_cast<int(*)()>(reinterpret_cast<intptr_t>(cb))
49
50 #include <pthread.h>
51
52 void memcached_set_sasl_callbacks(memcached_st *shell,
53 const sasl_callback_t *callbacks)
54 {
55 Memcached* self= memcached2Memcached(shell);
56 if (self)
57 {
58 self->sasl.callbacks= const_cast<sasl_callback_t *>(callbacks);
59 self->sasl.is_allocated= false;
60 }
61 }
62
63 sasl_callback_t *memcached_get_sasl_callbacks(memcached_st *shell)
64 {
65 Memcached* self= memcached2Memcached(shell);
66 if (self)
67 {
68 return self->sasl.callbacks;
69 }
70
71 return NULL;
72 }
73
74 /**
75 * Resolve the names for both ends of a connection
76 * @param fd socket to check
77 * @param laddr local address (out)
78 * @param raddr remote address (out)
79 * @return true on success false otherwise (errno contains more info)
80 */
81 static memcached_return_t resolve_names(memcached_instance_st& server, char *laddr, size_t laddr_length, char *raddr, size_t raddr_length)
82 {
83 char host[MEMCACHED_NI_MAXHOST];
84 char port[MEMCACHED_NI_MAXSERV];
85 struct sockaddr_storage saddr;
86 socklen_t salen= sizeof(saddr);
87
88 if (getsockname(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), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) < 0)
94 {
95 return memcached_set_error(server, MEMCACHED_HOST_LOOKUP_FAILURE, MEMCACHED_AT);
96 }
97
98 (void)snprintf(laddr, laddr_length, "%s;%s", host, port);
99 salen= sizeof(saddr);
100
101 if (getpeername(server.fd, (struct sockaddr *)&saddr, &salen) < 0)
102 {
103 return memcached_set_error(server, MEMCACHED_HOST_LOOKUP_FAILURE, MEMCACHED_AT);
104 }
105
106 if (getnameinfo((struct sockaddr *)&saddr, salen, host, sizeof(host),
107 port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) < 0)
108 {
109 return memcached_set_error(server, MEMCACHED_HOST_LOOKUP_FAILURE, MEMCACHED_AT);
110 }
111
112 (void)snprintf(raddr, raddr_length, "%s;%s", host, port);
113
114 return MEMCACHED_SUCCESS;
115 }
116
117 extern "C" {
118
119 static void sasl_shutdown_function()
120 {
121 sasl_done();
122 }
123
124 static std::atomic<int> sasl_startup_state(SASL_OK);
125 static pthread_mutex_t sasl_startup_state_LOCK= PTHREAD_MUTEX_INITIALIZER;
126 static pthread_once_t sasl_startup_once= PTHREAD_ONCE_INIT;
127 static void sasl_startup_function(void)
128 {
129 sasl_startup_state= sasl_client_init(NULL);
130
131 if (sasl_startup_state == SASL_OK)
132 {
133 (void)atexit(sasl_shutdown_function);
134 }
135 }
136
137 } // extern "C"
138
139 memcached_return_t memcached_sasl_authenticate_connection(memcached_instance_st* server)
140 {
141 if (LIBMEMCACHED_WITH_SASL_SUPPORT == 0)
142 {
143 return MEMCACHED_NOT_SUPPORTED;
144 }
145
146 if (server == NULL)
147 {
148 return MEMCACHED_INVALID_ARGUMENTS;
149 }
150
151 /* SANITY CHECK: SASL can only be used with the binary protocol */
152 if (memcached_is_binary(server->root) == false)
153 {
154 return memcached_set_error(*server, MEMCACHED_INVALID_ARGUMENTS, MEMCACHED_AT,
155 memcached_literal_param("memcached_sasl_authenticate_connection() is not supported via the ASCII protocol"));
156 }
157
158 /* Try to get the supported mech from the server. Servers without SASL
159 * support will return UNKNOWN COMMAND, so we can just treat that
160 * as authenticated
161 */
162 protocol_binary_request_no_extras request= { };
163
164 initialize_binary_request(server, request.message.header);
165
166 request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_LIST_MECHS;
167
168 if (memcached_io_write(server, request.bytes, sizeof(request.bytes), true) != sizeof(request.bytes))
169 {
170 return MEMCACHED_WRITE_FAILURE;
171 }
172 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
173
174 memcached_server_response_increment(server);
175
176 char mech[MEMCACHED_MAX_BUFFER] = {0};
177 memcached_return_t rc= memcached_response(server, mech, sizeof(mech) - 1, NULL);
178 if (memcached_failed(rc))
179 {
180 if (rc == MEMCACHED_PROTOCOL_ERROR)
181 {
182 /* If the server doesn't support SASL it will return PROTOCOL_ERROR.
183 * This error may also be returned for other errors, but let's assume
184 * that the server don't support SASL and treat it as success and
185 * let the client fail with the next operation if the error was
186 * caused by another problem....
187 */
188 rc= MEMCACHED_SUCCESS;
189 }
190
191 return rc;
192 }
193 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
194
195 /* set ip addresses */
196 char laddr[MEMCACHED_NI_MAXHOST + MEMCACHED_NI_MAXSERV];
197 char raddr[MEMCACHED_NI_MAXHOST + MEMCACHED_NI_MAXSERV];
198
199 if (memcached_failed(rc= resolve_names(*server, laddr, sizeof(laddr), raddr, sizeof(raddr))))
200 {
201 return rc;
202 }
203
204 int pthread_error;
205 if ((pthread_error= pthread_once(&sasl_startup_once, sasl_startup_function)) != 0)
206 {
207 return memcached_set_errno(*server, pthread_error, MEMCACHED_AT);
208 }
209
210 (void)pthread_mutex_lock(&sasl_startup_state_LOCK);
211 if (sasl_startup_state != SASL_OK)
212 {
213 const char *sasl_error_msg= sasl_errstring(sasl_startup_state, NULL, NULL);
214 return memcached_set_error(*server, MEMCACHED_AUTH_PROBLEM, MEMCACHED_AT,
215 memcached_string_make_from_cstr(sasl_error_msg));
216 }
217 (void)pthread_mutex_unlock(&sasl_startup_state_LOCK);
218
219 sasl_conn_t *conn;
220 int ret;
221 if ((ret= sasl_client_new("memcached", server->_hostname, laddr, raddr, server->root->sasl.callbacks, 0, &conn) ) != SASL_OK)
222 {
223 const char *sasl_error_msg= sasl_errstring(ret, NULL, NULL);
224
225 sasl_dispose(&conn);
226
227 return memcached_set_error(*server, MEMCACHED_AUTH_PROBLEM, MEMCACHED_AT,
228 memcached_string_make_from_cstr(sasl_error_msg));
229 }
230
231 const char *data;
232 const char *chosenmech;
233 unsigned int len;
234 ret= sasl_client_start(conn, mech, NULL, &data, &len, &chosenmech);
235 if (ret != SASL_OK and ret != SASL_CONTINUE)
236 {
237 const char *sasl_error_msg= sasl_errstring(ret, NULL, NULL);
238
239 sasl_dispose(&conn);
240
241 return memcached_set_error(*server, MEMCACHED_AUTH_PROBLEM, MEMCACHED_AT,
242 memcached_string_make_from_cstr(sasl_error_msg));
243 }
244 uint16_t keylen= (uint16_t)strlen(chosenmech);
245 request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_AUTH;
246 request.message.header.request.keylen= htons(keylen);
247 request.message.header.request.bodylen= htonl(len + keylen);
248
249 do {
250 /* send the packet */
251
252 libmemcached_io_vector_st vector[]=
253 {
254 { request.bytes, sizeof(request.bytes) },
255 { chosenmech, keylen },
256 { data, len }
257 };
258
259 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
260 if (memcached_io_writev(server, vector, 3, true) == false)
261 {
262 rc= MEMCACHED_WRITE_FAILURE;
263 break;
264 }
265 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
266 memcached_server_response_increment(server);
267
268 /* read the response */
269 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
270 rc= memcached_response(server, NULL, 0, NULL);
271 if (rc != MEMCACHED_AUTH_CONTINUE)
272 {
273 break;
274 }
275 assert_msg(server->fd != INVALID_SOCKET, "Programmer error, invalid socket");
276
277 ret= sasl_client_step(conn, memcached_result_value(&server->root->result),
278 (unsigned int)memcached_result_length(&server->root->result),
279 NULL, &data, &len);
280
281 if (ret != SASL_OK && ret != SASL_CONTINUE)
282 {
283 rc= MEMCACHED_AUTH_PROBLEM;
284 break;
285 }
286
287 request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_STEP;
288 request.message.header.request.bodylen= htonl(len + keylen);
289 } while (true);
290
291 /* Release resources */
292 sasl_dispose(&conn);
293
294 return memcached_set_error(*server, rc, MEMCACHED_AT);
295 }
296
297 static int get_username(void *context, int id, const char **result, unsigned int *len)
298 {
299 if (!context || !result || (id != SASL_CB_USER && id != SASL_CB_AUTHNAME))
300 {
301 return SASL_BADPARAM;
302 }
303
304 *result= (char *)context;
305 if (len)
306 {
307 *len= (unsigned int)strlen(*result);
308 }
309
310 return SASL_OK;
311 }
312
313 static int get_password(sasl_conn_t *conn, void *context, int id,
314 sasl_secret_t **psecret)
315 {
316 if (!conn || ! psecret || id != SASL_CB_PASS)
317 {
318 return SASL_BADPARAM;
319 }
320
321 *psecret= (sasl_secret_t *)context;
322
323 return SASL_OK;
324 }
325
326 memcached_return_t memcached_set_sasl_auth_data(memcached_st *shell,
327 const char *username,
328 const char *password)
329 {
330 Memcached* ptr= memcached2Memcached(shell);
331 if (LIBMEMCACHED_WITH_SASL_SUPPORT == 0)
332 {
333 return MEMCACHED_NOT_SUPPORTED;
334 }
335
336 if (ptr == NULL or username == NULL or password == NULL)
337 {
338 return MEMCACHED_INVALID_ARGUMENTS;
339 }
340
341 memcached_return_t ret;
342 if (memcached_failed(ret= memcached_behavior_set(ptr, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1)))
343 {
344 return memcached_set_error(*ptr, ret, MEMCACHED_AT, memcached_literal_param("Unable change to binary protocol which is required for SASL."));
345 }
346
347 memcached_destroy_sasl_auth_data(ptr);
348
349 sasl_callback_t *callbacks= libmemcached_xcalloc(ptr, 4, sasl_callback_t);
350 size_t password_length= strlen(password);
351 size_t username_length= strlen(username);
352 char *name= (char *)libmemcached_malloc(ptr, username_length +1);
353 sasl_secret_t *secret= (sasl_secret_t*)libmemcached_malloc(ptr, password_length +1 + sizeof(sasl_secret_t));
354
355 if (callbacks == NULL or name == NULL or secret == NULL)
356 {
357 libmemcached_free(ptr, callbacks);
358 libmemcached_free(ptr, name);
359 libmemcached_free(ptr, secret);
360 return memcached_set_error(*ptr, MEMCACHED_MEMORY_ALLOCATION_FAILURE, MEMCACHED_AT);
361 }
362
363 secret->len= password_length;
364 memcpy(secret->data, password, password_length);
365 secret->data[password_length]= 0;
366
367 callbacks[0].id= SASL_CB_USER;
368 callbacks[0].proc= CAST_SASL_CB(get_username);
369 callbacks[0].context= strncpy(name, username, username_length +1);
370 callbacks[1].id= SASL_CB_AUTHNAME;
371 callbacks[1].proc= CAST_SASL_CB(get_username);
372 callbacks[1].context= name;
373 callbacks[2].id= SASL_CB_PASS;
374 callbacks[2].proc= CAST_SASL_CB(get_password);
375 callbacks[2].context= secret;
376 callbacks[3].id= SASL_CB_LIST_END;
377
378 ptr->sasl.callbacks= callbacks;
379 ptr->sasl.is_allocated= true;
380
381 return MEMCACHED_SUCCESS;
382 }
383
384 memcached_return_t memcached_destroy_sasl_auth_data(memcached_st *shell)
385 {
386 if (LIBMEMCACHED_WITH_SASL_SUPPORT == 0)
387 {
388 return MEMCACHED_NOT_SUPPORTED;
389 }
390
391 Memcached* ptr= memcached2Memcached(shell);
392 if (ptr == NULL)
393 {
394 return MEMCACHED_INVALID_ARGUMENTS;
395 }
396
397 if (ptr->sasl.callbacks == NULL)
398 {
399 return MEMCACHED_SUCCESS;
400 }
401
402 if (ptr->sasl.is_allocated)
403 {
404 libmemcached_free(ptr, ptr->sasl.callbacks[0].context);
405 libmemcached_free(ptr, ptr->sasl.callbacks[2].context);
406 libmemcached_free(ptr, (void*)ptr->sasl.callbacks);
407 ptr->sasl.is_allocated= false;
408 }
409
410 ptr->sasl.callbacks= NULL;
411
412 return MEMCACHED_SUCCESS;
413 }
414
415 memcached_return_t memcached_clone_sasl(memcached_st *clone, const memcached_st *source)
416 {
417 if (LIBMEMCACHED_WITH_SASL_SUPPORT == 0)
418 {
419 return MEMCACHED_NOT_SUPPORTED;
420 }
421
422 if (clone == NULL or source == NULL)
423 {
424 return MEMCACHED_INVALID_ARGUMENTS;
425 }
426
427 if (source->sasl.callbacks == NULL)
428 {
429 return MEMCACHED_SUCCESS;
430 }
431
432 /* Hopefully we are using our own callback mechanisms.. */
433 if (source->sasl.callbacks[0].id == SASL_CB_USER &&
434 source->sasl.callbacks[0].proc == CAST_SASL_CB(get_username) &&
435 source->sasl.callbacks[1].id == SASL_CB_AUTHNAME &&
436 source->sasl.callbacks[1].proc == CAST_SASL_CB(get_username) &&
437 source->sasl.callbacks[2].id == SASL_CB_PASS &&
438 source->sasl.callbacks[2].proc == CAST_SASL_CB(get_password) &&
439 source->sasl.callbacks[3].id == SASL_CB_LIST_END)
440 {
441 sasl_secret_t *secret= (sasl_secret_t *)source->sasl.callbacks[2].context;
442 return memcached_set_sasl_auth_data(clone,
443 (const char*)source->sasl.callbacks[0].context,
444 (const char*)secret->data);
445 }
446
447 /*
448 * But we're not. It may work if we know what the user tries to pass
449 * into the list, but if we don't know the ID we don't know how to handle
450 * the context...
451 */
452 ptrdiff_t total= 0;
453
454 while (source->sasl.callbacks[total].id != SASL_CB_LIST_END)
455 {
456 switch (source->sasl.callbacks[total].id)
457 {
458 case SASL_CB_USER:
459 case SASL_CB_AUTHNAME:
460 case SASL_CB_PASS:
461 break;
462 default:
463 /* I don't know how to deal with this... */
464 return MEMCACHED_NOT_SUPPORTED;
465 }
466
467 ++total;
468 }
469
470 sasl_callback_t *callbacks= libmemcached_xcalloc(clone, total +1, sasl_callback_t);
471 if (callbacks == NULL)
472 {
473 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
474 }
475 memcpy(callbacks, source->sasl.callbacks, (total + 1) * sizeof(sasl_callback_t));
476
477 /* Now update the context... */
478 for (ptrdiff_t x= 0; x < total; ++x)
479 {
480 if (callbacks[x].id == SASL_CB_USER || callbacks[x].id == SASL_CB_AUTHNAME)
481 {
482 callbacks[x].context= (sasl_callback_t*)libmemcached_malloc(clone, strlen((const char*)source->sasl.callbacks[x].context));
483
484 if (callbacks[x].context == NULL)
485 {
486 /* Failed to allocate memory, clean up previously allocated memory */
487 for (ptrdiff_t y= 0; y < x; ++y)
488 {
489 libmemcached_free(clone, clone->sasl.callbacks[y].context);
490 }
491
492 libmemcached_free(clone, callbacks);
493 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
494 }
495 strncpy((char*)callbacks[x].context, (const char*)source->sasl.callbacks[x].context, sizeof(callbacks[x].context));
496 }
497 else
498 {
499 sasl_secret_t *src= (sasl_secret_t *)source->sasl.callbacks[x].context;
500 sasl_secret_t *n= (sasl_secret_t*)libmemcached_malloc(clone, src->len + 1 + sizeof(*n));
501 if (n == NULL)
502 {
503 /* Failed to allocate memory, clean up previously allocated memory */
504 for (ptrdiff_t y= 0; y < x; ++y)
505 {
506 libmemcached_free(clone, clone->sasl.callbacks[y].context);
507 }
508
509 libmemcached_free(clone, callbacks);
510 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
511 }
512 memcpy(n, src, src->len + 1 + sizeof(*n));
513 callbacks[x].context= n;
514 }
515 }
516
517 clone->sasl.callbacks= callbacks;
518 clone->sasl.is_allocated= true;
519
520 return MEMCACHED_SUCCESS;
521 }
522
523 #else
524
525 void memcached_set_sasl_callbacks(memcached_st *, const sasl_callback_t *)
526 {
527 }
528
529 sasl_callback_t *memcached_get_sasl_callbacks(memcached_st *)
530 {
531 return NULL;
532 }
533
534 memcached_return_t memcached_set_sasl_auth_data(memcached_st *, const char *, const char *)
535 {
536 return MEMCACHED_NOT_SUPPORTED;
537 }
538
539 memcached_return_t memcached_clone_sasl(memcached_st *, const memcached_st *)
540 {
541 return MEMCACHED_NOT_SUPPORTED;
542 }
543
544 #endif