bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

generic crypto


From: Simon Josefsson
Subject: generic crypto
Date: Thu, 06 Oct 2005 15:39:20 +0200
User-agent: Gnus/5.110004 (No Gnus v0.4) Emacs/22.0.50 (gnu/linux)

This is a first attempt at a "middle layer" crypto module.  I have
installed it in GNU SASL, which only need gc_md5, gc_hmac_md5 and
gc_nonce.  I have not tested this much.  To see a more complete gc.h
compare:

http://josefsson.org/cgi-bin/viewcvs.cgi/gsasl/lib/crypto/Attic/gc.h?rev=1.5&hideattic=0&view=auto
http://cvs.gnupg.org/cgi-bin/viewcvs.cgi/gnutls/crypto/gc.h?rev=1.15&root=GNU+TLS+Library&view=auto
http://cvs.gnupg.org/cgi-bin/viewcvs.cgi/gnutls/crypto/gc-nettle.c?rev=1.14&root=GNU+TLS+Library&view=auto

Some notes:

* Perhaps the /dev/*random reading should be separated into a separate
  module?  It might be useful outside of the gc layer too.  I'm also
  not sure about the names of those functions, they suggest a more
  higher-level API than what is really offered (i.e., the names
  "nonce" and "pseudo_random" and "random" imply certain cryptographic
  properties).  Perhaps gc_dev_random and gc_dev_urandom?  Or
  something.

* The 'void*' vs 'char*' vs 'uint8_t*' vs 'unsigned char*' issue apply
  to this module as well.

* The gc module doesn't depend directly on md5 nor hmac-md5.  Instead,
  it include the same files in the Files: section, and the M4 function
  of those modules are called conditioned on whether libgcrypt is used
  or not.  So if a file is added to the md5 module, the gc module
  would have to be updated too.  I don't like this, but thought this
  was the simplest solution now.  A better solution would involve
  making the md5/hmac-md5 m4 aware of gc, and that if gc is also used,
  and that if gc decide that libgcrypt should be used, the
  md5/hmac-md5 modules should not do anything.

I'm sure there are plenty of other problems too, but this passes self
tests in GNU SASL so it is fairly complete...  Once this is installed,
I can continue to add the modules that GnuTLS needs, which include
encryption modules such as DES and AES.

Thanks,
Simon

Index: modules/gc
===================================================================
RCS file: modules/gc
diff -N modules/gc
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ modules/gc  5 Oct 2005 15:46:17 -0000
@@ -0,0 +1,37 @@
+Description:
+Core files for generic crypto package
+
+Files:
+lib/gc.h
+lib/gc-libgcrypt.c
+lib/gc-gnulib.c
+m4/gc.m4
+lib/md5.h
+lib/md5.c
+m4/md5.m4
+m4/uint32_t.m4
+lib/hmac.h
+lib/hmac-md5.c
+m4/hmac-md5.m4
+lib/memxor.h
+lib/memxor.c
+m4/memxor.m4
+
+Depends-on:
+havelib
+restrict
+
+configure.ac:
+gl_GC
+
+Makefile.am:
+lib_LIBADD += $(LIBGCRYPT)
+
+Include:
+"gc.h"
+
+License:
+LGPL
+
+Maintainer:
+Simon Josefsson
Index: modules/gc-tests
===================================================================
RCS file: modules/gc-tests
diff -N modules/gc-tests
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ modules/gc-tests    5 Oct 2005 15:46:17 -0000
@@ -0,0 +1,11 @@
+Files:
+tests/test-gc.c
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-gc
+noinst_PROGRAMS += test-gc
+test_gc_SOURCES = test-gc.c
Index: m4/gc.m4
===================================================================
RCS file: m4/gc.m4
diff -N m4/gc.m4
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ m4/gc.m4    5 Oct 2005 15:46:17 -0000
@@ -0,0 +1,102 @@
+# gc.m4 serial 1
+dnl Copyright (C) 2005 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_GC],
+[
+  AC_LIBSOURCES([gc.h, gc-gnulib.c, gc-libgcrypt.c])
+  AC_ARG_WITH(libgcrypt,
+    AS_HELP_STRING([--with-libgcrypt], [use libgcrypt for low-level crypto]),
+    libgcrypt=$withval, libgcrypt=no)
+  if test "$libgcrypt" != no; then
+    AC_LIB_HAVE_LINKFLAGS([gcrypt])
+  fi
+  if test "$ac_cv_libgcrypt" = yes; then
+    AC_CHECK_HEADER(gcrypt.h)
+    AC_LIBOBJ([gc-libgcrypt])
+  else
+    AC_LIBOBJ([gc-gnulib])
+    gl_MD5
+    gl_MEMXOR
+    gl_HMAC_MD5
+
+    # Devices with randomness.
+    # FIXME: Are these the best defaults?
+
+    case "${target}" in
+      *-openbsd*)
+       NAME_OF_RANDOM_DEVICE="/dev/srandom"
+       NAME_OF_PSEUDO_RANDOM_DEVICE="/dev/prandom"
+       NAME_OF_NONCE_DEVICE="/dev/urandom"
+          ;;
+  
+      *-netbsd*)
+       NAME_OF_RANDOM_DEVICE="/dev/srandom"
+       NAME_OF_PSEUDO_RANDOM_DEVICE="/dev/urandom"
+       NAME_OF_NONCE_DEVICE="/dev/urandom"
+          ;;
+  
+      *-solaris* | *-irix* | *-dec-osf* )
+       NAME_OF_RANDOM_DEVICE="/dev/random"
+       NAME_OF_PSEUDO_RANDOM_DEVICE="/dev/random"
+       NAME_OF_NONCE_DEVICE="/dev/random"
+          ;;
+  
+      *)
+       NAME_OF_RANDOM_DEVICE="/dev/random"
+       NAME_OF_PSEUDO_RANDOM_DEVICE="/dev/urandom"
+       NAME_OF_NONCE_DEVICE="/dev/urandom"
+          ;;
+    esac
+  
+    AC_MSG_CHECKING([device with (strong) random data...])
+    AC_ARG_ENABLE(random-device,
+       AC_HELP_STRING([--enable-random-device],
+               [device with (strong) randomness (for Nettle)]),
+       NAME_OF_RANDOM_DEVICE=$enableval)
+    AC_MSG_RESULT($NAME_OF_RANDOM_DEVICE)
+  
+    AC_MSG_CHECKING([device with pseudo random data...])
+    AC_ARG_ENABLE(pseudo-random-device,
+       AC_HELP_STRING([--enable-pseudo-random-device],
+               [device with pseudo randomness (for Nettle)]),
+       NAME_OF_PSEUDO_RANDOM_DEVICE=$enableval)
+    AC_MSG_RESULT($NAME_OF_PSEUDO_RANDOM_DEVICE)
+  
+    AC_MSG_CHECKING([device with unpredictable data for nonces...])
+    AC_ARG_ENABLE(nonce-device,
+       AC_HELP_STRING([--enable-nonce-device],
+               [device with unpredictable nonces (for Nettle)]),
+       NAME_OF_NONCE_DEVICE=$enableval)
+    AC_MSG_RESULT($NAME_OF_NONCE_DEVICE)
+  
+    if test "$cross_compiling" != yes; then
+      AC_CHECK_FILE($NAME_OF_RANDOM_DEVICE,, AC_MSG_ERROR([[
+        *** Device for (strong) random data $NAME_OF_RANDOM_DEVICE does not 
exist
+      ]]))
+      AC_CHECK_FILE($NAME_OF_PSEUDO_RANDOM_DEVICE,, AC_MSG_ERROR([[
+        *** Device for pseudo-random data $NAME_OF_PSEUDO_RANDOM_DEVICE does 
not exist
+      ]]))
+      AC_CHECK_FILE($NAME_OF_NONCE_DEVICE,, AC_MSG_ERROR([[
+        *** Device for unpredictable nonces $NAME_OF_NONCE_DEVICE does not 
exist
+      ]]))
+    else
+      AC_MSG_NOTICE([[Cross compiling, assuming random devices exists...]])  
+    fi
+  
+    # FIXME: Open+read 42 bytes+close twice and compare data.  Should differ.
+  
+    AC_DEFINE_UNQUOTED(NAME_OF_RANDOM_DEVICE, "$NAME_OF_RANDOM_DEVICE",
+                     [defined to the name of the (strong) random device])
+    AC_DEFINE_UNQUOTED(NAME_OF_PSEUDO_RANDOM_DEVICE,
+                        "$NAME_OF_PSEUDO_RANDOM_DEVICE",
+                     [defined to the name of the pseudo random device])
+    AC_DEFINE_UNQUOTED(NAME_OF_NONCE_DEVICE, "$NAME_OF_NONCE_DEVICE",
+                     [defined to the name of the unpredictable nonce device])
+  fi
+])
+
+# Prerequisites of lib/gc.h
+AC_DEFUN([gl_PREREQ_GC], [:])
Index: lib/gc.h
===================================================================
RCS file: lib/gc.h
diff -N lib/gc.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ lib/gc.h    5 Oct 2005 15:46:17 -0000
@@ -0,0 +1,63 @@
+/* gc.h --- Header file for implementation agnostic crypto wrapper API.
+ * Copyright (C) 2002, 2003, 2004, 2005  Simon Josefsson
+ *
+ * This file 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, or (at your
+ * option) any later version.
+ *
+ * This file 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+#ifndef GC_H
+# define GC_H
+
+/* Get size_t. */
+# include <stddef.h>
+
+#define GC_MD5_DIGEST_SIZE 16
+
+enum Gc_rc
+  {
+    GC_OK = 0,
+    GC_MALLOC_ERROR,
+    GC_INIT_ERROR,
+    GC_RANDOM_ERROR,
+    GC_INVALID_CIPHER,
+    GC_INVALID_HASH,
+    GC_PKCS5_INVALID_ITERATION_COUNT,
+    GC_PKCS5_INVALID_DERIVED_KEY_LENGTH,
+    GC_PKCS5_DERIVED_KEY_TOO_LONG
+  };
+typedef enum Gc_rc Gc_rc;
+
+extern int gc_init (void);
+extern void gc_done (void);
+
+/* Memory allocation (avoid). */
+typedef void *(*gc_malloc_t) (size_t n);
+typedef int (*gc_secure_check_t) (const void *);
+typedef void *(*gc_realloc_t) (void *p, size_t n);
+typedef void (*gc_free_t) (void *);
+extern void gc_set_allocators (gc_malloc_t func_malloc,
+                              gc_malloc_t secure_malloc,
+                              gc_secure_check_t secure_check,
+                              gc_realloc_t func_realloc,
+                              gc_free_t func_free);
+
+/* One-call interface. */
+extern int gc_md5 (const void *in, size_t inlen, void *resbuf);
+extern int gc_hmac_md5 (const void *key, size_t keylen,
+                       const void *in, size_t inlen,
+                       char *resbuf);
+
+#endif /* GC_H */
Index: lib/gc-gnulib.c
===================================================================
RCS file: lib/gc-gnulib.c
diff -N lib/gc-gnulib.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ lib/gc-gnulib.c     5 Oct 2005 15:46:17 -0000
@@ -0,0 +1,149 @@
+/* gc-gl-common.c --- Common gnulib internal crypto interface functions
+ * Copyright (C) 2002, 2003, 2004, 2005  Simon Josefsson
+ *
+ * This file is part of GC.
+ *
+ * GC is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * GC 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 Lesser General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License License along with GC; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ */
+
+/* Note: This file is only built if GC uses internal functions. */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+
+/* Get prototype. */
+#include <gc.h>
+
+/* For randomize. */
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <string.h>
+
+int
+gc_init (void)
+{
+  return 0;
+}
+
+void
+gc_done (void)
+{
+  return;
+}
+
+/* Randomness. */
+
+static int
+randomize (int level, char *data, size_t datalen)
+{
+  int fd;
+  const char *device;
+  size_t len = 0;
+  int rc;
+
+  switch (level)
+    {
+    case 0:
+      device = NAME_OF_NONCE_DEVICE;
+      break;
+
+    case 1:
+      device = NAME_OF_PSEUDO_RANDOM_DEVICE;
+      break;
+
+    default:
+      device = NAME_OF_RANDOM_DEVICE;
+      break;
+    }
+
+  fd = open (device, O_RDONLY);
+  if (fd < 0)
+    return GC_RANDOM_ERROR;
+
+  do
+    {
+      ssize_t tmp;
+
+      tmp = read (fd, data, datalen);
+
+      if (tmp < 0)
+       return GC_RANDOM_ERROR;
+
+      len += tmp;
+    }
+  while (len < datalen);
+
+  rc = close (fd);
+  if (rc < 0)
+    return GC_RANDOM_ERROR;
+
+  return GC_OK;
+}
+
+int
+gc_nonce (char *data, size_t datalen)
+{
+  return randomize (0, data, datalen);
+}
+
+int
+gc_pseudo_random (char *data, size_t datalen)
+{
+  return randomize (1, data, datalen);
+}
+
+int
+gc_random (char *data, size_t datalen)
+{
+  return randomize (2, data, datalen);
+}
+
+/* Memory allocation. */
+
+void
+gc_set_allocators (gc_malloc_t func_malloc,
+                  gc_malloc_t secure_malloc,
+                  gc_secure_check_t secure_check,
+                  gc_realloc_t func_realloc, gc_free_t func_free)
+{
+  return;
+}
+
+#include "md5.h"
+
+int
+gc_md5 (const void *in, size_t inlen, void *resbuf)
+{
+  md5_buffer (in, inlen, resbuf);
+  return 0;
+}
+
+#include "hmac.h"
+
+int
+gc_hmac_md5 (const void *key, size_t keylen,
+            const void *in, size_t inlen, char *resbuf)
+{
+  hmac_md5 (key, keylen, in, inlen, resbuf);
+  return 0;
+}
Index: lib/gc-libgcrypt.c
===================================================================
RCS file: lib/gc-libgcrypt.c
diff -N lib/gc-libgcrypt.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ lib/gc-libgcrypt.c  5 Oct 2005 15:46:17 -0000
@@ -0,0 +1,156 @@
+/* gc-libgcrypt.c --- Crypto wrappers around Libgcrypt for GC.
+ * Copyright (C) 2002, 2003, 2004, 2005  Simon Josefsson
+ *
+ * This file 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, or (at your
+ * option) any later version.
+ *
+ * This file 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+/* Note: This file is only built if GC uses Libgcrypt. */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Get prototype. */
+#include "gc.h"
+
+/* Get libgcrypt API. */
+#include <gcrypt.h>
+
+#include <assert.h>
+
+/* Initialization. */
+
+int
+gc_init (void)
+{
+  gcry_error_t err;
+
+  err = gcry_control (GCRYCTL_ANY_INITIALIZATION_P);
+  if (err == GPG_ERR_NO_ERROR)
+    {
+      if (gcry_check_version (GCRYPT_VERSION) == NULL)
+       return GC_INIT_ERROR;
+
+      err = gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL, 0);
+      if (err != GPG_ERR_NO_ERROR)
+       return GC_INIT_ERROR;
+    }
+
+  return GC_OK;
+}
+
+void
+gc_done (void)
+{
+  return;
+}
+
+/* Randomness. */
+
+int
+gc_nonce (char *data, size_t datalen)
+{
+  gcry_create_nonce ((unsigned char *) data, datalen);
+  return GC_OK;
+}
+
+int
+gc_pseudo_random (char *data, size_t datalen)
+{
+  gcry_randomize ((unsigned char *) data, datalen, GCRY_STRONG_RANDOM);
+  return GC_OK;
+}
+
+int
+gc_random (char *data, size_t datalen)
+{
+  gcry_randomize ((unsigned char *) data, datalen, GCRY_VERY_STRONG_RANDOM);
+  return GC_OK;
+}
+
+/* Memory allocation. */
+
+void
+gc_set_allocators (gc_malloc_t func_malloc,
+                  gc_malloc_t secure_malloc,
+                  gc_secure_check_t secure_check,
+                  gc_realloc_t func_realloc, gc_free_t func_free)
+{
+  gcry_set_allocation_handler (func_malloc, secure_malloc, secure_check,
+                              func_realloc, func_free);
+}
+
+/* One-call interface. */
+
+int
+gc_md5 (const void *in, size_t inlen, void *resbuf)
+{
+  size_t outlen = gcry_md_get_algo_dlen (GCRY_MD_MD5);
+  gcry_md_hd_t hd;
+  gpg_error_t err;
+  unsigned char *p;
+
+  assert (outlen == 16);
+
+  err = gcry_md_open (&hd, GCRY_MD_MD5, 0);
+  if (err != GPG_ERR_NO_ERROR)
+    return GC_INVALID_HASH;
+
+  gcry_md_write (hd, in, inlen);
+
+  p = gcry_md_read (hd, GCRY_MD_MD5);
+  if (p == NULL)
+    return GC_INVALID_HASH;
+
+  memcpy (resbuf, p, outlen);
+
+  gcry_md_close (hd);
+
+  return GC_OK;
+}
+
+int
+gc_hmac_md5 (const void *key, size_t keylen,
+            const void *in, size_t inlen, char *resbuf)
+{
+  size_t hlen = gcry_md_get_algo_dlen (GCRY_MD_MD5);
+  gcry_md_hd_t mdh;
+  unsigned char *hash;
+  gpg_error_t err;
+
+  assert (hlen == 16);
+
+  err = gcry_md_open (&mdh, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
+  if (err != GPG_ERR_NO_ERROR)
+    return GC_INVALID_HASH;
+
+  err = gcry_md_setkey (mdh, key, keylen);
+  if (err != GPG_ERR_NO_ERROR)
+    return GC_INVALID_HASH;
+
+  gcry_md_write (mdh, in, inlen);
+
+  hash = gcry_md_read (mdh, GCRY_MD_MD5);
+  if (hash == NULL)
+    return GC_INVALID_HASH;
+
+  memcpy (resbuf, hash, hlen);
+
+  gcry_md_close (mdh);
+
+  return GC_OK;
+}
Index: tests/test-gc.c
===================================================================
RCS file: tests/test-gc.c
diff -N tests/test-gc.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ tests/test-gc.c     5 Oct 2005 15:46:17 -0000
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2005 Free Software Foundation
+ * Written by Simon Josefsson
+ *
+ * This program 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, or (at your option)
+ * any later version.
+ *
+ * This program 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.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.  */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "gc.h"
+
+int
+main (int argc, char *argv[])
+{
+
+  /* Test vectors from RFC 1321. */
+
+  {
+    char *in = "abcdefghijklmnopqrstuvwxyz";
+    size_t inlen = strlen (in);
+    char *expect =
+      "\xc3\xfc\xd3\xd7\x61\x92\xe4\x00\x7d\xfb\x49\x6c\xca\x67\xe1\x3b";
+    char out[16];
+
+    /* MD5 ("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b */
+
+    if (gc_md5 (in, inlen, out) != 0)
+      {
+       printf ("gc_md5 call failed\n");
+       return 1;
+      }
+
+    if (memcmp (out, expect, 16) != 0)
+      {
+       size_t i;
+       printf ("md5 1 missmatch. expected:\n");
+       for (i = 0; i < 16; i++)
+         printf ("%02x ", expect[i] & 0xFF);
+       printf ("\ncomputed:\n");
+       for (i = 0; i < 16; i++)
+         printf ("%02x ", out[i] & 0xFF);
+       printf ("\n");
+       return 1;
+      }
+  }
+
+    /* Test vectors from RFC 2104. */
+
+  {
+    char *key =
+      "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b";
+    size_t key_len = 16;
+    char *data = "Hi There";
+    size_t data_len = 8;
+    char *digest =
+      "\x92\x94\x72\x7a\x36\x38\xbb\x1c\x13\xf4\x8e\xf8\x15\x8b\xfc\x9d";
+    char out[16];
+
+    /*
+      key =         0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
+      key_len =     16 bytes
+      data =        "Hi There"
+      data_len =    8  bytes
+      digest =      0x9294727a3638bb1c13f48ef8158bfc9d
+    */
+
+    if (gc_hmac_md5 (key, key_len, data, data_len, out) != 0)
+      {
+       printf ("call failure\n");
+       return 1;
+      }
+
+    if (memcmp (digest, out, 16) != 0)
+      {
+       size_t i;
+       printf ("hash 1 missmatch. expected:\n");
+       for (i = 0; i < 16; i++)
+         printf ("%02x ", digest[i] & 0xFF);
+       printf ("\ncomputed:\n");
+       for (i = 0; i < 16; i++)
+         printf ("%02x ", out[i] & 0xFF);
+       printf ("\n");
+       return 1;
+      }
+  }
+
+  return 0;
+}




reply via email to

[Prev in Thread] Current Thread [Next in Thread]