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