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