Updated FSF's address.
[gnupg.git] / sm / base64.c
index f60015d..59ab6f2 100644 (file)
@@ -1,5 +1,5 @@
 /* base64.c 
- *     Copyright (C) 2001 Free Software Foundation, Inc.
+ *     Copyright (C) 2001, 2003 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -15,7 +15,8 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
  */
 
 #include <config.h>
 #include <time.h>
 #include <assert.h>
 
+#include "gpgsm.h"
+
 #include <ksba.h>
 
-#include "gpgsm.h"
 #include "i18n.h"
 
+#ifdef HAVE_DOSISH_SYSTEM
+  #define LF "\r\n"
+#else
+  #define LF "\n"
+#endif
 
 /* data used by the reader callbacks */
 struct reader_cb_parm_s {
@@ -42,13 +49,18 @@ struct reader_cb_parm_s {
   int have_lf;
   unsigned long line_counter;
 
-  int autodetect; /* try to detect the input encoding */
-  int assume_pem; /* assume input encoding is PEM */
-  int assume_base64; /* assume inpout is base64 encoded */
+  int allow_multi_pem;  /* Allow processing of multiple PEM objects. */
+  int autodetect;       /* Try to detect the input encoding. */
+  int assume_pem;       /* Assume input encoding is PEM. */
+  int assume_base64;    /* Assume input is base64 encoded. */
 
   int identified;
   int is_pem;
+  int is_base64;
   int stop_seen;
+  int might_be_smime;
+
+  int eof_seen;         
 
   struct {
     int idx;
@@ -57,14 +69,34 @@ struct reader_cb_parm_s {
   } base64;
 };
 
+/* data used by the writer callbacks */
+struct writer_cb_parm_s {
+  FILE *fp;
+  const char *pem_name;
+  
+  int wrote_begin;
+  int did_finish;
+
+  struct {
+    int idx;
+    int quad_count;
+    unsigned char radbuf[4];
+  } base64;
+
+};
+
 
+/* context for this module's functions */
 struct base64_context_s {
-  struct reader_cb_parm_s rparm;
+  union {
+    struct reader_cb_parm_s rparm;
+    struct writer_cb_parm_s wparm;
+  } u;
 };
 
 
 /* The base-64 character list */
-static unsigned char bintoasc[64] = 
+static char bintoasc[64] = 
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 
        "abcdefghijklmnopqrstuvwxyz" 
        "0123456789+/"; 
@@ -95,6 +127,31 @@ static unsigned char asctobin[256] = {
 };
 
 
+static int
+has_only_base64 (const unsigned char *line, int linelen)
+{
+  if (linelen < 20)
+    return 0;
+  for (; linelen; line++, linelen--)
+    {
+      if (*line == '\n' || (linelen > 1 && *line == '\r' && line[1] == '\n'))
+          break;
+      if ( !strchr (bintoasc, *line) )
+        return 0;
+    }
+  return 1;  /* yes */
+}
+
+static int
+is_empty_line (const unsigned char *line, int linelen)
+{
+  if (linelen >= 2 && *line == '\r' && line[1] == '\n')
+    return 1;
+  if (linelen >= 1 && *line == '\n')
+    return 1;
+  return 0;
+}
+
 
 static int
 base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread)
@@ -118,6 +175,7 @@ base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread)
           c = getc (parm->fp);
           if (c == EOF)
             {
+              parm->eof_seen = 1;
               if (ferror (parm->fp))
                 return -1;
               break; 
@@ -126,7 +184,7 @@ base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread)
           if (c == '\n')
             {
               parm->have_lf = 1;
-              /* FIXME: we need to skip overlong lines while detecting
+              /* Fixme: we need to skip overlong lines while detecting
                  the dashed lines */
               break;
             }
@@ -139,24 +197,65 @@ base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread)
 
   if (!parm->identified)
     {
-      if (parm->line_counter == 1 && !parm->have_lf)
+      if (!parm->autodetect)
+        {
+          if (parm->assume_pem)
+            {
+              /* wait for the header line */
+              parm->linelen = parm->readpos = 0;
+              if (!parm->have_lf 
+                  || strncmp ((char*)parm->line, "-----BEGIN ", 11)
+                  || !strncmp ((char*)parm->line+11, "PGP ", 4))
+                goto next;
+              parm->is_pem = 1;
+            }
+          else if (parm->assume_base64)
+            parm->is_base64 = 1;
+        }
+      else if (parm->line_counter == 1 && !parm->have_lf)
         {
           /* first line too long - assume DER encoding */
           parm->is_pem = 0;
         }
       else if (parm->line_counter == 1 && parm->linelen && *parm->line == 0x30)
         {
-          /* the very first bytes does pretty much look like a SEQUENCE tag*/
+          /* the very first byte does pretty much look like a SEQUENCE tag*/
           parm->is_pem = 0;
         }
-      else if ( parm->have_lf && !strncmp (parm->line, "-----BEGIN ", 11)
-                && strncmp (parm->line+11, "PGP ", 4) )
+      else if ( parm->have_lf
+                && !strncmp ((char*)parm->line, "-----BEGIN ", 11)
+                && strncmp ((char *)parm->line+11, "PGP ", 4) )
         {
           /* Fixme: we must only compare if the line really starts at
              the beginning */
           parm->is_pem = 1;
           parm->linelen = parm->readpos = 0;
         }
+      else if ( parm->have_lf && parm->line_counter == 1
+                && parm->linelen >= 13
+                && !ascii_memcasecmp (parm->line, "Content-Type:", 13))
+        { /* might be a S/MIME body */
+          parm->might_be_smime = 1;
+          parm->linelen = parm->readpos = 0;
+          goto next;
+        }
+      else if (parm->might_be_smime == 1
+               && is_empty_line (parm->line, parm->linelen))
+        {
+          parm->might_be_smime = 2;
+          parm->linelen = parm->readpos = 0;
+          goto next;
+        }
+      else if (parm->might_be_smime == 2)
+        {
+          parm->might_be_smime = 0;
+          if ( !has_only_base64 (parm->line, parm->linelen))
+            {
+              parm->linelen = parm->readpos = 0;
+              goto next;
+            }
+          parm->is_pem = 1;
+        }
       else
         {
           parm->linelen = parm->readpos = 0;
@@ -169,13 +268,29 @@ base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread)
   
 
   n = 0;
-  if (parm->is_pem)
+  if (parm->is_pem || parm->is_base64)
     {  
-      if (parm->have_lf && !strncmp (parm->line, "-----END ", 9))
+      if (parm->is_pem && parm->have_lf
+          && !strncmp ((char*)parm->line, "-----END ", 9))
         { 
           parm->identified = 0;
           parm->linelen = parm->readpos = 0;
-          /* let us return 0 */
+
+          /* If the caller want to read multiple PEM objects from one
+             file, we have to reset our internal state and return a
+             EOF immediately. The caller is the expected to use
+             ksba_reader_clear to clear the EOF condition and continue
+             to read.  If we don't want to do that we just return 0
+             bytes which will force the ksba_reader to skip until
+             EOF. */
+          if (parm->allow_multi_pem)
+            {
+              parm->identified = 0;
+              parm->autodetect = 0;
+              parm->assume_pem = 1;
+              parm->stop_seen = 0;
+              return -1; /* Send EOF now. */
+            }
         }
       else if (parm->stop_seen)
         { /* skip the rest of the line */
@@ -263,6 +378,7 @@ simple_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread)
       c = getc (parm->fp);
       if (c == EOF)
         {
+          parm->eof_seen = 1;
           if ( ferror (parm->fp) )
             return -1;
           if (n)
@@ -278,6 +394,122 @@ simple_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread)
 
 
 
+\f
+static int
+base64_writer_cb (void *cb_value, const void *buffer, size_t count)
+{
+  struct writer_cb_parm_s *parm = cb_value;
+  unsigned char radbuf[4];
+  int i, c, idx, quad_count;
+  const unsigned char *p;
+  FILE *fp = parm->fp;
+
+  if (!count)
+    return 0;
+
+  if (!parm->wrote_begin)
+    {
+      if (parm->pem_name)
+        {
+          fputs ("-----BEGIN ", fp);
+          fputs (parm->pem_name, fp);
+          fputs ("-----\n", fp);
+        }
+      parm->wrote_begin = 1;
+      parm->base64.idx = 0;
+      parm->base64.quad_count = 0;
+    }
+
+  idx = parm->base64.idx;
+  quad_count = parm->base64.quad_count;
+  for (i=0; i < idx; i++)
+    radbuf[i] = parm->base64.radbuf[i];
+
+  for (p=buffer; count; p++, count--)
+    {
+      radbuf[idx++] = *p;
+      if (idx > 2)
+        {
+          idx = 0;
+          c = bintoasc[(*radbuf >> 2) & 077];
+          putc (c, fp);
+          c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077];
+          putc (c, fp);
+          c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077];
+          putc (c, fp);
+          c = bintoasc[radbuf[2]&077];
+          putc (c, fp);
+          if (++quad_count >= (64/4)) 
+            {
+              fputs (LF, fp);
+              quad_count = 0;
+            }
+        }
+    }
+  for (i=0; i < idx; i++)
+    parm->base64.radbuf[i] = radbuf[i];
+  parm->base64.idx = idx;
+  parm->base64.quad_count = quad_count;
+
+  return ferror (fp) ? gpg_error_from_errno (errno) : 0;
+}
+
+static int
+base64_finish_write (struct writer_cb_parm_s *parm)
+{
+  unsigned char radbuf[4];
+  int i, c, idx, quad_count;
+  FILE *fp = parm->fp;
+
+  if (!parm->wrote_begin)
+    return 0; /* nothing written */
+
+  /* flush the base64 encoding */
+  idx = parm->base64.idx;
+  quad_count = parm->base64.quad_count;
+  for (i=0; i < idx; i++)
+    radbuf[i] = parm->base64.radbuf[i];
+
+  if (idx)
+    {
+      c = bintoasc[(*radbuf>>2)&077];
+      putc (c, fp);
+      if (idx == 1)
+        {
+          c = bintoasc[((*radbuf << 4) & 060) & 077];
+          putc (c, fp);
+          putc ('=', fp);
+          putc ('=', fp);
+        }
+      else 
+        { 
+          c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077];
+          putc (c, fp);
+          c = bintoasc[((radbuf[1] << 2) & 074) & 077];
+          putc (c, fp);
+          putc ('=', fp);
+
+        }
+      if (++quad_count >= (64/4)) 
+        {
+          fputs (LF, fp);
+          quad_count = 0;
+        }
+    }
+
+  if (quad_count)
+    fputs (LF, fp);
+
+  if (parm->pem_name)
+    {
+      fputs ("-----END ", fp);
+      fputs (parm->pem_name, fp);
+      fputs ("-----\n", fp);
+    }
+  return ferror (fp)? gpg_error (gpg_err_code_from_errno (errno)) : 0;
+}
+
+
 
 \f
 /* Create a reader for the given file descriptor.  Depending on the
@@ -285,51 +517,55 @@ simple_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread)
    The function returns a Base64Context object which must be passed to
    the gpgme_destroy_reader function.  The created KsbaReader object
    is also returned, but the caller must not call the
-   ksba_reader_release function on. */
+   ksba_reader_release function on.  If ALLOW_MULTI_PEM is true, the
+   reader expects that the caller uses ksba_reader_clear after EOF
+   until no more objects were found. */
 int
 gpgsm_create_reader (Base64Context *ctx,
-                     CTRL ctrl, FILE *fp, KsbaReader *r_reader)
+                     CTRL ctrl, FILE *fp, int allow_multi_pem,
+                     ksba_reader_t *r_reader)
 {
   int rc;
-  KsbaReader r;
+  ksba_reader_t r;
 
   *r_reader = NULL;
   *ctx = xtrycalloc (1, sizeof **ctx);
   if (!*ctx)
-    return seterr (Out_Of_Core);
+    return OUT_OF_CORE (errno);
+  (*ctx)->u.rparm.allow_multi_pem = allow_multi_pem;
 
-  r = ksba_reader_new ();
-  if (!r)
+  rc = ksba_reader_new (&r);
+  if (rc)
     {
       xfree (*ctx); *ctx = NULL;
-      return seterr (Out_Of_Core);
+      return rc;
     }
 
-  (*ctx)->rparm.fp = fp;
+  (*ctx)->u.rparm.fp = fp;
   if (ctrl->is_pem)
     {
-      (*ctx)->rparm.assume_pem = 1;
-      (*ctx)->rparm.assume_base64 = 1;
-      rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->rparm);
+      (*ctx)->u.rparm.assume_pem = 1;
+      (*ctx)->u.rparm.assume_base64 = 1;
+      rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm);
     }
   else if (ctrl->is_base64)
     {
-      (*ctx)->rparm.assume_base64 = 1;
-      rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->rparm);
+      (*ctx)->u.rparm.assume_base64 = 1;
+      rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm);
     }
   else if (ctrl->autodetect_encoding)
     {
-      (*ctx)->rparm.autodetect = 1;
-      rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->rparm);
+      (*ctx)->u.rparm.autodetect = 1;
+      rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm);
     }
   else
-      rc = ksba_reader_set_cb (r, simple_reader_cb, &(*ctx)->rparm);
+      rc = ksba_reader_set_cb (r, simple_reader_cb, &(*ctx)->u.rparm);
 
   if (rc)
     {
       ksba_reader_release (r);
       xfree (*ctx); *ctx = NULL;
-      return map_ksba_err (rc);
+      return rc;
     }
 
   *r_reader = r;
@@ -337,6 +573,12 @@ gpgsm_create_reader (Base64Context *ctx,
 }
 
 
+int
+gpgsm_reader_eof_seen (Base64Context ctx)
+{
+  return ctx && ctx->u.rparm.eof_seen;
+}
+
 void
 gpgsm_destroy_reader (Base64Context ctx)
 {
@@ -353,26 +595,30 @@ gpgsm_destroy_reader (Base64Context ctx)
    function on. */
 int
 gpgsm_create_writer (Base64Context *ctx,
-                     CTRL ctrl, FILE *fp, KsbaWriter *r_writer)
+                     CTRL ctrl, FILE *fp, ksba_writer_t *r_writer)
 {
   int rc;
-  KsbaWriter w;
+  ksba_writer_t w;
 
   *r_writer = NULL;
   *ctx = xtrycalloc (1, sizeof **ctx);
   if (!*ctx)
-    return seterr (Out_Of_Core);
+    return OUT_OF_CORE (errno);
 
-  w = ksba_writer_new ();
-  if (!w)
+  rc = ksba_writer_new (&w);
+  if (rc)
     {
       xfree (*ctx); *ctx = NULL;
-      return seterr (Out_Of_Core);
+      return rc;
     }
 
   if (ctrl->create_pem || ctrl->create_base64)
     {
-      return seterr (Not_Implemented);
+      (*ctx)->u.wparm.fp = fp;
+      if (ctrl->create_pem)
+        (*ctx)->u.wparm.pem_name = ctrl->pem_name? ctrl->pem_name
+                                                 : "CMS OBJECT";
+      rc = ksba_writer_set_cb (w, base64_writer_cb, &(*ctx)->u.wparm);
     }
   else
     rc = ksba_writer_set_file (w, fp);
@@ -381,7 +627,7 @@ gpgsm_create_writer (Base64Context *ctx,
     {
       ksba_writer_release (w);
       xfree (*ctx); *ctx = NULL;
-      return map_ksba_err (rc);
+      return rc;
     }
 
   *r_writer = w;
@@ -389,13 +635,24 @@ gpgsm_create_writer (Base64Context *ctx,
 }
 
 
+int
+gpgsm_finish_writer (Base64Context ctx)
+{
+  struct writer_cb_parm_s *parm;
+  
+  if (!ctx)
+    return gpg_error (GPG_ERR_INV_VALUE);
+  parm = &ctx->u.wparm;
+  if (parm->did_finish)
+    return 0; /* already done */
+  parm->did_finish = 1;
+  if (!parm->fp)
+    return 0; /* callback was not used */
+  return base64_finish_write (parm);
+}
+
 void
 gpgsm_destroy_writer (Base64Context ctx)
 {
   xfree (ctx);
 }
-
-
-
-
-