* server.c (gpgsm_server): Use assuan_deinit_server and setup
[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   if (DBG_AGENT)
482     assuan_set_log_stream (ctx, log_get_stream ());
483
484   for (;;)
485     {
486       rc = assuan_accept (ctx);
487       if (rc == -1)
488         {
489           break;
490         }
491       else if (rc)
492         {
493           log_info ("Assuan accept problem: %s\n", assuan_strerror (rc));
494           break;
495         }
496       
497       rc = assuan_process (ctx);
498       if (rc)
499         {
500           log_info ("Assuan processing failed: %s\n", assuan_strerror (rc));
501           continue;
502         }
503     }
504
505   gpgsm_release_certlist (ctrl.server_local->recplist);
506   ctrl.server_local->recplist = NULL;
507
508   assuan_deinit_server (ctx);
509 }
510
511
512 static const char *
513 get_status_string ( int no ) 
514 {
515   const char *s;
516
517   switch (no)
518     {
519     case STATUS_ENTER  : s = "ENTER"; break;
520     case STATUS_LEAVE  : s = "LEAVE"; break;
521     case STATUS_ABORT  : s = "ABORT"; break;
522     case STATUS_GOODSIG: s = "GOODSIG"; break;
523     case STATUS_SIGEXPIRED: s = "SIGEXPIRED"; break;
524     case STATUS_KEYREVOKED: s = "KEYREVOKED"; break;
525     case STATUS_BADSIG : s = "BADSIG"; break;
526     case STATUS_ERRSIG : s = "ERRSIG"; break;
527     case STATUS_BADARMOR : s = "BADARMOR"; break;
528     case STATUS_RSA_OR_IDEA : s= "RSA_OR_IDEA"; break;
529     case STATUS_TRUST_UNDEFINED: s = "TRUST_UNDEFINED"; break;
530     case STATUS_TRUST_NEVER      : s = "TRUST_NEVER"; break;
531     case STATUS_TRUST_MARGINAL : s = "TRUST_MARGINAL"; break;
532     case STATUS_TRUST_FULLY      : s = "TRUST_FULLY"; break;
533     case STATUS_TRUST_ULTIMATE : s = "TRUST_ULTIMATE"; break;
534     case STATUS_GET_BOOL         : s = "GET_BOOL"; break;
535     case STATUS_GET_LINE         : s = "GET_LINE"; break;
536     case STATUS_GET_HIDDEN       : s = "GET_HIDDEN"; break;
537     case STATUS_GOT_IT   : s = "GOT_IT"; break;
538     case STATUS_SHM_INFO         : s = "SHM_INFO"; break;
539     case STATUS_SHM_GET  : s = "SHM_GET"; break;
540     case STATUS_SHM_GET_BOOL     : s = "SHM_GET_BOOL"; break;
541     case STATUS_SHM_GET_HIDDEN : s = "SHM_GET_HIDDEN"; break;
542     case STATUS_NEED_PASSPHRASE: s = "NEED_PASSPHRASE"; break;
543     case STATUS_VALIDSIG         : s = "VALIDSIG"; break;
544     case STATUS_SIG_ID   : s = "SIG_ID"; break;
545     case STATUS_ENC_TO   : s = "ENC_TO"; break;
546     case STATUS_NODATA   : s = "NODATA"; break;
547     case STATUS_BAD_PASSPHRASE : s = "BAD_PASSPHRASE"; break;
548     case STATUS_NO_PUBKEY        : s = "NO_PUBKEY"; break;
549     case STATUS_NO_SECKEY        : s = "NO_SECKEY"; break;
550     case STATUS_NEED_PASSPHRASE_SYM: s = "NEED_PASSPHRASE_SYM"; break;
551     case STATUS_DECRYPTION_FAILED: s = "DECRYPTION_FAILED"; break;
552     case STATUS_DECRYPTION_OKAY: s = "DECRYPTION_OKAY"; break;
553     case STATUS_MISSING_PASSPHRASE: s = "MISSING_PASSPHRASE"; break;
554     case STATUS_GOOD_PASSPHRASE : s = "GOOD_PASSPHRASE"; break;
555     case STATUS_GOODMDC  : s = "GOODMDC"; break;
556     case STATUS_BADMDC   : s = "BADMDC"; break;
557     case STATUS_ERRMDC   : s = "ERRMDC"; break;
558     case STATUS_IMPORTED         : s = "IMPORTED"; break;
559     case STATUS_IMPORT_RES       : s = "IMPORT_RES"; break;
560     case STATUS_FILE_START       : s = "FILE_START"; break;
561     case STATUS_FILE_DONE        : s = "FILE_DONE"; break;
562     case STATUS_FILE_ERROR       : s = "FILE_ERROR"; break;
563     case STATUS_BEGIN_DECRYPTION:s = "BEGIN_DECRYPTION"; break;
564     case STATUS_END_DECRYPTION : s = "END_DECRYPTION"; break;
565     case STATUS_BEGIN_ENCRYPTION:s = "BEGIN_ENCRYPTION"; break;
566     case STATUS_END_ENCRYPTION : s = "END_ENCRYPTION"; break;
567     case STATUS_DELETE_PROBLEM : s = "DELETE_PROBLEM"; break;
568     case STATUS_PROGRESS         : s = "PROGRESS"; break;
569     case STATUS_SIG_CREATED      : s = "SIG_CREATED"; break;
570     case STATUS_SESSION_KEY      : s = "SESSION_KEY"; break;
571     case STATUS_NOTATION_NAME  : s = "NOTATION_NAME" ; break;
572     case STATUS_NOTATION_DATA  : s = "NOTATION_DATA" ; break;
573     case STATUS_POLICY_URL     : s = "POLICY_URL" ; break;
574     case STATUS_BEGIN_STREAM   : s = "BEGIN_STREAM"; break;
575     case STATUS_END_STREAM     : s = "END_STREAM"; break;
576     case STATUS_KEY_CREATED    : s = "KEY_CREATED"; break;
577     case STATUS_UNEXPECTED     : s = "UNEXPECTED"; break;
578     case STATUS_INV_RECP       : s = "INV_RECP"; break;
579     case STATUS_NO_RECP        : s = "NO_RECP"; break;
580     case STATUS_ALREADY_SIGNED : s = "ALREADY_SIGNED"; break;
581     default: s = "?"; break;
582     }
583   return s;
584 }
585
586
587
588 void
589 gpgsm_status (CTRL ctrl, int no, const char *text)
590 {
591   if (ctrl->no_server)
592     {
593       if (ctrl->status_fd == -1)
594         return; /* no status wanted */
595       if (!statusfp)
596         {
597           if (ctrl->status_fd == 1)
598             statusfp = stdout;
599           else if (ctrl->status_fd == 2)
600             statusfp = stderr;
601           else
602             statusfp = fdopen (ctrl->status_fd, "w");
603       
604           if (!statusfp)
605             {
606               log_fatal ("can't open fd %d for status output: %s\n",
607                          ctrl->status_fd, strerror(errno));
608             }
609         }
610       
611       fputs ("[GNUPG:] ", statusfp);
612       fputs (get_status_string (no), statusfp);
613     
614       if (text)
615         {
616           putc ( ' ', statusfp );
617           for (; *text; text++) 
618             {
619               if (*text == '\n')
620                 fputs ( "\\n", statusfp );
621               else if (*text == '\r')
622                 fputs ( "\\r", statusfp );
623               else 
624                 putc ( *(const byte *)text,  statusfp );
625             }
626         }
627       putc ('\n', statusfp);
628       fflush (statusfp);
629     }
630   else 
631     {
632       ASSUAN_CONTEXT ctx = ctrl->server_local->assuan_ctx;
633
634       assuan_write_status (ctx, get_status_string (no), text);
635     }
636 }
637
638
639 #if 0
640 /*
641  * Write a status line with a buffer using %XX escapes.  If WRAP is >
642  * 0 wrap the line after this length.  If STRING is not NULL it will
643  * be prepended to the buffer, no escaping is done for string.
644  * A wrap of -1 forces spaces not to be encoded as %20.
645  */
646 void
647 write_status_text_and_buffer ( int no, const char *string,
648                                const char *buffer, size_t len, int wrap )
649 {
650     const char *s, *text;
651     int esc, first;
652     int lower_limit = ' ';
653     size_t n, count, dowrap;
654
655     if( !statusfp )
656         return;  /* not enabled */
657     
658     if (wrap == -1) {
659         lower_limit--;
660         wrap = 0;
661     }
662
663     text = get_status_string (no);
664     count = dowrap = first = 1;
665     do {
666         if (dowrap) {
667             fprintf (statusfp, "[GNUPG:] %s ", text );
668             count = dowrap = 0;
669             if (first && string) {
670                 fputs (string, statusfp);
671                 count += strlen (string);
672             }
673             first = 0;
674         }
675         for (esc=0, s=buffer, n=len; n && !esc; s++, n-- ) {
676             if ( *s == '%' || *(const byte*)s <= lower_limit 
677                            || *(const byte*)s == 127 ) 
678                 esc = 1;
679             if ( wrap && ++count > wrap ) {
680                 dowrap=1;
681                 break;
682             }
683         }
684         if (esc) {
685             s--; n++;
686         }
687         if (s != buffer) 
688             fwrite (buffer, s-buffer, 1, statusfp );
689         if ( esc ) {
690             fprintf (statusfp, "%%%02X", *(const byte*)s );
691             s++; n--;
692         }
693         buffer = s;
694         len = n;
695         if ( dowrap && len )
696             putc ( '\n', statusfp );
697     } while ( len );
698
699     putc ('\n',statusfp);
700     fflush (statusfp);
701 }
702 #endif
703
704
705
706
707
708
709