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 "client/sock.h"
33 #include "utils/error.h"
34 #include "utils/strbuf.h"
35 #include "utils/proto.h"
36 #include "utils/os.h"
38 #include <arpa/inet.h>
40 #include <assert.h>
41 #include <errno.h>
42 #include <limits.h>
44 #include <stdlib.h>
46 #include <string.h>
47 #include <strings.h>
49 #include <unistd.h>
51 #include <sys/socket.h>
52 #include <sys/un.h>
54 #include <netdb.h>
56 /*
57 * private data types
58 */
60 struct sdb_client {
61 char *address;
62 int fd;
63 bool eof;
65 ssize_t (*read)(sdb_client_t *, sdb_strbuf_t *, size_t);
66 ssize_t (*write)(sdb_client_t *, const void *, size_t);
67 };
69 /*
70 * private helper functions
71 */
73 static ssize_t
74 client_read(sdb_client_t *client, sdb_strbuf_t *buf, size_t n)
75 {
76 return sdb_strbuf_read(buf, client->fd, n);
77 } /* client_read */
79 static ssize_t
80 client_write(sdb_client_t *client, const void *buf, size_t n)
81 {
82 return sdb_write(client->fd, n, buf);
83 } /* client_write */
85 static int
86 connect_unixsock(sdb_client_t *client, const char *address)
87 {
88 struct sockaddr_un sa;
90 client->fd = socket(AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
91 if (client->fd < 0) {
92 char errbuf[1024];
93 sdb_log(SDB_LOG_ERR, "Failed to open socket: %s",
94 sdb_strerror(errno, errbuf, sizeof(errbuf)));
95 return -1;
96 }
98 sa.sun_family = AF_UNIX;
99 strncpy(sa.sun_path, address, sizeof(sa.sun_path));
100 sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
102 if (connect(client->fd, (struct sockaddr *)&sa, sizeof(sa))) {
103 char errbuf[1024];
104 sdb_client_close(client);
105 sdb_log(SDB_LOG_ERR, "Failed to connect to '%s': %s",
106 sa.sun_path, sdb_strerror(errno, errbuf, sizeof(errbuf)));
107 return -1;
108 }
109 return client->fd;
110 } /* connect_unixsock */
112 static int
113 connect_tcp(sdb_client_t *client, const char *address)
114 {
115 struct addrinfo *ai, *ai_list = NULL;
116 int status;
118 if ((status = sdb_resolve(SDB_NET_TCP, address, &ai_list))) {
119 sdb_log(SDB_LOG_ERR, "Failed to resolve '%s': %s",
120 address, gai_strerror(status));
121 return -1;
122 }
124 for (ai = ai_list; ai != NULL; ai = ai->ai_next) {
125 client->fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
126 if (client->fd < 0) {
127 char errbuf[1024];
128 sdb_log(SDB_LOG_ERR, "Failed to open socket: %s",
129 sdb_strerror(errno, errbuf, sizeof(errbuf)));
130 continue;
131 }
133 if (connect(client->fd, ai->ai_addr, ai->ai_addrlen)) {
134 char host[1024], port[32], errbuf[1024];
135 sdb_client_close(client);
136 getnameinfo(ai->ai_addr, ai->ai_addrlen, host, sizeof(host),
137 port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV);
138 sdb_log(SDB_LOG_ERR, "Failed to connect to '%s:%s': %s",
139 host, port, sdb_strerror(errno, errbuf, sizeof(errbuf)));
140 continue;
141 }
142 break;
143 }
144 freeaddrinfo(ai_list);
145 return client->fd;
146 } /* connect_tcp */
148 /*
149 * public API
150 */
152 sdb_client_t *
153 sdb_client_create(const char *address)
154 {
155 sdb_client_t *client;
157 if (! address)
158 return NULL;
160 client = malloc(sizeof(*client));
161 if (! client) {
162 sdb_log(SDB_LOG_ERR, "Out of memory");
163 return NULL;
164 }
165 memset(client, 0, sizeof(*client));
166 client->fd = -1;
167 client->eof = 1;
169 client->read = client_read;
170 client->write = client_write;
172 client->address = strdup(address);
173 if (! client->address) {
174 sdb_client_destroy(client);
175 sdb_log(SDB_LOG_ERR, "Out of memory");
176 return NULL;
177 }
179 return client;
180 } /* sdb_client_create */
182 void
183 sdb_client_destroy(sdb_client_t *client)
184 {
185 if (! client)
186 return;
188 sdb_client_close(client);
190 if (client->address)
191 free(client->address);
192 client->address = NULL;
194 free(client);
195 } /* sdb_client_destroy */
197 int
198 sdb_client_connect(sdb_client_t *client, const char *username)
199 {
200 sdb_strbuf_t *buf;
201 ssize_t status;
202 uint32_t rstatus;
204 if ((! client) || (! client->address))
205 return -1;
207 if (client->fd >= 0)
208 return -1;
210 if (*client->address == '/')
211 connect_unixsock(client, client->address);
212 else if (!strncasecmp(client->address, "unix:", strlen("unix:")))
213 connect_unixsock(client, client->address + strlen("unix:"));
214 else if (!strncasecmp(client->address, "tcp:", strlen("tcp:")))
215 connect_tcp(client, client->address + strlen("tcp:"));
216 else
217 connect_tcp(client, client->address);
219 if (client->fd < 0)
220 return -1;
221 client->eof = 0;
223 /* XXX */
224 if (! username)
225 username = "";
227 status = sdb_client_send(client, SDB_CONNECTION_STARTUP,
228 (uint32_t)strlen(username), username);
229 if (status < 0) {
230 char errbuf[1024];
231 sdb_client_close(client);
232 sdb_log(SDB_LOG_ERR, "Failed to send STARTUP message to server: %s",
233 sdb_strerror(errno, errbuf, sizeof(errbuf)));
234 return (int)status;
235 }
237 buf = sdb_strbuf_create(64);
238 rstatus = 0;
239 status = sdb_client_recv(client, &rstatus, buf);
240 if ((status > 0) && (rstatus == SDB_CONNECTION_OK)) {
241 sdb_strbuf_destroy(buf);
242 return 0;
243 }
245 if (status < 0) {
246 char errbuf[1024];
247 sdb_log(SDB_LOG_ERR, "Failed to receive server response: %s",
248 sdb_strerror(errno, errbuf, sizeof(errbuf)));
249 }
250 else if (client->eof)
251 sdb_log(SDB_LOG_ERR, "Encountered end-of-file while waiting "
252 "for server response");
254 if (rstatus == SDB_CONNECTION_ERROR) {
255 sdb_log(SDB_LOG_ERR, "Access denied for user '%s': %s",
256 username, sdb_strbuf_string(buf));
257 status = -((int)rstatus);
258 }
259 else if (rstatus != SDB_CONNECTION_OK) {
260 sdb_log(SDB_LOG_ERR, "Received unsupported authentication request "
261 "(status %d) during startup", (int)rstatus);
262 status = -((int)rstatus);
263 }
265 sdb_client_close(client);
266 sdb_strbuf_destroy(buf);
267 return (int)status;
268 } /* sdb_client_connect */
270 int
271 sdb_client_sockfd(sdb_client_t *client)
272 {
273 if (! client)
274 return -1;
275 return client->fd;
276 } /* sdb_client_sockfd */
278 int
279 sdb_client_shutdown(sdb_client_t *client, int how)
280 {
281 if (! client) {
282 errno = ENOTSOCK;
283 return -1;
284 }
286 if (client->fd < 0) {
287 errno = EBADF;
288 return -1;
289 }
291 return shutdown(client->fd, how);
292 } /* sdb_client_shutdown */
294 void
295 sdb_client_close(sdb_client_t *client)
296 {
297 if (! client)
298 return;
300 close(client->fd);
301 client->fd = -1;
302 client->eof = 1;
303 } /* sdb_client_close */
305 ssize_t
306 sdb_client_rpc(sdb_client_t *client,
307 uint32_t cmd, uint32_t msg_len, const char *msg,
308 uint32_t *code, sdb_strbuf_t *buf)
309 {
310 uint32_t rcode = 0;
311 ssize_t status;
313 if (! buf)
314 return -1;
316 if (sdb_client_send(client, cmd, msg_len, msg) < 0) {
317 char errbuf[1024];
318 sdb_strbuf_sprintf(buf, "Failed to send %s message to server: %s",
319 SDB_CONN_MSGTYPE_TO_STRING(cmd),
320 sdb_strerror(errno, errbuf, sizeof(errbuf)));
321 if (code)
322 *code = SDB_CONNECTION_ERROR;
323 return -1;
324 }
326 while (42) {
327 size_t offset = sdb_strbuf_len(buf);
329 status = sdb_client_recv(client, &rcode, buf);
330 if (status < 0) {
331 char errbuf[1024];
332 sdb_strbuf_sprintf(buf, "Failed to receive server response: %s",
333 sdb_strerror(errno, errbuf, sizeof(errbuf)));
334 if (code)
335 *code = SDB_CONNECTION_ERROR;
336 return status;
337 }
339 if (rcode == SDB_CONNECTION_LOG) {
340 uint32_t prio = 0;
341 if (sdb_proto_unmarshal_int32(SDB_STRBUF_STR(buf), &prio) < 0) {
342 sdb_log(SDB_LOG_WARNING, "Received a LOG message "
343 "with invalid or missing priority");
344 prio = (uint32_t)SDB_LOG_ERR;
345 }
346 sdb_log((int)prio, "%s", sdb_strbuf_string(buf) + offset);
347 sdb_strbuf_skip(buf, offset, sdb_strbuf_len(buf) - offset);
348 continue;
349 }
350 break;
351 }
353 if (code)
354 *code = rcode;
355 return status;
356 } /* sdb_client_rpc */
358 ssize_t
359 sdb_client_send(sdb_client_t *client,
360 uint32_t cmd, uint32_t msg_len, const char *msg)
361 {
362 char buf[2 * sizeof(uint32_t) + msg_len];
364 if ((! client) || (! client->fd))
365 return -1;
366 if (sdb_proto_marshal(buf, sizeof(buf), cmd, msg_len, msg) < 0)
367 return -1;
369 return client->write(client, buf, sizeof(buf));
370 } /* sdb_client_send */
372 ssize_t
373 sdb_client_recv(sdb_client_t *client,
374 uint32_t *code, sdb_strbuf_t *buf)
375 {
376 uint32_t rstatus = UINT32_MAX;
377 uint32_t rlen = UINT32_MAX;
379 size_t total = 0;
380 size_t req = 2 * sizeof(uint32_t);
382 size_t data_offset = sdb_strbuf_len(buf);
384 if (code)
385 *code = UINT32_MAX;
387 if ((! client) || (! client->fd) || (! buf)) {
388 errno = EBADF;
389 return -1;
390 }
392 while (42) {
393 ssize_t status;
395 if (sdb_select(client->fd, SDB_SELECTIN))
396 return -1;
398 errno = 0;
399 status = client->read(client, buf, req);
400 if (status < 0) {
401 if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
402 continue;
403 return status;
404 }
405 else if (! status) {
406 client->eof = 1;
407 break;
408 }
410 total += (size_t)status;
412 if (total != req)
413 continue;
415 if (rstatus == UINT32_MAX) {
416 const char *str = sdb_strbuf_string(buf) + data_offset;
417 size_t len = sdb_strbuf_len(buf) - data_offset;
418 ssize_t n;
420 /* retrieve status and data len */
421 assert(len >= 2 * sizeof(uint32_t));
422 n = sdb_proto_unmarshal_int32(str, len, &rstatus);
423 str += n; len -= (size_t)n;
424 sdb_proto_unmarshal_int32(str, len, &rlen);
426 if (! rlen)
427 break;
429 req = (size_t)rlen;
430 total = 0;
431 }
432 else /* finished reading data */
433 break;
434 }
436 if (total != req) {
437 /* unexpected EOF; clear partially read data */
438 sdb_strbuf_skip(buf, data_offset, sdb_strbuf_len(buf));
439 return 0;
440 }
442 if (rstatus != UINT32_MAX)
443 /* remove status,len */
444 sdb_strbuf_skip(buf, data_offset, 2 * sizeof(rstatus));
446 if (code)
447 *code = rstatus;
449 return (ssize_t)total;
450 } /* sdb_client_recv */
452 bool
453 sdb_client_eof(sdb_client_t *client)
454 {
455 if ((! client) || (client->fd < 0))
456 return 1;
457 return client->eof;
458 } /* sdb_client_eof */
460 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */