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