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