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