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 /*
55 * private data types
56 */
58 struct sdb_client {
59 char *address;
60 int fd;
61 bool eof;
63 ssize_t (*read)(sdb_client_t *, sdb_strbuf_t *, size_t);
64 ssize_t (*write)(sdb_client_t *, const void *, size_t);
65 };
67 /*
68 * private helper functions
69 */
71 static ssize_t
72 client_read(sdb_client_t *client, sdb_strbuf_t *buf, size_t n)
73 {
74 return sdb_strbuf_read(buf, client->fd, n);
75 } /* client_read */
77 static ssize_t
78 client_write(sdb_client_t *client, const void *buf, size_t n)
79 {
80 return sdb_write(client->fd, n, buf);
81 } /* client_write */
83 static int
84 connect_unixsock(sdb_client_t *client, const char *address)
85 {
86 struct sockaddr_un sa;
88 client->fd = socket(AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
89 if (client->fd < 0) {
90 char errbuf[1024];
91 sdb_log(SDB_LOG_ERR, "Failed to open socket: %s",
92 sdb_strerror(errno, errbuf, sizeof(errbuf)));
93 return -1;
94 }
96 sa.sun_family = AF_UNIX;
97 strncpy(sa.sun_path, address, sizeof(sa.sun_path));
98 sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
100 if (connect(client->fd, (struct sockaddr *)&sa, sizeof(sa))) {
101 char errbuf[1024];
102 sdb_client_close(client);
103 sdb_log(SDB_LOG_ERR, "Failed to connect to '%s': %s",
104 sa.sun_path, sdb_strerror(errno, errbuf, sizeof(errbuf)));
105 return -1;
106 }
107 return client->fd;
108 } /* connect_unixsock */
110 /*
111 * public API
112 */
114 sdb_client_t *
115 sdb_client_create(const char *address)
116 {
117 sdb_client_t *client;
119 if (! address)
120 return NULL;
122 client = malloc(sizeof(*client));
123 if (! client) {
124 sdb_log(SDB_LOG_ERR, "Out of memory");
125 return NULL;
126 }
127 memset(client, 0, sizeof(*client));
128 client->fd = -1;
129 client->eof = 1;
131 client->read = client_read;
132 client->write = client_write;
134 client->address = strdup(address);
135 if (! client->address) {
136 sdb_client_destroy(client);
137 sdb_log(SDB_LOG_ERR, "Out of memory");
138 return NULL;
139 }
141 return client;
142 } /* sdb_client_create */
144 void
145 sdb_client_destroy(sdb_client_t *client)
146 {
147 if (! client)
148 return;
150 sdb_client_close(client);
152 if (client->address)
153 free(client->address);
154 client->address = NULL;
156 free(client);
157 } /* sdb_client_destroy */
159 int
160 sdb_client_connect(sdb_client_t *client, const char *username)
161 {
162 sdb_strbuf_t *buf;
163 ssize_t status;
164 uint32_t rstatus;
166 if ((! client) || (! client->address))
167 return -1;
169 if (client->fd >= 0)
170 return -1;
172 if (!strncasecmp(client->address, "unix:", strlen("unix:")))
173 connect_unixsock(client, client->address + strlen("unix:"));
174 else if (*client->address == '/')
175 connect_unixsock(client, client->address);
176 else {
177 sdb_log(SDB_LOG_ERR, "Unknown address type: %s", client->address);
178 return -1;
179 }
181 if (client->fd < 0)
182 return -1;
183 client->eof = 0;
185 /* XXX */
186 if (! username)
187 username = "";
189 status = sdb_client_send(client, SDB_CONNECTION_STARTUP,
190 (uint32_t)strlen(username), username);
191 if (status < 0) {
192 char errbuf[1024];
193 sdb_client_close(client);
194 sdb_log(SDB_LOG_ERR, "Failed to send STARTUP message to server: %s",
195 sdb_strerror(errno, errbuf, sizeof(errbuf)));
196 return (int)status;
197 }
199 buf = sdb_strbuf_create(64);
200 rstatus = 0;
201 status = sdb_client_recv(client, &rstatus, buf);
202 if ((status > 0) && (rstatus == SDB_CONNECTION_OK)) {
203 sdb_strbuf_destroy(buf);
204 return 0;
205 }
207 if (status < 0) {
208 char errbuf[1024];
209 sdb_log(SDB_LOG_ERR, "Failed to receive server response: %s",
210 sdb_strerror(errno, errbuf, sizeof(errbuf)));
211 }
212 else if (client->eof)
213 sdb_log(SDB_LOG_ERR, "Encountered end-of-file while waiting "
214 "for server response");
216 if (rstatus == SDB_CONNECTION_ERROR) {
217 sdb_log(SDB_LOG_ERR, "Access denied for user '%s': %s",
218 username, sdb_strbuf_string(buf));
219 status = -((int)rstatus);
220 }
221 else if (rstatus != SDB_CONNECTION_OK) {
222 sdb_log(SDB_LOG_ERR, "Received unsupported authentication request "
223 "(status %d) during startup", (int)rstatus);
224 status = -((int)rstatus);
225 }
227 sdb_client_close(client);
228 sdb_strbuf_destroy(buf);
229 return (int)status;
230 } /* sdb_client_connect */
232 int
233 sdb_client_sockfd(sdb_client_t *client)
234 {
235 if (! client)
236 return -1;
237 return client->fd;
238 } /* sdb_client_sockfd */
240 int
241 sdb_client_shutdown(sdb_client_t *client, int how)
242 {
243 if (! client) {
244 errno = ENOTSOCK;
245 return -1;
246 }
248 if (client->fd < 0) {
249 errno = EBADF;
250 return -1;
251 }
253 return shutdown(client->fd, how);
254 } /* sdb_client_shutdown */
256 void
257 sdb_client_close(sdb_client_t *client)
258 {
259 if (! client)
260 return;
262 close(client->fd);
263 client->fd = -1;
264 client->eof = 1;
265 } /* sdb_client_close */
267 ssize_t
268 sdb_client_send(sdb_client_t *client,
269 uint32_t cmd, uint32_t msg_len, const char *msg)
270 {
271 char buf[2 * sizeof(uint32_t) + msg_len];
273 if ((! client) || (! client->fd))
274 return -1;
275 if (sdb_proto_marshal(buf, sizeof(buf), cmd, msg_len, msg) < 0)
276 return -1;
278 return client->write(client, buf, sizeof(buf));
279 } /* sdb_client_send */
281 ssize_t
282 sdb_client_recv(sdb_client_t *client,
283 uint32_t *code, sdb_strbuf_t *buf)
284 {
285 uint32_t rstatus = UINT32_MAX;
286 uint32_t rlen = UINT32_MAX;
288 size_t total = 0;
289 size_t req = 2 * sizeof(uint32_t);
291 size_t data_offset = sdb_strbuf_len(buf);
293 if (code)
294 *code = UINT32_MAX;
296 if ((! client) || (! client->fd) || (! buf)) {
297 errno = EBADF;
298 return -1;
299 }
301 while (42) {
302 ssize_t status;
304 if (sdb_select(client->fd, SDB_SELECTIN))
305 return -1;
307 errno = 0;
308 status = client->read(client, buf, req);
309 if (status < 0) {
310 if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
311 continue;
312 return status;
313 }
314 else if (! status) {
315 client->eof = 1;
316 break;
317 }
319 total += (size_t)status;
321 if (total != req)
322 continue;
324 if (rstatus == UINT32_MAX) {
325 const char *str = sdb_strbuf_string(buf) + data_offset;
326 size_t len = sdb_strbuf_len(buf) - data_offset;
327 ssize_t n;
329 /* retrieve status and data len */
330 assert(len >= 2 * sizeof(uint32_t));
331 n = sdb_proto_unmarshal_int32(str, len, &rstatus);
332 str += n; len -= (size_t)n;
333 sdb_proto_unmarshal_int32(str, len, &rlen);
335 if (! rlen)
336 break;
338 req = (size_t)rlen;
339 total = 0;
340 }
341 else /* finished reading data */
342 break;
343 }
345 if (total != req) {
346 /* unexpected EOF; clear partially read data */
347 sdb_strbuf_skip(buf, data_offset, sdb_strbuf_len(buf));
348 return 0;
349 }
351 if (rstatus != UINT32_MAX)
352 /* remove status,len */
353 sdb_strbuf_skip(buf, data_offset, 2 * sizeof(rstatus));
355 if (code)
356 *code = rstatus;
358 return (ssize_t)total;
359 } /* sdb_client_recv */
361 bool
362 sdb_client_eof(sdb_client_t *client)
363 {
364 if ((! client) || (client->fd < 0))
365 return 1;
366 return client->eof;
367 } /* sdb_client_eof */
369 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */