Update head to match stable 1.0
[gnupg.git] / g10 / armor.c
index 7269b32..9c7858f 100644 (file)
@@ -1,14 +1,14 @@
-/* armor.c - Armor filter
- *     Copyright (c) 1997 by Werner Koch (dd9jn)
+/* armor.c - Armor flter
+ * Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
  *
- * This file is part of G10.
+ * This file is part of GnuPG.
  *
- * G10 is free software; you can redistribute it and/or modify
+ * GnuPG is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
  *
- * G10 is distributed in the hope that it will be useful,
+ * GnuPG is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
@@ -24,6 +24,7 @@
 #include <string.h>
 #include <errno.h>
 #include <assert.h>
+#include <ctype.h>
 
 #include "errors.h"
 #include "iobuf.h"
 #include "filter.h"
 #include "packet.h"
 #include "options.h"
+#include "main.h"
+#include "status.h"
+#include "i18n.h"
 
+#ifdef HAVE_DOSISH_SYSTEM
+  #define LF "\r\n"
+#else
+  #define LF "\n"
+#endif
 
-
+#define MAX_LINELEN 20000
 
 #define CRCINIT 0xB704CE
 #define CRCPOLY 0X864CFB
@@ -51,7 +60,7 @@ static int is_initialized;
 
 
 typedef enum {
-    fhdrHASArmor,
+    fhdrHASArmor = 0,
     fhdrNOArmor,
     fhdrINIT,
     fhdrINITCont,
@@ -62,12 +71,21 @@ typedef enum {
     fhdrSKIPHeader,
     fhdrCLEARSIG,
     fhdrREADClearsig,
+    fhdrNullClearsig,
     fhdrEMPTYClearsig,
     fhdrCHECKClearsig,
     fhdrCHECKClearsig2,
+    fhdrCHECKDashEscaped,
+    fhdrCHECKDashEscaped2,
+    fhdrCHECKDashEscaped3,
     fhdrREADClearsigNext,
     fhdrENDClearsig,
+    fhdrENDClearsigHelp,
+    fhdrTESTSpaces,
+    fhdrCLEARSIGSimple,
+    fhdrCLEARSIGSimpleNext,
     fhdrTEXT,
+    fhdrTEXTSimple,
     fhdrERROR,
     fhdrERRORShow,
     fhdrEOF
@@ -75,26 +93,31 @@ typedef enum {
 
 
 /* if we encounter this armor string with this index, go
- * into a mode, which fakes packets and wait for the next armor */
+ * into a mode which fakes packets and wait for the next armor */
+#define BEGIN_SIGNATURE 2
 #define BEGIN_SIGNED_MSG_IDX 3
 static char *head_strings[] = {
     "BEGIN PGP MESSAGE",
     "BEGIN PGP PUBLIC KEY BLOCK",
     "BEGIN PGP SIGNATURE",
     "BEGIN PGP SIGNED MESSAGE",
+    "BEGIN PGP ARMORED FILE",       /* gnupg extension */
+    "BEGIN PGP PRIVATE KEY BLOCK",
+    "BEGIN PGP SECRET KEY BLOCK",   /* only used by pgp2 */
     NULL
 };
 static char *tail_strings[] = {
     "END PGP MESSAGE",
     "END PGP PUBLIC KEY BLOCK",
     "END PGP SIGNATURE",
+    "END dummy",
+    "END PGP ARMORED FILE",
+    "END PGP PRIVATE KEY BLOCK",
+    "END PGP SECRET KEY BLOCK",
     NULL
 };
 
 
-static fhdr_state_t find_header( fhdr_state_t state,
-                         byte *buf, size_t *r_buflen, IOBUF a, size_t n);
-
 
 static void
 initialize(void)
@@ -128,12 +151,13 @@ initialize(void)
 }
 
 /****************
- * Check wether this is a armored file or not
- * See also parse-packet.c for details on this code
+ * Check whether this is an armored file or not See also
+ * parse-packet.c for details on this code For unknown historic
+ * reasons we use a string here but only the first byte will be used.
  * Returns: True if it seems to be armored
  */
 static int
-is_armored( byte *buf )
+is_armored( const byte *buf )
 {
     int ctb, pkttype;
 
@@ -142,11 +166,15 @@ is_armored( byte *buf )
        return 1; /* invalid packet: assume it is armored */
     pkttype =  ctb & 0x40 ? (ctb & 0x3f) : ((ctb>>2)&0xf);
     switch( pkttype ) {
-      case PKT_PUBLIC_CERT:
-      case PKT_SECRET_CERT:
+      case PKT_MARKER:
+      case PKT_SYMKEY_ENC:
+      case PKT_ONEPASS_SIG:
+      case PKT_PUBLIC_KEY:
+      case PKT_SECRET_KEY:
       case PKT_PUBKEY_ENC:
       case PKT_SIGNATURE:
       case PKT_COMMENT:
+      case PKT_OLD_COMMENT:
       case PKT_PLAINTEXT:
       case PKT_COMPRESSED:
       case PKT_ENCRYPTED:
@@ -158,369 +186,438 @@ is_armored( byte *buf )
 
 
 /****************
- * parse an ascii armor.
- * Returns: the state,
- *         the remaining bytes in BUF are returned in RBUFLEN.
+ * Try to check whether the iobuf is armored
+ * Returns true if this may be the case; the caller should use the
+ *        filter to do further processing.
  */
-static fhdr_state_t
-find_header( fhdr_state_t state, byte *buf, size_t *r_buflen, IOBUF a, size_t n)
+int
+use_armor_filter( IOBUF a )
 {
-    int c, i;
-    const char *s;
-    char *p;
-    size_t buflen;
-    int cont;
-    int clearsig=0;
-    int hdr_line=0;
-
-    buflen = *r_buflen;
-    assert(buflen >= 100 );
-    buflen--;
-
-    do {
-       switch( state ) {
-         case fhdrHASArmor:
-           /* read 28 bytes, which is the bare minimum for a BEGIN...
-            * and check wether this has a Armor. */
-           c = 0;
-           for(n=0; n < 28 && (c=iobuf_get(a)) != -1 && c != '\n'; )
-               buf[n++] = c;
-           if( n < 28 || c == -1 )
-               state = fhdrNOArmor; /* too short */
-           else if( !is_armored( buf ) )
-               state = fhdrNOArmor;
-           else
-               state = fhdrINITCont;
-           break;
+    byte buf[1];
+    int n;
 
-         case fhdrINIT: /* read some stuff into buffer */
-           n = 0;
-         case fhdrINITCont: /* read more stuff into buffer */
-           c = 0;
-           for(; n < buflen && (c=iobuf_get(a)) != -1 && c != '\n'; )
-               buf[n++] = c;
-           state = c == '\n' ? fhdrCHECKBegin :
-                    c == -1  ? fhdrEOF : fhdrINITSkip;
-           break;
+    /* fixme: there might be a problem with iobuf_peek */
+    n = iobuf_peek(a, buf, 1 );
+    if( n == -1 )
+       return 0; /* EOF, doesn't matter whether armored or not */
+    if( !n )
+       return 1; /* can't check it: try armored */
+    return is_armored(buf);
+}
 
-         case fhdrINITSkip:
-           while( (c=iobuf_get(a)) != -1 && c != '\n' )
-               ;
-           state =  c == -1? fhdrEOF : fhdrINIT;
-           break;
 
-         case fhdrSKIPHeader:
-           while( (c=iobuf_get(a)) != -1 && c != '\n' )
-               ;
-           state =  c == -1? fhdrEOF : fhdrWAITHeader;
-           break;
 
-         case fhdrWAITHeader: /* wait for Header lines */
-           c = 0;
-           for(n=0; n < buflen && (c=iobuf_get(a)) != -1 && c != '\n'; )
-               buf[n++] = c;
-           buf[n] = 0;
-           if( n < buflen || c == '\n' ) {
-               if( n && buf[0] != '\r') { /* maybe a header */
-                   if( strchr( buf, ':') ) { /* yes */
-                       log_debug("armor header: ");
-                       print_string( stderr, buf, n );
-                       putc('\n', stderr);
-                       state = fhdrWAITHeader;
-                   }
-                   else
-                       state = fhdrTEXT;
-               }
-               else if( !n || (buf[0] == '\r' && !buf[1]) ) { /* empty line */
-                   if( clearsig )
-                       state = fhdrWAITClearsig;
-                   else {
-                       /* this is not really correct: if we do not have
-                        * a clearsig and not armor lines we are not allowed
-                        * to have an empty line */
-                       n = 0;
-                       state = fhdrTEXT;
-                   }
-               }
-               else {
-                   log_debug("invalid armor header: ");
-                   print_string( stderr, buf, n );
-                   putc('\n', stderr);
-                   state = fhdrERROR;
-               }
-           }
-           else if( c != -1 ) {
-               if( strchr( buf, ':') ) { /* buffer to short, but this is okay*/
-                   log_debug("armor header: ");
-                   print_string( stderr, buf, n );
-                   fputs("[...]\n", stderr);  /* indicate it is truncated */
-                   state = fhdrSKIPHeader;  /* skip rest of line */
-               }
-               else /* line too long */
-                   state = fhdrERROR;
-           }
-           else
-               state = fhdrEOF;
-           break;
 
-         case fhdrWAITClearsig: /* skip all empty lines (for clearsig) */
-           c = 0;
-           for(n=0; n < buflen && (c=iobuf_get(a)) != -1 && c != '\n'; )
-               buf[n++] = c;
-           if( n < buflen || c == '\n' ) {
-               buf[n] = 0;
-               if( !n || (buf[0]=='\r' && !buf[1]) ) /* empty line */
-                   ;
-               else
-                   state = fhdrTEXT;
-           }
-           else
-               state = fhdrEOF;
-           break;
+static void
+invalid_armor(void)
+{
+    write_status(STATUS_BADARMOR);
+    g10_exit(1); /* stop here */
+}
 
-         case fhdrENDClearsig:
-         case fhdrCHECKBegin:
-           state = state == fhdrCHECKBegin ? fhdrINITSkip : fhdrERRORShow;
-           if( n < 15 )
-               break;  /* too short */
-           if( memcmp( buf, "-----", 5 ) )
-               break;
-           buf[n] = 0;
-           p = strstr(buf+5, "-----");
-           if( !p )
-               break;
-           *p = 0;
-           p += 5;
-           if( *p == '\r' )
-               p++;
-           if( *p )
-               break; /* garbage after dashes */
-           p = buf+5;
-           for(i=0; (s=head_strings[i]); i++ )
-               if( !strcmp(s, p) )
-                   break;
-           if( !s )
-               break; /* unknown begin line */
-           /* found the begin line */
-           hdr_line = i;
-           state = fhdrWAITHeader;
-           if( hdr_line == BEGIN_SIGNED_MSG_IDX )
-               clearsig = 1;
-           log_debug("armor: %s\n", head_strings[hdr_line]);
-           break;
 
-         case fhdrCLEARSIG:
-         case fhdrEMPTYClearsig:
-         case fhdrREADClearsig:
-           /* we are at the start of a line: read a clearsig into the buffer
-            * we have to look for a the header line or dashd escaped text*/
-           n = 0;
-           c = 0;
-           for(; n < buflen && (c=iobuf_get(a)) != -1 && c != '\n'; )
-               buf[n++] = c;
-           buf[n] = 0;
-           if( c == -1 )
-               state = fhdrEOF;
-           else if( !n || ( buf[0]=='\r' && !buf[1] ) ) {
-               state = fhdrEMPTYClearsig;
-               /* FIXME: handle it */
-           }
-           else if( c == '\n' )
-               state = fhdrCHECKClearsig2;
-           else
-               state = fhdrCHECKClearsig;
+/****************
+ * check whether the armor header is valid on a signed message.
+ * this is for security reasons: the header lines are not included in the
+ * hash and by using some creative formatting rules, Mallory could fake
+ * any text at the beginning of a document; assuming it is read with
+ * a simple viewer. We only allow the Hash Header.
+ */
+static int
+parse_hash_header( const char *line )
+{
+    const char *s, *s2;
+    unsigned found = 0;
+
+    if( strlen(line) < 6  || strlen(line) > 60 )
+       return 0; /* too short or too long */
+    if( memcmp( line, "Hash:", 5 ) )
+       return 0; /* invalid header */
+    s = line+5;
+    for(s=line+5;;s=s2) {
+       for(; *s && (*s==' ' || *s == '\t'); s++ )
+           ;
+       if( !*s )
            break;
+       for(s2=s+1; *s2 && *s2!=' ' && *s2 != '\t' && *s2 != ','; s2++ )
+           ;
+       if( !strncmp( s, "RIPEMD160", s2-s ) )
+           found |= 1;
+       else if( !strncmp( s, "SHA1", s2-s ) )
+           found |= 2;
+       else if( !strncmp( s, "MD5", s2-s ) )
+           found |= 4;
+       else if( !strncmp( s, "TIGER192", s2-s ) )
+           found |= 8;
+       else if( !strncmp( s, "TIGER", s2-s ) ) /* used by old versions */
+           found |= 8;
+       else
+           return 0;
+       for(; *s2 && (*s2==' ' || *s2 == '\t'); s2++ )
+           ;
+       if( *s2 && *s2 != ',' )
+           return 0;
+       if( *s2 )
+           s2++;
+    }
+    return found;
+}
 
-         case fhdrCHECKClearsig:
-         case fhdrCHECKClearsig2:
-           /* check the clearsig line */
-           if( n > 15 && !memcmp(buf, "-----", 5 ) )
-               state = fhdrENDClearsig;
-           else if( buf[0] == '-' && buf[1] == ' ' ) {
-               /* dash escaped line */
-               if( buf[2] == '-' || ( n > 6 && !memcmp(buf+2, "From ", 5))) {
-                   for(i=2; i < n; i++ )
-                       buf[i-2] = buf[i];
-                   n -= 2;
-                   buf[n] = 0; /* not really needed */
-                   state = state == fhdrCHECKClearsig2 ?
-                                    fhdrREADClearsig : fhdrREADClearsigNext;
-                   /* FIXME: add the lf to the buffer */
-               }
-               else {
-                   log_debug("invalid dash escaped line: ");
-                   print_string( stderr, buf, n );
-                   putc('\n', stderr);
-                   state = fhdrERROR;
-               }
-           }
-           else {
-               state = state == fhdrCHECKClearsig2 ?
-                                 fhdrREADClearsig : fhdrREADClearsigNext;
-               /* FIXME: add the lf to the buffer */
-           }
-           break;
 
-         case fhdrREADClearsigNext:
-           /* Read to the end of the line, do not care about checking
-            * for dashed escaped text of headers */
-           break;
 
-         case fhdrERRORShow:
-           log_debug("invalid clear text header: ");
-           print_string( stderr, buf, n );
-           putc('\n', stderr);
-           state = fhdrERROR;
+/****************
+ * Check whether this is a armor line.
+ * returns: -1 if it is not a armor header or the index number of the
+ * armor header.
+ */
+static int
+is_armor_header( byte *line, unsigned len )
+{
+    const char *s;
+    byte *save_p, *p;
+    int save_c;
+    int i;
+
+    if( len < 15 )
+       return -1; /* too short */
+    if( memcmp( line, "-----", 5 ) )
+       return -1; /* no */
+    p = strstr( line+5, "-----");
+    if( !p )
+       return -1;
+    save_p = p;
+    p += 5;
+
+    /* Some mail programs on Windows seem to add spaces to the end of
+       the line.  This becomes strict if --openpgp is set. */
+
+    if(!opt.rfc2440)
+      while(*p==' ')
+       p++;
+
+    if( *p == '\r' )
+       p++;
+    if( *p == '\n' )
+       p++;
+    if( *p )
+       return -1; /* garbage after dashes */
+    save_c = *save_p; *save_p = 0;
+    p = line+5;
+    for(i=0; (s=head_strings[i]); i++ )
+       if( !strcmp(s, p) )
            break;
+    *save_p = save_c;
+    if( !s )
+       return -1; /* unknown armor line */
+
+    if( opt.verbose > 1 )
+       log_info(_("armor: %s\n"), head_strings[i]);
+    return i;
+}
 
-         default: BUG();
-       }
-       switch( state ) {
-         case fhdrINIT:
-         case fhdrINITCont:
-         case fhdrINITSkip:
-         case fhdrCHECKBegin:
-         case fhdrWAITHeader:
-         case fhdrWAITClearsig:
-         case fhdrSKIPHeader:
-         case fhdrEMPTYClearsig:
-         case fhdrCHECKClearsig:
-         case fhdrCHECKClearsig2:
-         case fhdrERRORShow:
-           cont = 1;
-           break;
-         default: cont = 0;
-       }
-    } while( cont );
 
-    if( clearsig && state == fhdrTEXT )
-       state = fhdrCLEARSIG;
-    *r_buflen = n;
-    return state;
+
+/****************
+ * Parse a header lines
+ * Return 0: Empty line (end of header lines)
+ *      -1: invalid header line
+ *      >0: Good header line
+ */
+static int
+parse_header_line( armor_filter_context_t *afx, byte *line, unsigned int len )
+{
+    byte *p;
+    int hashes=0;
+    unsigned int len2;
+
+    len2 = check_trailing_ws( line, len );
+    if( !len2 ) {
+        afx->buffer_pos = len2;  /* (it is not the fine way to do it here) */
+       return 0; /* WS only: same as empty line */
+    }
+    len = len2;
+    line[len2] = 0;
+
+    p = strchr( line, ':');
+    if( !p || !p[1] ) {
+       log_error(_("invalid armor header: "));
+       print_string( stderr, line, len, 0 );
+       putc('\n', stderr);
+       return -1;
+    }
+
+    if( opt.verbose ) {
+       log_info(_("armor header: "));
+       print_string( stderr, line, len, 0 );
+       putc('\n', stderr);
+    }
+
+    if( afx->in_cleartext ) {
+       if( (hashes=parse_hash_header( line )) )
+           afx->hashes |= hashes;
+       else if( strlen(line) > 15 && !memcmp( line, "NotDashEscaped:", 15 ) )
+           afx->not_dash_escaped = 1;
+       else {
+           log_error(_("invalid clearsig header\n"));
+           return -1;
+       }
+    }
+    return 1;
 }
 
 
-/* figure out wether the data is armored or not */
+
+/* figure out whether the data is armored or not */
 static int
 check_input( armor_filter_context_t *afx, IOBUF a )
 {
     int rc = 0;
-    size_t n;
-    fhdr_state_t state = afx->parse_state;
+    int i;
+    byte *line;
+    unsigned len;
+    unsigned maxlen;
+    int hdr_line = -1;
+
+    /* read the first line to see whether this is armored data */
+    maxlen = MAX_LINELEN;
+    len = afx->buffer_len = iobuf_read_line( a, &afx->buffer,
+                                            &afx->buffer_size, &maxlen );
+    line = afx->buffer;
+    if( !maxlen ) {
+       /* line has been truncated: assume not armored */
+       afx->inp_checked = 1;
+       afx->inp_bypass = 1;
+       return 0;
+    }
 
-    if( state != fhdrENDClearsig )
-       state = fhdrHASArmor;
+    if( !len ) {
+       return -1; /* eof */
+    }
 
-    n = DIM(afx->helpbuf);
-    state = find_header( state, afx->helpbuf, &n, a, afx->helplen );
-    switch( state ) {
-      case fhdrNOArmor:
+    /* (the line is always a C string but maybe longer) */
+    if( *line == '\n' || ( len && (*line == '\r' && line[1]=='\n') ) )
+       ;
+    else if( !is_armored( line ) ) {
        afx->inp_checked = 1;
        afx->inp_bypass = 1;
-       afx->helplen = n;
-       break;
+       return 0;
+    }
 
-      case fhdrERROR:
-      case fhdrEOF:
-       rc = -1;
-       break;
+    /* find the armor header */
+    while(len) {
+       i = is_armor_header( line, len );
+       if( i >= 0 && !(afx->only_keyblocks && i != 1 && i != 5 && i != 6 )) {
+           hdr_line = i;
+           if( hdr_line == BEGIN_SIGNED_MSG_IDX ) {
+               if( afx->in_cleartext ) {
+                   log_error(_("nested clear text signatures\n"));
+                   rc = G10ERR_INVALID_ARMOR;
+               }
+               afx->in_cleartext = 1;
+           }
+           break;
+       }
+       /* read the next line (skip all truncated lines) */
+       do {
+           maxlen = MAX_LINELEN;
+           afx->buffer_len = iobuf_read_line( a, &afx->buffer,
+                                              &afx->buffer_size, &maxlen );
+           line = afx->buffer;
+           len = afx->buffer_len;
+       } while( !maxlen );
+    }
+
+    /* parse the header lines */
+    while(len) {
+       /* read the next line (skip all truncated lines) */
+       do {
+           maxlen = MAX_LINELEN;
+           afx->buffer_len = iobuf_read_line( a, &afx->buffer,
+                                              &afx->buffer_size, &maxlen );
+           line = afx->buffer;
+           len = afx->buffer_len;
+       } while( !maxlen );
+
+       i = parse_header_line( afx, line, len );
+       if( i <= 0 ) {
+           if( i )
+               rc = G10ERR_INVALID_ARMOR;
+           break;
+       }
+    }
 
-      case fhdrCLEARSIG: /* start fake package mode (for clear signatures) */
-       afx->helplen = n;
-       afx->helpidx = 0;
-       afx->faked = 1;
-       break;
 
-      case fhdrTEXT:
-       afx->helplen = n;
-       afx->helpidx = 0;
+    if( rc )
+       invalid_armor();
+    else if( afx->in_cleartext )
+       afx->faked = 1;
+    else {
        afx->inp_checked = 1;
        afx->crc = CRCINIT;
        afx->idx = 0;
        afx->radbuf[0] = 0;
-       break;
-
-      default: BUG();
     }
 
-    afx->parse_state = state;
     return rc;
 }
 
 
 
-/* fake a literal data packet and wait for an armor line */
+/****************
+ * Fake a literal data packet and wait for the next armor line
+ * fixme: empty line handling and null length clear text signature are
+ *       not implemented/checked.
+ */
 static int
 fake_packet( armor_filter_context_t *afx, IOBUF a,
             size_t *retn, byte *buf, size_t size  )
 {
     int rc = 0;
     size_t len = 0;
-    size_t n, nn;
-    fhdr_state_t state = afx->parse_state;
-
-    size = 100; /* FIXME: only used for testing (remove it)  */
+    int lastline = 0;
+    unsigned maxlen, n;
+    byte *p;
 
     len = 2;   /* reserve 2 bytes for the length header */
-    size -= 2; /* and 2 for the term header */
+    size -= 2; /* and 2 for the terminating header */
     while( !rc && len < size ) {
-       if( afx->helpidx < afx->helplen ) { /* flush the last buffer */
-           n = afx->helplen;
-           for(nn=afx->helpidx; len < size && nn < n ; nn++ )
-               buf[len++] = afx->helpbuf[nn];
-           afx->helpidx = nn;
-           continue;
+       /* copy what we have in the line buffer */
+       if( afx->faked == 1 )
+           afx->faked++; /* skip the first (empty) line */
+       else {
+           while( len < size && afx->buffer_pos < afx->buffer_len )
+               buf[len++] = afx->buffer[afx->buffer_pos++];
+           if( len >= size )
+               continue;
        }
-       if( state == fhdrEOF ) {
-           rc = -1;
+
+       /* read the next line */
+       maxlen = MAX_LINELEN;
+       afx->buffer_pos = 0;
+       afx->buffer_len = iobuf_read_line( a, &afx->buffer,
+                                          &afx->buffer_size, &maxlen );
+       if( !afx->buffer_len ) {
+           rc = -1; /* eof (should not happen) */
            continue;
        }
-       /* read a new one */
-       n = DIM(afx->helpbuf);
-       afx->helpidx = 0;
-       state = find_header( state, afx->helpbuf, &n, a, 0 );
-       switch( state) {
-         case fhdrERROR:
-         case fhdrEOF:
-           rc = -1;
-           break;
-
-         case fhdrCLEARSIG:
-         case fhdrREADClearsig:
-         case fhdrREADClearsigNext:
-           afx->helplen = n;
-           break;
-
-         case fhdrENDClearsig:
-           afx->helplen = n;
-           afx->faked = 0;
-           rc = -1;
-           break;
-
-         default: BUG();
+       if( !maxlen )
+           afx->truncated++;
+       if( !afx->not_dash_escaped ) {
+           int crlf;
+           p = afx->buffer;
+           n = afx->buffer_len;
+           crlf = n > 1 && p[n-2] == '\r' && p[n-1]=='\n';
+
+           /* PGP2 does not treat a tab as white space character */
+           afx->buffer_len = trim_trailing_chars( p, n,
+                                        afx->pgp2mode ? " \r\n" : " \t\r\n");
+           /* the buffer is always allocated with enough space to append
+            * the removed [CR], LF and a Nul
+            * The reason for this complicated procedure is to keep at least
+            * the original type of lineending - handling of the removed
+            * trailing spaces seems to be impossible in our method
+            * of faking a packet; either we have to use a temporary file
+            * or calculate the hash here in this module and somehow find
+            * a way to send the hash down the processing line (well, a special
+            * faked packet could do the job).
+            */
+           if( crlf )
+               afx->buffer[afx->buffer_len++] = '\r';
+           afx->buffer[afx->buffer_len++] = '\n';
+           afx->buffer[afx->buffer_len] = 0;
+       }
+       p = afx->buffer;
+       n = afx->buffer_len;
+
+       if( n > 2 && *p == '-' ) {
+           /* check for dash escaped or armor header */
+           if( p[1] == ' ' && !afx->not_dash_escaped ) {
+               /* issue a warning if it is not regular encoded */
+               if( p[2] != '-' && !( n > 6 && !memcmp(p+2, "From ", 5))) {
+                   log_info(_("invalid dash escaped line: "));
+                   print_string( stderr, p, n, 0 );
+                   putc('\n', stderr);
+               }
+               afx->buffer_pos = 2; /* skip */
+           }
+           else if( n >= 15 &&  p[1] == '-' && p[2] == '-' && p[3] == '-' ) {
+               int type = is_armor_header( p, n );
+               if( afx->not_dash_escaped && type != BEGIN_SIGNATURE )
+                   ; /* this is okay */
+               else {
+                   if( type != BEGIN_SIGNATURE ) {
+                       log_info(_("unexpected armor:"));
+                       print_string( stderr, p, n, 0 );
+                       putc('\n', stderr);
+                   }
+                   lastline = 1;
+                   rc = -1;
+               }
+           }
        }
     }
+
     buf[0] = (len-2) >> 8;
     buf[1] = (len-2);
-    if( state == fhdrENDClearsig ) { /* write last (ending) length header */
-       buf[len++] = 0;
-       buf[len++] = 0;
+    if( lastline ) { /* write last (ending) length header */
+       if( buf[0] || buf[1] ) { /* only if we have some text */
+           buf[len++] = 0;
+           buf[len++] = 0;
+       }
        rc = 0;
+       afx->faked = 0;
+       afx->in_cleartext = 0;
+       /* and now read the header lines */
+       afx->buffer_pos = 0;
+       for(;;) {
+           int i;
+
+           /* read the next line (skip all truncated lines) */
+           do {
+               maxlen = MAX_LINELEN;
+               afx->buffer_len = iobuf_read_line( a, &afx->buffer,
+                                                &afx->buffer_size, &maxlen );
+           } while( !maxlen );
+           p = afx->buffer;
+           n = afx->buffer_len;
+           if( !n ) {
+               rc = -1;
+               break; /* eof */
+           }
+           i = parse_header_line( afx, p , n );
+           if( i <= 0 ) {
+               if( i )
+                   invalid_armor();
+               break;
+           }
+       }
+       afx->inp_checked = 1;
+       afx->crc = CRCINIT;
+       afx->idx = 0;
+       afx->radbuf[0] = 0;
     }
 
-    afx->parse_state = state;
     *retn = len;
     return rc;
 }
 
 
+static int
+invalid_crc(void)
+{
+    if ( opt.ignore_crc_error )
+        return 0;
+    log_inc_errorcount();
+    return G10ERR_INVALID_ARMOR;
+}
+
 
 static int
 radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn,
              byte *buf, size_t size )
 {
     byte val;
-    int c, c2;
+    int c=0, c2; /*init c because gcc is not clever enough for the continue*/
     int checkcrc=0;
     int rc = 0;
     size_t n = 0;
@@ -531,20 +628,51 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn,
     idx = afx->idx;
     val = afx->radbuf[0];
     for( n=0; n < size; ) {
-       if( afx->helpidx < afx->helplen )
-           c = afx->helpbuf[afx->helpidx++];
-       else if( (c=iobuf_get(a)) == -1 )
-           break;
+
+       if( afx->buffer_pos < afx->buffer_len )
+           c = afx->buffer[afx->buffer_pos++];
+       else { /* read the next line */
+           unsigned maxlen = MAX_LINELEN;
+           afx->buffer_pos = 0;
+           afx->buffer_len = iobuf_read_line( a, &afx->buffer,
+                                              &afx->buffer_size, &maxlen );
+           if( !maxlen )
+               afx->truncated++;
+           if( !afx->buffer_len )
+               break; /* eof */
+           continue;
+       }
+
+      again:
        if( c == '\n' || c == ' ' || c == '\r' || c == '\t' )
            continue;
        else if( c == '=' ) { /* pad character: stop */
+           /* some mailers leave quoted-printable encoded characters
+            * so we try to workaround this */
+           if( afx->buffer_pos+2 < afx->buffer_len ) {
+               int cc1, cc2, cc3;
+               cc1 = afx->buffer[afx->buffer_pos];
+               cc2 = afx->buffer[afx->buffer_pos+1];
+               cc3 = afx->buffer[afx->buffer_pos+2];
+               if( isxdigit(cc1) && isxdigit(cc2)
+                                 && strchr( "=\n\r\t ", cc3 )) {
+                   /* well it seems to be the case - adjust */
+                   c = isdigit(cc1)? (cc1 - '0'): (ascii_toupper(cc1)-'A'+10);
+                   c <<= 4;
+                   c |= isdigit(cc2)? (cc2 - '0'): (ascii_toupper(cc2)-'A'+10);
+                   afx->buffer_pos += 2;
+                   afx->qp_detected = 1;
+                   goto again;
+               }
+           }
+
            if( idx == 1 )
                buf[n++] = val;
            checkcrc++;
            break;
        }
        else if( (c = asctobin[(c2=c)]) == 255 ) {
-           log_error("invalid radix64 character %02x skipped\n", c2);
+           log_error(_("invalid radix64 character %02x skipped\n"), c2);
            continue;
        }
        switch(idx) {
@@ -555,26 +683,39 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn,
        }
        idx = (idx+1) % 4;
     }
+
     for(i=0; i < n; i++ )
        crc = (crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ buf[i]];
     crc &= 0x00ffffff;
     afx->crc = crc;
     afx->idx = idx;
     afx->radbuf[0] = val;
+
     if( checkcrc ) {
-       afx->inp_eof = 1; /*assume eof */
+       afx->any_data = 1;
+       afx->inp_checked=0;
+       afx->faked = 0;
        for(;;) { /* skip lf and pad characters */
-           if( afx->helpidx < afx->helplen )
-               c = afx->helpbuf[afx->helpidx++];
-           else if( (c=iobuf_get(a)) == -1 )
-               break;
+           if( afx->buffer_pos < afx->buffer_len )
+               c = afx->buffer[afx->buffer_pos++];
+           else { /* read the next line */
+               unsigned maxlen = MAX_LINELEN;
+               afx->buffer_pos = 0;
+               afx->buffer_len = iobuf_read_line( a, &afx->buffer,
+                                                  &afx->buffer_size, &maxlen );
+               if( !maxlen )
+                   afx->truncated++;
+               if( !afx->buffer_len )
+                   break; /* eof */
+               continue;
+           }
            if( c == '\n' || c == ' ' || c == '\r'
                || c == '\t' || c == '=' )
                continue;
            break;
        }
        if( c == -1 )
-           log_error("premature eof (no CRC)\n");
+           log_error(_("premature eof (no CRC)\n"));
        else {
            u32 mycrc = 0;
            idx = 0;
@@ -587,34 +728,63 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn,
                  case 2: val |= (c>>2)&15; mycrc |= val << 8;val=(c<<6)&0xc0;break;
                  case 3: val |= c&0x3f; mycrc |= val; break;
                }
-               if( afx->helpidx < afx->helplen )
-                   c = afx->helpbuf[afx->helpidx++];
-               else if( (c=iobuf_get(a)) == -1 )
+               for(;;) {
+                   if( afx->buffer_pos < afx->buffer_len )
+                       c = afx->buffer[afx->buffer_pos++];
+                   else { /* read the next line */
+                       unsigned maxlen = MAX_LINELEN;
+                       afx->buffer_pos = 0;
+                       afx->buffer_len = iobuf_read_line( a, &afx->buffer,
+                                                          &afx->buffer_size,
+                                                               &maxlen );
+                       if( !maxlen )
+                           afx->truncated++;
+                       if( !afx->buffer_len )
+                           break; /* eof */
+                       continue;
+                   }
                    break;
+               }
+               if( !afx->buffer_len )
+                   break; /* eof */
            } while( ++idx < 4 );
-           if( c == -1 )
-               log_error("premature eof (in CRC)\n");
-           else if( idx != 4 )
-               log_error("malformed CRC\n");
-           else if( mycrc != afx->crc )
-               log_error("CRC error; %06lx - %06lx\n",
+           if( c == -1 ) {
+               log_info(_("premature eof (in CRC)\n"));
+               rc = invalid_crc();
+                           }
+           else if( idx != 4 ) {
+               log_info(_("malformed CRC\n"));
+               rc = invalid_crc();
+           }
+           else if( mycrc != afx->crc ) {
+                log_info (_("CRC error; %06lx - %06lx\n"),
                                    (ulong)afx->crc, (ulong)mycrc);
+                rc = invalid_crc();
+           }
            else {
+               rc = 0;
+                /* FIXME: Here we should emit another control packet,
+                 * so that we know in mainproc that we are processing
+                 * a clearsign message */
+             #if 0
                for(rc=0;!rc;) {
                    rc = 0 /*check_trailer( &fhdr, c )*/;
                    if( !rc ) {
-                       if( afx->helpidx < afx->helplen )
-                           c = afx->helpbuf[afx->helpidx++];
-                       else if( (c=iobuf_get(a)) == -1 )
+                       if( (c=iobuf_get(a)) == -1 )
                            rc = 2;
                    }
                }
                if( rc == -1 )
                    rc = 0;
-               else if( rc == 2 )
-                   log_error("premature eof (in Trailer)\n");
-               else
-                   log_error("error in trailer line\n");
+               else if( rc == 2 ) {
+                   log_error(_("premature eof (in Trailer)\n"));
+                   rc = G10ERR_INVALID_ARMOR;
+               }
+               else {
+                   log_error(_("error in trailer line\n"));
+                   rc = G10ERR_INVALID_ARMOR;
+               }
+             #endif
            }
        }
     }
@@ -626,9 +796,8 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn,
     return rc;
 }
 
-
 /****************
- * The filter is used to handle the armor stuff
+ * This filter is used to handle the armor stuff
  */
 int
 armor_filter( void *opaque, int control,
@@ -641,11 +810,26 @@ armor_filter( void *opaque, int control,
     int  idx, idx2;
     size_t n=0;
     u32 crc;
+  #if 0
+    static FILE *fp ;
+
+    if( !fp ) {
+       fp = fopen("armor.out", "w");
+       assert(fp);
+    }
+  #endif
 
     if( DBG_FILTER )
        log_debug("armor-filter: control: %d\n", control );
     if( control == IOBUFCTRL_UNDERFLOW && afx->inp_bypass ) {
-       for( n=0; n < size; n++ ) {
+       n = 0;
+       if( afx->buffer_len ) {
+           for(; n < size && afx->buffer_pos < afx->buffer_len; n++ )
+               buf[n++] = afx->buffer[afx->buffer_pos++];
+           if( afx->buffer_pos >= afx->buffer_len )
+               afx->buffer_len = 0;
+       }
+       for(; n < size; n++ ) {
            if( (c=iobuf_get(a)) == -1 )
                break;
            buf[n] = c & 0xff;
@@ -655,54 +839,124 @@ armor_filter( void *opaque, int control,
        *ret_len = n;
     }
     else if( control == IOBUFCTRL_UNDERFLOW ) {
-       if( size < 20 )
-           BUG(); /* supplied buffer maybe too short */
-
-       if( afx->inp_eof ) {
-           *ret_len = 0;
-           if( DBG_FILTER )
-               log_debug("armor-filter: eof due to inp_eof flag\n" );
-           return -1;
-       }
+        /* We need some space for the faked packet.  The minmum required
+         * size is ~18 + length of the session marker */
+       if( size < 50 ) 
+           BUG(); /* supplied buffer too short */
 
        if( afx->faked )
            rc = fake_packet( afx, a, &n, buf, size );
        else if( !afx->inp_checked ) {
            rc = check_input( afx, a );
-           if( afx->inp_bypass )
-               ;
+           if( afx->inp_bypass ) {
+               for(n=0; n < size && afx->buffer_pos < afx->buffer_len; )
+                   buf[n++] = afx->buffer[afx->buffer_pos++];
+               if( afx->buffer_pos >= afx->buffer_len )
+                   afx->buffer_len = 0;
+               if( !n )
+                   rc = -1;
+           }
            else if( afx->faked ) {
-               /* the buffer is at least 20 bytes long, so it
-                * is easy to construct a packet */
-               buf[0] = 0xaf; /* old packet format, type 11, var length */
-               buf[1] = 0;    /* set the length header */
-               buf[2] = 6;
-               buf[3] = 't';  /* canonical text */
-               buf[4] = 0;    /* namelength */
-               buf[5] = buf[6] = buf[7] = buf[8] = 0; /* timestamp */
-               n = 9;
+               unsigned int hashes = afx->hashes;
+                const byte *sesmark;
+                size_t sesmarklen;
+                
+                sesmark = get_session_marker( &sesmarklen );
+                if ( sesmarklen > 20 )
+                    BUG();
+
+               /* the buffer is at least 15+n*15 bytes long, so it
+                * is easy to construct the packets */
+
+               hashes &= 1|2|4|8;
+               if( !hashes ) {
+                   hashes |= 4;  /* default to MD 5 */
+                   if( opt.pgp2_workarounds )
+                       afx->pgp2mode = 1;
+               }
+               n=0;
+                /* first a gpg control packet */
+                buf[n++] = 0xff; /* new format, type 63, 1 length byte */
+                n++;   /* see below */
+                memcpy(buf+n, sesmark, sesmarklen ); n+= sesmarklen;
+                buf[n++] = CTRLPKT_CLEARSIGN_START; 
+                buf[n++] = afx->not_dash_escaped? 0:1; /* sigclass */
+                if( hashes & 1 ) 
+                    buf[n++] = DIGEST_ALGO_RMD160;
+                if( hashes & 2 ) 
+                    buf[n++] = DIGEST_ALGO_SHA1;
+                if( hashes & 4 ) 
+                    buf[n++] = DIGEST_ALGO_MD5;
+                if( hashes & 8 ) 
+                    buf[n++] = DIGEST_ALGO_TIGER;
+                buf[1] = n - 2;
+
+               /* followed by a plaintext packet */
+               buf[n++] = 0xaf; /* old packet format, type 11, var length */
+               buf[n++] = 0;    /* set the length header */
+               buf[n++] = 6;
+               buf[n++] = 't';  /* canonical text mode */
+               buf[n++] = 0;    /* namelength */
+               memset(buf+n, 0, 4); /* timestamp */
+               n += 4;
            }
            else if( !rc )
                rc = radix64_read( afx, a, &n, buf, size );
        }
        else
            rc = radix64_read( afx, a, &n, buf, size );
-
+      #if 0
+       if( n )
+           if( fwrite(buf, n, 1, fp ) != 1 )
+               BUG();
+      #endif
        *ret_len = n;
     }
-    else if( control == IOBUFCTRL_FLUSH ) {
+    else if( control == IOBUFCTRL_FLUSH && !afx->cancel ) {
        if( !afx->status ) { /* write the header line */
+           const char *s;
+
            if( afx->what >= DIM(head_strings) )
                log_bug("afx->what=%d", afx->what);
            iobuf_writestr(a, "-----");
            iobuf_writestr(a, head_strings[afx->what] );
-           iobuf_writestr(a, "-----\n");
-           iobuf_writestr(a, "Version: G10 pre-release "  VERSION "\n");
-           iobuf_writestr(a, "Comment: This is an alpha test version!\n\n");
+           iobuf_writestr(a, "-----" LF );
+           if( !opt.no_version )
+               iobuf_writestr(a, "Version: GnuPG v"  VERSION " ("
+                                             PRINTABLE_OS_NAME ")" LF );
+
+           /* write the comment string or a default one */
+           s = opt.comment_string;
+           if( s && *s ) {
+               iobuf_writestr(a, "Comment: " );
+               for( ; *s; s++ ) {
+                   if( *s == '\n' )
+                       iobuf_writestr(a, "\\n" );
+                   else if( *s == '\r' )
+                       iobuf_writestr(a, "\\r" );
+                   else if( *s == '\v' )
+                       iobuf_writestr(a, "\\v" );
+                   else
+                       iobuf_put(a, *s );
+               }
+               iobuf_writestr(a, LF );
+           }
+
+           if ( afx->hdrlines ) {
+                for ( s = afx->hdrlines; *s; s++ ) {
+                  #ifdef HAVE_DOSISH_SYSTEM
+                    if ( *s == '\n' )
+                        iobuf_put( a, '\r');
+                  #endif
+                    iobuf_put(a, *s );
+                }
+            }
+           iobuf_writestr(a, LF );
            afx->status++;
            afx->idx = 0;
            afx->idx2 = 0;
            afx->crc = CRCINIT;
+
        }
        crc = afx->crc;
        idx = afx->idx;
@@ -726,8 +980,8 @@ armor_filter( void *opaque, int control,
                iobuf_put(a, c);
                c = bintoasc[radbuf[2]&077];
                iobuf_put(a, c);
-               if( ++idx2 > (72/4) ) {
-                   iobuf_put(a, '\n');
+               if( ++idx2 >= (64/4) ) { /* pgp doesn't like 72 here */
+                   iobuf_writestr(a, LF );
                    idx2=0;
                }
            }
@@ -742,8 +996,13 @@ armor_filter( void *opaque, int control,
        if( !is_initialized )
            initialize();
     }
+    else if( control == IOBUFCTRL_CANCEL ) {
+       afx->cancel = 1;
+    }
     else if( control == IOBUFCTRL_FREE ) {
-       if( afx->status ) { /* pad, write cecksum, and bottom line */
+       if( afx->cancel )
+           ;
+       else if( afx->status ) { /* pad, write cecksum, and bottom line */
            crc = afx->crc;
            idx = afx->idx;
            idx2 = afx->idx2;
@@ -765,11 +1024,14 @@ armor_filter( void *opaque, int control,
                    iobuf_put(a, c);
                    iobuf_put(a, '=');
                }
-               ++idx2;
+               if( ++idx2 >= (64/4) ) { /* pgp doesn't like 72 here */
+                   iobuf_writestr(a, LF );
+                   idx2=0;
+               }
            }
            /* may need a linefeed */
-           if( idx2 < (72/4) )
-               iobuf_put(a, '\n');
+           if( idx2 )
+               iobuf_writestr(a, LF );
            /* write the CRC */
            iobuf_put(a, '=');
            radbuf[0] = crc >>16;
@@ -783,17 +1045,278 @@ armor_filter( void *opaque, int control,
            iobuf_put(a, c);
            c = bintoasc[radbuf[2]&077];
            iobuf_put(a, c);
-           iobuf_put(a, '\n');
+           iobuf_writestr(a, LF );
            /* and the the trailer */
            if( afx->what >= DIM(tail_strings) )
                log_bug("afx->what=%d", afx->what);
            iobuf_writestr(a, "-----");
            iobuf_writestr(a, tail_strings[afx->what] );
-           iobuf_writestr(a, "-----\n");
+           iobuf_writestr(a, "-----" LF );
+       }
+       else if( !afx->any_data && !afx->inp_bypass ) {
+           log_error(_("no valid OpenPGP data found.\n"));
+           afx->no_openpgp_data = 1;
+           write_status_text( STATUS_NODATA, "1" );
        }
+       if( afx->truncated )
+           log_info(_("invalid armor: line longer than %d characters\n"),
+                     MAX_LINELEN );
+       /* issue an error to enforce dissemination of correct software */
+       if( afx->qp_detected )
+           log_error(_("quoted printable character in armor - "
+                       "probably a buggy MTA has been used\n") );
+       m_free( afx->buffer );
+       afx->buffer = NULL;
     }
     else if( control == IOBUFCTRL_DESC )
        *(char**)buf = "armor_filter";
     return rc;
 }
 
+
+/****************
+ * create a radix64 encoded string.
+ */
+char *
+make_radix64_string( const byte *data, size_t len )
+{
+    char *buffer, *p;
+
+    buffer = p = m_alloc( (len+2)/3*4 + 1 );
+    for( ; len >= 3 ; len -= 3, data += 3 ) {
+       *p++ = bintoasc[(data[0] >> 2) & 077];
+       *p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077];
+       *p++ = bintoasc[(((data[1]<<2)&074)|((data[2]>>6)&03))&077];
+       *p++ = bintoasc[data[2]&077];
+    }
+    if( len == 2 ) {
+       *p++ = bintoasc[(data[0] >> 2) & 077];
+       *p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077];
+       *p++ = bintoasc[((data[1]<<2)&074)];
+    }
+    else if( len == 1 ) {
+       *p++ = bintoasc[(data[0] >> 2) & 077];
+       *p++ = bintoasc[(data[0] <<4)&060];
+    }
+    *p = 0;
+    return buffer;
+}
+
+
+/***********************************************
+ *  For the pipemode command we can't use the armor filter for various
+ *  reasons, so we use this new unarmor_pump stuff to remove the armor 
+ */
+
+enum unarmor_state_e {
+    STA_init = 0,
+    STA_bypass,
+    STA_wait_newline,
+    STA_wait_dash,
+    STA_first_dash, 
+    STA_compare_header,
+    STA_found_header_wait_newline,
+    STA_skip_header_lines,
+    STA_skip_header_lines_non_ws,
+    STA_read_data,
+    STA_wait_crc,
+    STA_read_crc,
+    STA_ready
+};
+
+struct unarmor_pump_s {
+    enum unarmor_state_e state;
+    byte val;
+    int checkcrc;
+    int pos;   /* counts from 0..3 */
+    u32 crc;
+    u32 mycrc; /* the one store in the data */
+};
+
+
+
+UnarmorPump
+unarmor_pump_new (void)
+{
+    UnarmorPump x;
+
+    if( !is_initialized )
+        initialize();
+    x = m_alloc_clear (sizeof *x);
+    return x;
+}
+
+void
+unarmor_pump_release (UnarmorPump x)
+{
+    m_free (x);
+}
+
+/* 
+ * Get the next character from the ascii armor taken from the IOBUF
+ * created earlier by unarmor_pump_new().
+ * Return:  c = Character
+ *        256 = ignore this value
+ *         -1 = End of current armor 
+ *         -2 = Premature EOF (not used)
+ *         -3 = Invalid armor
+ */
+int
+unarmor_pump (UnarmorPump x, int c)
+{
+    int rval = 256; /* default is to ignore the return value */
+
+    switch (x->state) {
+      case STA_init:
+        { 
+            byte tmp[1];
+            tmp[0] = c; 
+            if ( is_armored (tmp) )
+                x->state = c == '-'? STA_first_dash : STA_wait_newline;
+            else {
+                x->state = STA_bypass;
+                return c;
+            }
+        }
+        break;
+      case STA_bypass:
+        return c; /* return here to avoid crc calculation */
+      case STA_wait_newline:
+        if (c == '\n')
+            x->state = STA_wait_dash;
+        break;
+      case STA_wait_dash:
+        x->state = c == '-'? STA_first_dash : STA_wait_newline;
+        break;
+      case STA_first_dash: /* just need for initalization */
+        x->pos = 0;
+        x->state = STA_compare_header;
+      case STA_compare_header:
+        if ( "-----BEGIN PGP SIGNATURE-----"[++x->pos] == c ) {
+            if ( x->pos == 28 ) 
+                x->state = STA_found_header_wait_newline;
+        }
+        else 
+            x->state = c == '\n'? STA_wait_dash : STA_wait_newline;
+        break;
+      case STA_found_header_wait_newline:
+        /* to make CR,LF issues easier we simply allow for white space
+           behind the 5 dashes */
+        if ( c == '\n' )
+            x->state = STA_skip_header_lines;
+        else if ( c != '\r' && c != ' ' && c != '\t' )
+            x->state = STA_wait_dash; /* garbage after the header line */
+        break;
+      case STA_skip_header_lines:
+        /* i.e. wait for one empty line */
+        if ( c == '\n' ) {
+            x->state = STA_read_data;
+            x->crc = CRCINIT;
+            x->val = 0;
+            x->pos = 0;
+        }
+        else if ( c != '\r' && c != ' ' && c != '\t' )
+            x->state = STA_skip_header_lines_non_ws;
+        break;
+      case STA_skip_header_lines_non_ws:
+        /* like above but we already encountered non white space */
+        if ( c == '\n' )
+            x->state = STA_skip_header_lines;
+        break;
+      case STA_read_data:
+        /* fixme: we don't check for the trailing dash lines but rely
+         * on the armor stop characters */
+        if( c == '\n' || c == ' ' || c == '\r' || c == '\t' )
+            break; /* skip all kind of white space */
+
+        if( c == '=' ) { /* pad character: stop */
+            if( x->pos == 1 ) /* in this case val has some value */
+                rval = x->val;
+            x->state = STA_wait_crc;
+            break;
+        }
+
+        {
+            int c2;
+            if( (c = asctobin[(c2=c)]) == 255 ) {
+                log_error(_("invalid radix64 character %02x skipped\n"), c2);
+                break;
+            }
+        }
+        
+        switch(x->pos) {
+          case 0:
+            x->val = c << 2;
+            break;
+          case 1:
+            x->val |= (c>>4)&3;
+            rval = x->val;
+            x->val = (c<<4)&0xf0;
+            break;
+          case 2:
+            x->val |= (c>>2)&15;
+            rval = x->val;
+            x->val = (c<<6)&0xc0;
+            break;
+          case 3:
+            x->val |= c&0x3f;
+            rval = x->val;
+            break;
+        }
+        x->pos = (x->pos+1) % 4;
+        break;
+      case STA_wait_crc:
+        if( c == '\n' || c == ' ' || c == '\r' || c == '\t' || c == '=' )
+            break; /* skip ws and pad characters */
+        /* assume that we are at the next line */
+        x->state = STA_read_crc;
+        x->pos = 0;
+        x->mycrc = 0;
+      case STA_read_crc:
+        if( (c = asctobin[c]) == 255 ) {
+            rval = -1; /* ready */
+            if( x->crc != x->mycrc ) {
+                log_info (_("CRC error; %06lx - %06lx\n"),
+                          (ulong)x->crc, (ulong)x->mycrc);
+                if ( invalid_crc() )
+                    rval = -3;
+            }
+            x->state = STA_ready; /* not sure whether this is correct */
+            break;
+        }
+        
+        switch(x->pos) {
+          case 0:
+            x->val = c << 2;
+            break;
+          case 1:
+            x->val |= (c>>4)&3;
+            x->mycrc |= x->val << 16;
+            x->val = (c<<4)&0xf0;
+            break;
+          case 2:
+            x->val |= (c>>2)&15;
+            x->mycrc |= x->val << 8;
+            x->val = (c<<6)&0xc0;
+            break;
+          case 3:
+            x->val |= c&0x3f;
+            x->mycrc |= x->val;
+            break;
+        }
+        x->pos = (x->pos+1) % 4;
+        break;
+      case STA_ready:
+        rval = -1;
+        break;
+    }
+
+    if ( !(rval & ~255) ) { /* compute the CRC */
+        x->crc = (x->crc << 8) ^ crc_table[((x->crc >> 16)&0xff) ^ rval];
+        x->crc &= 0x00ffffff;
+    }
+
+    return rval;
+}
+
+