* call-dirmngr.c (inq_certificate): Changed for new interface semantic.
[gnupg.git] / sm / server.c
1 /* server.c - Server mode and main entry point 
2  *      Copyright (C) 2001, 2002 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22 #include <errno.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <ctype.h>
27 #include <unistd.h>
28
29 #include "gpgsm.h"
30 #include "../assuan/assuan.h"
31
32 #define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t))
33
34
35 /* The filepointer for status message used in non-server mode */
36 static FILE *statusfp;
37
38 /* Data used to assuciate an Assuan context with local server data */
39 struct server_local_s {
40   ASSUAN_CONTEXT assuan_ctx;
41   int message_fd;
42   CERTLIST recplist;
43 };
44
45
46 static void 
47 close_message_fd (CTRL ctrl)
48 {
49   if (ctrl->server_local->message_fd != -1)
50     {
51       close (ctrl->server_local->message_fd);
52       ctrl->server_local->message_fd = -1;
53     }
54 }
55
56 static void
57 reset_notify (ASSUAN_CONTEXT ctx)
58 {
59   CTRL ctrl = assuan_get_pointer (ctx);
60
61   gpgsm_release_certlist (ctrl->server_local->recplist);
62   ctrl->server_local->recplist = NULL;
63   close_message_fd (ctrl);
64 }
65
66
67 static void
68 input_notify (ASSUAN_CONTEXT ctx, const char *line)
69 {
70   CTRL ctrl = assuan_get_pointer (ctx);
71
72   ctrl->autodetect_encoding = 0;
73   ctrl->is_pem = 0;
74   ctrl->is_base64 = 0;
75   if (strstr (line, "--armor"))
76     ctrl->is_pem = 1;  
77   else if (strstr (line, "--base64"))
78     ctrl->is_base64 = 1; 
79   else if (strstr (line, "--binary"))
80     ;
81   else
82     ctrl->autodetect_encoding = 1;
83 }
84
85 static void
86 output_notify (ASSUAN_CONTEXT ctx, const char *line)
87 {
88   CTRL ctrl = assuan_get_pointer (ctx);
89
90   ctrl->create_pem = 0;
91   ctrl->create_base64 = 0;
92   if (strstr (line, "--armor"))
93     ctrl->create_pem = 1;  
94   else if (strstr (line, "--base64"))
95     ctrl->create_base64 = 1; /* just the raw output */
96 }
97
98
99
100 /*  RECIPIENT <userID>
101
102   Set the recipient for the encryption.  <userID> should be the
103   internal representation of the key; the server may accept any other
104   way of specification [we will support this].  If this is a valid and
105   trusted recipient the server does respond with OK, otherwise the
106   return is an ERR with the reason why the recipient can't be used,
107   the encryption will then not be done for this recipient.  IF the
108   policy is not to encrypt at all if not all recipients are valid, the
109   client has to take care of this.  All RECIPIENT commands are
110   cumulative until a RESET or an successful ENCRYPT command.  */
111 static int 
112 cmd_recipient (ASSUAN_CONTEXT ctx, char *line)
113 {
114   CTRL ctrl = assuan_get_pointer (ctx);
115   int rc;
116
117   rc = gpgsm_add_to_certlist (line, &ctrl->server_local->recplist);
118
119   return map_to_assuan_status (rc);
120 }
121
122
123 /* ENCRYPT 
124
125   Do the actual encryption process. Takes the plaintext from the INPUT
126   command, writes to the ciphertext to the file descriptor set with
127   the OUTPUT command, take the recipients form all the recipients set
128   so far.  If this command fails the clients should try to delete all
129   output currently done or otherwise mark it as invalid.  GPGSM does
130   ensure that there won't be any security problem with leftover data
131   on the output in this case.
132
133   This command should in general not fail, as all necessary checks
134   have been done while setting the recipients.  The input and output
135   pipes are closed. */
136 static int 
137 cmd_encrypt (ASSUAN_CONTEXT ctx, char *line)
138 {
139   CTRL ctrl = assuan_get_pointer (ctx);
140   int inp_fd, out_fd;
141   FILE *out_fp;
142   int rc;
143
144   inp_fd = assuan_get_input_fd (ctx);
145   if (inp_fd == -1)
146     return set_error (No_Input, NULL);
147   out_fd = assuan_get_output_fd (ctx);
148   if (out_fd == -1)
149     return set_error (No_Output, NULL);
150
151   out_fp = fdopen ( dup(out_fd), "w");
152   if (!out_fp)
153     return set_error (General_Error, "fdopen() failed");
154   rc = gpgsm_encrypt (assuan_get_pointer (ctx),
155                       ctrl->server_local->recplist,
156                       inp_fd, out_fp);
157   fclose (out_fp);
158
159   if (!rc)
160     {
161       gpgsm_release_certlist (ctrl->server_local->recplist);
162       ctrl->server_local->recplist = NULL;
163       /* close and reset the fd */
164       close_message_fd (ctrl);
165       assuan_close_input_fd (ctx);
166       assuan_close_output_fd (ctx);
167     }
168   return map_to_assuan_status (rc);
169 }
170
171 /* DECRYPT
172
173   This performs the decrypt operation after doing some check on the
174   internal state. (e.g. that only needed data has been set).  Because
175   it utilizes the GPG-Agent for the session key decryption, there is
176   no need to ask the client for a protecting passphrase - GpgAgent
177   does take care of this by requesting this from the user. */
178 static int 
179 cmd_decrypt (ASSUAN_CONTEXT ctx, char *line)
180 {
181   CTRL ctrl = assuan_get_pointer (ctx);
182   int inp_fd, out_fd;
183   FILE *out_fp;
184   int rc;
185
186   inp_fd = assuan_get_input_fd (ctx);
187   if (inp_fd == -1)
188     return set_error (No_Input, NULL);
189   out_fd = assuan_get_output_fd (ctx);
190   if (out_fd == -1)
191     return set_error (No_Output, NULL);
192
193   out_fp = fdopen ( dup(out_fd), "w");
194   if (!out_fp)
195     return set_error (General_Error, "fdopen() failed");
196   rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); 
197   fclose (out_fp);
198
199   if (!rc)
200     {
201       /* close and reset the fd */
202       close_message_fd (ctrl);
203       assuan_close_input_fd (ctx);
204       assuan_close_output_fd (ctx);
205     }
206
207   return map_to_assuan_status (rc);
208 }
209
210
211 /* VERIFY
212
213   This does a verify operation on the message send to the input-FD.
214   The result is written out using status lines.  If an output FD was
215   given, the signed text will be written to that.
216   
217   If the signature is a detached one, the server will inquire about
218   the signed material and the client must provide it.
219   */
220 static int 
221 cmd_verify (ASSUAN_CONTEXT ctx, char *line)
222 {
223   int rc;
224   CTRL ctrl = assuan_get_pointer (ctx);
225   int fd = assuan_get_input_fd (ctx);
226   int out_fd = assuan_get_output_fd (ctx);
227   FILE *out_fp = NULL;
228
229   if (fd == -1)
230     return set_error (No_Input, NULL);
231
232   if (out_fd != -1)
233     {
234       out_fp = fdopen ( dup(out_fd), "w");
235       if (!out_fp)
236         return set_error (General_Error, "fdopen() failed");
237     }
238
239   rc = gpgsm_verify (assuan_get_pointer (ctx), fd,
240                      ctrl->server_local->message_fd, out_fp);
241   if (out_fp)
242     fclose (out_fp);
243
244   if (!rc)
245     {
246       /* close and reset the fd */
247       close_message_fd (ctrl);
248       assuan_close_input_fd (ctx);
249       assuan_close_output_fd (ctx);
250     }
251
252   return map_to_assuan_status (rc);
253 }
254
255
256 /* SIGN [--detached]
257
258   Sign the data set with the INPUT command and write it to the sink
259   set by OUTPUT.  With "--detached" specified, a detached signature is
260   created (surprise).  */
261 static int 
262 cmd_sign (ASSUAN_CONTEXT ctx, char *line)
263 {
264   CTRL ctrl = assuan_get_pointer (ctx);
265   int inp_fd, out_fd;
266   FILE *out_fp;
267   int detached;
268   int rc;
269
270   inp_fd = assuan_get_input_fd (ctx);
271   if (inp_fd == -1)
272     return set_error (No_Input, NULL);
273   out_fd = assuan_get_output_fd (ctx);
274   if (out_fd == -1)
275     return set_error (No_Output, NULL);
276
277   detached = !!strstr (line, "--detached");  /* fixme: this is ambiguous */
278
279   out_fp = fdopen ( dup(out_fd), "w");
280   if (!out_fp)
281     return set_error (General_Error, "fdopen() failed");
282   rc = gpgsm_sign (assuan_get_pointer (ctx), inp_fd, detached, out_fp);
283   fclose (out_fp);
284
285   if (!rc)
286     {
287       /* close and reset the fd */
288       close_message_fd (ctrl);
289       assuan_close_input_fd (ctx);
290       assuan_close_output_fd (ctx);
291     }
292
293   return map_to_assuan_status (rc);
294 }
295
296
297 /* IMPORT
298
299   Import the certificates read form the input-fd, return status
300   message for each imported one.  The import checks the validity of
301   the certificate but not of the path.  It is possible to import
302   expired certificates.  */
303 static int 
304 cmd_import (ASSUAN_CONTEXT ctx, char *line)
305 {
306   CTRL ctrl = assuan_get_pointer (ctx);
307   int rc;
308   int fd = assuan_get_input_fd (ctx);
309
310   if (fd == -1)
311     return set_error (No_Input, NULL);
312
313   rc = gpgsm_import (assuan_get_pointer (ctx), fd);
314
315   if (!rc)
316     {
317       /* close and reset the fd */
318       close_message_fd (ctrl);
319       assuan_close_input_fd (ctx);
320       assuan_close_output_fd (ctx);
321     }
322   return map_to_assuan_status (rc);
323 }
324
325 /* MESSAGE FD=<n>
326
327    Set the file descriptor to read a message which is used with
328    detached signatures */
329 static int 
330 cmd_message (ASSUAN_CONTEXT ctx, char *line)
331 {
332   char *endp;
333   int fd;
334   CTRL ctrl = assuan_get_pointer (ctx);
335
336   if (strncmp (line, "FD=", 3))
337     return set_error (Syntax_Error, "FD=<n> expected");
338   line += 3;
339   if (!digitp (line))
340     return set_error (Syntax_Error, "number required");
341   fd = strtoul (line, &endp, 10);
342   if (*endp)
343     return set_error (Syntax_Error, "garbage found");
344   if (fd == -1)
345     return set_error (No_Input, NULL);
346
347   ctrl->server_local->message_fd = fd;
348   return 0;
349 }
350
351 static int 
352 cmd_listkeys (ASSUAN_CONTEXT ctx, char *line)
353 {
354   CTRL ctrl = assuan_get_pointer (ctx);
355
356   ctrl->with_colons = 1;
357   /* fixme: check that the returned data_fp is not NULL */
358   gpgsm_list_keys (assuan_get_pointer (ctx), NULL,
359                         assuan_get_data_fp (ctx));
360
361   return 0;
362 }
363
364 \f
365 /* GENKEY
366
367    Read the parameters in native format from the input fd and write a
368    certificate request to the output.
369  */
370 static int 
371 cmd_genkey (ASSUAN_CONTEXT ctx, char *line)
372 {
373   CTRL ctrl = assuan_get_pointer (ctx);
374   int inp_fd, out_fd;
375   FILE *out_fp;
376   int rc;
377
378   inp_fd = assuan_get_input_fd (ctx);
379   if (inp_fd == -1)
380     return set_error (No_Input, NULL);
381   out_fd = assuan_get_output_fd (ctx);
382   if (out_fd == -1)
383     return set_error (No_Output, NULL);
384
385   out_fp = fdopen ( dup(out_fd), "w");
386   if (!out_fp)
387     return set_error (General_Error, "fdopen() failed");
388   rc = gpgsm_genkey (ctrl, inp_fd, out_fp);
389   fclose (out_fp);
390
391   if (!rc)
392     {
393       /* close and reset the fds */
394       assuan_close_input_fd (ctx);
395       assuan_close_output_fd (ctx);
396     }
397   return map_to_assuan_status (rc);
398 }
399
400
401
402
403 \f
404 /* Tell the assuan library about our commands */
405 static int
406 register_commands (ASSUAN_CONTEXT ctx)
407 {
408   static struct {
409     const char *name;
410     int cmd_id;
411     int (*handler)(ASSUAN_CONTEXT, char *line);
412   } table[] = {
413     { "RECIPIENT",  0,  cmd_recipient },
414     { "ENCRYPT",    0,  cmd_encrypt },
415     { "DECRYPT",    0,  cmd_decrypt },
416     { "VERIFY",     0,  cmd_verify },
417     { "SIGN",       0,  cmd_sign },
418     { "IMPORT",     0,  cmd_import },
419     { "",     ASSUAN_CMD_INPUT, NULL }, 
420     { "",     ASSUAN_CMD_OUTPUT, NULL }, 
421     { "MESSAGE",    0,  cmd_message },
422     { "LISTKEYS",   0,  cmd_listkeys },
423     { "GENKEY",     0,  cmd_genkey },
424     { NULL }
425   };
426   int i, j, rc;
427
428   for (i=j=0; table[i].name; i++)
429     {
430       rc = assuan_register_command (ctx,
431                                     table[i].cmd_id? table[i].cmd_id
432                                                    : (ASSUAN_CMD_USER + j++),
433                                     table[i].name, table[i].handler);
434       if (rc)
435         return rc;
436     } 
437   return 0;
438 }
439
440 /* Startup the server */
441 void
442 gpgsm_server (void)
443 {
444   int rc;
445   int filedes[2];
446   ASSUAN_CONTEXT ctx;
447   struct server_control_s ctrl;
448
449   memset (&ctrl, 0, sizeof ctrl);
450
451   /* For now we use a simple pipe based server so that we can work
452      from scripts.  We will later add options to run as a daemon and
453      wait for requests on a Unix domain socket */
454   filedes[0] = 0;
455   filedes[1] = 1;
456   rc = assuan_init_pipe_server (&ctx, filedes);
457   if (rc)
458     {
459       log_error ("failed to initialize the server: %s\n",
460                  assuan_strerror(rc));
461       gpgsm_exit (2);
462     }
463   rc = register_commands (ctx);
464   if (rc)
465     {
466       log_error ("failed to the register commands with Assuan: %s\n",
467                  assuan_strerror(rc));
468       gpgsm_exit (2);
469     }
470   assuan_set_hello_line (ctx, "GNU Privacy Guard's S/M server ready");
471
472   assuan_register_reset_notify (ctx, reset_notify);
473   assuan_register_input_notify (ctx, input_notify);
474   assuan_register_output_notify (ctx, output_notify);
475
476   assuan_set_pointer (ctx, &ctrl);
477   ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local);
478   ctrl.server_local->assuan_ctx = ctx;
479   ctrl.server_local->message_fd = -1;
480
481   for (;;)
482     {
483       rc = assuan_accept (ctx);
484       if (rc == -1)
485         {
486           break;
487         }
488       else if (rc)
489         {
490           log_info ("Assuan accept problem: %s\n", assuan_strerror (rc));
491           break;
492         }
493       
494       rc = assuan_process (ctx);
495       if (rc)
496         {
497           log_info ("Assuan processing failed: %s\n", assuan_strerror (rc));
498           continue;
499         }
500     }
501
502   gpgsm_release_certlist (ctrl.server_local->recplist);
503   ctrl.server_local->recplist = NULL;
504
505   assuan_deinit_pipe_server (ctx);
506 }
507
508
509 static const char *
510 get_status_string ( int no ) 
511 {
512   const char *s;
513
514   switch (no)
515     {
516     case STATUS_ENTER  : s = "ENTER"; break;
517     case STATUS_LEAVE  : s = "LEAVE"; break;
518     case STATUS_ABORT  : s = "ABORT"; break;
519     case STATUS_GOODSIG: s = "GOODSIG"; break;
520     case STATUS_SIGEXPIRED: s = "SIGEXPIRED"; break;
521     case STATUS_KEYREVOKED: s = "KEYREVOKED"; break;
522     case STATUS_BADSIG : s = "BADSIG"; break;
523     case STATUS_ERRSIG : s = "ERRSIG"; break;
524     case STATUS_BADARMOR : s = "BADARMOR"; break;
525     case STATUS_RSA_OR_IDEA : s= "RSA_OR_IDEA"; break;
526     case STATUS_TRUST_UNDEFINED: s = "TRUST_UNDEFINED"; break;
527     case STATUS_TRUST_NEVER      : s = "TRUST_NEVER"; break;
528     case STATUS_TRUST_MARGINAL : s = "TRUST_MARGINAL"; break;
529     case STATUS_TRUST_FULLY      : s = "TRUST_FULLY"; break;
530     case STATUS_TRUST_ULTIMATE : s = "TRUST_ULTIMATE"; break;
531     case STATUS_GET_BOOL         : s = "GET_BOOL"; break;
532     case STATUS_GET_LINE         : s = "GET_LINE"; break;
533     case STATUS_GET_HIDDEN       : s = "GET_HIDDEN"; break;
534     case STATUS_GOT_IT   : s = "GOT_IT"; break;
535     case STATUS_SHM_INFO         : s = "SHM_INFO"; break;
536     case STATUS_SHM_GET  : s = "SHM_GET"; break;
537     case STATUS_SHM_GET_BOOL     : s = "SHM_GET_BOOL"; break;
538     case STATUS_SHM_GET_HIDDEN : s = "SHM_GET_HIDDEN"; break;
539     case STATUS_NEED_PASSPHRASE: s = "NEED_PASSPHRASE"; break;
540     case STATUS_VALIDSIG         : s = "VALIDSIG"; break;
541     case STATUS_SIG_ID   : s = "SIG_ID"; break;
542     case STATUS_ENC_TO   : s = "ENC_TO"; break;
543     case STATUS_NODATA   : s = "NODATA"; break;
544     case STATUS_BAD_PASSPHRASE : s = "BAD_PASSPHRASE"; break;
545     case STATUS_NO_PUBKEY        : s = "NO_PUBKEY"; break;
546     case STATUS_NO_SECKEY        : s = "NO_SECKEY"; break;
547     case STATUS_NEED_PASSPHRASE_SYM: s = "NEED_PASSPHRASE_SYM"; break;
548     case STATUS_DECRYPTION_FAILED: s = "DECRYPTION_FAILED"; break;
549     case STATUS_DECRYPTION_OKAY: s = "DECRYPTION_OKAY"; break;
550     case STATUS_MISSING_PASSPHRASE: s = "MISSING_PASSPHRASE"; break;
551     case STATUS_GOOD_PASSPHRASE : s = "GOOD_PASSPHRASE"; break;
552     case STATUS_GOODMDC  : s = "GOODMDC"; break;
553     case STATUS_BADMDC   : s = "BADMDC"; break;
554     case STATUS_ERRMDC   : s = "ERRMDC"; break;
555     case STATUS_IMPORTED         : s = "IMPORTED"; break;
556     case STATUS_IMPORT_RES       : s = "IMPORT_RES"; break;
557     case STATUS_FILE_START       : s = "FILE_START"; break;
558     case STATUS_FILE_DONE        : s = "FILE_DONE"; break;
559     case STATUS_FILE_ERROR       : s = "FILE_ERROR"; break;
560     case STATUS_BEGIN_DECRYPTION:s = "BEGIN_DECRYPTION"; break;
561     case STATUS_END_DECRYPTION : s = "END_DECRYPTION"; break;
562     case STATUS_BEGIN_ENCRYPTION:s = "BEGIN_ENCRYPTION"; break;
563     case STATUS_END_ENCRYPTION : s = "END_ENCRYPTION"; break;
564     case STATUS_DELETE_PROBLEM : s = "DELETE_PROBLEM"; break;
565     case STATUS_PROGRESS         : s = "PROGRESS"; break;
566     case STATUS_SIG_CREATED      : s = "SIG_CREATED"; break;
567     case STATUS_SESSION_KEY      : s = "SESSION_KEY"; break;
568     case STATUS_NOTATION_NAME  : s = "NOTATION_NAME" ; break;
569     case STATUS_NOTATION_DATA  : s = "NOTATION_DATA" ; break;
570     case STATUS_POLICY_URL     : s = "POLICY_URL" ; break;
571     case STATUS_BEGIN_STREAM   : s = "BEGIN_STREAM"; break;
572     case STATUS_END_STREAM     : s = "END_STREAM"; break;
573     case STATUS_KEY_CREATED    : s = "KEY_CREATED"; break;
574     case STATUS_UNEXPECTED     : s = "UNEXPECTED"; break;
575     case STATUS_INV_RECP       : s = "INV_RECP"; break;
576     case STATUS_NO_RECP        : s = "NO_RECP"; break;
577     case STATUS_ALREADY_SIGNED : s = "ALREADY_SIGNED"; break;
578     default: s = "?"; break;
579     }
580   return s;
581 }
582
583
584
585 void
586 gpgsm_status (CTRL ctrl, int no, const char *text)
587 {
588   if (ctrl->no_server)
589     {
590       if (ctrl->status_fd == -1)
591         return; /* no status wanted */
592       if (!statusfp)
593         {
594           if (ctrl->status_fd == 1)
595             statusfp = stdout;
596           else if (ctrl->status_fd == 2)
597             statusfp = stderr;
598           else
599             statusfp = fdopen (ctrl->status_fd, "w");
600       
601           if (!statusfp)
602             {
603               log_fatal ("can't open fd %d for status output: %s\n",
604                          ctrl->status_fd, strerror(errno));
605             }
606         }
607       
608       fputs ("[GNUPG:] ", statusfp);
609       fputs (get_status_string (no), statusfp);
610     
611       if (text)
612         {
613           putc ( ' ', statusfp );
614           for (; *text; text++) 
615             {
616               if (*text == '\n')
617                 fputs ( "\\n", statusfp );
618               else if (*text == '\r')
619                 fputs ( "\\r", statusfp );
620               else 
621                 putc ( *(const byte *)text,  statusfp );
622             }
623         }
624       putc ('\n', statusfp);
625       fflush (statusfp);
626     }
627   else 
628     {
629       ASSUAN_CONTEXT ctx = ctrl->server_local->assuan_ctx;
630
631       assuan_write_status (ctx, get_status_string (no), text);
632     }
633 }
634
635
636 #if 0
637 /*
638  * Write a status line with a buffer using %XX escapes.  If WRAP is >
639  * 0 wrap the line after this length.  If STRING is not NULL it will
640  * be prepended to the buffer, no escaping is done for string.
641  * A wrap of -1 forces spaces not to be encoded as %20.
642  */
643 void
644 write_status_text_and_buffer ( int no, const char *string,
645                                const char *buffer, size_t len, int wrap )
646 {
647     const char *s, *text;
648     int esc, first;
649     int lower_limit = ' ';
650     size_t n, count, dowrap;
651
652     if( !statusfp )
653         return;  /* not enabled */
654     
655     if (wrap == -1) {
656         lower_limit--;
657         wrap = 0;
658     }
659
660     text = get_status_string (no);
661     count = dowrap = first = 1;
662     do {
663         if (dowrap) {
664             fprintf (statusfp, "[GNUPG:] %s ", text );
665             count = dowrap = 0;
666             if (first && string) {
667                 fputs (string, statusfp);
668                 count += strlen (string);
669             }
670             first = 0;
671         }
672         for (esc=0, s=buffer, n=len; n && !esc; s++, n-- ) {
673             if ( *s == '%' || *(const byte*)s <= lower_limit 
674                            || *(const byte*)s == 127 ) 
675                 esc = 1;
676             if ( wrap && ++count > wrap ) {
677                 dowrap=1;
678                 break;
679             }
680         }
681         if (esc) {
682             s--; n++;
683         }
684         if (s != buffer) 
685             fwrite (buffer, s-buffer, 1, statusfp );
686         if ( esc ) {
687             fprintf (statusfp, "%%%02X", *(const byte*)s );
688             s++; n--;
689         }
690         buffer = s;
691         len = n;
692         if ( dowrap && len )
693             putc ( '\n', statusfp );
694     } while ( len );
695
696     putc ('\n',statusfp);
697     fflush (statusfp);
698 }
699 #endif
700
701
702
703
704
705
706