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