Add a lot of more stuff.
authorWerner Koch <wk@gnupg.org>
Wed, 2 Apr 2014 19:50:27 +0000 (21:50 +0200)
committerWerner Koch <wk@gnupg.org>
Wed, 2 Apr 2014 19:50:27 +0000 (21:50 +0200)
* src/cred.c, src/cred.h: New.  Based on code from libassuan.
* src/t-connection.c: New.
* src/util.c (trim_spaces): New.  From GnuPG.
(keyvalue_put, keyvalue_putf): Add sanity check.
* src/util.h (spacep, digitp, hexdigitp): New.
(ascii_isspace): New.
* src/stripe.c (stripe_create_card_token): Replace test key by option
(stripe_charge_card): New.
* src/payprocd.c: Add option parsing.  Beautify disagnositcs for new
connections.  Allow setting of a stripe key.
(already_running_p): Implement.
(create_socket): Move log file setting to ...
(launch_server): here.
* src/connection.c (set_error): New.
(struct conn_s): Add field IDNO.
(currency_table): New.
(new_connection_obj): Set IDNO
(fd_from_connection_obj): New.
(id_from_connection_obj): New.
(capitalize_name): Add special case for brackets.
(valid_currency_p): New.
(convert_amount): New.
(cmd_cardtoken): Add plausibility checks.
(cmd_chargecard): New.
(cmd_getinfo): New.
(cmd_ping): New.
(connection_handler): Add new commands.  Call es_fflush.

15 files changed:
.gitignore
configure.ac
src/Makefile.am
src/connection.c
src/connection.h
src/cred.c [new file with mode: 0644]
src/cred.h [new file with mode: 0644]
src/estream.c
src/payprocd.c
src/payprocd.h
src/stripe.c
src/stripe.h
src/t-connection.c [new file with mode: 0644]
src/util.c
src/util.h

index 9e0757d..f7ca90a 100644 (file)
@@ -28,3 +28,7 @@
 /src/y1
 /src/y2
 /src/y3
+/src/stripe.livekey
+/src/stripe.testkey
+/src/t-connection
+/src/out2
index 2d4f5d3..3c9c4f1 100644 (file)
@@ -130,7 +130,7 @@ AM_PATH_GPG_ERROR("$NEED_GPG_ERROR_VERSION",
 AM_PATH_NPTH("$NEED_NPTH_API:$NEED_NPTH_VERSION",have_npth=yes,have_npth=no)
 if test "$have_npth" = "yes"; then
   # We define this macro because some code parts require it.
-  AC_DEFINE(USE_NPTH, 1,
+  AC_DEFINE(HAVE_NPTH, 1,
               [Defined if the New Portable Thread Library is available])
 else
   AC_MSG_WARN([[
@@ -139,6 +139,7 @@ else
 *** we need the New Portable Threads Library.
 ***]])
 fi
+AC_DEFINE(USE_NPTH, 1, [Defined to use New Portable Thread Library])
 
 #
 # GNUTLS
@@ -200,6 +201,48 @@ AC_CHECK_FUNCS([strerror strlwr])
 # For http.c
 AC_CHECK_FUNCS([strtoull])
 
+# Check for the getsockopt SO_PEERCRED
+AC_MSG_CHECKING(for SO_PEERCRED)
+AC_CACHE_VAL(payproc_cv_sys_so_peercred,
+      [AC_TRY_COMPILE([#include <sys/socket.h>],
+         [struct ucred cr;
+          int cl = sizeof cr;
+          getsockopt (1, SOL_SOCKET, SO_PEERCRED, &cr, &cl);],
+          payproc_cv_sys_so_peercred=yes,
+          payproc_cv_sys_so_peercred=no)
+       ])
+AC_MSG_RESULT($payproc_cv_sys_so_peercred)
+
+if test $payproc_cv_sys_so_peercred = yes; then
+  AC_DEFINE(HAVE_SO_PEERCRED, 1,
+            [Defined if SO_PEERCRED is supported (Linux specific)])
+else
+  # Check for the getsockopt LOCAL_PEEREID (NetBSD)
+  AC_MSG_CHECKING(for LOCAL_PEEREID)
+  AC_CACHE_VAL(payproc_cv_sys_so_local_peereid,
+      [AC_TRY_COMPILE([#include <sys/socket.>
+         #include <sys/un.h>],
+         [struct unpcbid unp;
+          int unpl = sizeof unp;
+          getsockopt (1, SOL_SOCKET, LOCAL_PEEREID, &unp, &unpl);],
+          payproc_cv_sys_so_local_peereid=yes,
+          payproc_cv_sys_so_local_peereid=no)
+       ])
+  AC_MSG_RESULT($payproc_cv_sys_so_local_peereid)
+
+  if test $payproc_cv_sys_so_local_peereid = yes; then
+    AC_DEFINE(HAVE_LOCAL_PEEREID, 1,
+              [Defined if LOCAL_PEEREID is supported (NetBSD specific)])
+  else
+    # (Open)Solaris
+    AC_CHECK_FUNCS([getpeerucred], AC_CHECK_HEADERS([ucred.h]))
+    if test $ac_cv_func_getpeerucred != yes; then
+        # FreeBSD
+        AC_CHECK_FUNCS([getpeereid])
+    fi
+  fi
+fi
+
 
 #
 # Setup gcc specific options
index 3216881..9337b37 100644 (file)
@@ -30,19 +30,21 @@ utility_sources = \
        http.c http.h \
        cJSON.c cJSON.h \
        membuf.c membuf.h \
-       strlist.c strlist.h
+       strlist.c strlist.h \
+       argparse.c argparse.h
 
 payprocd_SOURCES = \
        payprocd.c payprocd.h \
        connection.c connection.h \
        stripe.c stripe.h \
        tlssupport.c tlssupport.h \
+       cred.c cred.h \
        $(utility_sources)
 
 noinst_PROGRAMS = $(module_tests) t-http
 TESTS = $(module_tests)
 
-module_tests =
+module_tests = t-connection
 
 AM_CFLAGS = $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(LIBGNUTLS_CFLAGS)
 LDADD  = -lm $(GPG_ERROR_LIBS) $(NPTH_LIBS) $(LIBGNUTLS_LIBS)
@@ -55,3 +57,7 @@ t_common_ldadd   = $(GPG_ERROR_LIBS) $(NPTH_LIBS) $(LIBGNUTLS_LIBS) -lm
 t_http_SOURCES = t-http.c $(t_common_sources)
 t_http_CFLAGS  = $(t_common_cflags)
 t_http_LDADD   = $(t_common_ldadd)
+
+t_connection_SOURCES = t-connection.c stripe.c $(t_common_sources)
+t_connection_CFLAGS  = $(t_common_cflags)
+t_connection_LDADD   = $(t_common_ldadd)
index 556a563..64b2fca 100644 (file)
 /* Maximum length of an input line.  */
 #define MAX_LINELEN  2048
 
+/* Helper macro for the cmd_ handlers.  */
+#define set_error(a,b)                          \
+  do {                                          \
+    err = gpg_error (GPG_ERR_ ## a);            \
+    conn->errdesc = (b);                        \
+  } while (0)
+
 
 /* Object describing a connection.  */
 struct conn_s
 {
+  unsigned int idno;     /* Connection id for logging.  */
   int fd;                /* File descriptor for this connection.  */
   estream_t stream;      /* The corresponding stream object.  */
   char *command;         /* The command line (malloced). */
   keyvalue_t dataitems;  /* The data items.  */
+  const char *errdesc;   /* Optional description of an error.  */
 };
 
 
+/* The list of supported currencies  */
+static struct
+{
+  const char *name;
+  unsigned char decdigits;
+  const char *desc;
+} currency_table[] = {
+  { "USD", 2, "US Dollar" },
+  { "EUR", 2, "Euro" },
+  { "GBP", 2, "British Pound" },
+  { "JPY", 0, "Yen" },
+  { NULL }
+};
+
 
 \f
 /* Allocate a new conenction object and return it.  Returns
@@ -52,7 +75,16 @@ struct conn_s
 conn_t
 new_connection_obj (void)
 {
-  return xtrycalloc (1, sizeof (struct conn_s));
+  static unsigned int counter;
+  conn_t conn;
+
+  conn = xtrycalloc (1, sizeof *conn);
+  if (conn)
+    {
+      conn->idno = ++counter;
+      conn->fd = -1;
+    }
+  return conn;
 }
 
 /* Initialize a connection object which has been alloacted with
@@ -82,17 +114,41 @@ release_connection_obj (conn_t conn)
 }
 
 
+/* Return the file descriptor for the conenction CONN.  */
+int
+fd_from_connection_obj (conn_t conn)
+{
+  return conn->fd;
+}
+
+
+unsigned int
+id_from_connection_obj (conn_t conn)
+{
+  return conn->idno;
+}
+
+
 /* Transform a data line name into a standard capitalized format; e.g.
    "Content-Type".  Conversion stops at the colon.  As usual we don't
-   use the localized versions of ctype.h. */
+   use the localized versions of ctype.h.  Parts inside of brackets
+   ([]) are not changed. */
 static void
 capitalize_name (char *name)
 {
   int first = 1;
+  int bracket = 0;
 
   for (; *name && *name != ':'; name++)
     {
-      if (*name == '-')
+      if (bracket)
+        {
+          if (*name == ']')
+            bracket--;
+        }
+      else if (*name == '[')
+        bracket++;
+      else if (*name == '-')
         first = 1;
       else if (first)
         {
@@ -120,11 +176,16 @@ store_data_line (conn_t conn, char *line)
     {
       /* Continuation.  */
       if (!conn->dataitems)
-        return GPG_ERR_PROTOCOL_VIOLATION;
+        return gpg_error (GPG_ERR_PROTOCOL_VIOLATION);
       return keyvalue_append_to_last (conn->dataitems, line);
     }
 
+  /* A name must start with a letter.  Note that for items used only
+     internally a name may start with an underscore. */
   capitalize_name (line);
+  if (*line < 'A' || *line > 'Z')
+    return gpg_error (GPG_ERR_INV_NAME);
+
   p = strchr (line, ':');
   if (!p)
     return GPG_ERR_PROTOCOL_VIOLATION;
@@ -254,7 +315,80 @@ read_request (conn_t conn)
 }
 
 
-/* The CARDTOKEN command creates a token for a card.  The follwing
+\f
+/*
+ * Helper functions.
+ */
+
+/* Check that the currency described by STRING is valid.  Returns true
+   if so.  The number of of digits after the decimal point for that
+   currency is stored at R_DECDIGITS.  */
+static int
+valid_currency_p (const char *string, int *r_decdigits)
+{
+  int i;
+
+  for (i=0; currency_table[i].name; i++)
+    if (!strcasecmp (string, currency_table[i].name))
+      {
+        *r_decdigits = currency_table[i].decdigits;
+        return 1;
+      }
+  return 0;
+}
+
+
+/* Check the amount given in STRING and convert it to the smallest
+   currency unit.  DECDIGITS gives the number of allowed post decimal
+   positions.  Return 0 on error or the converted amount.  */
+static unsigned int
+convert_amount (const char *string, int decdigits)
+{
+  const char *s;
+  int ndots = 0;
+  int nfrac = 0;
+  unsigned int value = 0;
+  unsigned int v;
+
+  if (*string == '+')
+    string++; /* Skip an optioanl leading plsu sign.  */
+  for (s = string; *s; s++)
+    {
+      if (*s == '.')
+        {
+          if (!decdigits)
+            return 0; /* Post decimal digits are not allowed.  */
+          if (++ndots > 1)
+            return 0; /* Too many decimal points.  */
+        }
+      else if (!strchr ("0123456789", *s))
+        return 0;
+      else if (ndots && ++nfrac > decdigits)
+        return 0; /* Too many post decimal digits.  */
+      else
+        {
+          v = 10*value + (*s - '0');
+          if (v < value)
+            return 0; /* Overflow.  */
+          value = v;
+        }
+    }
+
+  for (; nfrac < decdigits; nfrac++)
+    {
+      v = 10*value;
+      if (v < value)
+        return 0; /* Overflow.  */
+      value = v;
+    }
+
+  return value;
+}
+
+
+
+\f
+/* The CARDTOKEN command creates a token for a card.  The following
    values are expected in the dataitems:
 
    Number:     The number of the card
@@ -265,7 +399,7 @@ read_request (conn_t conn)
 
    On success these items are returned:
 
-   Token:     The once time use token
+   Token:     The one time use token
    Last4:     The last 4 digits of the card for display
    Live:      f in test mode, t in live mode.
 
@@ -274,15 +408,134 @@ static gpg_error_t
 cmd_cardtoken (conn_t conn, char *args)
 {
   gpg_error_t err;
+  keyvalue_t dict = conn->dataitems;
   keyvalue_t result = NULL;
   keyvalue_t kv;
+  const char *s;
+  int aint;
 
   (void)args;
 
+  s = keyvalue_get_string (dict, "Number");
+  if (!*s)
+    {
+      set_error (MISSING_VALUE, "Credit card number not given");
+      goto leave;
+    }
+
+  s = keyvalue_get_string (dict, "Exp-Year");
+  if (!*s || (aint = atoi (s)) < 2014 || aint > 2199 )
+    {
+      set_error (INV_VALUE, "Expiration year out of range");
+      goto leave;
+    }
+
+  s = keyvalue_get_string (dict, "Exp-Month");
+  if (!*s || (aint = atoi (s)) < 1 || aint > 12 )
+    {
+      set_error (INV_VALUE, "Invalid expiration month");
+      goto leave;
+    }
+
+  s = keyvalue_get_string (dict, "Cvc");
+  if (!*s || (aint = atoi (s)) < 100 || aint > 9999 )
+    {
+      set_error (INV_VALUE, "The CVC has not 2 or 4 digits");
+      goto leave;
+    }
+
+
   err = stripe_create_card_token (conn->dataitems, &result);
 
+ leave:
+  if (err)
+    es_fprintf (conn->stream, "ERR %d (%s)\n", err,
+                conn->errdesc? conn->errdesc : gpg_strerror (err));
+  else
+    es_fprintf (conn->stream, "OK\n");
+  for (kv = result; kv; kv = kv->next)
+    es_fprintf (conn->stream, "%s: %s\n", kv->name, kv->value);
+  keyvalue_release (result);
+
+  return err;
+}
+
+
+\f
+/* The CHARGECARD command charges the given amount to a card.  The
+   following values are expected in the dataitems:
+
+   Amount:     The amount to charge with optional decimal fraction.
+   Currency:   A 3 letter currency code (EUR, USD, GBP, JPY)
+   Card-Token: The token returned by the CARDTOKEN command.
+   Capture:    Optional; defaults to true.  If set to false
+               this command creates only an authorization.
+               The command CAPTURECHARGE must then be used
+               to actually charge the card. [currently ignored]
+   Desc:       Optional description of the charge.
+   Stmt-Desc:  Optional string to be displayed on the credit
+               card statement.  Will be truncated at about 15 characters.
+   Email:      Optional contact mail address of the customer
+   Meta[NAME]: Meta data further described by NAME.  This is used convey
+               application specific data to the log file.
+
+   On success these items are returned:
+
+   Charge-Id:  The ID describing this charge
+   Live:       f in test mode, t in live mode.
+   Currency:   The currency of the charge.
+   Amount:     The charged amount with optional decimal fraction.
+
+ */
+static gpg_error_t
+cmd_chargecard (conn_t conn, char *args)
+{
+  gpg_error_t err;
+  keyvalue_t dict = conn->dataitems;
+  keyvalue_t result = NULL;
+  keyvalue_t kv;
+  const char *s;
+  unsigned int cents;
+  int decdigs;
+
+  (void)args;
+
+  /* Get currency and amount.  */
+  s = keyvalue_get_string (dict, "Currency");
+  if (!valid_currency_p (s, &decdigs))
+    {
+      set_error (MISSING_VALUE, "Currency missing or not supported");
+      goto leave;
+    }
+
+  s = keyvalue_get_string (dict, "Amount");
+  if (!*s || !(cents = convert_amount (s, decdigs)))
+    {
+      set_error (MISSING_VALUE, "Amount missing or invalid");
+      goto leave;
+    }
+  err = keyvalue_putf (&conn->dataitems, "_amount", "%u", cents);
+  dict = conn->dataitems;
   if (err)
-    es_fprintf (conn->stream, "ERR %d (%s)\n", err, gpg_strerror (err));
+    goto leave;
+
+  /* We only support the use of a card token and no direct supply of
+     card details.  This makes it easies to protect or audit the
+     actual credit card data.  The token may only be used once.  */
+  s = keyvalue_get_string (dict, "Card-Token");
+  if (!*s)
+    {
+      set_error (MISSING_VALUE, "Card-Token missing");
+      goto leave;
+    }
+
+  /* Let's ask Stripe to process it.  */
+  err = stripe_charge_card (conn->dataitems, &result);
+
+ leave:
+  if (err)
+    es_fprintf (conn->stream, "ERR %d (%s)\n", err,
+                conn->errdesc? conn->errdesc : gpg_strerror (err));
   else
     es_fprintf (conn->stream, "OK\n");
   for (kv = result; kv; kv = kv->next)
@@ -293,6 +546,52 @@ cmd_cardtoken (conn_t conn, char *args)
 }
 
 
+\f
+/* GETINFO is a multipurpose command to return certain config data. It
+   requires a subcommand:
+
+     list-currencies
+
+
+ */
+static gpg_error_t
+cmd_getinfo (conn_t conn, char *args)
+{
+  int i;
+
+  if (has_leading_keyword (args, "list-currencies"))
+    {
+      es_fputs ("OK\n", conn->stream);
+      for (i=0; currency_table[i].name; i++)
+        es_fprintf (conn->stream, "# %s - %s\n",
+                    currency_table[i].name, currency_table[i].desc);
+    }
+  else
+    {
+      es_fputs ("ERR 1 (Unknown sub-command)\n"
+                "# Supported sub-commands are:\n"
+                "#   list-currencies  - List supported currencies\n"
+                , conn->stream);
+    }
+
+  return 0;
+}
+
+
+/* Process a PING command.  */
+static gpg_error_t
+cmd_ping (conn_t conn, char *args)
+{
+  if (*args)
+    es_fprintf (conn->stream, "OK %s\n",args);
+  else
+    es_fputs ("OK pong\n", conn->stream);
+
+  return 0;
+}
+
+
+\f
 /* The handler serving a connection.  */
 void
 connection_handler (conn_t conn)
@@ -317,13 +616,20 @@ connection_handler (conn_t conn)
       es_fprintf (conn->stream, "ERR %u %s\n", err, gpg_strerror (err));
       return;
     }
+  es_fflush (conn->stream);
 
   if ((cmdargs = has_leading_keyword (conn->command, "CARDTOKEN")))
     err = cmd_cardtoken (conn, cmdargs);
+  else if ((cmdargs = has_leading_keyword (conn->command, "CHARGECARD")))
+    err = cmd_chargecard (conn, cmdargs);
+  else if ((cmdargs = has_leading_keyword (conn->command, "GETINFO")))
+    err = cmd_getinfo (conn, cmdargs);
+  else if ((cmdargs = has_leading_keyword (conn->command, "PING")))
+    err = cmd_ping (conn, cmdargs);
   else
     {
       es_fprintf (conn->stream, "ERR 1 (Unknown command)\n");
-      es_fprintf (conn->stream, "CMD: '%s'\n", conn->command);
+      es_fprintf (conn->stream, "_cmd: %s\n", conn->command);
       for (kv = conn->dataitems; kv; kv = kv->next)
         es_fprintf (conn->stream, "%s: %s\n", kv->name, kv->value);
     }
index ab325e6..682d8d8 100644 (file)
@@ -28,6 +28,8 @@ typedef struct conn_s *conn_t;
 conn_t new_connection_obj (void);
 void init_connection_obj (conn_t conn, int fd);
 void release_connection_obj (conn_t conn);
+unsigned int id_from_connection_obj (conn_t conn);
+int fd_from_connection_obj (conn_t conn);
 
 void connection_handler (conn_t conn);
 
diff --git a/src/cred.c b/src/cred.c
new file mode 100644 (file)
index 0000000..d055913
--- /dev/null
@@ -0,0 +1,95 @@
+/* cred.c - Credentials support
+ * Copyright (C) 2014 g10 Code GmbH
+ *
+ * This file is part of Payproc.
+ *
+ * Payproc 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Payproc 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#ifdef HAVE_UCRED_H
+# include <ucred.h>
+#endif
+
+
+#include "util.h"
+#include "cred.h"
+
+
+/* Retrieve the credentials from the peer using the connect file
+   descriptor FD.  Returns 0 on success or -1 on error.  */
+int
+credentials_from_socket (int fd, pid_t *r_pid, uid_t *r_uid, gid_t *r_gid)
+{
+  int rc = -1;
+
+#ifdef HAVE_SO_PEERCRED
+  {
+    struct ucred cr;
+    socklen_t cl = sizeof cr;
+
+    if (!getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl))
+      {
+         *r_pid = cr.pid;
+         *r_uid = cr.uid;
+         *r_gid = cr.gid;
+         rc = 0;
+      }
+  }
+#elif defined (HAVE_GETPEERUCRED)
+  {
+    ucred_t *ucred = NULL;
+
+    if (getpeerucred (fd, &ucred) != -1)
+      {
+       *r_pid = ucred_getpid (ucred);
+        *r_uid = ucred_geteuid (ucred);
+        *r_gid = ucred_getegid (ucred);
+        rc = 0;
+       ucred_free (ucred);
+      }
+  }
+#elif defined (HAVE_LOCAL_PEEREID)
+  {
+    struct unpcbid unp;
+    socklen_t unpl = sizeof unp;
+
+    if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1)
+      {
+       *r_pid = unp.unp_pid;
+       *r_uid = unp.unp_euid;
+       *r_gid = unp.unp_egid;
+       rc = 0;
+      }
+  }
+#elif defined(HAVE_GETPEEREID)
+  {
+    if (getpeereid (fd, r_uid, r_gid) != -1)
+      {
+       r_pid = (pid_t)(-1);
+       rc = 0;
+      }
+  }
+#endif
+
+  return rc;
+}
diff --git a/src/cred.h b/src/cred.h
new file mode 100644 (file)
index 0000000..276b7f6
--- /dev/null
@@ -0,0 +1,26 @@
+/* cred.h - Definitions pertaining to credentials.
+ * Copyright (C) 2014 g10 Code GmbH
+ *
+ * This file is part of Payproc.
+ *
+ * Payproc 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Payproc 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CRED_H
+#define CRED_H
+
+int credentials_from_socket (int fd, pid_t *r_pid, uid_t *r_uid, gid_t *r_gid);
+
+
+#endif /*CRED_H*/
index be791a8..a2e1509 100644 (file)
 # undef USE_NPTH
 #endif
 
+
+#undef HAVE_NPTH
+#warning Fix for npth
+
 #ifdef HAVE_NPTH
 # include <npth.h>
 #endif
@@ -164,16 +168,16 @@ typedef void (*func_free_t) (void *mem);
 
 #ifdef HAVE_NPTH
 
-typedef pth_mutex_t estream_mutex_t;
-# define ESTREAM_MUTEX_INITIALIZER PTH_MUTEX_INIT
+typedef npth_mutex_t estream_mutex_t;
+# define ESTREAM_MUTEX_INITIALIZER NPTH_MUTEX_INIT
 # define ESTREAM_MUTEX_LOCK(mutex)        \
-  pth_mutex_acquire (&(mutex), 0, NULL)
+  npth_mutex_lock (&(mutex))
 # define ESTREAM_MUTEX_UNLOCK(mutex)      \
-  pth_mutex_release (&(mutex))
+  npth_mutex_unlock (&(mutex))
 # define ESTREAM_MUTEX_TRYLOCK(mutex)     \
-  ((pth_mutex_acquire (&(mutex), 1, NULL) == TRUE) ? 0 : -1)
+  (npth_mutex_trylock (&(mutex))? 0 : -1)
 # define ESTREAM_MUTEX_INITIALIZE(mutex)  \
-  pth_mutex_init    (&(mutex))
+  npth_mutex_init (&(mutex), NULL)
 
 #else /*!HAVE_NPTH*/
 
@@ -203,9 +207,9 @@ dummy_mutex_call_int (estream_mutex_t mutex)
 /* Primitive system I/O.  */
 
 #ifdef HAVE_NPTH
-# define ESTREAM_SYS_READ  do_pth_read
-# define ESTREAM_SYS_WRITE do_pth_write
-# define ESTREAM_SYS_YIELD() pth_yield (NULL)
+# define ESTREAM_SYS_READ  do_npth_read
+# define ESTREAM_SYS_WRITE do_npth_write
+# define ESTREAM_SYS_YIELD() npth_usleep (0)
 #else
 # define ESTREAM_SYS_READ  read
 # define ESTREAM_SYS_WRITE write
@@ -450,35 +454,35 @@ do_list_remove (estream_t stream, int with_locked_list)
  * I/O Helper
  *
  * Unfortunately our Pth emulation for Windows expects system handles
- * for pth_read and pth_write.  We use a simple approach to fix this:
+ * for npth_read and npth_write.  We use a simple approach to fix this:
  * If the function returns an error we fall back to a vanilla read or
  * write, assuming that we do I/O on a plain file where the operation
- * can't block.
+ * can't block.  FIXME:  Is this still needed for npth?
  */
 #ifdef HAVE_NPTH
 static int
-do_pth_read (int fd, void *buffer, size_t size)
+do_npth_read (int fd, void *buffer, size_t size)
 {
 # ifdef HAVE_W32_SYSTEM
-  int rc = pth_read (fd, buffer, size);
+  int rc = npth_read (fd, buffer, size);
   if (rc == -1 && errno == EINVAL)
     rc = read (fd, buffer, size);
   return rc;
 # else /*!HAVE_W32_SYSTEM*/
-  return pth_read (fd, buffer, size);
+  return npth_read (fd, buffer, size);
 # endif /* !HAVE_W32_SYSTEM*/
 }
 
 static int
-do_pth_write (int fd, const void *buffer, size_t size)
+do_npth_write (int fd, const void *buffer, size_t size)
 {
 # ifdef HAVE_W32_SYSTEM
-  int rc = pth_write (fd, buffer, size);
+  int rc = npth_write (fd, buffer, size);
   if (rc == -1 && errno == EINVAL)
     rc = write (fd, buffer, size);
   return rc;
 # else /*!HAVE_W32_SYSTEM*/
-  return pth_write (fd, buffer, size);
+  return npth_write (fd, buffer, size);
 # endif /* !HAVE_W32_SYSTEM*/
 }
 #endif /*HAVE_NPTH*/
@@ -513,9 +517,7 @@ do_init (void)
   if (!initialized)
     {
 #ifdef HAVE_NPTH
-      if (!pth_init () && errno != EPERM )
-        return -1;
-      if (pth_mutex_init (&estream_list_lock))
+      if (npth_mutex_init (&estream_list_lock, NULL))
         initialized = 1;
 #else
       initialized = 1;
@@ -1039,8 +1041,9 @@ es_func_w32_read (void *cookie, void *buffer, size_t size)
       do
         {
 #ifdef HAVE_NPTH
-          /* Note: Our pth_read actually uses HANDLE! */
-          bytes_read = pth_read ((int)w32_cookie->hd, buffer, size);
+          /* Note: Our pth_read actually uses HANDLE!
+             FIXME: Check  whether this is the case for npth. */
+          bytes_read = npth_read ((int)w32_cookie->hd, buffer, size);
 #else
           DWORD nread, ec;
 
@@ -1085,7 +1088,7 @@ es_func_w32_write (void *cookie, const void *buffer, size_t size)
         {
 #ifdef HAVE_NPTH
           /* Note: Our pth_write actually uses HANDLE! */
-          bytes_written = pth_write ((int)w32_cookie->hd, buffer, size);
+          bytes_written = npth_write ((int)w32_cookie->hd, buffer, size);
 #else
           DWORD nwritten;
 
index 83885eb..88ac743 100644 (file)
 
 #include "util.h"
 #include "logging.h"
+#include "argparse.h"
 #include "estream.h"
 #include "connection.h"
 #include "tlssupport.h"
+#include "cred.h"
 #include "payprocd.h"
 
 
@@ -45,8 +47,6 @@
 /* The interval in seconds for the housekeeping thread.  */
 #define TIMERTICK_INTERVAL  60
 
-
-
 /* Flag indicating that the socket shall shall be removed by
    cleanup.  */
 static int remove_socket_flag;
@@ -57,12 +57,45 @@ static int shutdown_pending;
 /* Number of active connections.  */
 static int active_connections;
 
+/* Constants to identify the options. */
+enum opt_values
+  {
+    aNull = 0,
+    oVerbose   = 'v',
+    oAllowUID   = 'U',
+    oAllowGID   = 'G',
+
+    oLogFile   = 500,
+    oNoDetach,
+    oStripeKey,
+    oLive,
+
+    oLast
+  };
+
+
+/* The list of commands and options. */
+static ARGPARSE_OPTS opts[] = {
+  ARGPARSE_group (301, "@Options:\n "),
+
+  ARGPARSE_s_n (oVerbose,  "verbose",   "verbose"),
+  ARGPARSE_s_n (oNoDetach, "no-detach", "do not detach from the console"),
+  ARGPARSE_s_s (oLogFile,  "log-file",  "|FILE|write log output to FILE"),
+  ARGPARSE_s_s (oAllowUID, "allow-uid", "|N|allow access from uid N"),
+  ARGPARSE_s_s (oAllowGID, "allow-gid", "|N|allow access from gid N"),
+  ARGPARSE_s_s (oStripeKey,
+                "stripe-key", "|FILE|read key for Stripe account from FILE"),
+  ARGPARSE_s_n (oLive, "live",  "enable live mode"),
+
+  ARGPARSE_end ()
+};
+
 
 
 \f
 /* Local prototypes.  */
 static void cleanup (void);
-static void launch_server (void);
+static void launch_server (const char *logfile);
 static void server_loop (int fd);
 static void handle_signal (int signo);
 static void *connection_thread (void *arg);
@@ -70,18 +103,66 @@ static void *connection_thread (void *arg);
 
 
 \f
+static const char *
+my_strusage( int level )
+{
+  const char *p;
+
+  switch (level)
+    {
+    case 11: p = "payprocd"; break;
+    case 13: p = PACKAGE_VERSION; break;
+    case 19: p = "Please report bugs to bugs@g10code.com.\n"; break;
+    case 1:
+    case 40: p = "Usage: payprocd [options] (-h for help)"; break;
+    case 41: p = ("Syntax: payprocd [options]\n"
+                  "Start the payment processing daemon\n"); break;
+    default: p = NULL; break;
+    }
+  return p;
+}
+
+/* Set the Stripe secret key from file FNAME.  */
+static void
+set_stripe_key (const char *fname)
+{
+  FILE *fp;
+  char buf[128];
+
+  fp = fopen (fname, "r");
+  if (!fp)
+    log_error ("error opening key file '%s': %s\n", fname, strerror (errno));
+  else
+    {
+      if (!fgets (buf, sizeof buf, fp))
+        log_error ("error reading key from '%s': %s\n",
+                   fname, strerror (errno));
+      else
+        {
+          trim_spaces (buf);
+          if (strncmp (buf, "sk_test_", 8) && strncmp (buf, "sk_live_", 8))
+            log_error ("file '%s' seems not to carry a Stripe secret key\n",
+                       fname);
+          else
+            {
+              xfree (opt.stripe_secret_key);
+              opt.stripe_secret_key = xstrdup (buf);
+            }
+        }
+      fclose (fp);
+    }
+}
+
+
 int
 main (int argc, char **argv)
 {
-  const char *logfile = NULL;/*"tmp/payprocd.log";*/
-
-  (void)argc;
-  (void)argv;
-  opt.verbose = 1;
-  opt.nodetach = 1;
+  ARGPARSE_ARGS pargs;
+  const char *logfile = NULL;
 
   /* Set program name etc.  */
-  log_set_prefix ("payprocd", JNLIB_LOG_WITH_PREFIX|JNLIB_LOG_WITH_PID);
+  set_strusage (my_strusage);
+  log_set_prefix ("payprocd", JNLIB_LOG_WITH_PREFIX);
 
   /* Make sure that our subsystems are ready.  */
   es_init ();
@@ -107,16 +188,42 @@ main (int argc, char **argv)
   /* Initialze processing subsystems.  */
   init_tls_subsystem ();
 
-  /* Now start with logging to a file if this is desired. */
-  if (logfile)
+  /* Parse the command line. */
+  pargs.argc  = &argc;
+  pargs.argv  = &argv;
+  pargs.flags = ARGPARSE_FLAG_KEEP;
+  while (optfile_parse (NULL, NULL, NULL, &pargs, opts))
     {
-      log_set_file (logfile);
-      log_set_prefix (NULL, (JNLIB_LOG_WITH_PREFIX
-                             |JNLIB_LOG_WITH_TIME
-                             |JNLIB_LOG_WITH_PID));
+      switch (pargs.r_opt)
+        {
+        case oVerbose:  opt.verbose++; break;
+        case oNoDetach: opt.nodetach = 1; break;
+        case oLogFile:  logfile = pargs.r.ret_str; break;
+        case oAllowUID: /*FIXME*/ break;
+        case oAllowGID: /*FIXME*/ break;
+        case oStripeKey: set_stripe_key (pargs.r.ret_str); break;
+        case oLive: opt.livemode = 1; break;
+
+        default: pargs.err = ARGPARSE_PRINT_ERROR; break;
+       }
     }
 
-  launch_server ();
+  if (opt.livemode && (!opt.stripe_secret_key
+                       || strncmp (opt.stripe_secret_key, "sk_live_", 8)))
+    log_error ("live mode requested but no live key given\n");
+  else if (!opt.livemode
+           && opt.stripe_secret_key
+           && !strncmp (opt.stripe_secret_key, "sk_live_", 8))
+    log_error ("test mode requested but live key given\n");
+
+  if (log_get_errorcount (0))
+    exit (2);
+
+  if (opt.livemode)
+    log_fatal ("live mode rejected - we need more testing first\n");
+
+  /* Start the server.  */
+  launch_server (logfile);
 
   return 0;
 }
@@ -141,29 +248,63 @@ cleanup (void)
 static int
 already_running_p (const char *name)
 {
-  /* int rc; */
-  /* char *infostr, *p; */
-  /* int prot, pid; */
-
-  /* rc = assuan_new (&ctx); */
-  /* if (! rc) */
-  /*   rc = assuan_socket_connect (ctx, infostr, pid, 0); */
-  /* xfree (infostr); */
-  /* if (rc) */
-  /*   { */
-  /*     if (!mode && !silent) */
-  /*       log_error ("can't connect to the agent: %s\n", gpg_strerror (rc)); */
-
-  /*     if (ctx) */
-  /*       assuan_release (ctx); */
-  /*     return -1; */
-  /*   } */
-
-  /* if (!opt.quiet && !silent) */
-  /*   log_info ("gpg-agent running and available\n"); */
-
-  /* assuan_release (ctx); */
-  return 0;
+  struct sockaddr_un *addr;
+  socklen_t len;
+  int rc;
+  int fd;
+  estream_t stream;
+  char buffer[256];
+
+  fd = socket (AF_UNIX, SOCK_STREAM, 0);
+  if (fd == -1)
+    {
+      log_error ("error creating socket: %s\n",
+                 gpg_strerror (gpg_error_from_syserror()));
+      exit (2);
+    }
+
+  addr = xcalloc (1, sizeof *addr);
+  addr->sun_family = AF_UNIX;
+  if (strlen (name) + 1 >= sizeof (addr->sun_path))
+    {
+      log_error ("socket name '%s' is too long\n", name);
+      exit (2);
+    }
+  strcpy (addr->sun_path, name);
+  len = SUN_LEN (addr);
+
+  rc = connect (fd, (struct sockaddr *)addr, len);
+  if (rc == -1)
+    {
+      close (fd);
+      return 0; /* Probably not running.  Well, as long as the
+                   permissions are suitable.  */
+    }
+
+  /* Also do an alive check for diagnositc reasons.  */
+  stream = es_fdopen (fd, "r+b");
+  if (!stream)
+    {
+      log_error ("failed to fdopen connected socket: %s\n",
+                 gpg_strerror (gpg_error_from_syserror()));
+      close (fd);
+      return 1;  /* Assume it is running.  */
+    }
+  es_fputs ("PING\n\n", stream);
+  es_fflush (stream);
+  if (!es_fgets (buffer, sizeof buffer, stream))
+    {
+      log_error ("failed to read PING response from '%s': %s\n", name,
+                 gpg_strerror (gpg_error_from_syserror()));
+    }
+  else if (!has_leading_keyword (buffer, "OK"))
+    {
+      log_error ("PING command on '%s' failed *%s)\n", name, buffer);
+    }
+
+  es_fclose (stream);
+
+  return 1;  /* Assume the server is running.  */
 }
 
 
@@ -200,11 +341,8 @@ create_socket (char *name)
     {
       if (already_running_p (name))
         {
-          log_set_prefix (NULL, JNLIB_LOG_WITH_PREFIX);
-          log_set_file (NULL);
           log_error ("a payprocd process is already running - "
                      "not starting a new one\n");
-          *name = 0; /* Inhibit removal of the socket by cleanup(). */
           close (fd);
           exit (2);
         }
@@ -237,12 +375,9 @@ create_socket (char *name)
 }
 
 
-
-
-
 /* Fire up the server.  */
 static void
-launch_server (void)
+launch_server (const char *logfile)
 {
   int fd;
   pid_t pid;
@@ -269,6 +404,17 @@ launch_server (void)
 
   remove_socket_flag = 1;
 
+  if (logfile)
+    {
+      log_set_file (logfile);
+      log_set_prefix (NULL, (JNLIB_LOG_WITH_PREFIX
+                             |JNLIB_LOG_WITH_TIME
+                             |JNLIB_LOG_WITH_PID));
+    }
+  else
+    log_set_prefix (NULL, (JNLIB_LOG_WITH_PREFIX
+                           |JNLIB_LOG_WITH_PID));
+
   /* Detach from tty and put process into a new session */
   if (!opt.nodetach )
     {
@@ -505,16 +651,28 @@ static void *
 connection_thread (void *arg)
 {
   conn_t conn = arg;
+  pid_t pid;
+  uid_t uid;
+  gid_t gid;
+
+  if (credentials_from_socket (fd_from_connection_obj (conn), &pid, &uid, &gid))
+    {
+      log_error ("connection %u: credentials missing - closing\n",
+                 id_from_connection_obj (conn));
+      goto leave;
+    }
 
   if (opt.verbose)
-    log_info ("handler 0x%lx for %p started\n",
-              (unsigned long) npth_self (), conn);
+    log_info ("connection %u: started - pid=%u uid=%u gid=%u\n",
+              id_from_connection_obj (conn),
+              (unsigned int)pid, (unsigned int)uid, (unsigned int)gid);
 
   connection_handler (conn);
+
   if (opt.verbose)
-    log_info ("handler 0x%lx for %p terminated\n",
-              (unsigned long) npth_self (), conn);
+    log_info ("connection %u: terminated\n", id_from_connection_obj (conn));
 
+ leave:
   release_connection_obj (conn);
   return NULL;
 }
index b70ba41..2804905 100644 (file)
 /* The global options.  */
 struct
 {
-  int verbose;
-  int nodetach;  /* Run in foreground.  */
+  int verbose;   /* Verbose logging.  */
+  int nodetach;  /* Do not detach from the console.  */
+
+  int livemode;  /* Expect to be in live mode.  Default is test mode.  */
+  char *stripe_secret_key;  /* The secret key for stripe.com */
 
 } opt;
 
index 76e101e..9192ced 100644 (file)
@@ -190,7 +190,7 @@ call_stripe (const char *keystring, const char *method, const char *data,
 }
 
 
-
+/* The implementation of CARDTOKEN.  */
 gpg_error_t
 stripe_create_card_token (keyvalue_t dict, keyvalue_t *r_result)
 {
@@ -253,7 +253,7 @@ stripe_create_card_token (keyvalue_t dict, keyvalue_t *r_result)
     }
 
 
-  err = call_stripe ("sk_test_z1tnxBb2WbySaJBpuszRdzDn:",
+  err = call_stripe (opt.stripe_secret_key,
                      "tokens", NULL, query, &status, &json);
   log_debug ("call_stripe => %s status=%d\n", gpg_strerror (err), status);
   if (!err)
@@ -298,3 +298,127 @@ stripe_create_card_token (keyvalue_t dict, keyvalue_t *r_result)
   cJSON_Delete (json);
   return err;
 }
+
+
+/* The implementation of CHARGECARD.  */
+gpg_error_t
+stripe_charge_card (keyvalue_t dict, keyvalue_t *r_result)
+{
+  gpg_error_t err;
+  int status;
+  keyvalue_t query = NULL;
+  cjson_t json = NULL;
+  const char *s;
+  cjson_t j_obj;
+
+  *r_result = NULL;
+
+  s = keyvalue_get_string (dict, "Currency");
+  if (!*s)
+    {
+      err = gpg_error (GPG_ERR_MISSING_VALUE);
+      goto leave;
+    }
+  err = keyvalue_put (&query, "currency", s);
+  if (err)
+    goto leave;
+
+  /* _amount is the amount in the smallest unit of the currency.  */
+  s = keyvalue_get_string (dict, "_amount");
+  if (!*s)
+    {
+      err = gpg_error (GPG_ERR_MISSING_VALUE);
+      goto leave;
+    }
+  err = keyvalue_put (&query, "amount", s);
+  if (err)
+    goto leave;
+
+  s = keyvalue_get_string (dict, "Card-Token");
+  if (!*s)
+    {
+      err = gpg_error (GPG_ERR_MISSING_VALUE);
+      goto leave;
+    }
+  err = keyvalue_put (&query, "card", s);
+  if (err)
+    goto leave;
+
+  s = keyvalue_get_string (dict, "Desc");
+  if (*s)
+    {
+      err = keyvalue_put (&query, "description", s);
+      if (err)
+        goto leave;
+    }
+
+  s = keyvalue_get_string (dict, "Stmt-Desc");
+  if (*s)
+    {
+      err = keyvalue_put (&query, "statement_description", s);
+      if (err)
+        goto leave;
+    }
+
+
+  err = call_stripe (opt.stripe_secret_key,
+                     "charges", NULL, query, &status, &json);
+  log_debug ("call_stripe => %s status=%d\n", gpg_strerror (err), status);
+  if (!err)
+    log_debug ("Result:\n%s\n", cJSON_Print(json));
+  if (status != 200)
+    {
+      log_error ("charge_card: error: status=%u\n", status);
+      err = gpg_error (GPG_ERR_GENERAL);
+      goto leave;
+    }
+
+  j_obj = cJSON_GetObjectItem (json, "id");
+  if (!j_obj || !cjson_is_string (j_obj))
+    {
+      log_error ("charge_card: bad or missing 'id'\n");
+      err = gpg_error (GPG_ERR_GENERAL);
+      goto leave;
+    }
+  err = keyvalue_put (r_result, "Charge-Id", j_obj->valuestring);
+  if (err)
+    goto leave;
+
+  j_obj = cJSON_GetObjectItem (json, "livemode");
+  if (!j_obj || !(cjson_is_boolean (j_obj)))
+    {
+      log_error ("charge_card: bad or missing 'livemode'\n");
+      err = gpg_error (GPG_ERR_GENERAL);
+      goto leave;
+    }
+  err = keyvalue_put (r_result, "Live", cjson_is_true (j_obj)?"t":"f");
+  if (err)
+    goto leave;
+
+  j_obj = cJSON_GetObjectItem (json, "currency");
+  if (!j_obj || !cjson_is_string (j_obj))
+    {
+      log_error ("charge_card: bad or missing 'currency'\n");
+      err = gpg_error (GPG_ERR_GENERAL);
+      goto leave;
+    }
+  err = keyvalue_put (r_result, "Currency", j_obj->valuestring);
+  if (err)
+    goto leave;
+
+  j_obj = cJSON_GetObjectItem (json, "amount");
+  if (!j_obj || !cjson_is_number (j_obj))
+    {
+      log_error ("charge_card: bad or missing 'amount'\n");
+      err = gpg_error (GPG_ERR_GENERAL);
+      goto leave;
+    }
+  err = keyvalue_putf (r_result, "_amount", "%d", j_obj->valueint);
+  if (err)
+    goto leave;
+
+ leave:
+  keyvalue_release (query);
+  cJSON_Delete (json);
+  return err;
+}
index dcc99fc..2c71866 100644 (file)
@@ -21,6 +21,7 @@
 #define STRIPE_H
 
 gpg_error_t stripe_create_card_token (keyvalue_t dict, keyvalue_t *r_result);
+gpg_error_t stripe_charge_card (keyvalue_t dict, keyvalue_t *r_result);
 
 
 #endif /*STRIPE_H*/
diff --git a/src/t-connection.c b/src/t-connection.c
new file mode 100644 (file)
index 0000000..09d55ce
--- /dev/null
@@ -0,0 +1,109 @@
+/* t-connection.c - Regression test for parts of connection.c
+ * Copyright (C) 2014 g10 Code GmbH
+ *
+ * This file is part of Payproc.
+ *
+ * Payproc 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Payproc 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <string.h>
+
+#include "t-common.h"
+
+#include "connection.c" /* The module under test.  */
+
+
+static void
+test_convert_amount (void)
+{
+  static struct
+  {
+    int digits;
+    const char *string;
+    unsigned int expected;
+  } tv[] = {
+    { 0, "", 0 },
+    { 0, " ", 0 },
+    { 0, "\t", 0 },
+    { 0, "-1", 0 },
+    { 2, "1.23", 123 },
+    { 2, "+1.23", 123 },
+    { 2, "-1.23", 0 },
+    { 2, "1.2", 120 },
+    { 2, "1.", 100 },
+    { 2, "1", 100 },
+    { 2, "20", 2000 },
+    { 2, "20.01", 2001 },
+    { 2, "20.09", 2009 },
+    { 2, "23.59", 2359 },
+    { 2, "23.50", 2350 },
+    { 2, "23.5",  2350 },
+    { 2, "23",    2300 },
+    { 2, "23+",   0 },
+    { 2, "451",    45100 },
+    { 2, "451.00", 45100 },
+    { 2, "451..00", 0 },
+    { 2, "45.1.00", 0 },
+    { 2, "4512.00", 451200 },
+    { 2, "451200000000000000000000000000000000000000000000.00", 0 },
+    { 3, "20", 20000 },
+    { 3, "20.01", 20010 },
+    { 3, "20.09", 20090 },
+    { 3, "23.59", 23590 },
+    { 3, "23.50", 23500 },
+    { 3, "23.507",23507 },
+    { 3, "23.5",  23500 },
+    { 1, "20",      200 },
+    { 1, "20.01",     0 },
+    { 1, "20.09",     0 },
+    { 1, "23.59",     0 },
+    { 1, "23.50",     0 },
+    { 1, "23.5",    235 },
+    { 1, "23",      230 },
+    { 0, "20",       20 },
+    { 0, "20.01",     0 },
+    { 0, "20.09",     0 },
+    { 0, "23.59",     0 },
+    { 0, "23.50",     0 },
+    { 0, "23.5",      0 },
+    { 0, "23",       23 }
+  };
+  int tidx;
+
+  for (tidx=0; tidx < DIM (tv); tidx++)
+    {
+      unsigned int value;
+
+      value = convert_amount (tv[tidx].string, tv[tidx].digits);
+      if (value == tv[tidx].expected)
+        pass ();
+      else
+        fail (tidx);
+    }
+}
+
+
+int
+main (int argc, char **argv)
+{
+  if (argc > 1 && !strcmp (argv[1], "--verbose"))
+    verbose = 1;
+
+  test_convert_amount ();
+
+  return !!errorcount;
+}
index 5a8e412..13b21ad 100644 (file)
@@ -27,6 +27,7 @@
 #include <unistd.h>
 #include <stdarg.h>
 #include <errno.h>
+#include <ctype.h>
 
 #include "util.h"
 #include "estream.h"
@@ -161,6 +162,36 @@ has_leading_keyword (const char *string, const char *keyword)
 }
 
 
+/*
+ * Remove leading and trailing white space from STR.  Return STR.
+ */
+char *
+trim_spaces (char *str)
+{
+  char *string, *p, *mark;
+
+  string = str;
+  /* Find first non space character.  */
+  for( p=string; *p && isspace (*(unsigned char*)p) ; p++ )
+    ;
+  /* Move characters. */
+  for (mark = NULL; (*string = *p); string++, p++ )
+    {
+      if (isspace (*(unsigned char*)p))
+        {
+          if (!mark)
+            mark = string;
+        }
+      else
+        mark = NULL;
+    }
+  if (mark)
+    *mark = '\0' ;  /* Remove trailing spaces. */
+
+  return str;
+}
+
+
 \f
 keyvalue_t
 keyvalue_create (const char *key, const char *value)
@@ -206,6 +237,9 @@ keyvalue_put (keyvalue_t *list, const char *key, const char *value)
 {
   keyvalue_t kv;
 
+  if (!key || !*key || !value)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
   kv = keyvalue_create (key, value);
   if (!kv)
     return gpg_error_from_syserror ();
@@ -222,6 +256,9 @@ keyvalue_putf (keyvalue_t *list, const char *key, const char *format, ...)
   char *value;
   keyvalue_t kv;
 
+  if (!key || !*key)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
   va_start (arg_ptr, format);
   value = es_vasprintf (format, arg_ptr);
   va_end (arg_ptr);
index 9b6ee5b..68ae341 100644 (file)
                   } while(0)
 #define wipememory(_ptr,_len) wipememory2(_ptr,0,_len)
 
+/* Macros to replace ctype ones to avoid locale problems. */
+#define spacep(p)   (*(p) == ' ' || *(p) == '\t')
+#define digitp(p)   (*(p) >= '0' && *(p) <= '9')
+#define hexdigitp(a) (digitp (a)                     \
+                      || (*(a) >= 'A' && *(a) <= 'F')  \
+                      || (*(a) >= 'a' && *(a) <= 'f'))
+/* Note this isn't identical to a C locale isspace() without \f and
+   \v, but works for the purposes used here. */
+#define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
+
 
 /* The default error source of the application.  This is different
    from GPG_ERR_SOURCE_DEFAULT in that it does not depend on the
@@ -91,6 +101,7 @@ char *strconcat (const char *s1, ...) JNLIB_GCC_A_SENTINEL(0);
 
 
 char *has_leading_keyword (const char *string, const char *keyword);
+char *trim_spaces (char *str);
 
 
 /* Object to store a key value pair.  */