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