1 /*
2 * SysDB - src/client/sock.c
3 * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
28 #if HAVE_CONFIG_H
29 # include "config.h"
30 #endif /* HAVE_CONFIG_H */
32 #include "sysdb.h"
33 #include "client/sock.h"
34 #include "utils/error.h"
35 #include "utils/strbuf.h"
36 #include "utils/proto.h"
37 #include "utils/os.h"
38 #include "utils/ssl.h"
40 #include <arpa/inet.h>
42 #include <assert.h>
43 #include <errno.h>
44 #include <limits.h>
46 #include <stdlib.h>
48 #include <string.h>
49 #include <strings.h>
51 #include <unistd.h>
53 #include <sys/socket.h>
54 #include <sys/un.h>
56 #include <netdb.h>
58 /*
59 * private data types
60 */
62 struct sdb_client {
63 char *address;
64 int fd;
65 bool eof;
67 /* optional SSL settings */
68 sdb_ssl_options_t ssl_opts;
69 sdb_ssl_client_t *ssl;
70 sdb_ssl_session_t *ssl_session;
72 ssize_t (*read)(sdb_client_t *, sdb_strbuf_t *, size_t);
73 ssize_t (*write)(sdb_client_t *, const void *, size_t);
74 };
76 /*
77 * private helper functions
78 */
80 static ssize_t
81 ssl_read(sdb_client_t *client, sdb_strbuf_t *buf, size_t n)
82 {
83 char tmp[n];
84 ssize_t ret;
86 ret = sdb_ssl_session_read(client->ssl_session, tmp, n);
87 if (ret <= 0)
88 return ret;
90 sdb_strbuf_memappend(buf, tmp, ret);
91 return ret;
92 } /* ssl_read */
94 static ssize_t
95 ssl_write(sdb_client_t *client, const void *buf, size_t n)
96 {
97 return sdb_ssl_session_write(client->ssl_session, buf, n);
98 } /* ssl_write */
100 static ssize_t
101 client_read(sdb_client_t *client, sdb_strbuf_t *buf, size_t n)
102 {
103 return sdb_strbuf_read(buf, client->fd, n);
104 } /* client_read */
106 static ssize_t
107 client_write(sdb_client_t *client, const void *buf, size_t n)
108 {
109 return sdb_write(client->fd, n, buf);
110 } /* client_write */
112 static int
113 connect_unixsock(sdb_client_t *client, const char *address)
114 {
115 struct sockaddr_un sa;
117 client->fd = socket(AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
118 if (client->fd < 0) {
119 char errbuf[1024];
120 sdb_log(SDB_LOG_ERR, "client: Failed to open socket: %s",
121 sdb_strerror(errno, errbuf, sizeof(errbuf)));
122 return -1;
123 }
125 sa.sun_family = AF_UNIX;
126 strncpy(sa.sun_path, address, sizeof(sa.sun_path));
127 sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
129 if (connect(client->fd, (struct sockaddr *)&sa, sizeof(sa))) {
130 char errbuf[1024];
131 sdb_client_close(client);
132 sdb_log(SDB_LOG_ERR, "client: Failed to connect to '%s': %s",
133 sa.sun_path, sdb_strerror(errno, errbuf, sizeof(errbuf)));
134 return -1;
135 }
136 return client->fd;
137 } /* connect_unixsock */
139 static int
140 connect_tcp(sdb_client_t *client, const char *address)
141 {
142 char host[SDB_MAX(strlen("localhost"), (address ? strlen(address) : 0)) + 1];
143 struct addrinfo *ai, *ai_list = NULL;
144 char *peer, *tmp;
145 int status;
147 if ((status = sdb_resolve(SDB_NET_TCP, address, &ai_list))) {
148 sdb_log(SDB_LOG_ERR, "client: Failed to resolve '%s': %s",
149 address, gai_strerror(status));
150 return -1;
151 }
153 for (ai = ai_list; ai != NULL; ai = ai->ai_next) {
154 client->fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
155 if (client->fd < 0) {
156 char errbuf[1024];
157 sdb_log(SDB_LOG_ERR, "client: Failed to open socket: %s",
158 sdb_strerror(errno, errbuf, sizeof(errbuf)));
159 continue;
160 }
162 if (connect(client->fd, ai->ai_addr, ai->ai_addrlen)) {
163 char h[1024], p[32], errbuf[1024];
164 sdb_client_close(client);
165 getnameinfo(ai->ai_addr, ai->ai_addrlen, h, sizeof(h),
166 p, sizeof(p), NI_NUMERICHOST | NI_NUMERICSERV);
167 sdb_log(SDB_LOG_ERR, "client: Failed to connect to '%s:%s': %s",
168 h, p, sdb_strerror(errno, errbuf, sizeof(errbuf)));
169 continue;
170 }
171 break;
172 }
173 freeaddrinfo(ai_list);
175 if (client->fd < 0)
176 return -1;
178 client->ssl = sdb_ssl_client_create(&client->ssl_opts);
179 if (! client->ssl) {
180 sdb_client_close(client);
181 return -1;
182 }
183 client->ssl_session = sdb_ssl_client_connect(client->ssl, client->fd);
184 if (! client->ssl_session) {
185 sdb_client_close(client);
186 return -1;
187 }
189 strncpy(host, address, sizeof(host));
190 if ((tmp = strrchr(host, (int)':')))
191 *tmp = '\0';
192 if (! host[0])
193 strncpy(host, "localhost", sizeof(host));
194 peer = sdb_ssl_session_peer(client->ssl_session);
195 if ((! peer) || strcasecmp(peer, host)) {
196 /* TODO: also check alt-name */
197 sdb_log(SDB_LOG_ERR, "client: Failed to connect to '%s': "
198 "peer name '%s' does not match host address",
199 address, peer);
200 sdb_client_close(client);
201 if (peer)
202 free(peer);
203 return -1;
204 }
205 free(peer);
207 client->read = ssl_read;
208 client->write = ssl_write;
209 return client->fd;
210 } /* connect_tcp */
212 /*
213 * public API
214 */
216 sdb_client_t *
217 sdb_client_create(const char *address)
218 {
219 sdb_client_t *client;
221 if (! address)
222 return NULL;
224 client = malloc(sizeof(*client));
225 if (! client) {
226 sdb_log(SDB_LOG_ERR, "client: Out of memory");
227 return NULL;
228 }
229 memset(client, 0, sizeof(*client));
230 client->fd = -1;
231 client->eof = 1;
233 client->ssl = NULL;
234 client->read = client_read;
235 client->write = client_write;
237 client->address = strdup(address);
238 if (! client->address) {
239 sdb_client_destroy(client);
240 sdb_log(SDB_LOG_ERR, "client: Out of memory");
241 return NULL;
242 }
244 return client;
245 } /* sdb_client_create */
247 void
248 sdb_client_destroy(sdb_client_t *client)
249 {
250 if (! client)
251 return;
253 sdb_client_close(client);
255 if (client->address)
256 free(client->address);
257 client->address = NULL;
259 sdb_ssl_free_options(&client->ssl_opts);
261 free(client);
262 } /* sdb_client_destroy */
264 int
265 sdb_client_set_ssl_options(sdb_client_t *client, const sdb_ssl_options_t *opts)
266 {
267 int ret = 0;
269 if ((! client) || (! opts))
270 return -1;
272 sdb_ssl_free_options(&client->ssl_opts);
274 if (opts->ca_file) {
275 client->ssl_opts.ca_file = strdup(opts->ca_file);
276 if (! client->ssl_opts.ca_file)
277 ret = -1;
278 }
279 if (opts->key_file) {
280 client->ssl_opts.key_file = strdup(opts->key_file);
281 if (! client->ssl_opts.key_file)
282 ret = -1;
283 }
284 if (opts->cert_file) {
285 client->ssl_opts.cert_file = strdup(opts->cert_file);
286 if (! client->ssl_opts.cert_file)
287 ret = -1;
288 }
289 if (opts->crl_file) {
290 client->ssl_opts.crl_file = strdup(opts->crl_file);
291 if (! client->ssl_opts.crl_file)
292 ret = -1;
293 }
295 if (ret)
296 sdb_ssl_free_options(&client->ssl_opts);
297 return ret;
298 } /* sdb_client_set_ssl_options */
300 int
301 sdb_client_connect(sdb_client_t *client, const char *username)
302 {
303 sdb_strbuf_t *buf;
304 ssize_t status;
305 uint32_t rstatus;
307 if ((! client) || (! client->address))
308 return -1;
310 if (client->fd >= 0)
311 return -1;
313 if (*client->address == '/')
314 connect_unixsock(client, client->address);
315 else if (!strncasecmp(client->address, "unix:", strlen("unix:")))
316 connect_unixsock(client, client->address + strlen("unix:"));
317 else if (!strncasecmp(client->address, "tcp:", strlen("tcp:")))
318 connect_tcp(client, client->address + strlen("tcp:"));
319 else
320 connect_tcp(client, client->address);
322 if (client->fd < 0)
323 return -1;
324 client->eof = 0;
326 /* XXX */
327 if (! username)
328 username = "";
330 buf = sdb_strbuf_create(64);
331 rstatus = 0;
332 status = sdb_client_rpc(client, SDB_CONNECTION_STARTUP,
333 (uint32_t)strlen(username), username, &rstatus, buf);
334 if ((status >= 0) && (rstatus == SDB_CONNECTION_OK)) {
335 sdb_strbuf_destroy(buf);
336 return 0;
337 }
339 if (status < 0) {
340 sdb_log(SDB_LOG_ERR, "client: %s", sdb_strbuf_string(buf));
341 sdb_client_close(client);
342 sdb_strbuf_destroy(buf);
343 return (int)status;
344 }
345 if (client->eof)
346 sdb_log(SDB_LOG_ERR, "client: Encountered end-of-file while waiting "
347 "for server response");
349 if (rstatus == SDB_CONNECTION_ERROR) {
350 sdb_log(SDB_LOG_ERR, "client: Access denied for user '%s': %s",
351 username, sdb_strbuf_string(buf));
352 status = -((int)rstatus);
353 }
354 else if (rstatus != SDB_CONNECTION_OK) {
355 sdb_log(SDB_LOG_ERR, "client: Received unsupported authentication "
356 "request (status %d) during startup", (int)rstatus);
357 status = -((int)rstatus);
358 }
360 sdb_client_close(client);
361 sdb_strbuf_destroy(buf);
362 return (int)status;
363 } /* sdb_client_connect */
365 int
366 sdb_client_sockfd(sdb_client_t *client)
367 {
368 if (! client)
369 return -1;
370 return client->fd;
371 } /* sdb_client_sockfd */
373 int
374 sdb_client_shutdown(sdb_client_t *client, int how)
375 {
376 if (! client) {
377 errno = ENOTSOCK;
378 return -1;
379 }
381 if (client->fd < 0) {
382 errno = EBADF;
383 return -1;
384 }
386 return shutdown(client->fd, how);
387 } /* sdb_client_shutdown */
389 void
390 sdb_client_close(sdb_client_t *client)
391 {
392 if (! client)
393 return;
395 if (client->ssl_session) {
396 sdb_ssl_session_destroy(client->ssl_session);
397 client->ssl_session = NULL;
398 }
399 if (client->ssl) {
400 sdb_ssl_client_destroy(client->ssl);
401 client->ssl = NULL;
402 }
404 close(client->fd);
405 client->fd = -1;
406 client->eof = 1;
407 } /* sdb_client_close */
409 ssize_t
410 sdb_client_rpc(sdb_client_t *client,
411 uint32_t cmd, uint32_t msg_len, const char *msg,
412 uint32_t *code, sdb_strbuf_t *buf)
413 {
414 uint32_t rcode = 0;
415 ssize_t status;
417 if (! buf)
418 return -1;
420 if (sdb_client_send(client, cmd, msg_len, msg) < 0) {
421 char errbuf[1024];
422 sdb_strbuf_sprintf(buf, "Failed to send %s message to server: %s",
423 SDB_CONN_MSGTYPE_TO_STRING(cmd),
424 sdb_strerror(errno, errbuf, sizeof(errbuf)));
425 if (code)
426 *code = SDB_CONNECTION_ERROR;
427 return -1;
428 }
430 while (42) {
431 size_t offset = sdb_strbuf_len(buf);
433 status = sdb_client_recv(client, &rcode, buf);
434 if (status < 0) {
435 char errbuf[1024];
436 sdb_strbuf_sprintf(buf, "Failed to receive server response: %s",
437 sdb_strerror(errno, errbuf, sizeof(errbuf)));
438 if (code)
439 *code = SDB_CONNECTION_ERROR;
440 return status;
441 }
443 if (rcode == SDB_CONNECTION_LOG) {
444 uint32_t prio = 0;
445 if (sdb_proto_unmarshal_int32(SDB_STRBUF_STR(buf), &prio) < 0) {
446 sdb_log(SDB_LOG_WARNING, "client: Received a LOG message "
447 "with invalid or missing priority");
448 prio = (uint32_t)SDB_LOG_ERR;
449 }
450 sdb_log((int)prio, "client: %s", sdb_strbuf_string(buf) + offset);
451 sdb_strbuf_skip(buf, offset, sdb_strbuf_len(buf) - offset);
452 continue;
453 }
454 break;
455 }
457 if (code)
458 *code = rcode;
459 return status;
460 } /* sdb_client_rpc */
462 ssize_t
463 sdb_client_send(sdb_client_t *client,
464 uint32_t cmd, uint32_t msg_len, const char *msg)
465 {
466 char buf[2 * sizeof(uint32_t) + msg_len];
468 if ((! client) || (! client->fd))
469 return -1;
470 if (sdb_proto_marshal(buf, sizeof(buf), cmd, msg_len, msg) < 0)
471 return -1;
473 return client->write(client, buf, sizeof(buf));
474 } /* sdb_client_send */
476 ssize_t
477 sdb_client_recv(sdb_client_t *client,
478 uint32_t *code, sdb_strbuf_t *buf)
479 {
480 uint32_t rstatus = UINT32_MAX;
481 uint32_t rlen = UINT32_MAX;
483 size_t total = 0;
484 size_t req = 2 * sizeof(uint32_t);
486 size_t data_offset = sdb_strbuf_len(buf);
488 if (code)
489 *code = UINT32_MAX;
491 if ((! client) || (! client->fd) || (! buf)) {
492 errno = EBADF;
493 return -1;
494 }
496 while (42) {
497 ssize_t status;
499 errno = 0;
500 status = client->read(client, buf, req);
501 if (status < 0) {
502 if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
503 continue;
504 return status;
505 }
506 else if (! status) {
507 client->eof = 1;
508 break;
509 }
511 total += (size_t)status;
513 if (total != req)
514 continue;
516 if (rstatus == UINT32_MAX) {
517 const char *str = sdb_strbuf_string(buf) + data_offset;
518 size_t len = sdb_strbuf_len(buf) - data_offset;
519 ssize_t n;
521 /* retrieve status and data len */
522 assert(len >= 2 * sizeof(uint32_t));
523 n = sdb_proto_unmarshal_int32(str, len, &rstatus);
524 str += n; len -= (size_t)n;
525 sdb_proto_unmarshal_int32(str, len, &rlen);
527 if (! rlen)
528 break;
530 req = (size_t)rlen;
531 total = 0;
532 }
533 else /* finished reading data */
534 break;
535 }
537 if (total != req) {
538 /* unexpected EOF; clear partially read data */
539 sdb_strbuf_skip(buf, data_offset, sdb_strbuf_len(buf));
540 return 0;
541 }
543 if (rstatus != UINT32_MAX)
544 /* remove status,len */
545 sdb_strbuf_skip(buf, data_offset, 2 * sizeof(rstatus));
547 if (code)
548 *code = rstatus;
550 return (ssize_t)total;
551 } /* sdb_client_recv */
553 bool
554 sdb_client_eof(sdb_client_t *client)
555 {
556 if ((! client) || (client->fd < 0))
557 return 1;
558 return client->eof;
559 } /* sdb_client_eof */
561 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */