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