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