chiark / gitweb /
inputline variant which uses a source
[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);
05b3f1f6 234 int n;
0bc1d67c
RK
235 if(nlist < 0) {
236 for(nlist = 0; list[nlist]; ++nlist)
237 ;
238 }
05b3f1f6 239 for(n = 0; n < nlist; ++n) {
0bc1d67c
RK
240 dynstr_append(&d, ' ');
241 dynstr_append_string(&d, quoteutf8(arg));
242 }
bcb2af72
RK
243 } else if(arg == disorder__integer) {
244 long n = va_arg(ap, long);
245 char buffer[16];
3c95f40e 246 byte_snprintf(buffer, sizeof buffer, "%ld", n);
bcb2af72
RK
247 dynstr_append(&d, ' ');
248 dynstr_append_string(&d, buffer);
249 } else if(arg == disorder__time) {
250 time_t n = va_arg(ap, time_t);
251 char buffer[16];
3c95f40e 252 byte_snprintf(buffer, sizeof buffer, "%lld", (long long)n);
bcb2af72
RK
253 dynstr_append(&d, ' ');
254 dynstr_append_string(&d, buffer);
08af2413
RK
255 } else {
256 dynstr_append(&d, ' ');
257 dynstr_append_string(&d, quoteutf8(arg));
258 }
460b9539 259 }
260 dynstr_append(&d, '\n');
261 dynstr_terminate(&d);
262 D(("command: %s", d.vec));
39c53343
RK
263 if(fputs(d.vec, c->fpout) < 0)
264 goto write_error;
f74f4f32 265 xfree(d.vec);
08af2413 266 if(has_body) {
05b3f1f6 267 int n;
39c53343
RK
268 if(nbody < 0)
269 for(nbody = 0; body[nbody]; ++nbody)
270 ;
05b3f1f6 271 for(n = 0; n < nbody; ++n) {
39c53343
RK
272 if(body[n][0] == '.')
273 if(fputc('.', c->fpout) < 0)
274 goto write_error;
275 if(fputs(body[n], c->fpout) < 0)
276 goto write_error;
277 if(fputc('\n', c->fpout) < 0)
278 goto write_error;
279 }
280 if(fputs(".\n", c->fpout) < 0)
281 goto write_error;
460b9539 282 }
39c53343
RK
283 if(fflush(c->fpout))
284 goto write_error;
460b9539 285 }
286 return check_response(c, rp);
39c53343
RK
287write_error:
288 byte_xasprintf((char **)&c->last, "write error: %s", strerror(errno));
2e9ba080 289 disorder_error(errno, "error writing to %s", c->ident);
39c53343 290 return -1;
460b9539 291}
292
c4e2cfdd
RK
293/** @brief Issue a command and parse a simple response
294 * @param c Client
295 * @param rp Where to store result, or NULL (UTF-8)
296 * @param cmd Command
297 * @return 0 on success, non-0 on error
298 *
299 * The remaining arguments are command arguments, terminated by (char
300 * *)0. They should be in UTF-8.
301 *
302 * 5xx responses count as errors.
303 *
304 * @p rp will NOT be filled in for xx9 responses (where it is just
305 * commentary for a command where it would normally be meaningful).
306 *
307 * NB that the response will NOT be converted to the local encoding
308 * nor will quotes be stripped. See dequote().
460b9539 309 */
310static int disorder_simple(disorder_client *c,
311 char **rp,
312 const char *cmd, ...) {
313 va_list ap;
314 int ret;
315
316 va_start(ap, cmd);
08af2413 317 ret = disorder_simple_v(c, rp, cmd, ap);
460b9539 318 va_end(ap);
319 return ret;
320}
321
dab87ecc
RK
322/** @brief Issue a command and split the response
323 * @param c Client
324 * @param vecp Where to store results
325 * @param nvecp Where to store count of results
326 * @param expected Expected count (or -1 to not check)
327 * @param cmd Command
328 * @return 0 on success, non-0 on error
329 *
330 * The remaining arguments are command arguments, terminated by (char
331 * *)0. They should be in UTF-8.
332 *
333 * 5xx responses count as errors.
334 *
335 * @p rp will NOT be filled in for xx9 responses (where it is just
336 * commentary for a command where it would normally be meaningful).
337 *
338 * NB that the response will NOT be converted to the local encoding
339 * nor will quotes be stripped. See dequote().
340 */
341static int disorder_simple_split(disorder_client *c,
342 char ***vecp,
343 int *nvecp,
344 int expected,
345 const char *cmd, ...) {
346 va_list ap;
347 int ret;
348 char *r;
349 char **vec;
350 int nvec;
351
352 va_start(ap, cmd);
353 ret = disorder_simple_v(c, &r, cmd, ap);
354 va_end(ap);
355 if(!ret) {
356 vec = split(r, &nvec, SPLIT_QUOTES, 0, 0);
357 xfree(r);
358 if(expected < 0 || nvec == expected) {
359 *vecp = vec;
360 *nvecp = nvec;
361 } else {
362 disorder_error(0, "malformed reply to %s", cmd);
363 c->last = "malformed reply";
364 ret = -1;
365 free_strings(nvec, vec);
366 }
367 }
368 if(ret) {
369 *vecp = NULL;
370 *nvecp = 0;
371 }
372 return ret;
373}
374
0227f67d
RK
375/** @brief Dequote a result string
376 * @param rc 0 on success, non-0 on error
377 * @param rp Where result string is stored (UTF-8)
378 * @return @p rc
379 *
380 * This is used as a wrapper around disorder_simple() to dequote
381 * results in place.
382 */
383static int dequote(int rc, char **rp) {
384 char **rr;
2bead829 385
0227f67d
RK
386 if(!rc) {
387 if((rr = split(*rp, 0, SPLIT_QUOTES, 0, 0)) && *rr) {
f74f4f32 388 xfree(*rp);
0227f67d 389 *rp = *rr;
f74f4f32 390 xfree(rr);
0227f67d
RK
391 return 0;
392 }
2e9ba080 393 disorder_error(0, "invalid reply: %s", *rp);
460b9539 394 }
2bead829 395 return rc;
460b9539 396}
397
0227f67d 398/** @brief Generic connection routine
319d7107 399 * @param conf Configuration to follow
c4e2cfdd 400 * @param c Client
0227f67d
RK
401 * @param username Username to log in with or NULL
402 * @param password Password to log in with or NULL
403 * @param cookie Cookie to log in with or NULL
c4e2cfdd
RK
404 * @return 0 on success, non-0 on error
405 *
0227f67d
RK
406 * @p cookie is tried first if not NULL. If it is NULL then @p
407 * username must not be. If @p username is not NULL then nor may @p
408 * password be.
c4e2cfdd 409 */
319d7107
RK
410int disorder_connect_generic(struct config *conf,
411 disorder_client *c,
412 const char *username,
413 const char *password,
414 const char *cookie) {
f74f4f32
RK
415 int fd = -1, fd2 = -1, nrvec = 0, rc;
416 unsigned char *nonce = NULL;
460b9539 417 size_t nl;
f74f4f32
RK
418 char *res = NULL;
419 char *r = NULL, **rvec = NULL;
7b32e917 420 const char *protocol, *algorithm, *challenge;
f74f4f32 421 struct sockaddr *sa = NULL;
0227f67d 422 socklen_t salen;
460b9539 423
319d7107 424 if((salen = find_server(conf, &sa, &c->ident)) == (socklen_t)-1)
460b9539 425 return -1;
460b9539 426 c->fpin = c->fpout = 0;
427 if((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
e9e8a16d 428 byte_xasprintf((char **)&c->last, "socket: %s", strerror(errno));
2e9ba080 429 disorder_error(errno, "error calling socket");
460b9539 430 return -1;
431 }
96af1d7e 432 c->family = sa->sa_family;
0227f67d 433 if(connect(fd, sa, salen) < 0) {
e9e8a16d 434 byte_xasprintf((char **)&c->last, "connect: %s", strerror(errno));
2e9ba080 435 disorder_error(errno, "error calling connect");
460b9539 436 goto error;
437 }
438 if((fd2 = dup(fd)) < 0) {
e9e8a16d 439 byte_xasprintf((char **)&c->last, "dup: %s", strerror(errno));
2e9ba080 440 disorder_error(errno, "error calling dup");
460b9539 441 goto error;
442 }
443 if(!(c->fpin = fdopen(fd, "rb"))) {
e9e8a16d 444 byte_xasprintf((char **)&c->last, "fdopen: %s", strerror(errno));
2e9ba080 445 disorder_error(errno, "error calling fdopen");
460b9539 446 goto error;
447 }
448 fd = -1;
449 if(!(c->fpout = fdopen(fd2, "wb"))) {
e9e8a16d 450 byte_xasprintf((char **)&c->last, "fdopen: %s", strerror(errno));
2e9ba080 451 disorder_error(errno, "error calling fdopen");
460b9539 452 goto error;
453 }
454 fd2 = -1;
2bead829 455 if((rc = disorder_simple(c, &r, 0, (const char *)0)))
456 goto error_rc;
637fdea3 457 if(!(rvec = split(r, &nrvec, SPLIT_QUOTES, 0, 0)))
2bead829 458 goto error;
7b32e917 459 if(nrvec != 3) {
e9e8a16d 460 c->last = "cannot parse server greeting";
2e9ba080 461 disorder_error(0, "cannot parse server greeting %s", r);
2bead829 462 goto error;
637fdea3 463 }
f74f4f32 464 protocol = rvec[0];
7b32e917 465 if(strcmp(protocol, "2")) {
e9e8a16d 466 c->last = "unknown protocol version";
2e9ba080 467 disorder_error(0, "unknown protocol version: %s", protocol);
2bead829 468 goto error;
7b32e917 469 }
f74f4f32
RK
470 algorithm = rvec[1];
471 challenge = rvec[2];
7b32e917 472 if(!(nonce = unhex(challenge, &nl)))
2bead829 473 goto error;
0227f67d
RK
474 if(cookie) {
475 if(!dequote(disorder_simple(c, &c->user, "cookie", cookie, (char *)0),
476 &c->user))
477 return 0; /* success */
478 if(!username) {
e9e8a16d 479 c->last = "cookie failed and no username";
2e9ba080 480 disorder_error(0, "cookie did not work and no username available");
2bead829 481 goto error;
0227f67d
RK
482 }
483 }
e9e8a16d
RK
484 if(!(res = authhash(nonce, nl, password, algorithm))) {
485 c->last = "error computing authorization hash";
486 goto error;
487 }
2bead829 488 if((rc = disorder_simple(c, 0, "user", username, res, (char *)0)))
489 goto error_rc;
460b9539 490 c->user = xstrdup(username);
f74f4f32
RK
491 xfree(res);
492 free_strings(nrvec, rvec);
493 xfree(nonce);
494 xfree(sa);
495 xfree(r);
460b9539 496 return 0;
497error:
2bead829 498 rc = -1;
499error_rc:
500 if(c->fpin) {
501 fclose(c->fpin);
502 c->fpin = 0;
503 }
504 if(c->fpout) {
505 fclose(c->fpout);
506 c->fpout = 0;
507 }
460b9539 508 if(fd2 != -1) close(fd2);
509 if(fd != -1) close(fd);
2bead829 510 return rc;
460b9539 511}
512
fdf98378 513/** @brief Connect a client with a specified username and password
514 * @param c Client
515 * @param username Username to log in with
516 * @param password Password to log in with
517 * @return 0 on success, non-0 on error
518 */
519int disorder_connect_user(disorder_client *c,
520 const char *username,
521 const char *password) {
319d7107
RK
522 return disorder_connect_generic(config,
523 c,
fdf98378 524 username,
525 password,
526 0);
527}
528
0227f67d
RK
529/** @brief Connect a client
530 * @param c Client
531 * @return 0 on success, non-0 on error
532 *
533 * The connection will use the username and password found in @ref
534 * config, or directly from the database if no password is found and
535 * the database is readable (usually only for root).
536 */
537int disorder_connect(disorder_client *c) {
538 const char *username, *password;
539
540 if(!(username = config->username)) {
e9e8a16d 541 c->last = "no username";
2e9ba080 542 disorder_error(0, "no username configured");
0227f67d
RK
543 return -1;
544 }
545 password = config->password;
1be9101e
RK
546 /* If we're connecting as 'root' guess that we're the system root
547 * user (or the jukebox user), both of which can use the privileged
548 * socket. They can also furtle with the db directly: that is why
549 * privileged socket does not represent a privilege escalation. */
550 if(!password
551 && !strcmp(username, "root"))
552 password = "anything will do for root";
0227f67d
RK
553 if(!password) {
554 /* Oh well */
e9e8a16d 555 c->last = "no password";
2e9ba080 556 disorder_error(0, "no password configured for user '%s'", username);
0227f67d
RK
557 return -1;
558 }
319d7107
RK
559 return disorder_connect_generic(config,
560 c,
0227f67d
RK
561 username,
562 password,
563 0);
564}
565
566/** @brief Connect a client
567 * @param c Client
568 * @param cookie Cookie to log in with, or NULL
569 * @return 0 on success, non-0 on error
570 *
571 * If @p cookie is NULL or does not work then we attempt to log in as
572 * guest instead (so when the cookie expires only an extra round trip
70adc86b 573 * is needed rather than a complete new login).
0227f67d
RK
574 */
575int disorder_connect_cookie(disorder_client *c,
576 const char *cookie) {
319d7107
RK
577 return disorder_connect_generic(config,
578 c,
0227f67d
RK
579 "guest",
580 "",
581 cookie);
582}
583
c4e2cfdd
RK
584/** @brief Close a client
585 * @param c Client
586 * @return 0 on succcess, non-0 on errior
587 *
588 * The client is still closed even on error. It might well be
589 * appropriate to ignore the return value.
590 */
460b9539 591int disorder_close(disorder_client *c) {
592 int ret = 0;
593
594 if(c->fpin) {
595 if(fclose(c->fpin) < 0) {
e9e8a16d 596 byte_xasprintf((char **)&c->last, "fclose: %s", strerror(errno));
2e9ba080 597 disorder_error(errno, "error calling fclose");
460b9539 598 ret = -1;
599 }
600 c->fpin = 0;
601 }
602 if(c->fpout) {
603 if(fclose(c->fpout) < 0) {
e9e8a16d 604 byte_xasprintf((char **)&c->last, "fclose: %s", strerror(errno));
2e9ba080 605 disorder_error(errno, "error calling fclose");
460b9539 606 ret = -1;
607 }
608 c->fpout = 0;
609 }
f74f4f32 610 xfree(c->ident);
0227f67d 611 c->ident = 0;
f74f4f32 612 xfree(c->user);
0227f67d 613 c->user = 0;
375d9478 614 return ret;
460b9539 615}
616
460b9539 617static void client_error(const char *msg,
618 void attribute((unused)) *u) {
2e9ba080 619 disorder_error(0, "error parsing reply: %s", msg);
460b9539 620}
621
ec9c0462 622/** @brief Get a single queue entry
c4e2cfdd 623 * @param c Client
ec9c0462 624 * @param cmd Command
c4e2cfdd
RK
625 * @param qp Where to store track information
626 * @return 0 on success, non-0 on error
c4e2cfdd 627 */
ec9c0462
RK
628static int onequeue(disorder_client *c, const char *cmd,
629 struct queue_entry **qp) {
460b9539 630 char *r;
631 struct queue_entry *q;
2bead829 632 int rc;
460b9539 633
ec9c0462 634 if((rc = disorder_simple(c, &r, cmd, (char *)0)))
2bead829 635 return rc;
460b9539 636 if(r) {
637 q = xmalloc(sizeof *q);
638 if(queue_unmarshall(q, r, client_error, 0))
639 return -1;
640 *qp = q;
641 } else
642 *qp = 0;
643 return 0;
644}
645
0590cedc 646/** @brief Fetch the queue, recent list, etc */
c12575c6
RK
647static int readqueue(disorder_client *c,
648 struct queue_entry **qp) {
460b9539 649 struct queue_entry *qh, **qt = &qh, *q;
650 char *l;
651
460b9539 652 while(inputline(c->ident, c->fpin, &l, '\n') >= 0) {
653 if(!strcmp(l, ".")) {
654 *qt = 0;
655 *qp = qh;
b251ac34 656 xfree(l);
460b9539 657 return 0;
658 }
659 q = xmalloc(sizeof *q);
660 if(!queue_unmarshall(q, l, client_error, 0)) {
661 *qt = q;
662 qt = &q->next;
663 }
b251ac34 664 xfree(l);
460b9539 665 }
e9e8a16d
RK
666 if(ferror(c->fpin)) {
667 byte_xasprintf((char **)&c->last, "input error: %s", strerror(errno));
2e9ba080 668 disorder_error(errno, "error reading %s", c->ident);
e9e8a16d 669 } else {
c12575c6 670 c->last = "input error: unexpected EOF";
2e9ba080 671 disorder_error(0, "error reading %s: unexpected EOF", c->ident);
e9e8a16d 672 }
460b9539 673 return -1;
674}
675
c4e2cfdd
RK
676/** @brief Read a dot-stuffed list
677 * @param c Client
678 * @param vecp Where to store list (UTF-8)
679 * @param nvecp Where to store number of items, or NULL
680 * @return 0 on success, non-0 on error
681 *
682 * The list will have a final NULL not counted in @p nvecp.
683 */
460b9539 684static int readlist(disorder_client *c, char ***vecp, int *nvecp) {
685 char *l;
686 struct vector v;
687
688 vector_init(&v);
689 while(inputline(c->ident, c->fpin, &l, '\n') >= 0) {
690 if(!strcmp(l, ".")) {
691 vector_terminate(&v);
692 if(nvecp)
693 *nvecp = v.nvec;
694 *vecp = v.vec;
5d2b941b 695 xfree(l);
460b9539 696 return 0;
697 }
5d2b941b
RK
698 vector_append(&v, xstrdup(l + (*l == '.')));
699 xfree(l);
460b9539 700 }
e9e8a16d
RK
701 if(ferror(c->fpin)) {
702 byte_xasprintf((char **)&c->last, "input error: %s", strerror(errno));
2e9ba080 703 disorder_error(errno, "error reading %s", c->ident);
e9e8a16d
RK
704 } else {
705 c->last = "input error: unexpxected EOF";
2e9ba080 706 disorder_error(0, "error reading %s: unexpected EOF", c->ident);
e9e8a16d 707 }
460b9539 708 return -1;
709}
710
c4e2cfdd
RK
711/** @brief Return the user we logged in with
712 * @param c Client
713 * @return User name (owned by @p c, don't modify)
714 */
460b9539 715char *disorder_user(disorder_client *c) {
716 return c->user;
717}
718
5dc19ffd 719static void pairlist_error_handler(const char *msg,
460b9539 720 void attribute((unused)) *u) {
5dc19ffd 721 disorder_error(0, "error handling key-value pair reply: %s", msg);
460b9539 722}
723
5dc19ffd 724/** @brief Get a list of key-value pairs
c4e2cfdd 725 * @param c Client
c4e2cfdd 726 * @param kp Where to store linked list of preferences
5dc19ffd
RK
727 * @param cmd Command
728 * @param ... Arguments
c4e2cfdd
RK
729 * @return 0 on success, non-0 on error
730 */
5dc19ffd 731static int pairlist(disorder_client *c, struct kvp **kp, const char *cmd, ...) {
460b9539 732 char **vec, **pvec;
2bead829 733 int nvec, npvec, n, rc;
460b9539 734 struct kvp *k;
5dc19ffd 735 va_list ap;
460b9539 736
5dc19ffd
RK
737 va_start(ap, cmd);
738 rc = disorder_simple_v(c, 0, cmd, ap);
739 va_end(ap);
740 if(rc)
2bead829 741 return rc;
5dc19ffd
RK
742 if((rc = readlist(c, &vec, &nvec)))
743 return rc;
460b9539 744 for(n = 0; n < nvec; ++n) {
5dc19ffd 745 if(!(pvec = split(vec[n], &npvec, SPLIT_QUOTES, pairlist_error_handler, 0)))
460b9539 746 return -1;
747 if(npvec != 2) {
5dc19ffd 748 pairlist_error_handler("malformed response", 0);
460b9539 749 return -1;
750 }
751 *kp = k = xmalloc(sizeof *k);
752 k->name = pvec[0];
753 k->value = pvec[1];
754 kp = &k->next;
0d047a12 755 xfree(pvec);
460b9539 756 }
0d047a12 757 free_strings(nvec, vec);
460b9539 758 *kp = 0;
759 return 0;
760}
761
c4e2cfdd
RK
762/** @brief Parse a boolean response
763 * @param cmd Command for use in error messsage
764 * @param value Result from server
765 * @param flagp Where to store result
766 * @return 0 on success, non-0 on error
767 */
460b9539 768static int boolean(const char *cmd, const char *value,
769 int *flagp) {
770 if(!strcmp(value, "yes")) *flagp = 1;
771 else if(!strcmp(value, "no")) *flagp = 0;
772 else {
2e9ba080 773 disorder_error(0, "malformed response to '%s'", cmd);
460b9539 774 return -1;
775 }
776 return 0;
777}
778
c4e2cfdd
RK
779/** @brief Log to a sink
780 * @param c Client
781 * @param s Sink to write log lines to
782 * @return 0 on success, non-0 on error
783 */
460b9539 784int disorder_log(disorder_client *c, struct sink *s) {
785 char *l;
2bead829 786 int rc;
460b9539 787
2bead829 788 if((rc = disorder_simple(c, 0, "log", (char *)0)))
789 return rc;
460b9539 790 while(inputline(c->ident, c->fpin, &l, '\n') >= 0 && strcmp(l, "."))
791 if(sink_printf(s, "%s\n", l) < 0) return -1;
e9e8a16d
RK
792 if(ferror(c->fpin) || feof(c->fpin)) {
793 byte_xasprintf((char **)&c->last, "input error: %s",
794 ferror(c->fpin) ? strerror(errno) : "unexpxected EOF");
795 return -1;
796 }
460b9539 797 return 0;
798}
799
7788b7c7
RK
800#include "client-stubs.c"
801
460b9539 802/*
803Local Variables:
804c-basic-offset:2
805comment-column:40
806End:
807*/