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