chiark / gitweb /
log: more general error message formatting
[disorder] / lib / client.c
CommitLineData
460b9539 1/*
2 * This file is part of DisOrder.
cca89d7c 3 * Copyright (C) 2004-13 Richard Kettlewell
460b9539 4 *
e7eb3a27 5 * This program is free software: you can redistribute it and/or modify
460b9539 6 * it under the terms of the GNU General Public License as published by
e7eb3a27 7 * the Free Software Foundation, either version 3 of the License, or
460b9539 8 * (at your option) any later version.
e7eb3a27
RK
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
460b9539 15 * You should have received a copy of the GNU General Public License
e7eb3a27 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
460b9539 17 */
c4e2cfdd
RK
18/** @file lib/client.c
19 * @brief Simple C client
20 *
158d0961 21 * See @ref lib/eclient.c for an asynchronous-capable client
c4e2cfdd
RK
22 * implementation.
23 */
460b9539 24
05b75f8d 25#include "common.h"
460b9539 26
27#include <sys/types.h>
cca89d7c
RK
28#if HAVE_SYS_SOCKET_H
29# include <sys/socket.h>
30#endif
31#if HAVE_NETINET_IN_H
32# include <netinet/in.h>
33#endif
34#if HAVE_SYS_UN_H
35# include <sys/un.h>
36#endif
37#if HAVE_UNISTD_H
38# include <unistd.h>
39#endif
460b9539 40#include <errno.h>
cca89d7c
RK
41#if HAVE_NETDB_H
42# include <netdb.h>
43#endif
44#if HAVE_PCRE_H
45# include <pcre.h>
46#endif
460b9539 47
48#include "log.h"
49#include "mem.h"
460b9539 50#include "queue.h"
51#include "client.h"
52#include "charset.h"
53#include "hex.h"
54#include "split.h"
55#include "vector.h"
56#include "inputline.h"
57#include "kvp.h"
58#include "syscalls.h"
59#include "printf.h"
60#include "sink.h"
61#include "addr.h"
62#include "authhash.h"
63#include "client-common.h"
5df73aeb 64#include "rights.h"
758aa6c3 65#include "kvp.h"
460b9539 66
0590cedc 67/** @brief Client handle contents */
460b9539 68struct disorder_client {
0590cedc
RK
69 /** @brief Stream to read from */
70 FILE *fpin;
71 /** @brief Stream to write to */
72 FILE *fpout;
73 /** @brief Peer description */
460b9539 74 char *ident;
0590cedc 75 /** @brief Username */
460b9539 76 char *user;
0590cedc 77 /** @brief Report errors to @c stderr */
460b9539 78 int verbose;
0590cedc 79 /** @brief Last error string */
e9e8a16d 80 const char *last;
96af1d7e
RK
81 /** @brief Address family */
82 int family;
460b9539 83};
84
c4e2cfdd
RK
85/** @brief Create a new client
86 * @param verbose If nonzero, write extra junk to stderr
87 * @return Pointer to new client
88 *
fdf98378 89 * You must call disorder_connect(), disorder_connect_user() or
90 * disorder_connect_cookie() to connect it. Use disorder_close() to
91 * dispose of the client when finished with it.
c4e2cfdd 92 */
460b9539 93disorder_client *disorder_new(int verbose) {
94 disorder_client *c = xmalloc(sizeof (struct disorder_client));
95
96 c->verbose = verbose;
96af1d7e 97 c->family = -1;
460b9539 98 return c;
99}
100
96af1d7e
RK
101/** @brief Return the address family used by this client */
102int disorder_client_af(disorder_client *c) {
103 return c->family;
104}
105
c4e2cfdd
RK
106/** @brief Read a response line
107 * @param c Client
108 * @param rp Where to store response, or NULL (UTF-8)
109 * @return Response code 0-999 or -1 on error
110 */
460b9539 111static int response(disorder_client *c, char **rp) {
112 char *r;
113
e9e8a16d
RK
114 if(inputline(c->ident, c->fpin, &r, '\n')) {
115 byte_xasprintf((char **)&c->last, "input error: %s", strerror(errno));
460b9539 116 return -1;
e9e8a16d 117 }
460b9539 118 D(("response: %s", r));
119 if(rp)
120 *rp = r;
121 if(r[0] >= '0' && r[0] <= '9'
122 && r[1] >= '0' && r[1] <= '9'
123 && r[2] >= '0' && r[2] <= '9'
bb037be2 124 && r[3] == ' ') {
125 c->last = r + 4;
460b9539 126 return (r[0] * 10 + r[1]) * 10 + r[2] - 111 * '0';
bb037be2 127 } else {
e9e8a16d 128 c->last = "invalid reply format";
2e9ba080 129 disorder_error(0, "invalid reply format from %s", c->ident);
460b9539 130 return -1;
131 }
132}
133
bb037be2 134/** @brief Return last response string
135 * @param c Client
136 * @return Last response string (UTF-8, English) or NULL
137 */
138const char *disorder_last(disorder_client *c) {
139 return c->last;
140}
141
c4e2cfdd
RK
142/** @brief Read and partially parse a response
143 * @param c Client
144 * @param rp Where to store response text (or NULL) (UTF-8)
145 * @return 0 on success, non-0 on error
146 *
147 * 5xx responses count as errors.
148 *
149 * @p rp will NOT be filled in for xx9 responses (where it is just
150 * commentary for a command where it would normally be meaningful).
151 *
152 * NB that the response will NOT be converted to the local encoding.
460b9539 153 */
154static int check_response(disorder_client *c, char **rp) {
155 int rc;
156 char *r;
157
158 if((rc = response(c, &r)) == -1)
159 return -1;
160 else if(rc / 100 == 2) {
161 if(rp)
162 *rp = (rc % 10 == 9) ? 0 : xstrdup(r + 4);
f74f4f32 163 xfree(r);
460b9539 164 return 0;
165 } else {
166 if(c->verbose)
2e9ba080 167 disorder_error(0, "from %s: %s", c->ident, utf82mb(r));
f74f4f32 168 xfree(r);
2bead829 169 return rc;
460b9539 170 }
171}
172
c4e2cfdd
RK
173/** @brief Issue a command and parse a simple response
174 * @param c Client
175 * @param rp Where to store result, or NULL
176 * @param cmd Command
177 * @param ap Arguments (UTF-8), terminated by (char *)0
178 * @return 0 on success, non-0 on error
179 *
180 * 5xx responses count as errors.
181 *
182 * @p rp will NOT be filled in for xx9 responses (where it is just
183 * commentary for a command where it would normally be meaningful).
184 *
185 * NB that the response will NOT be converted to the local encoding
186 * nor will quotes be stripped. See dequote().
39c53343 187 *
ad131c25 188 * Put @ref disorder__body in the argument list followed by a char **
08af2413 189 * and int giving the body to follow the command. If the int is @c -1
0bc1d67c
RK
190 * then the list is assumed to be NULL-terminated. This may be used
191 * only once.
192 *
ad131c25 193 * Put @ref disorder__list in the argument list followed by a char **
0bc1d67c
RK
194 * and int giving a list of arguments to include. If the int is @c -1
195 * then the list is assumed to be NULL-terminated. This may be used
196 * any number of times.
39c53343 197 *
bcb2af72
RK
198 * Put @ref disorder__integer in the argument list followed by a long to
199 * send its value in decimal. This may be used any number of times.
200 *
201 * Put @ref disorder__time in the argument list followed by a time_t
202 * to send its value in decimal. This may be used any number of
203 * times.
204 *
39c53343
RK
205 * Usually you would call this via one of the following interfaces:
206 * - disorder_simple()
c4e2cfdd 207 */
460b9539 208static int disorder_simple_v(disorder_client *c,
209 char **rp,
39c53343 210 const char *cmd,
39c53343 211 va_list ap) {
460b9539 212 const char *arg;
213 struct dynstr d;
08af2413
RK
214 char **body = NULL;
215 int nbody = 0;
216 int has_body = 0;
460b9539 217
62ef2216 218 if(!c->fpout) {
e9e8a16d 219 c->last = "not connected";
2e9ba080 220 disorder_error(0, "not connected to server");
62ef2216 221 return -1;
222 }
460b9539 223 if(cmd) {
224 dynstr_init(&d);
225 dynstr_append_string(&d, cmd);
226 while((arg = va_arg(ap, const char *))) {
ad131c25 227 if(arg == disorder__body) {
08af2413
RK
228 body = va_arg(ap, char **);
229 nbody = va_arg(ap, int);
230 has_body = 1;
ad131c25 231 } else if(arg == disorder__list) {
0bc1d67c
RK
232 char **list = va_arg(ap, char **);
233 int nlist = va_arg(ap, int);
234 if(nlist < 0) {
235 for(nlist = 0; list[nlist]; ++nlist)
236 ;
237 }
238 for(int n = 0; n < nlist; ++n) {
239 dynstr_append(&d, ' ');
240 dynstr_append_string(&d, quoteutf8(arg));
241 }
bcb2af72
RK
242 } else if(arg == disorder__integer) {
243 long n = va_arg(ap, long);
244 char buffer[16];
245 snprintf(buffer, sizeof buffer, "%ld", n);
246 dynstr_append(&d, ' ');
247 dynstr_append_string(&d, buffer);
248 } else if(arg == disorder__time) {
249 time_t n = va_arg(ap, time_t);
250 char buffer[16];
251 snprintf(buffer, sizeof buffer, "%lld", (long long)n);
252 dynstr_append(&d, ' ');
253 dynstr_append_string(&d, buffer);
08af2413
RK
254 } else {
255 dynstr_append(&d, ' ');
256 dynstr_append_string(&d, quoteutf8(arg));
257 }
460b9539 258 }
259 dynstr_append(&d, '\n');
260 dynstr_terminate(&d);
261 D(("command: %s", d.vec));
39c53343
RK
262 if(fputs(d.vec, c->fpout) < 0)
263 goto write_error;
f74f4f32 264 xfree(d.vec);
08af2413 265 if(has_body) {
39c53343
RK
266 if(nbody < 0)
267 for(nbody = 0; body[nbody]; ++nbody)
268 ;
269 for(int n = 0; n < nbody; ++n) {
270 if(body[n][0] == '.')
271 if(fputc('.', c->fpout) < 0)
272 goto write_error;
273 if(fputs(body[n], c->fpout) < 0)
274 goto write_error;
275 if(fputc('\n', c->fpout) < 0)
276 goto write_error;
277 }
278 if(fputs(".\n", c->fpout) < 0)
279 goto write_error;
460b9539 280 }
39c53343
RK
281 if(fflush(c->fpout))
282 goto write_error;
460b9539 283 }
284 return check_response(c, rp);
39c53343
RK
285write_error:
286 byte_xasprintf((char **)&c->last, "write error: %s", strerror(errno));
2e9ba080 287 disorder_error(errno, "error writing to %s", c->ident);
39c53343 288 return -1;
460b9539 289}
290
c4e2cfdd
RK
291/** @brief Issue a command and parse a simple response
292 * @param c Client
293 * @param rp Where to store result, or NULL (UTF-8)
294 * @param cmd Command
295 * @return 0 on success, non-0 on error
296 *
297 * The remaining arguments are command arguments, terminated by (char
298 * *)0. They should be in UTF-8.
299 *
300 * 5xx responses count as errors.
301 *
302 * @p rp will NOT be filled in for xx9 responses (where it is just
303 * commentary for a command where it would normally be meaningful).
304 *
305 * NB that the response will NOT be converted to the local encoding
306 * nor will quotes be stripped. See dequote().
460b9539 307 */
308static int disorder_simple(disorder_client *c,
309 char **rp,
310 const char *cmd, ...) {
311 va_list ap;
312 int ret;
313
314 va_start(ap, cmd);
08af2413 315 ret = disorder_simple_v(c, rp, cmd, ap);
460b9539 316 va_end(ap);
317 return ret;
318}
319
dab87ecc
RK
320/** @brief Issue a command and split the response
321 * @param c Client
322 * @param vecp Where to store results
323 * @param nvecp Where to store count of results
324 * @param expected Expected count (or -1 to not check)
325 * @param cmd Command
326 * @return 0 on success, non-0 on error
327 *
328 * The remaining arguments are command arguments, terminated by (char
329 * *)0. They should be in UTF-8.
330 *
331 * 5xx responses count as errors.
332 *
333 * @p rp will NOT be filled in for xx9 responses (where it is just
334 * commentary for a command where it would normally be meaningful).
335 *
336 * NB that the response will NOT be converted to the local encoding
337 * nor will quotes be stripped. See dequote().
338 */
339static int disorder_simple_split(disorder_client *c,
340 char ***vecp,
341 int *nvecp,
342 int expected,
343 const char *cmd, ...) {
344 va_list ap;
345 int ret;
346 char *r;
347 char **vec;
348 int nvec;
349
350 va_start(ap, cmd);
351 ret = disorder_simple_v(c, &r, cmd, ap);
352 va_end(ap);
353 if(!ret) {
354 vec = split(r, &nvec, SPLIT_QUOTES, 0, 0);
355 xfree(r);
356 if(expected < 0 || nvec == expected) {
357 *vecp = vec;
358 *nvecp = nvec;
359 } else {
360 disorder_error(0, "malformed reply to %s", cmd);
361 c->last = "malformed reply";
362 ret = -1;
363 free_strings(nvec, vec);
364 }
365 }
366 if(ret) {
367 *vecp = NULL;
368 *nvecp = 0;
369 }
370 return ret;
371}
372
0227f67d
RK
373/** @brief Dequote a result string
374 * @param rc 0 on success, non-0 on error
375 * @param rp Where result string is stored (UTF-8)
376 * @return @p rc
377 *
378 * This is used as a wrapper around disorder_simple() to dequote
379 * results in place.
380 */
381static int dequote(int rc, char **rp) {
382 char **rr;
2bead829 383
0227f67d
RK
384 if(!rc) {
385 if((rr = split(*rp, 0, SPLIT_QUOTES, 0, 0)) && *rr) {
f74f4f32 386 xfree(*rp);
0227f67d 387 *rp = *rr;
f74f4f32 388 xfree(rr);
0227f67d
RK
389 return 0;
390 }
2e9ba080 391 disorder_error(0, "invalid reply: %s", *rp);
460b9539 392 }
2bead829 393 return rc;
460b9539 394}
395
0227f67d 396/** @brief Generic connection routine
319d7107 397 * @param conf Configuration to follow
c4e2cfdd 398 * @param c Client
0227f67d
RK
399 * @param username Username to log in with or NULL
400 * @param password Password to log in with or NULL
401 * @param cookie Cookie to log in with or NULL
c4e2cfdd
RK
402 * @return 0 on success, non-0 on error
403 *
0227f67d
RK
404 * @p cookie is tried first if not NULL. If it is NULL then @p
405 * username must not be. If @p username is not NULL then nor may @p
406 * password be.
c4e2cfdd 407 */
319d7107
RK
408int disorder_connect_generic(struct config *conf,
409 disorder_client *c,
410 const char *username,
411 const char *password,
412 const char *cookie) {
f74f4f32
RK
413 int fd = -1, fd2 = -1, nrvec = 0, rc;
414 unsigned char *nonce = NULL;
460b9539 415 size_t nl;
f74f4f32
RK
416 char *res = NULL;
417 char *r = NULL, **rvec = NULL;
7b32e917 418 const char *protocol, *algorithm, *challenge;
f74f4f32 419 struct sockaddr *sa = NULL;
0227f67d 420 socklen_t salen;
460b9539 421
319d7107 422 if((salen = find_server(conf, &sa, &c->ident)) == (socklen_t)-1)
460b9539 423 return -1;
460b9539 424 c->fpin = c->fpout = 0;
425 if((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
e9e8a16d 426 byte_xasprintf((char **)&c->last, "socket: %s", strerror(errno));
2e9ba080 427 disorder_error(errno, "error calling socket");
460b9539 428 return -1;
429 }
96af1d7e 430 c->family = sa->sa_family;
0227f67d 431 if(connect(fd, sa, salen) < 0) {
e9e8a16d 432 byte_xasprintf((char **)&c->last, "connect: %s", strerror(errno));
2e9ba080 433 disorder_error(errno, "error calling connect");
460b9539 434 goto error;
435 }
436 if((fd2 = dup(fd)) < 0) {
e9e8a16d 437 byte_xasprintf((char **)&c->last, "dup: %s", strerror(errno));
2e9ba080 438 disorder_error(errno, "error calling dup");
460b9539 439 goto error;
440 }
441 if(!(c->fpin = fdopen(fd, "rb"))) {
e9e8a16d 442 byte_xasprintf((char **)&c->last, "fdopen: %s", strerror(errno));
2e9ba080 443 disorder_error(errno, "error calling fdopen");
460b9539 444 goto error;
445 }
446 fd = -1;
447 if(!(c->fpout = fdopen(fd2, "wb"))) {
e9e8a16d 448 byte_xasprintf((char **)&c->last, "fdopen: %s", strerror(errno));
2e9ba080 449 disorder_error(errno, "error calling fdopen");
460b9539 450 goto error;
451 }
452 fd2 = -1;
2bead829 453 if((rc = disorder_simple(c, &r, 0, (const char *)0)))
454 goto error_rc;
637fdea3 455 if(!(rvec = split(r, &nrvec, SPLIT_QUOTES, 0, 0)))
2bead829 456 goto error;
7b32e917 457 if(nrvec != 3) {
e9e8a16d 458 c->last = "cannot parse server greeting";
2e9ba080 459 disorder_error(0, "cannot parse server greeting %s", r);
2bead829 460 goto error;
637fdea3 461 }
f74f4f32 462 protocol = rvec[0];
7b32e917 463 if(strcmp(protocol, "2")) {
e9e8a16d 464 c->last = "unknown protocol version";
2e9ba080 465 disorder_error(0, "unknown protocol version: %s", protocol);
2bead829 466 goto error;
7b32e917 467 }
f74f4f32
RK
468 algorithm = rvec[1];
469 challenge = rvec[2];
7b32e917 470 if(!(nonce = unhex(challenge, &nl)))
2bead829 471 goto error;
0227f67d
RK
472 if(cookie) {
473 if(!dequote(disorder_simple(c, &c->user, "cookie", cookie, (char *)0),
474 &c->user))
475 return 0; /* success */
476 if(!username) {
e9e8a16d 477 c->last = "cookie failed and no username";
2e9ba080 478 disorder_error(0, "cookie did not work and no username available");
2bead829 479 goto error;
0227f67d
RK
480 }
481 }
e9e8a16d
RK
482 if(!(res = authhash(nonce, nl, password, algorithm))) {
483 c->last = "error computing authorization hash";
484 goto error;
485 }
2bead829 486 if((rc = disorder_simple(c, 0, "user", username, res, (char *)0)))
487 goto error_rc;
460b9539 488 c->user = xstrdup(username);
f74f4f32
RK
489 xfree(res);
490 free_strings(nrvec, rvec);
491 xfree(nonce);
492 xfree(sa);
493 xfree(r);
460b9539 494 return 0;
495error:
2bead829 496 rc = -1;
497error_rc:
498 if(c->fpin) {
499 fclose(c->fpin);
500 c->fpin = 0;
501 }
502 if(c->fpout) {
503 fclose(c->fpout);
504 c->fpout = 0;
505 }
460b9539 506 if(fd2 != -1) close(fd2);
507 if(fd != -1) close(fd);
2bead829 508 return rc;
460b9539 509}
510
fdf98378 511/** @brief Connect a client with a specified username and password
512 * @param c Client
513 * @param username Username to log in with
514 * @param password Password to log in with
515 * @return 0 on success, non-0 on error
516 */
517int disorder_connect_user(disorder_client *c,
518 const char *username,
519 const char *password) {
319d7107
RK
520 return disorder_connect_generic(config,
521 c,
fdf98378 522 username,
523 password,
524 0);
525}
526
0227f67d
RK
527/** @brief Connect a client
528 * @param c Client
529 * @return 0 on success, non-0 on error
530 *
531 * The connection will use the username and password found in @ref
532 * config, or directly from the database if no password is found and
533 * the database is readable (usually only for root).
534 */
535int disorder_connect(disorder_client *c) {
536 const char *username, *password;
537
538 if(!(username = config->username)) {
e9e8a16d 539 c->last = "no username";
2e9ba080 540 disorder_error(0, "no username configured");
0227f67d
RK
541 return -1;
542 }
543 password = config->password;
1be9101e
RK
544 /* If we're connecting as 'root' guess that we're the system root
545 * user (or the jukebox user), both of which can use the privileged
546 * socket. They can also furtle with the db directly: that is why
547 * privileged socket does not represent a privilege escalation. */
548 if(!password
549 && !strcmp(username, "root"))
550 password = "anything will do for root";
0227f67d
RK
551 if(!password) {
552 /* Oh well */
e9e8a16d 553 c->last = "no password";
2e9ba080 554 disorder_error(0, "no password configured for user '%s'", username);
0227f67d
RK
555 return -1;
556 }
319d7107
RK
557 return disorder_connect_generic(config,
558 c,
0227f67d
RK
559 username,
560 password,
561 0);
562}
563
564/** @brief Connect a client
565 * @param c Client
566 * @param cookie Cookie to log in with, or NULL
567 * @return 0 on success, non-0 on error
568 *
569 * If @p cookie is NULL or does not work then we attempt to log in as
570 * guest instead (so when the cookie expires only an extra round trip
571 * is needed rathre than a complete new login).
572 */
573int disorder_connect_cookie(disorder_client *c,
574 const char *cookie) {
319d7107
RK
575 return disorder_connect_generic(config,
576 c,
0227f67d
RK
577 "guest",
578 "",
579 cookie);
580}
581
c4e2cfdd
RK
582/** @brief Close a client
583 * @param c Client
584 * @return 0 on succcess, non-0 on errior
585 *
586 * The client is still closed even on error. It might well be
587 * appropriate to ignore the return value.
588 */
460b9539 589int disorder_close(disorder_client *c) {
590 int ret = 0;
591
592 if(c->fpin) {
593 if(fclose(c->fpin) < 0) {
e9e8a16d 594 byte_xasprintf((char **)&c->last, "fclose: %s", strerror(errno));
2e9ba080 595 disorder_error(errno, "error calling fclose");
460b9539 596 ret = -1;
597 }
598 c->fpin = 0;
599 }
600 if(c->fpout) {
601 if(fclose(c->fpout) < 0) {
e9e8a16d 602 byte_xasprintf((char **)&c->last, "fclose: %s", strerror(errno));
2e9ba080 603 disorder_error(errno, "error calling fclose");
460b9539 604 ret = -1;
605 }
606 c->fpout = 0;
607 }
f74f4f32 608 xfree(c->ident);
0227f67d 609 c->ident = 0;
f74f4f32 610 xfree(c->user);
0227f67d 611 c->user = 0;
375d9478 612 return ret;
460b9539 613}
614
460b9539 615static void client_error(const char *msg,
616 void attribute((unused)) *u) {
2e9ba080 617 disorder_error(0, "error parsing reply: %s", msg);
460b9539 618}
619
ec9c0462 620/** @brief Get a single queue entry
c4e2cfdd 621 * @param c Client
ec9c0462 622 * @param cmd Command
c4e2cfdd
RK
623 * @param qp Where to store track information
624 * @return 0 on success, non-0 on error
c4e2cfdd 625 */
ec9c0462
RK
626static int onequeue(disorder_client *c, const char *cmd,
627 struct queue_entry **qp) {
460b9539 628 char *r;
629 struct queue_entry *q;
2bead829 630 int rc;
460b9539 631
ec9c0462 632 if((rc = disorder_simple(c, &r, cmd, (char *)0)))
2bead829 633 return rc;
460b9539 634 if(r) {
635 q = xmalloc(sizeof *q);
636 if(queue_unmarshall(q, r, client_error, 0))
637 return -1;
638 *qp = q;
639 } else
640 *qp = 0;
641 return 0;
642}
643
0590cedc 644/** @brief Fetch the queue, recent list, etc */
c12575c6
RK
645static int readqueue(disorder_client *c,
646 struct queue_entry **qp) {
460b9539 647 struct queue_entry *qh, **qt = &qh, *q;
648 char *l;
649
460b9539 650 while(inputline(c->ident, c->fpin, &l, '\n') >= 0) {
651 if(!strcmp(l, ".")) {
652 *qt = 0;
653 *qp = qh;
b251ac34 654 xfree(l);
460b9539 655 return 0;
656 }
657 q = xmalloc(sizeof *q);
658 if(!queue_unmarshall(q, l, client_error, 0)) {
659 *qt = q;
660 qt = &q->next;
661 }
b251ac34 662 xfree(l);
460b9539 663 }
e9e8a16d
RK
664 if(ferror(c->fpin)) {
665 byte_xasprintf((char **)&c->last, "input error: %s", strerror(errno));
2e9ba080 666 disorder_error(errno, "error reading %s", c->ident);
e9e8a16d 667 } else {
c12575c6 668 c->last = "input error: unexpected EOF";
2e9ba080 669 disorder_error(0, "error reading %s: unexpected EOF", c->ident);
e9e8a16d 670 }
460b9539 671 return -1;
672}
673
c4e2cfdd
RK
674/** @brief Read a dot-stuffed list
675 * @param c Client
676 * @param vecp Where to store list (UTF-8)
677 * @param nvecp Where to store number of items, or NULL
678 * @return 0 on success, non-0 on error
679 *
680 * The list will have a final NULL not counted in @p nvecp.
681 */
460b9539 682static int readlist(disorder_client *c, char ***vecp, int *nvecp) {
683 char *l;
684 struct vector v;
685
686 vector_init(&v);
687 while(inputline(c->ident, c->fpin, &l, '\n') >= 0) {
688 if(!strcmp(l, ".")) {
689 vector_terminate(&v);
690 if(nvecp)
691 *nvecp = v.nvec;
692 *vecp = v.vec;
5d2b941b 693 xfree(l);
460b9539 694 return 0;
695 }
5d2b941b
RK
696 vector_append(&v, xstrdup(l + (*l == '.')));
697 xfree(l);
460b9539 698 }
e9e8a16d
RK
699 if(ferror(c->fpin)) {
700 byte_xasprintf((char **)&c->last, "input error: %s", strerror(errno));
2e9ba080 701 disorder_error(errno, "error reading %s", c->ident);
e9e8a16d
RK
702 } else {
703 c->last = "input error: unexpxected EOF";
2e9ba080 704 disorder_error(0, "error reading %s: unexpected EOF", c->ident);
e9e8a16d 705 }
460b9539 706 return -1;
707}
708
c4e2cfdd
RK
709/** @brief Return the user we logged in with
710 * @param c Client
711 * @return User name (owned by @p c, don't modify)
712 */
460b9539 713char *disorder_user(disorder_client *c) {
714 return c->user;
715}
716
5dc19ffd 717static void pairlist_error_handler(const char *msg,
460b9539 718 void attribute((unused)) *u) {
5dc19ffd 719 disorder_error(0, "error handling key-value pair reply: %s", msg);
460b9539 720}
721
5dc19ffd 722/** @brief Get a list of key-value pairs
c4e2cfdd 723 * @param c Client
c4e2cfdd 724 * @param kp Where to store linked list of preferences
5dc19ffd
RK
725 * @param cmd Command
726 * @param ... Arguments
c4e2cfdd
RK
727 * @return 0 on success, non-0 on error
728 */
5dc19ffd 729static int pairlist(disorder_client *c, struct kvp **kp, const char *cmd, ...) {
460b9539 730 char **vec, **pvec;
2bead829 731 int nvec, npvec, n, rc;
460b9539 732 struct kvp *k;
5dc19ffd 733 va_list ap;
460b9539 734
5dc19ffd
RK
735 va_start(ap, cmd);
736 rc = disorder_simple_v(c, 0, cmd, ap);
737 va_end(ap);
738 if(rc)
2bead829 739 return rc;
5dc19ffd
RK
740 if((rc = readlist(c, &vec, &nvec)))
741 return rc;
460b9539 742 for(n = 0; n < nvec; ++n) {
5dc19ffd 743 if(!(pvec = split(vec[n], &npvec, SPLIT_QUOTES, pairlist_error_handler, 0)))
460b9539 744 return -1;
745 if(npvec != 2) {
5dc19ffd 746 pairlist_error_handler("malformed response", 0);
460b9539 747 return -1;
748 }
749 *kp = k = xmalloc(sizeof *k);
750 k->name = pvec[0];
751 k->value = pvec[1];
752 kp = &k->next;
0d047a12 753 xfree(pvec);
460b9539 754 }
0d047a12 755 free_strings(nvec, vec);
460b9539 756 *kp = 0;
757 return 0;
758}
759
c4e2cfdd
RK
760/** @brief Parse a boolean response
761 * @param cmd Command for use in error messsage
762 * @param value Result from server
763 * @param flagp Where to store result
764 * @return 0 on success, non-0 on error
765 */
460b9539 766static int boolean(const char *cmd, const char *value,
767 int *flagp) {
768 if(!strcmp(value, "yes")) *flagp = 1;
769 else if(!strcmp(value, "no")) *flagp = 0;
770 else {
2e9ba080 771 disorder_error(0, "malformed response to '%s'", cmd);
460b9539 772 return -1;
773 }
774 return 0;
775}
776
c4e2cfdd
RK
777/** @brief Log to a sink
778 * @param c Client
779 * @param s Sink to write log lines to
780 * @return 0 on success, non-0 on error
781 */
460b9539 782int disorder_log(disorder_client *c, struct sink *s) {
783 char *l;
2bead829 784 int rc;
460b9539 785
2bead829 786 if((rc = disorder_simple(c, 0, "log", (char *)0)))
787 return rc;
460b9539 788 while(inputline(c->ident, c->fpin, &l, '\n') >= 0 && strcmp(l, "."))
789 if(sink_printf(s, "%s\n", l) < 0) return -1;
e9e8a16d
RK
790 if(ferror(c->fpin) || feof(c->fpin)) {
791 byte_xasprintf((char **)&c->last, "input error: %s",
792 ferror(c->fpin) ? strerror(errno) : "unexpxected EOF");
793 return -1;
794 }
460b9539 795 return 0;
796}
797
7788b7c7
RK
798#include "client-stubs.c"
799
460b9539 800/*
801Local Variables:
802c-basic-offset:2
803comment-column:40
804End:
805*/