Update to error messages.
[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 (memcached_is_binary(server->root) == false)
137 {
138 return memcached_set_error(*server, MEMCACHED_INVALID_ARGUMENTS, MEMCACHED_AT,
139 memcached_literal_param("memcached_sasl_authenticate_connection() is not supported via the ASCII protocol"));
140 }
141
142 /* Try to get the supported mech from the server. Servers without SASL
143 * support will return UNKNOWN COMMAND, so we can just treat that
144 * as authenticated
145 */
146 protocol_binary_request_no_extras request= { };
147 request.message.header.request.magic= PROTOCOL_BINARY_REQ;
148 request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_LIST_MECHS;
149
150 if (memcached_io_write(server, request.bytes,
151 sizeof(request.bytes), 1) != sizeof(request.bytes))
152 {
153 return MEMCACHED_WRITE_FAILURE;
154 }
155
156 memcached_server_response_increment(server);
157
158 char mech[MEMCACHED_MAX_BUFFER];
159 memcached_return_t rc= memcached_response(server, mech, sizeof(mech), NULL);
160 if (memcached_failed(rc))
161 {
162 if (rc == MEMCACHED_PROTOCOL_ERROR)
163 {
164 /* If the server doesn't support SASL it will return PROTOCOL_ERROR.
165 * This error may also be returned for other errors, but let's assume
166 * that the server don't support SASL and treat it as success and
167 * let the client fail with the next operation if the error was
168 * caused by another problem....
169 */
170 rc= MEMCACHED_SUCCESS;
171 }
172
173 return rc;
174 }
175
176 /* set ip addresses */
177 char laddr[NI_MAXHOST + NI_MAXSERV];
178 char raddr[NI_MAXHOST + NI_MAXSERV];
179
180 if (memcached_failed(rc= resolve_names(*server, laddr, sizeof(laddr), raddr, sizeof(raddr))))
181 {
182 return rc;
183 }
184
185 int pthread_error;
186 if ((pthread_error= pthread_once(&sasl_startup_once, sasl_startup_function)) != 0)
187 {
188 return memcached_set_errno(*server, pthread_error, MEMCACHED_AT);
189 }
190
191 (void)pthread_mutex_lock(&sasl_startup_state_LOCK);
192 if (sasl_startup_state != SASL_OK)
193 {
194 const char *sasl_error_msg= sasl_errstring(sasl_startup_state, NULL, NULL);
195 return memcached_set_error(*server, MEMCACHED_AUTH_PROBLEM, MEMCACHED_AT,
196 memcached_string_make_from_cstr(sasl_error_msg));
197 }
198 (void)pthread_mutex_unlock(&sasl_startup_state_LOCK);
199
200 sasl_conn_t *conn;
201 int ret;
202 if ((ret= sasl_client_new("memcached", server->hostname, laddr, raddr, server->root->sasl.callbacks, 0, &conn) ) != SASL_OK)
203 {
204 const char *sasl_error_msg= sasl_errstring(ret, NULL, NULL);
205
206 sasl_dispose(&conn);
207
208 return memcached_set_error(*server, MEMCACHED_AUTH_PROBLEM, MEMCACHED_AT,
209 memcached_string_make_from_cstr(sasl_error_msg));
210 }
211
212 const char *data;
213 const char *chosenmech;
214 unsigned int len;
215 ret= sasl_client_start(conn, mech, NULL, &data, &len, &chosenmech);
216 if (ret != SASL_OK and ret != SASL_CONTINUE)
217 {
218 const char *sasl_error_msg= sasl_errstring(ret, NULL, NULL);
219
220 sasl_dispose(&conn);
221
222 return memcached_set_error(*server, MEMCACHED_AUTH_PROBLEM, MEMCACHED_AT,
223 memcached_string_make_from_cstr(sasl_error_msg));
224 }
225 uint16_t keylen= (uint16_t)strlen(chosenmech);
226 request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_AUTH;
227 request.message.header.request.keylen= htons(keylen);
228 request.message.header.request.bodylen= htonl(len + keylen);
229
230 do {
231 /* send the packet */
232
233 libmemcached_io_vector_st vector[]=
234 {
235 { request.bytes, sizeof(request.bytes) },
236 { chosenmech, keylen },
237 { data, len }
238 };
239
240 if (memcached_io_writev(server, vector, 3, true) == -1)
241 {
242 rc= MEMCACHED_WRITE_FAILURE;
243 break;
244 }
245 memcached_server_response_increment(server);
246
247 /* read the response */
248 rc= memcached_response(server, NULL, 0, NULL);
249 if (rc != MEMCACHED_AUTH_CONTINUE)
250 {
251 break;
252 }
253
254 ret= sasl_client_step(conn, memcached_result_value(&server->root->result),
255 (unsigned int)memcached_result_length(&server->root->result),
256 NULL, &data, &len);
257
258 if (ret != SASL_OK && ret != SASL_CONTINUE)
259 {
260 rc= MEMCACHED_AUTH_PROBLEM;
261 break;
262 }
263
264 request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_STEP;
265 request.message.header.request.bodylen= htonl(len + keylen);
266 } while (true);
267
268 /* Release resources */
269 sasl_dispose(&conn);
270
271 return memcached_set_error(*server, rc, MEMCACHED_AT);
272 }
273
274 static int get_username(void *context, int id, const char **result, unsigned int *len)
275 {
276 if (!context || !result || (id != SASL_CB_USER && id != SASL_CB_AUTHNAME))
277 {
278 return SASL_BADPARAM;
279 }
280
281 *result= (char *)context;
282 if (len)
283 {
284 *len= (unsigned int)strlen(*result);
285 }
286
287 return SASL_OK;
288 }
289
290 static int get_password(sasl_conn_t *conn, void *context, int id,
291 sasl_secret_t **psecret)
292 {
293 if (!conn || ! psecret || id != SASL_CB_PASS)
294 {
295 return SASL_BADPARAM;
296 }
297
298 *psecret= (sasl_secret_t *)context;
299
300 return SASL_OK;
301 }
302
303 memcached_return_t memcached_set_sasl_auth_data(memcached_st *ptr,
304 const char *username,
305 const char *password)
306 {
307 if (LIBMEMCACHED_WITH_SASL_SUPPORT == 0)
308 {
309 return MEMCACHED_NOT_SUPPORTED;
310 }
311
312 if (ptr == NULL or username == NULL or password == NULL)
313 {
314 return MEMCACHED_INVALID_ARGUMENTS;
315 }
316
317 memcached_return_t ret;
318 if (memcached_failed(ret= memcached_behavior_set(ptr, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1)))
319 {
320 return memcached_set_error(*ptr, ret, MEMCACHED_AT, memcached_literal_param("Unable change to binary protocol which is required for SASL."));
321 }
322
323 memcached_destroy_sasl_auth_data(ptr);
324
325 sasl_callback_t *callbacks= libmemcached_xcalloc(ptr, 4, sasl_callback_t);
326 size_t password_length= strlen(password);
327 size_t username_length= strlen(username);
328 char *name= (char *)libmemcached_malloc(ptr, username_length +1);
329 sasl_secret_t *secret= (sasl_secret_t*)libmemcached_malloc(ptr, password_length +1 + sizeof(sasl_secret_t));
330
331 if (callbacks == NULL or name == NULL or secret == NULL)
332 {
333 libmemcached_free(ptr, callbacks);
334 libmemcached_free(ptr, name);
335 libmemcached_free(ptr, secret);
336 return memcached_set_error(*ptr, MEMCACHED_MEMORY_ALLOCATION_FAILURE, MEMCACHED_AT);
337 }
338
339 secret->len= password_length;
340 memcpy(secret->data, password, password_length);
341 secret->data[password_length]= 0;
342
343 callbacks[0].id= SASL_CB_USER;
344 callbacks[0].proc= (int (*)())get_username;
345 callbacks[0].context= strncpy(name, username, username_length +1);
346 callbacks[1].id= SASL_CB_AUTHNAME;
347 callbacks[1].proc= (int (*)())get_username;
348 callbacks[1].context= name;
349 callbacks[2].id= SASL_CB_PASS;
350 callbacks[2].proc= (int (*)())get_password;
351 callbacks[2].context= secret;
352 callbacks[3].id= SASL_CB_LIST_END;
353
354 ptr->sasl.callbacks= callbacks;
355 ptr->sasl.is_allocated= true;
356
357 return MEMCACHED_SUCCESS;
358 }
359
360 memcached_return_t memcached_destroy_sasl_auth_data(memcached_st *ptr)
361 {
362 if (LIBMEMCACHED_WITH_SASL_SUPPORT == 0)
363 {
364 return MEMCACHED_NOT_SUPPORTED;
365 }
366
367 if (ptr == NULL)
368 {
369 return MEMCACHED_INVALID_ARGUMENTS;
370 }
371
372 if (ptr->sasl.callbacks == NULL)
373 {
374 return MEMCACHED_SUCCESS;
375 }
376
377 if (ptr->sasl.is_allocated)
378 {
379 libmemcached_free(ptr, ptr->sasl.callbacks[0].context);
380 libmemcached_free(ptr, ptr->sasl.callbacks[2].context);
381 libmemcached_free(ptr, (void*)ptr->sasl.callbacks);
382 ptr->sasl.is_allocated= false;
383 }
384
385 ptr->sasl.callbacks= NULL;
386
387 return MEMCACHED_SUCCESS;
388 }
389
390 memcached_return_t memcached_clone_sasl(memcached_st *clone, const memcached_st *source)
391 {
392 if (LIBMEMCACHED_WITH_SASL_SUPPORT == 0)
393 {
394 return MEMCACHED_NOT_SUPPORTED;
395 }
396
397 if (clone == NULL or source == NULL)
398 {
399 return MEMCACHED_INVALID_ARGUMENTS;
400 }
401
402 if (source->sasl.callbacks == NULL)
403 {
404 return MEMCACHED_SUCCESS;
405 }
406
407 /* Hopefully we are using our own callback mechanisms.. */
408 if (source->sasl.callbacks[0].id == SASL_CB_USER &&
409 source->sasl.callbacks[0].proc == (int (*)())get_username &&
410 source->sasl.callbacks[1].id == SASL_CB_AUTHNAME &&
411 source->sasl.callbacks[1].proc == (int (*)())get_username &&
412 source->sasl.callbacks[2].id == SASL_CB_PASS &&
413 source->sasl.callbacks[2].proc == (int (*)())get_password &&
414 source->sasl.callbacks[3].id == SASL_CB_LIST_END)
415 {
416 sasl_secret_t *secret= (sasl_secret_t *)source->sasl.callbacks[2].context;
417 return memcached_set_sasl_auth_data(clone,
418 (const char*)source->sasl.callbacks[0].context,
419 (const char*)secret->data);
420 }
421
422 /*
423 * But we're not. It may work if we know what the user tries to pass
424 * into the list, but if we don't know the ID we don't know how to handle
425 * the context...
426 */
427 size_t total= 0;
428
429 while (source->sasl.callbacks[total].id != SASL_CB_LIST_END)
430 {
431 switch (source->sasl.callbacks[total].id)
432 {
433 case SASL_CB_USER:
434 case SASL_CB_AUTHNAME:
435 case SASL_CB_PASS:
436 break;
437 default:
438 /* I don't know how to deal with this... */
439 return MEMCACHED_NOT_SUPPORTED;
440 }
441
442 ++total;
443 }
444
445 sasl_callback_t *callbacks= libmemcached_xcalloc(clone, total +1, sasl_callback_t);
446 if (callbacks == NULL)
447 {
448 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
449 }
450 memcpy(callbacks, source->sasl.callbacks, (total + 1) * sizeof(sasl_callback_t));
451
452 /* Now update the context... */
453 for (size_t x= 0; x < total; ++x)
454 {
455 if (callbacks[x].id == SASL_CB_USER || callbacks[x].id == SASL_CB_AUTHNAME)
456 {
457 callbacks[x].context= (sasl_callback_t*)libmemcached_malloc(clone, strlen((const char*)source->sasl.callbacks[x].context));
458
459 if (callbacks[x].context == NULL)
460 {
461 /* Failed to allocate memory, clean up previously allocated memory */
462 for (size_t y= 0; y < x; ++y)
463 {
464 libmemcached_free(clone, clone->sasl.callbacks[y].context);
465 }
466
467 libmemcached_free(clone, callbacks);
468 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
469 }
470 strncpy((char*)callbacks[x].context, (const char*)source->sasl.callbacks[x].context, sizeof(callbacks[x].context));
471 }
472 else
473 {
474 sasl_secret_t *src= (sasl_secret_t *)source->sasl.callbacks[x].context;
475 sasl_secret_t *n= (sasl_secret_t*)libmemcached_malloc(clone, src->len + 1 + sizeof(*n));
476 if (n == NULL)
477 {
478 /* Failed to allocate memory, clean up previously allocated memory */
479 for (size_t y= 0; y < x; ++y)
480 {
481 libmemcached_free(clone, clone->sasl.callbacks[y].context);
482 }
483
484 libmemcached_free(clone, callbacks);
485 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
486 }
487 memcpy(n, src, src->len + 1 + sizeof(*n));
488 callbacks[x].context= n;
489 }
490 }
491
492 clone->sasl.callbacks= callbacks;
493 clone->sasl.is_allocated= true;
494
495 return MEMCACHED_SUCCESS;
496 }
497
498 #else
499
500 void memcached_set_sasl_callbacks(memcached_st *, const sasl_callback_t *)
501 {
502 }
503
504 sasl_callback_t *memcached_get_sasl_callbacks(memcached_st *)
505 {
506 return NULL;
507 }
508
509 memcached_return_t memcached_set_sasl_auth_data(memcached_st *, const char *, const char *)
510 {
511 return MEMCACHED_NOT_SUPPORTED;
512 }
513
514 memcached_return_t memcached_clone_sasl(memcached_st *, const memcached_st *)
515 {
516 return MEMCACHED_NOT_SUPPORTED;
517 }
518
519 #endif