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