1 /* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
5 * Copyright (C) 2011-2012 Data Differential, http://datadifferential.com/
6 * Copyright (C) 2006-2009 Brian Aker All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
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
20 * * The names of its contributors may not be used to endorse or
21 * promote products derived from this software without specific prior
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.
38 #include "libmemcached/common.h"
42 #if defined(LIBMEMCACHED_WITH_SASL_SUPPORT) && LIBMEMCACHED_WITH_SASL_SUPPORT
44 #if defined(HAVE_LIBSASL) && HAVE_LIBSASL
45 #include <sasl/sasl.h>
48 #define CAST_SASL_CB(cb) reinterpret_cast<int(*)()>(reinterpret_cast<intptr_t>(cb))
52 void memcached_set_sasl_callbacks(memcached_st
*shell
,
53 const sasl_callback_t
*callbacks
)
55 Memcached
* self
= memcached2Memcached(shell
);
58 self
->sasl
.callbacks
= const_cast<sasl_callback_t
*>(callbacks
);
59 self
->sasl
.is_allocated
= false;
63 sasl_callback_t
*memcached_get_sasl_callbacks(memcached_st
*shell
)
65 Memcached
* self
= memcached2Memcached(shell
);
68 return self
->sasl
.callbacks
;
75 * Resolve the names for both ends of a connection
76 * @param fd socket to check
77 * @param laddr local address (out)
78 * @param raddr remote address (out)
79 * @return true on success false otherwise (errno contains more info)
81 static memcached_return_t
resolve_names(memcached_instance_st
& server
, char *laddr
, size_t laddr_length
, char *raddr
, size_t raddr_length
)
83 char host
[MEMCACHED_NI_MAXHOST
];
84 char port
[MEMCACHED_NI_MAXSERV
];
85 struct sockaddr_storage saddr
;
86 socklen_t salen
= sizeof(saddr
);
88 if (getsockname(server
.fd
, (struct sockaddr
*)&saddr
, &salen
) < 0)
90 return memcached_set_error(server
, MEMCACHED_HOST_LOOKUP_FAILURE
, MEMCACHED_AT
);
93 if (getnameinfo((struct sockaddr
*)&saddr
, salen
, host
, sizeof(host
), port
, sizeof(port
), NI_NUMERICHOST
| NI_NUMERICSERV
) < 0)
95 return memcached_set_error(server
, MEMCACHED_HOST_LOOKUP_FAILURE
, MEMCACHED_AT
);
98 (void)snprintf(laddr
, laddr_length
, "%s;%s", host
, port
);
101 if (getpeername(server
.fd
, (struct sockaddr
*)&saddr
, &salen
) < 0)
103 return memcached_set_error(server
, MEMCACHED_HOST_LOOKUP_FAILURE
, MEMCACHED_AT
);
106 if (getnameinfo((struct sockaddr
*)&saddr
, salen
, host
, sizeof(host
),
107 port
, sizeof(port
), NI_NUMERICHOST
| NI_NUMERICSERV
) < 0)
109 return memcached_set_error(server
, MEMCACHED_HOST_LOOKUP_FAILURE
, MEMCACHED_AT
);
112 (void)snprintf(raddr
, raddr_length
, "%s;%s", host
, port
);
114 return MEMCACHED_SUCCESS
;
119 static void sasl_shutdown_function()
124 static std::atomic
<int> sasl_startup_state(SASL_OK
);
125 pthread_mutex_t sasl_startup_state_LOCK
= PTHREAD_MUTEX_INITIALIZER
;
126 static pthread_once_t sasl_startup_once
= PTHREAD_ONCE_INIT
;
127 static void sasl_startup_function(void)
129 sasl_startup_state
= sasl_client_init(NULL
);
131 if (sasl_startup_state
== SASL_OK
)
133 (void)atexit(sasl_shutdown_function
);
139 memcached_return_t
memcached_sasl_authenticate_connection(memcached_instance_st
* server
)
141 if (LIBMEMCACHED_WITH_SASL_SUPPORT
== 0)
143 return MEMCACHED_NOT_SUPPORTED
;
148 return MEMCACHED_INVALID_ARGUMENTS
;
151 /* SANITY CHECK: SASL can only be used with the binary protocol */
152 if (memcached_is_binary(server
->root
) == false)
154 return memcached_set_error(*server
, MEMCACHED_INVALID_ARGUMENTS
, MEMCACHED_AT
,
155 memcached_literal_param("memcached_sasl_authenticate_connection() is not supported via the ASCII protocol"));
158 /* Try to get the supported mech from the server. Servers without SASL
159 * support will return UNKNOWN COMMAND, so we can just treat that
162 protocol_binary_request_no_extras request
= { };
164 initialize_binary_request(server
, request
.message
.header
);
166 request
.message
.header
.request
.opcode
= PROTOCOL_BINARY_CMD_SASL_LIST_MECHS
;
168 if (memcached_io_write(server
, request
.bytes
, sizeof(request
.bytes
), true) != sizeof(request
.bytes
))
170 return MEMCACHED_WRITE_FAILURE
;
172 assert_msg(server
->fd
!= INVALID_SOCKET
, "Programmer error, invalid socket");
174 memcached_server_response_increment(server
);
176 char mech
[MEMCACHED_MAX_BUFFER
];
177 memcached_return_t rc
= memcached_response(server
, mech
, sizeof(mech
), NULL
);
178 if (memcached_failed(rc
))
180 if (rc
== MEMCACHED_PROTOCOL_ERROR
)
182 /* If the server doesn't support SASL it will return PROTOCOL_ERROR.
183 * This error may also be returned for other errors, but let's assume
184 * that the server don't support SASL and treat it as success and
185 * let the client fail with the next operation if the error was
186 * caused by another problem....
188 rc
= MEMCACHED_SUCCESS
;
193 assert_msg(server
->fd
!= INVALID_SOCKET
, "Programmer error, invalid socket");
195 /* set ip addresses */
196 char laddr
[MEMCACHED_NI_MAXHOST
+ MEMCACHED_NI_MAXSERV
];
197 char raddr
[MEMCACHED_NI_MAXHOST
+ MEMCACHED_NI_MAXSERV
];
199 if (memcached_failed(rc
= resolve_names(*server
, laddr
, sizeof(laddr
), raddr
, sizeof(raddr
))))
205 if ((pthread_error
= pthread_once(&sasl_startup_once
, sasl_startup_function
)) != 0)
207 return memcached_set_errno(*server
, pthread_error
, MEMCACHED_AT
);
210 (void)pthread_mutex_lock(&sasl_startup_state_LOCK
);
211 if (sasl_startup_state
!= SASL_OK
)
213 const char *sasl_error_msg
= sasl_errstring(sasl_startup_state
, NULL
, NULL
);
214 return memcached_set_error(*server
, MEMCACHED_AUTH_PROBLEM
, MEMCACHED_AT
,
215 memcached_string_make_from_cstr(sasl_error_msg
));
217 (void)pthread_mutex_unlock(&sasl_startup_state_LOCK
);
221 if ((ret
= sasl_client_new("memcached", server
->_hostname
, laddr
, raddr
, server
->root
->sasl
.callbacks
, 0, &conn
) ) != SASL_OK
)
223 const char *sasl_error_msg
= sasl_errstring(ret
, NULL
, NULL
);
227 return memcached_set_error(*server
, MEMCACHED_AUTH_PROBLEM
, MEMCACHED_AT
,
228 memcached_string_make_from_cstr(sasl_error_msg
));
232 const char *chosenmech
;
234 ret
= sasl_client_start(conn
, mech
, NULL
, &data
, &len
, &chosenmech
);
235 if (ret
!= SASL_OK
and ret
!= SASL_CONTINUE
)
237 const char *sasl_error_msg
= sasl_errstring(ret
, NULL
, NULL
);
241 return memcached_set_error(*server
, MEMCACHED_AUTH_PROBLEM
, MEMCACHED_AT
,
242 memcached_string_make_from_cstr(sasl_error_msg
));
244 uint16_t keylen
= (uint16_t)strlen(chosenmech
);
245 request
.message
.header
.request
.opcode
= PROTOCOL_BINARY_CMD_SASL_AUTH
;
246 request
.message
.header
.request
.keylen
= htons(keylen
);
247 request
.message
.header
.request
.bodylen
= htonl(len
+ keylen
);
250 /* send the packet */
252 libmemcached_io_vector_st vector
[]=
254 { request
.bytes
, sizeof(request
.bytes
) },
255 { chosenmech
, keylen
},
259 assert_msg(server
->fd
!= INVALID_SOCKET
, "Programmer error, invalid socket");
260 if (memcached_io_writev(server
, vector
, 3, true) == false)
262 rc
= MEMCACHED_WRITE_FAILURE
;
265 assert_msg(server
->fd
!= INVALID_SOCKET
, "Programmer error, invalid socket");
266 memcached_server_response_increment(server
);
268 /* read the response */
269 assert_msg(server
->fd
!= INVALID_SOCKET
, "Programmer error, invalid socket");
270 rc
= memcached_response(server
, NULL
, 0, NULL
);
271 if (rc
!= MEMCACHED_AUTH_CONTINUE
)
275 assert_msg(server
->fd
!= INVALID_SOCKET
, "Programmer error, invalid socket");
277 ret
= sasl_client_step(conn
, memcached_result_value(&server
->root
->result
),
278 (unsigned int)memcached_result_length(&server
->root
->result
),
281 if (ret
!= SASL_OK
&& ret
!= SASL_CONTINUE
)
283 rc
= MEMCACHED_AUTH_PROBLEM
;
287 request
.message
.header
.request
.opcode
= PROTOCOL_BINARY_CMD_SASL_STEP
;
288 request
.message
.header
.request
.bodylen
= htonl(len
+ keylen
);
291 /* Release resources */
294 return memcached_set_error(*server
, rc
, MEMCACHED_AT
);
297 static int get_username(void *context
, int id
, const char **result
, unsigned int *len
)
299 if (!context
|| !result
|| (id
!= SASL_CB_USER
&& id
!= SASL_CB_AUTHNAME
))
301 return SASL_BADPARAM
;
304 *result
= (char *)context
;
307 *len
= (unsigned int)strlen(*result
);
313 static int get_password(sasl_conn_t
*conn
, void *context
, int id
,
314 sasl_secret_t
**psecret
)
316 if (!conn
|| ! psecret
|| id
!= SASL_CB_PASS
)
318 return SASL_BADPARAM
;
321 *psecret
= (sasl_secret_t
*)context
;
326 memcached_return_t
memcached_set_sasl_auth_data(memcached_st
*shell
,
327 const char *username
,
328 const char *password
)
330 Memcached
* ptr
= memcached2Memcached(shell
);
331 if (LIBMEMCACHED_WITH_SASL_SUPPORT
== 0)
333 return MEMCACHED_NOT_SUPPORTED
;
336 if (ptr
== NULL
or username
== NULL
or password
== NULL
)
338 return MEMCACHED_INVALID_ARGUMENTS
;
341 memcached_return_t ret
;
342 if (memcached_failed(ret
= memcached_behavior_set(ptr
, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL
, 1)))
344 return memcached_set_error(*ptr
, ret
, MEMCACHED_AT
, memcached_literal_param("Unable change to binary protocol which is required for SASL."));
347 memcached_destroy_sasl_auth_data(ptr
);
349 sasl_callback_t
*callbacks
= libmemcached_xcalloc(ptr
, 4, sasl_callback_t
);
350 size_t password_length
= strlen(password
);
351 size_t username_length
= strlen(username
);
352 char *name
= (char *)libmemcached_malloc(ptr
, username_length
+1);
353 sasl_secret_t
*secret
= (sasl_secret_t
*)libmemcached_malloc(ptr
, password_length
+1 + sizeof(sasl_secret_t
));
355 if (callbacks
== NULL
or name
== NULL
or secret
== NULL
)
357 libmemcached_free(ptr
, callbacks
);
358 libmemcached_free(ptr
, name
);
359 libmemcached_free(ptr
, secret
);
360 return memcached_set_error(*ptr
, MEMCACHED_MEMORY_ALLOCATION_FAILURE
, MEMCACHED_AT
);
363 secret
->len
= password_length
;
364 memcpy(secret
->data
, password
, password_length
);
365 secret
->data
[password_length
]= 0;
367 callbacks
[0].id
= SASL_CB_USER
;
368 callbacks
[0].proc
= CAST_SASL_CB(get_username
);
369 callbacks
[0].context
= strncpy(name
, username
, username_length
+1);
370 callbacks
[1].id
= SASL_CB_AUTHNAME
;
371 callbacks
[1].proc
= CAST_SASL_CB(get_username
);
372 callbacks
[1].context
= name
;
373 callbacks
[2].id
= SASL_CB_PASS
;
374 callbacks
[2].proc
= CAST_SASL_CB(get_password
);
375 callbacks
[2].context
= secret
;
376 callbacks
[3].id
= SASL_CB_LIST_END
;
378 ptr
->sasl
.callbacks
= callbacks
;
379 ptr
->sasl
.is_allocated
= true;
381 return MEMCACHED_SUCCESS
;
384 memcached_return_t
memcached_destroy_sasl_auth_data(memcached_st
*shell
)
386 if (LIBMEMCACHED_WITH_SASL_SUPPORT
== 0)
388 return MEMCACHED_NOT_SUPPORTED
;
391 Memcached
* ptr
= memcached2Memcached(shell
);
394 return MEMCACHED_INVALID_ARGUMENTS
;
397 if (ptr
->sasl
.callbacks
== NULL
)
399 return MEMCACHED_SUCCESS
;
402 if (ptr
->sasl
.is_allocated
)
404 libmemcached_free(ptr
, ptr
->sasl
.callbacks
[0].context
);
405 libmemcached_free(ptr
, ptr
->sasl
.callbacks
[2].context
);
406 libmemcached_free(ptr
, (void*)ptr
->sasl
.callbacks
);
407 ptr
->sasl
.is_allocated
= false;
410 ptr
->sasl
.callbacks
= NULL
;
412 return MEMCACHED_SUCCESS
;
415 memcached_return_t
memcached_clone_sasl(memcached_st
*clone
, const memcached_st
*source
)
417 if (LIBMEMCACHED_WITH_SASL_SUPPORT
== 0)
419 return MEMCACHED_NOT_SUPPORTED
;
422 if (clone
== NULL
or source
== NULL
)
424 return MEMCACHED_INVALID_ARGUMENTS
;
427 if (source
->sasl
.callbacks
== NULL
)
429 return MEMCACHED_SUCCESS
;
432 /* Hopefully we are using our own callback mechanisms.. */
433 if (source
->sasl
.callbacks
[0].id
== SASL_CB_USER
&&
434 source
->sasl
.callbacks
[0].proc
== CAST_SASL_CB(get_username
) &&
435 source
->sasl
.callbacks
[1].id
== SASL_CB_AUTHNAME
&&
436 source
->sasl
.callbacks
[1].proc
== CAST_SASL_CB(get_username
) &&
437 source
->sasl
.callbacks
[2].id
== SASL_CB_PASS
&&
438 source
->sasl
.callbacks
[2].proc
== CAST_SASL_CB(get_password
) &&
439 source
->sasl
.callbacks
[3].id
== SASL_CB_LIST_END
)
441 sasl_secret_t
*secret
= (sasl_secret_t
*)source
->sasl
.callbacks
[2].context
;
442 return memcached_set_sasl_auth_data(clone
,
443 (const char*)source
->sasl
.callbacks
[0].context
,
444 (const char*)secret
->data
);
448 * But we're not. It may work if we know what the user tries to pass
449 * into the list, but if we don't know the ID we don't know how to handle
454 while (source
->sasl
.callbacks
[total
].id
!= SASL_CB_LIST_END
)
456 switch (source
->sasl
.callbacks
[total
].id
)
459 case SASL_CB_AUTHNAME
:
463 /* I don't know how to deal with this... */
464 return MEMCACHED_NOT_SUPPORTED
;
470 sasl_callback_t
*callbacks
= libmemcached_xcalloc(clone
, total
+1, sasl_callback_t
);
471 if (callbacks
== NULL
)
473 return MEMCACHED_MEMORY_ALLOCATION_FAILURE
;
475 memcpy(callbacks
, source
->sasl
.callbacks
, (total
+ 1) * sizeof(sasl_callback_t
));
477 /* Now update the context... */
478 for (ptrdiff_t x
= 0; x
< total
; ++x
)
480 if (callbacks
[x
].id
== SASL_CB_USER
|| callbacks
[x
].id
== SASL_CB_AUTHNAME
)
482 callbacks
[x
].context
= (sasl_callback_t
*)libmemcached_malloc(clone
, strlen((const char*)source
->sasl
.callbacks
[x
].context
));
484 if (callbacks
[x
].context
== NULL
)
486 /* Failed to allocate memory, clean up previously allocated memory */
487 for (ptrdiff_t y
= 0; y
< x
; ++y
)
489 libmemcached_free(clone
, clone
->sasl
.callbacks
[y
].context
);
492 libmemcached_free(clone
, callbacks
);
493 return MEMCACHED_MEMORY_ALLOCATION_FAILURE
;
495 strncpy((char*)callbacks
[x
].context
, (const char*)source
->sasl
.callbacks
[x
].context
, sizeof(callbacks
[x
].context
));
499 sasl_secret_t
*src
= (sasl_secret_t
*)source
->sasl
.callbacks
[x
].context
;
500 sasl_secret_t
*n
= (sasl_secret_t
*)libmemcached_malloc(clone
, src
->len
+ 1 + sizeof(*n
));
503 /* Failed to allocate memory, clean up previously allocated memory */
504 for (ptrdiff_t y
= 0; y
< x
; ++y
)
506 libmemcached_free(clone
, clone
->sasl
.callbacks
[y
].context
);
509 libmemcached_free(clone
, callbacks
);
510 return MEMCACHED_MEMORY_ALLOCATION_FAILURE
;
512 memcpy(n
, src
, src
->len
+ 1 + sizeof(*n
));
513 callbacks
[x
].context
= n
;
517 clone
->sasl
.callbacks
= callbacks
;
518 clone
->sasl
.is_allocated
= true;
520 return MEMCACHED_SUCCESS
;
525 void memcached_set_sasl_callbacks(memcached_st
*, const sasl_callback_t
*)
529 sasl_callback_t
*memcached_get_sasl_callbacks(memcached_st
*)
534 memcached_return_t
memcached_set_sasl_auth_data(memcached_st
*, const char *, const char *)
536 return MEMCACHED_NOT_SUPPORTED
;
539 memcached_return_t
memcached_clone_sasl(memcached_st
*, const memcached_st
*)
541 return MEMCACHED_NOT_SUPPORTED
;