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