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