434d2db6af7ccd2d97a0d41b08cc50b112a5c28c
[m6w6/libmemcached] / libmemcached / sasl.c
1 /* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
2 *
3 * Libmemcached library
4 *
5 * Copyright (C) 2011 Data Differential, http://datadifferential.com/
6 * Copyright (C) 2006-2009 Brian Aker All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are
10 * met:
11 *
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
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
18 * distribution.
19 *
20 * * The names of its contributors may not be used to endorse or
21 * promote products derived from this software without specific prior
22 * written permission.
23 *
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.
35 *
36 */
37
38 #include <libmemcached/common.h>
39 #include <iso646.h>
40
41 void memcached_set_sasl_callbacks(memcached_st *ptr,
42 const sasl_callback_t *callbacks)
43 {
44 ptr->sasl.callbacks= callbacks;
45 ptr->sasl.is_allocated= false;
46 }
47
48 const sasl_callback_t *memcached_get_sasl_callbacks(memcached_st *ptr)
49 {
50 return ptr->sasl.callbacks;
51 }
52
53 /**
54 * Resolve the names for both ends of a connection
55 * @param fd socket to check
56 * @param laddr local address (out)
57 * @param raddr remote address (out)
58 * @return true on success false otherwise (errno contains more info)
59 */
60 static memcached_return_t resolve_names(int fd, char *laddr, size_t laddr_length, char *raddr, size_t raddr_length)
61 {
62 char host[NI_MAXHOST];
63 char port[NI_MAXSERV];
64 struct sockaddr_storage saddr;
65 socklen_t salen= sizeof(saddr);
66
67 if (getsockname(fd, (struct sockaddr *)&saddr, &salen) < 0)
68 {
69 return MEMCACHED_ERRNO;
70 }
71
72 if (getnameinfo((struct sockaddr *)&saddr, salen, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) < 0)
73 {
74 return MEMCACHED_HOST_LOOKUP_FAILURE;
75 }
76
77 (void)snprintf(laddr, laddr_length, "%s;%s", host, port);
78 salen= sizeof(saddr);
79
80 if (getpeername(fd, (struct sockaddr *)&saddr, &salen) < 0)
81 {
82 return MEMCACHED_ERRNO;
83 }
84
85 if (getnameinfo((struct sockaddr *)&saddr, salen, host, sizeof(host),
86 port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) < 0)
87 {
88 return MEMCACHED_HOST_LOOKUP_FAILURE;
89 }
90
91 (void)snprintf(raddr, raddr_length, "%s;%s", host, port);
92
93 return MEMCACHED_SUCCESS;
94 }
95
96 memcached_return_t memcached_sasl_authenticate_connection(memcached_server_st *server)
97 {
98 /* SANITY CHECK: SASL can only be used with the binary protocol */
99 if (!server->root->flags.binary_protocol)
100 return MEMCACHED_FAILURE;
101
102 /* Try to get the supported mech from the server. Servers without SASL
103 * support will return UNKNOWN COMMAND, so we can just treat that
104 * as authenticated
105 */
106 protocol_binary_request_no_extras request= {
107 .message.header.request= {
108 .magic= PROTOCOL_BINARY_REQ,
109 .opcode= PROTOCOL_BINARY_CMD_SASL_LIST_MECHS
110 }
111 };
112
113 if (memcached_io_write(server, request.bytes,
114 sizeof(request.bytes), 1) != sizeof(request.bytes))
115 {
116 return MEMCACHED_WRITE_FAILURE;
117 }
118
119 memcached_server_response_increment(server);
120
121 char mech[MEMCACHED_MAX_BUFFER];
122 memcached_return_t rc= memcached_response(server, mech, sizeof(mech), NULL);
123 if (memcached_failed(rc))
124 {
125 if (rc == MEMCACHED_PROTOCOL_ERROR)
126 {
127 /* If the server doesn't support SASL it will return PROTOCOL_ERROR.
128 * This error may also be returned for other errors, but let's assume
129 * that the server don't support SASL and treat it as success and
130 * let the client fail with the next operation if the error was
131 * caused by another problem....
132 */
133 rc= MEMCACHED_SUCCESS;
134 }
135
136 return rc;
137 }
138
139 /* set ip addresses */
140 char laddr[NI_MAXHOST + NI_MAXSERV];
141 char raddr[NI_MAXHOST + NI_MAXSERV];
142
143 if (memcached_failed(rc= resolve_names(server->fd, laddr, sizeof(laddr), raddr, sizeof(raddr))))
144 {
145 return rc;
146 }
147
148 sasl_conn_t *conn;
149 int ret= sasl_client_new("memcached", server->hostname, laddr, raddr, server->root->sasl.callbacks, 0, &conn);
150 if (ret != SASL_OK)
151 {
152 return MEMCACHED_AUTH_PROBLEM;
153 }
154
155 const char *data;
156 const char *chosenmech;
157 unsigned int len;
158 ret= sasl_client_start(conn, mech, NULL, &data, &len, &chosenmech);
159
160 if (ret != SASL_OK && ret != SASL_CONTINUE)
161 {
162 rc= MEMCACHED_AUTH_PROBLEM;
163 goto end;
164 }
165
166 uint16_t keylen= (uint16_t)strlen(chosenmech);
167 request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_AUTH;
168 request.message.header.request.keylen= htons(keylen);
169 request.message.header.request.bodylen= htonl(len + keylen);
170
171 do {
172 /* send the packet */
173
174 struct libmemcached_io_vector_st vector[]=
175 {
176 { .length= sizeof(request.bytes), .buffer= request.bytes },
177 { .length= keylen, .buffer= chosenmech },
178 { .length= len, .buffer= data }
179 };
180
181 if (memcached_io_writev(server, vector, 3, true) == -1)
182 {
183 rc= MEMCACHED_WRITE_FAILURE;
184 goto end;
185 }
186 memcached_server_response_increment(server);
187
188 /* read the response */
189 rc= memcached_response(server, NULL, 0, NULL);
190 if (rc != MEMCACHED_AUTH_CONTINUE)
191 {
192 goto end;
193 }
194
195 ret= sasl_client_step(conn, memcached_result_value(&server->root->result),
196 (unsigned int)memcached_result_length(&server->root->result),
197 NULL, &data, &len);
198
199 if (ret != SASL_OK && ret != SASL_CONTINUE)
200 {
201 rc= MEMCACHED_AUTH_PROBLEM;
202 goto end;
203 }
204
205 request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_STEP;
206 request.message.header.request.bodylen= htonl(len + keylen);
207 } while (true);
208
209 end:
210 /* Release resources */
211 sasl_dispose(&conn);
212
213 return rc;
214 }
215
216 static int get_username(void *context, int id, const char **result,
217 unsigned int *len)
218 {
219 if (!context || !result || (id != SASL_CB_USER && id != SASL_CB_AUTHNAME))
220 {
221 return SASL_BADPARAM;
222 }
223
224 *result= context;
225 if (len)
226 {
227 *len= (unsigned int)strlen(*result);
228 }
229
230 return SASL_OK;
231 }
232
233 static int get_password(sasl_conn_t *conn, void *context, int id,
234 sasl_secret_t **psecret)
235 {
236 if (!conn || ! psecret || id != SASL_CB_PASS)
237 {
238 return SASL_BADPARAM;
239 }
240
241 *psecret= context;
242
243 return SASL_OK;
244 }
245
246 memcached_return_t memcached_set_sasl_auth_data(memcached_st *ptr,
247 const char *username,
248 const char *password)
249 {
250 if (ptr == NULL || username == NULL ||
251 password == NULL || ptr->sasl.callbacks != NULL)
252 {
253 return MEMCACHED_FAILURE;
254 }
255
256 sasl_callback_t *callbacks= libmemcached_calloc(ptr, 4, sizeof(sasl_callback_t));
257 size_t password_length= strlen(password);
258 size_t username_length= strlen(username);
259 char *name= libmemcached_malloc(ptr, username_length +1);
260 sasl_secret_t *secret= libmemcached_malloc(ptr, password_length +1 + sizeof(sasl_secret_t));
261
262 if (callbacks == NULL || name == NULL || secret == NULL)
263 {
264 libmemcached_free(ptr, callbacks);
265 libmemcached_free(ptr, name);
266 libmemcached_free(ptr, secret);
267 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
268 }
269
270 secret->len= password_length;
271 memcpy(secret->data, password, password_length);
272 secret->data[password_length]= 0;
273
274 callbacks[0].id= SASL_CB_USER;
275 callbacks[0].proc= get_username;
276 callbacks[0].context= strncpy(name, username, username_length +1);
277 callbacks[1].id= SASL_CB_AUTHNAME;
278 callbacks[1].proc= get_username;
279 callbacks[1].context= name;
280 callbacks[2].id= SASL_CB_PASS;
281 callbacks[2].proc= get_password;
282 callbacks[2].context= secret;
283 callbacks[3].id= SASL_CB_LIST_END;
284
285 ptr->sasl.callbacks= callbacks;
286 ptr->sasl.is_allocated= true;
287
288 return MEMCACHED_SUCCESS;
289 }
290
291 memcached_return_t memcached_destroy_sasl_auth_data(memcached_st *ptr)
292 {
293 if (ptr == NULL || ptr->sasl.callbacks == NULL)
294 {
295 return MEMCACHED_FAILURE;
296 }
297
298 if (ptr->sasl.is_allocated)
299 {
300 libmemcached_free(ptr, ptr->sasl.callbacks[0].context);
301 libmemcached_free(ptr, ptr->sasl.callbacks[2].context);
302 libmemcached_free(ptr, (void*)ptr->sasl.callbacks);
303 ptr->sasl.is_allocated= false;
304 }
305
306 ptr->sasl.callbacks= NULL;
307
308 return MEMCACHED_SUCCESS;
309 }
310
311 memcached_return_t memcached_clone_sasl(memcached_st *clone, const memcached_st *source)
312 {
313
314 if (source->sasl.callbacks == NULL)
315 {
316 return MEMCACHED_SUCCESS;
317 }
318
319 /* Hopefully we are using our own callback mechanisms.. */
320 if (source->sasl.callbacks[0].id == SASL_CB_USER &&
321 source->sasl.callbacks[0].proc == get_username &&
322 source->sasl.callbacks[1].id == SASL_CB_AUTHNAME &&
323 source->sasl.callbacks[1].proc == get_username &&
324 source->sasl.callbacks[2].id == SASL_CB_PASS &&
325 source->sasl.callbacks[2].proc == get_password &&
326 source->sasl.callbacks[3].id == SASL_CB_LIST_END)
327 {
328 sasl_secret_t *secret= source->sasl.callbacks[2].context;
329 return memcached_set_sasl_auth_data(clone,
330 source->sasl.callbacks[0].context,
331 (const char*)secret->data);
332 }
333
334 /*
335 * But we're not. It may work if we know what the user tries to pass
336 * into the list, but if we don't know the ID we don't know how to handle
337 * the context...
338 */
339 size_t total= 0;
340
341 while (source->sasl.callbacks[total].id != SASL_CB_LIST_END)
342 {
343 switch (source->sasl.callbacks[total].id)
344 {
345 case SASL_CB_USER:
346 case SASL_CB_AUTHNAME:
347 case SASL_CB_PASS:
348 break;
349 default:
350 /* I don't know how to deal with this... */
351 return MEMCACHED_NOT_SUPPORTED;
352 }
353
354 ++total;
355 }
356
357 sasl_callback_t *callbacks= libmemcached_calloc(clone, total + 1, sizeof(sasl_callback_t));
358 if (callbacks == NULL)
359 {
360 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
361 }
362 memcpy(callbacks, source->sasl.callbacks, (total + 1) * sizeof(sasl_callback_t));
363
364 /* Now update the context... */
365 for (size_t x= 0; x < total; ++x)
366 {
367 if (callbacks[x].id == SASL_CB_USER || callbacks[x].id == SASL_CB_AUTHNAME)
368 {
369 callbacks[x].context= libmemcached_malloc(clone, strlen(source->sasl.callbacks[x].context));
370
371 if (callbacks[x].context == NULL)
372 {
373 /* Failed to allocate memory, clean up previously allocated memory */
374 for (size_t y= 0; y < x; ++y)
375 {
376 libmemcached_free(clone, clone->sasl.callbacks[y].context);
377 }
378
379 libmemcached_free(clone, callbacks);
380 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
381 }
382 strncpy(callbacks[x].context, source->sasl.callbacks[x].context, sizeof(callbacks[x].context));
383 }
384 else
385 {
386 sasl_secret_t *src = source->sasl.callbacks[x].context;
387 sasl_secret_t *n = libmemcached_malloc(clone, src->len + 1 + sizeof(*n));
388 if (n == NULL)
389 {
390 /* Failed to allocate memory, clean up previously allocated memory */
391 for (size_t y= 0; y < x; ++y)
392 {
393 libmemcached_free(clone, clone->sasl.callbacks[y].context);
394 }
395
396 libmemcached_free(clone, callbacks);
397 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
398 }
399 memcpy(n, src, src->len + 1 + sizeof(*n));
400 callbacks[x].context= n;
401 }
402 }
403
404 clone->sasl.callbacks= callbacks;
405 clone->sasl.is_allocated= true;
406
407 return MEMCACHED_SUCCESS;
408 }