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