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