Detect unsigned time_t and adjust y2038 detection.
[gnupg.git] / util / assuan-buffer.c
1 /* assuan-buffer.c - read and send data
2  *      Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
3  *
4  * This file is part of Assuan.
5  *
6  * Assuan is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * Assuan is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /* Please note that this is a stripped down and modified version of
21    the orginal Assuan code from libassuan. */
22
23
24 #include <config.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <assert.h>
31 #ifdef HAVE_W32_SYSTEM
32 #include <process.h>
33 #endif
34 #include "assuan-defs.h"
35
36 #ifndef HAVE_MEMRCHR
37 void *memrchr(const void *s, int c, size_t n);
38 #endif
39
40 static int
41 writen (assuan_context_t ctx, const char *buffer, size_t length)
42 {
43   while (length)
44     {
45       ssize_t nwritten = ctx->io->writefnc (ctx, buffer, length);
46       
47       if (nwritten < 0)
48         {
49           if (errno == EINTR)
50             continue;
51           return -1; /* write error */
52         }
53       length -= nwritten;
54       buffer += nwritten;
55     }
56   return 0;  /* okay */
57 }
58
59 /* Read an entire line.  */
60 static int
61 readaline (assuan_context_t ctx, char *buf, size_t buflen,
62           int *r_nread, int *r_eof)
63 {
64   size_t nleft = buflen;
65   char *p;
66
67   *r_eof = 0;
68   *r_nread = 0;
69   while (nleft > 0)
70     {
71       ssize_t n = ctx->io->readfnc (ctx, buf, nleft);
72
73       if (n < 0)
74         {
75           if (errno == EINTR)
76             continue;
77           return -1; /* read error */
78         }
79       else if (!n)
80         {
81           *r_eof = 1;
82           break; /* allow incomplete lines */
83         }
84       p = buf;
85       nleft -= n;
86       buf += n;
87       *r_nread += n;
88
89       p = memrchr (p, '\n', n);
90       if (p)
91         break; /* at least one full line available - that's enough for now */
92     }
93
94   return 0;
95 }
96
97
98 int
99 _assuan_read_line (assuan_context_t ctx)
100 {
101   char *line = ctx->inbound.line;
102   int nread, atticlen;
103   int rc;
104   char *endp = 0;
105
106   if (ctx->inbound.eof)
107     return -1;
108
109   atticlen = ctx->inbound.attic.linelen;
110   if (atticlen)
111     {
112       memcpy (line, ctx->inbound.attic.line, atticlen);
113       ctx->inbound.attic.linelen = 0;
114
115       endp = memchr (line, '\n', atticlen);
116       if (endp)
117         /* Found another line in the attic.  */
118         {
119           rc = 0;
120           nread = atticlen;
121           atticlen = 0;
122         }
123       else
124         /* There is pending data but not a full line.  */
125         {
126           assert (atticlen < LINELENGTH);
127           rc = readaline (ctx, line + atticlen,
128                          LINELENGTH - atticlen, &nread, &ctx->inbound.eof);
129         }
130     }
131   else
132     /* No pending data.  */
133     rc = readaline (ctx, line, LINELENGTH,
134                    &nread, &ctx->inbound.eof);
135   if (rc)
136     {
137       if (ctx->log_fp)
138         fprintf (ctx->log_fp, "%s[%u.%p] DBG: <- [Error: %s]\n",
139                  assuan_get_assuan_log_prefix (),
140                  (unsigned int)getpid (), (void *)ctx, strerror (errno));
141       return ASSUAN_Read_Error;
142     }
143   if (!nread)
144     {
145       assert (ctx->inbound.eof);
146       if (ctx->log_fp)
147         fprintf (ctx->log_fp, "%s[%u.%p] DBG: <- [EOF]\n",
148                  assuan_get_assuan_log_prefix (),
149                  (unsigned int)getpid (), (void *)ctx);
150       return -1;
151     }
152
153   ctx->inbound.attic.pending = 0;
154   nread += atticlen;
155
156   if (! endp)
157     endp = memchr (line, '\n', nread);
158
159   if (endp)
160     {
161       int n = endp - line + 1;
162       if (n < nread)
163         /* LINE contains more than one line.  We copy it to the attic
164            now as handlers are allowed to modify the passed
165            buffer.  */
166         {
167           int len = nread - n;
168           memcpy (ctx->inbound.attic.line, endp + 1, len);
169           ctx->inbound.attic.pending = memrchr (endp + 1, '\n', len) ? 1 : 0;
170           ctx->inbound.attic.linelen = len;
171         }
172
173       if (endp != line && endp[-1] == '\r')
174         endp --;
175       *endp = 0;
176
177       ctx->inbound.linelen = endp - line;
178       if (ctx->log_fp)
179         {
180           fprintf (ctx->log_fp, "%s[%u.%p] DBG: <- ",
181                    assuan_get_assuan_log_prefix (),
182                    (unsigned int)getpid (), (void *)ctx);
183           if (ctx->confidential)
184             fputs ("[Confidential data not shown]", ctx->log_fp);
185           else
186             _assuan_log_print_buffer (ctx->log_fp,
187                                       ctx->inbound.line,
188                                       ctx->inbound.linelen);
189           putc ('\n', ctx->log_fp);
190         }
191       return 0;
192     }
193   else
194     {
195       if (ctx->log_fp)
196         fprintf (ctx->log_fp, "%s[%u.%p] DBG: <- [Invalid line]\n",
197                  assuan_get_assuan_log_prefix (),
198                  (unsigned int)getpid (), (void *)ctx);
199       *line = 0;
200       ctx->inbound.linelen = 0;
201       return ctx->inbound.eof ? ASSUAN_Line_Not_Terminated
202         : ASSUAN_Line_Too_Long;
203     }
204 }
205
206
207 /* Read the next line from the client or server and return a pointer
208    in *LINE to a buffer holding the line.  LINELEN is the length of
209    *LINE.  The buffer is valid until the next read operation on it.
210    The caller may modify the buffer.  The buffer is invalid (i.e. must
211    not be used) if an error is returned.
212
213    Returns 0 on success or an assuan error code.
214    See also: assuan_pending_line().
215 */
216 assuan_error_t
217 assuan_read_line (assuan_context_t ctx, char **line, size_t *linelen)
218 {
219   assuan_error_t err;
220
221   if (!ctx)
222     return ASSUAN_Invalid_Value;
223
224   err = _assuan_read_line (ctx);
225   *line = ctx->inbound.line;
226   *linelen = ctx->inbound.linelen;
227   return err;
228 }
229
230
231 /* Return true if a full line is buffered (i.e. an entire line may be
232    read without any I/O).  */
233 int
234 assuan_pending_line (assuan_context_t ctx)
235 {
236   return ctx && ctx->inbound.attic.pending;
237 }
238
239
240 assuan_error_t 
241 _assuan_write_line (assuan_context_t ctx, const char *prefix,
242                     const char *line, size_t len)
243 {
244   int rc = 0;
245   size_t prefixlen = prefix? strlen (prefix):0;
246
247   /* Make sure that the line is short enough. */
248   if (len + prefixlen + 2 > ASSUAN_LINELENGTH)
249     {
250       if (ctx->log_fp)
251         fprintf (ctx->log_fp, "%s[%u.%p] DBG: -> "
252                  "[supplied line too long -truncated]\n",
253                  assuan_get_assuan_log_prefix (),
254                  (unsigned int)getpid (), (void *)ctx);
255       if (prefixlen > 5)
256         prefixlen = 5;
257       if (len > ASSUAN_LINELENGTH - prefixlen - 2)
258         len = ASSUAN_LINELENGTH - prefixlen - 2 - 1;
259     }
260
261   /* Fixme: we should do some kind of line buffering.  */
262   if (ctx->log_fp)
263     {
264       fprintf (ctx->log_fp, "%s[%u.%p] DBG: -> ",
265                assuan_get_assuan_log_prefix (),
266                (unsigned int)getpid (), (void *)ctx);
267       if (ctx->confidential)
268         fputs ("[Confidential data not shown]", ctx->log_fp);
269       else
270         _assuan_log_print_buffer (ctx->log_fp, line, len);
271       putc ('\n', ctx->log_fp);
272     }
273
274   if (prefixlen)
275     {
276       rc = writen (ctx, prefix, prefixlen);
277       if (rc)
278         rc = ASSUAN_Write_Error;
279     }
280   if (!rc)
281     {
282       rc = writen (ctx, line, len);
283       if (rc)
284         rc = ASSUAN_Write_Error;
285       if (!rc)
286         {
287           rc = writen (ctx, "\n", 1);
288           if (rc)
289             rc = ASSUAN_Write_Error;
290         }
291     }
292   return rc;
293 }
294
295
296 assuan_error_t 
297 assuan_write_line (assuan_context_t ctx, const char *line)
298 {
299   size_t len;
300   const char *s;
301
302   if (!ctx)
303     return ASSUAN_Invalid_Value;
304
305   /* Make sure that we never take a LF from the user - this might
306      violate the protocol. */
307   s = strchr (line, '\n');
308   len = s? (s-line) : strlen (line);
309
310   if (ctx->log_fp && s)
311     fprintf (ctx->log_fp, "%s[%u.%p] DBG: -> "
312              "[supplied line contained a LF -truncated]\n",
313              assuan_get_assuan_log_prefix (),
314              (unsigned int)getpid (), (void *)ctx);
315
316   return _assuan_write_line (ctx, NULL, line, len);
317 }
318
319
320 \f
321 /* Write out the data in buffer as datalines with line wrapping and
322    percent escaping.  This function is used for GNU's custom streams */
323 int
324 _assuan_cookie_write_data (void *cookie, const char *buffer, size_t orig_size)
325 {
326   assuan_context_t ctx = cookie;
327   size_t size = orig_size;
328   char *line;
329   size_t linelen;
330
331   if (ctx->outbound.data.error)
332     return 0;
333
334   line = ctx->outbound.data.line;
335   linelen = ctx->outbound.data.linelen;
336   line += linelen;
337   while (size)
338     {
339       /* insert data line header */
340       if (!linelen)
341         {
342           *line++ = 'D';
343           *line++ = ' ';
344           linelen += 2;
345         }
346       
347       /* copy data, keep some space for the CRLF and to escape one character */
348       while (size && linelen < LINELENGTH-2-2)
349         {
350           if (*buffer == '%' || *buffer == '\r' || *buffer == '\n')
351             {
352               sprintf (line, "%%%02X", *(unsigned char*)buffer);
353               line += 3;
354               linelen += 3;
355               buffer++;
356             }
357           else
358             {
359               *line++ = *buffer++;
360               linelen++;
361             }
362           size--;
363         }
364       
365       if (linelen >= LINELENGTH-2-2)
366         {
367           if (ctx->log_fp)
368             {
369               fprintf (ctx->log_fp, "%s[%u.%p] DBG: -> ",
370                        assuan_get_assuan_log_prefix (),
371                        (unsigned int)getpid (), (void *)ctx);
372
373               if (ctx->confidential)
374                 fputs ("[Confidential data not shown]", ctx->log_fp);
375               else 
376                 _assuan_log_print_buffer (ctx->log_fp, 
377                                           ctx->outbound.data.line,
378                                           linelen);
379               putc ('\n', ctx->log_fp);
380             }
381           *line++ = '\n';
382           linelen++;
383           if (writen (ctx, ctx->outbound.data.line, linelen))
384             {
385               ctx->outbound.data.error = ASSUAN_Write_Error;
386               return 0;
387             }
388           line = ctx->outbound.data.line;
389           linelen = 0;
390         }
391     }
392
393   ctx->outbound.data.linelen = linelen;
394   return (int)orig_size;
395 }
396
397
398 /* Write out any buffered data 
399    This function is used for GNU's custom streams */
400 int
401 _assuan_cookie_write_flush (void *cookie)
402 {
403   assuan_context_t ctx = cookie;
404   char *line;
405   size_t linelen;
406
407   if (ctx->outbound.data.error)
408     return 0;
409
410   line = ctx->outbound.data.line;
411   linelen = ctx->outbound.data.linelen;
412   line += linelen;
413   if (linelen)
414     {
415       if (ctx->log_fp)
416         {
417           fprintf (ctx->log_fp, "%s[%u.%p] DBG: -> ",
418                    assuan_get_assuan_log_prefix (),
419                    (unsigned int)getpid (), (void *)ctx);
420           if (ctx->confidential)
421             fputs ("[Confidential data not shown]", ctx->log_fp);
422           else
423             _assuan_log_print_buffer (ctx->log_fp,
424                                       ctx->outbound.data.line, linelen);
425           putc ('\n', ctx->log_fp);
426         }
427       *line++ = '\n';
428       linelen++;
429       if (writen (ctx, ctx->outbound.data.line, linelen))
430         {
431           ctx->outbound.data.error = ASSUAN_Write_Error;
432           return 0;
433         }
434       ctx->outbound.data.linelen = 0;
435     }
436   return 0;
437 }
438
439
440 /**
441  * assuan_send_data:
442  * @ctx: An assuan context
443  * @buffer: Data to send or NULL to flush
444  * @length: length of the data to send/
445  * 
446  * This function may be used by the server or the client to send data
447  * lines.  The data will be escaped as required by the Assuan protocol
448  * and may get buffered until a line is full.  To force sending the
449  * data out @buffer may be passed as NULL (in which case @length must
450  * also be 0); however when used by a client this flush operation does
451  * also send the terminating "END" command to terminate the reponse on
452  * a INQUIRE response.  However, when assuan_transact() is used, this
453  * function takes care of sending END itself.
454  * 
455  * Return value: 0 on success or an error code
456  **/
457 \f
458 assuan_error_t
459 assuan_send_data (assuan_context_t ctx, const void *buffer, size_t length)
460 {
461   if (!ctx)
462     return ASSUAN_Invalid_Value;
463   if (!buffer && length)
464     return ASSUAN_Invalid_Value;
465
466   if (!buffer)
467     { /* flush what we have */
468       _assuan_cookie_write_flush (ctx);
469       if (ctx->outbound.data.error)
470         return ctx->outbound.data.error;
471       if (!ctx->is_server)
472         return assuan_write_line (ctx, "END");
473     }
474   else
475     {
476       _assuan_cookie_write_data (ctx, buffer, length);
477       if (ctx->outbound.data.error)
478         return ctx->outbound.data.error;
479     }
480
481   return 0;
482 }
483