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 +--------------------------------------------------------------------+
16 #include "libmemcached/common.h"
21 #if defined(LIBMEMCACHED_WITH_SASL_SUPPORT) && LIBMEMCACHED_WITH_SASL_SUPPORT
23 # if defined(HAVE_LIBSASL) && HAVE_LIBSASL
24 # include <sasl/sasl.h>
27 # define CAST_SASL_CB(cb) reinterpret_cast<int (*)()>(reinterpret_cast<intptr_t>(cb))
31 void memcached_set_sasl_callbacks(memcached_st
*shell
, const sasl_callback_t
*callbacks
) {
32 Memcached
*self
= memcached2Memcached(shell
);
34 self
->sasl
.callbacks
= const_cast<sasl_callback_t
*>(callbacks
);
35 self
->sasl
.is_allocated
= false;
39 sasl_callback_t
*memcached_get_sasl_callbacks(memcached_st
*shell
) {
40 Memcached
*self
= memcached2Memcached(shell
);
42 return self
->sasl
.callbacks
;
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)
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
);
62 if (getsockname(server
.fd
, (struct sockaddr
*) &saddr
, &salen
) < 0) {
63 return memcached_set_error(server
, MEMCACHED_HOST_LOOKUP_FAILURE
, MEMCACHED_AT
);
66 if (getnameinfo((struct sockaddr
*) &saddr
, salen
, host
, sizeof(host
), port
, sizeof(port
),
67 NI_NUMERICHOST
| NI_NUMERICSERV
)
70 return memcached_set_error(server
, MEMCACHED_HOST_LOOKUP_FAILURE
, MEMCACHED_AT
);
73 (void) snprintf(laddr
, laddr_length
, "%s;%s", host
, port
);
74 salen
= sizeof(saddr
);
76 if (getpeername(server
.fd
, (struct sockaddr
*) &saddr
, &salen
) < 0) {
77 return memcached_set_error(server
, MEMCACHED_HOST_LOOKUP_FAILURE
, MEMCACHED_AT
);
80 if (getnameinfo((struct sockaddr
*) &saddr
, salen
, host
, sizeof(host
), port
, sizeof(port
),
81 NI_NUMERICHOST
| NI_NUMERICSERV
)
84 return memcached_set_error(server
, MEMCACHED_HOST_LOOKUP_FAILURE
, MEMCACHED_AT
);
87 (void) snprintf(raddr
, raddr_length
, "%s;%s", host
, port
);
89 return MEMCACHED_SUCCESS
;
94 static void sasl_shutdown_function() {
95 #if HAVE_SASL_CLIENT_DONE
96 (void) sasl_client_done();
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
);
108 if (sasl_startup_state
== SASL_OK
) {
109 (void) atexit(sasl_shutdown_function
);
115 memcached_return_t
memcached_sasl_authenticate_connection(memcached_instance_st
*server
) {
116 if (LIBMEMCACHED_WITH_SASL_SUPPORT
== 0) {
117 return MEMCACHED_NOT_SUPPORTED
;
120 if (server
== NULL
) {
121 return MEMCACHED_INVALID_ARGUMENTS
;
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"));
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
136 protocol_binary_request_no_extras request
= {};
138 initialize_binary_request(server
, request
.message
.header
);
140 request
.message
.header
.request
.opcode
= PROTOCOL_BINARY_CMD_SASL_LIST_MECHS
;
142 if (memcached_io_write(server
, request
.bytes
, sizeof(request
.bytes
), true)
143 != sizeof(request
.bytes
)) {
144 return MEMCACHED_WRITE_FAILURE
;
146 assert_msg(server
->fd
!= INVALID_SOCKET
, "Programmer error, invalid socket");
148 memcached_server_response_increment(server
);
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....
160 rc
= MEMCACHED_SUCCESS
;
165 assert_msg(server
->fd
!= INVALID_SOCKET
, "Programmer error, invalid socket");
167 /* set ip addresses */
168 char laddr
[MEMCACHED_NI_MAXHOST
+ MEMCACHED_NI_MAXSERV
];
169 char raddr
[MEMCACHED_NI_MAXHOST
+ MEMCACHED_NI_MAXSERV
];
171 if (memcached_failed(rc
= resolve_names(*server
, laddr
, sizeof(laddr
), raddr
, sizeof(raddr
)))) {
176 if ((pthread_error
= pthread_once(&sasl_startup_once
, sasl_startup_function
))) {
177 return memcached_set_errno(*server
, pthread_error
, MEMCACHED_AT
);
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
));
186 (void) pthread_mutex_unlock(&sasl_startup_state_LOCK
);
190 if ((ret
= sasl_client_new("memcached", server
->_hostname
, laddr
, raddr
,
191 server
->root
->sasl
.callbacks
, 0, &conn
))
194 const char *sasl_error_msg
= sasl_errstring(ret
, NULL
, NULL
);
198 return memcached_set_error(*server
, MEMCACHED_AUTH_PROBLEM
, MEMCACHED_AT
,
199 memcached_string_make_from_cstr(sasl_error_msg
));
203 const char *chosenmech
;
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
);
211 return memcached_set_error(*server
, MEMCACHED_AUTH_PROBLEM
, MEMCACHED_AT
,
212 memcached_string_make_from_cstr(sasl_error_msg
));
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
);
220 /* send the packet */
222 libmemcached_io_vector_st vector
[] = {
223 {request
.bytes
, sizeof(request
.bytes
)}, {chosenmech
, keylen
}, {data
, len
}};
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
;
230 assert_msg(server
->fd
!= INVALID_SOCKET
, "Programmer error, invalid socket");
231 memcached_server_response_increment(server
);
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
) {
239 assert_msg(server
->fd
!= INVALID_SOCKET
, "Programmer error, invalid socket");
241 ret
= sasl_client_step(conn
, memcached_result_value(&server
->root
->result
),
242 (unsigned int) memcached_result_length(&server
->root
->result
), NULL
,
245 if (ret
!= SASL_OK
&& ret
!= SASL_CONTINUE
) {
246 rc
= MEMCACHED_AUTH_PROBLEM
;
250 request
.message
.header
.request
.opcode
= PROTOCOL_BINARY_CMD_SASL_STEP
;
251 request
.message
.header
.request
.bodylen
= htonl(len
+ keylen
);
254 /* Release resources */
257 return memcached_set_error(*server
, rc
, MEMCACHED_AT
);
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
;
265 *result
= (char *) context
;
267 *len
= (unsigned int) strlen(*result
);
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
;
278 *psecret
= (sasl_secret_t
*) context
;
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
;
290 if (ptr
== NULL
or username
== NULL
or password
== NULL
) {
291 return MEMCACHED_INVALID_ARGUMENTS
;
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."));
301 memcached_destroy_sasl_auth_data(ptr
);
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
));
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
);
317 secret
->len
= password_length
;
318 memcpy(secret
->data
, password
, password_length
);
319 secret
->data
[password_length
] = 0;
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
;
332 ptr
->sasl
.callbacks
= callbacks
;
333 ptr
->sasl
.is_allocated
= true;
335 return MEMCACHED_SUCCESS
;
338 memcached_return_t
memcached_destroy_sasl_auth_data(memcached_st
*shell
) {
339 if (LIBMEMCACHED_WITH_SASL_SUPPORT
== 0) {
340 return MEMCACHED_NOT_SUPPORTED
;
343 Memcached
*ptr
= memcached2Memcached(shell
);
345 return MEMCACHED_INVALID_ARGUMENTS
;
348 if (ptr
->sasl
.callbacks
== NULL
) {
349 return MEMCACHED_SUCCESS
;
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;
359 ptr
->sasl
.callbacks
= NULL
;
361 return MEMCACHED_SUCCESS
;
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
;
369 if (clone
== NULL
or source
== NULL
) {
370 return MEMCACHED_INVALID_ARGUMENTS
;
373 if (source
->sasl
.callbacks
== NULL
) {
374 return MEMCACHED_SUCCESS
;
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
)
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
);
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
398 while (source
->sasl
.callbacks
[total
].id
!= SASL_CB_LIST_END
) {
399 switch (source
->sasl
.callbacks
[total
].id
) {
401 case SASL_CB_AUTHNAME
:
405 /* I don't know how to deal with this... */
406 return MEMCACHED_NOT_SUPPORTED
;
412 sasl_callback_t
*callbacks
= libmemcached_xcalloc(clone
, total
+ 1, sasl_callback_t
);
413 if (callbacks
== NULL
) {
414 return MEMCACHED_MEMORY_ALLOCATION_FAILURE
;
416 memcpy(callbacks
, source
->sasl
.callbacks
, (total
+ 1) * sizeof(sasl_callback_t
));
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
));
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
);
430 libmemcached_free(clone
, callbacks
);
431 return MEMCACHED_MEMORY_ALLOCATION_FAILURE
;
433 strncpy((char *) callbacks
[x
].context
, (const char *) source
->sasl
.callbacks
[x
].context
,
434 sizeof(callbacks
[x
].context
));
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
));
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
);
444 libmemcached_free(clone
, callbacks
);
445 return MEMCACHED_MEMORY_ALLOCATION_FAILURE
;
447 memcpy(n
, src
, src
->len
+ 1 + sizeof(*n
));
448 callbacks
[x
].context
= n
;
452 clone
->sasl
.callbacks
= callbacks
;
453 clone
->sasl
.is_allocated
= true;
455 return MEMCACHED_SUCCESS
;
460 void memcached_set_sasl_callbacks(memcached_st
*, const sasl_callback_t
*) {}
462 sasl_callback_t
*memcached_get_sasl_callbacks(memcached_st
*) {
466 memcached_return_t
memcached_set_sasl_auth_data(memcached_st
*, const char *, const char *) {
467 return MEMCACHED_NOT_SUPPORTED
;
470 memcached_return_t
memcached_clone_sasl(memcached_st
*, const memcached_st
*) {
471 return MEMCACHED_NOT_SUPPORTED
;
474 memcached_return_t
memcached_destroy_sasl_auth_data(memcached_st
*) {
475 return MEMCACHED_NOT_SUPPORTED
;
478 memcached_return_t
memcached_sasl_authenticate_connection(memcached_instance_st
*) {
479 return MEMCACHED_NOT_SUPPORTED
;