SESSION - Add command.
authorWerner Koch <wk@gnupg.org>
Fri, 23 May 2014 18:45:00 +0000 (20:45 +0200)
committerWerner Koch <wk@gnupg.org>
Fri, 23 May 2014 18:45:00 +0000 (20:45 +0200)
* configure.ac (NEED_LIBGCRYPT_VERSION): Require Libgcrypt.
* src/session.c, src/session.h: New.
* src/Makefile.am (payprocd_SOURCES): Add them.
(LDADD, AM_CFLAGS): Add Libgcrypt.
(t_connection_CFLAGS, t_connection_LDADD): Ditto.
(t_connection_SOURCES): Add session.c
* src/payprocd.c (TIMERTICK_INTERVAL, HOUSEKEEPING_INTERVAL): New.
(main): Init Libgcrypt.
(time_for_housekeeping_p): New.
(housekeeping_thread): New.
(handle_tick): New.
(server_loop): Call handle_tick.
* src/util.h (JNLIB_GCC_HAVE_PUSH_PRAGMA): New.
* src/connection.c (cmd_session): New.
(connection_handler): Add command SESSION.

NEWS
configure.ac
doc/api-ref.org
src/Makefile.am
src/connection.c
src/payprocd.c
src/session.c [new file with mode: 0644]
src/session.h [new file with mode: 0644]
src/util.h

diff --git a/NEWS b/NEWS
index 0a1a0ed..706c2f3 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,11 @@
       Payproc - The Payment Processor
 
+Noteworthy changes in version 0.2.0 (2014-05-23)
+------------------------------------------------
+
+ * Add session mangement.
+
+
 Noteworthy changes in version 0.1.0 (2014-04-17)
 ------------------------------------------------
 
index ed79c6d..dcec2d3 100644 (file)
@@ -50,6 +50,9 @@ NEED_LIBASSUAN_VERSION=2.1.0
 NEED_NPTH_API=1
 NEED_NPTH_VERSION=0.91
 
+NEED_LIBGCRYPT_API=1
+NEED_LIBGCRYPT_VERSION=1.6.0
+
 NEED_GNUTLS_VERSION=3.0
 
 development_version=mym4_isgit
@@ -64,6 +67,8 @@ AC_GNU_SOURCE
 
 AC_DEFINE_UNQUOTED(PACKAGE_BUGREPORT, "$PACKAGE_BUGREPORT",
                                         [Bug report address])
+AC_DEFINE_UNQUOTED(NEED_LIBGCRYPT_VERSION, "$NEED_LIBGCRYPT_VERSION",
+                                       [Required version of Libgcrypt])
 
 
 #
@@ -143,6 +148,15 @@ else
 fi
 AC_DEFINE(USE_NPTH, 1, [Defined to use New Portable Thread Library])
 
+
+#
+# We need Libgcrypt for its RNG and the hash fucntion.  Maybe for more
+# things in the future.
+#
+AM_PATH_LIBGCRYPT("$NEED_LIBGCRYPT_API:$NEED_LIBGCRYPT_VERSION",
+        have_libgcrypt=yes,have_libgcrypt=no)
+
+
 #
 # GNUTLS
 #
@@ -346,6 +360,16 @@ if test "$have_npth" = "no"; then
 *** (at least version $NEED_NPTH_VERSION (API $NEED_NPTH_API) is required).
 ***]])
 fi
+if test "$have_libgcrypt" = "no"; then
+   die=yes
+   AC_MSG_NOTICE([[
+***
+*** You need libgcrypt to build this program.
+**  This library is for example available at
+***   ftp://ftp.gnupg.org/gcrypt/libgcrypt/
+*** (at least version $NEED_LIBGCRYPT_VERSION (API $NEED_LIBGCRYPT_API) is required.)
+***]])
+fi
 
 if test "$die" = "yes"; then
     AC_MSG_ERROR([[
index 78aaebc..c27efc7 100644 (file)
@@ -104,3 +104,43 @@ Currency: Eur
 Amount: 17.3
 
 #+end_example
+
+
+** SESSION
+
+This is a multipurpose command to help implement a state-full service.
+Note that the state information is intentional not persistent and thus
+won't survive a daemon restart.
+
+The following sub-commands are available:
+
+- Create a new session
+
+  : create [TTL]
+
+  A new session is created and the provided data dictionary is stored
+  by payprocd for future requests.  The data dictionary is optional.
+  On success the returned data has an "_SESSID" item which is to be
+  used for all further requests.  If TTL has been given this is used
+  instead of the defaul TTL value.
+
+ - Destroy a session.
+
+   : destroy SESSID
+
+   This shall be used to free the internal storage required for the
+   session and to avoid leaving sensitive information in RAM.
+
+ - Get data from a session.
+
+   : get SESSID
+
+   Return the data stored in the session identified by SESSID.
+
+ - Put data into a session.
+
+   : put SESSID
+
+   Store or update the given data in the session.  Deleting an item
+   from the session dictionary is possible by putting an empty string
+   for it.
index 4b5386a..cbb9579 100644 (file)
@@ -40,6 +40,7 @@ payprocd_SOURCES = \
        tlssupport.c tlssupport.h \
        cred.c cred.h \
        journal.c journal.h \
+       session.c session.h \
        $(utility_sources)
 
 noinst_PROGRAMS = $(module_tests) t-http
@@ -47,8 +48,10 @@ 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)
+AM_CFLAGS = $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(LIBGCRYPT_CFLAGS) \
+            $(LIBGNUTLS_CFLAGS)
+LDADD  = -lm $(GPG_ERROR_LIBS) $(NPTH_LIBS) $(LIBGCRYPT_LIBS) \
+        $(LIBGNUTLS_LIBS)
 
 t_common_sources = t-common.h $(utility_sources)
 t_common_cflags  = $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(LIBGNUTLS_CFLAGS)
@@ -59,6 +62,7 @@ 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 journal.c $(t_common_sources)
-t_connection_CFLAGS  = $(t_common_cflags)
-t_connection_LDADD   = $(t_common_ldadd)
+t_connection_SOURCES = t-connection.c stripe.c journal.c session.c \
+                      $(t_common_sources)
+t_connection_CFLAGS  = $(t_common_cflags) $(LIBGCRYPT_CFLAGS)
+t_connection_LDADD   = $(t_common_ldadd) $(LIBGCRYPT_LIBS)
index c7bb3d3..1033d3c 100644 (file)
@@ -30,6 +30,7 @@
 #include "payprocd.h"
 #include "stripe.h"
 #include "journal.h"
+#include "session.h"
 #include "connection.h"
 
 /* Maximum length of an input line.  */
@@ -433,6 +434,126 @@ reconvert_amount (int cents, int decdigits)
 }
 
 
+\f
+/* SESSION is a multipurpose command to help implement a state-full
+   service.  Note that the state information is intentional not
+   persistent and thus won't survive a daemon restart.
+
+   The following sub-commands are available:
+
+   create [TTL]
+
+     Create a new session
+
+     A new session is created and the provided data dictionary is
+     stored by payprocd for future requests.  The data dictionary is
+     optional.  On success the returned data has an "_SESSID" item
+     which is to be used for all further requests.  If TTL has been
+     given this is used instead of the defaul TTL value.
+
+   destroy SESSID
+
+     Destroy a session.
+
+     This shall be used to free the internal storage required for the
+     session and to avoid leaving sensitive information in RAM.
+
+   get SESSID
+
+     Get data from a session.
+
+     Return the data stored in the session identified by SESSID.
+
+   put SESSID
+
+     Put data into a session.
+
+     Store or update the given data in the session.  Deleting an item
+     from the session dictionary is possible by putting an empty
+     string for it.
+ */
+static gpg_error_t
+cmd_session (conn_t conn, char *args)
+{
+  gpg_error_t err;
+  keyvalue_t kv;
+  char *options;
+  char *sessid = NULL;
+  char *errdesc;
+
+  if ((options = has_leading_keyword (args, "create")))
+    {
+      int ttl = atoi (options);
+      err = session_create (ttl, conn->dataitems, &sessid);
+      keyvalue_release (conn->dataitems);
+      conn->dataitems = NULL;
+    }
+  else if ((options = has_leading_keyword (args, "get")))
+    {
+      keyvalue_release (conn->dataitems);
+      conn->dataitems = NULL;
+      err = session_get (options, &conn->dataitems);
+    }
+  else if ((options = has_leading_keyword (args, "put")))
+    {
+      err = session_put (options, conn->dataitems);
+      if (gpg_err_code (err) == GPG_ERR_ENOMEM)
+        {
+          /* We are tight on memory - better destroy the session so
+             that the caller can't try over and over again.  */
+          session_destroy (options);
+        }
+      keyvalue_release (conn->dataitems);
+      conn->dataitems = NULL;
+    }
+  else if ((options = has_leading_keyword (args, "destroy")))
+    {
+      err = session_destroy (options);
+      keyvalue_release (conn->dataitems);
+      conn->dataitems = NULL;
+    }
+  else
+    {
+      es_fputs ("ERR 1 (Unknown sub-command)\n"
+                "# Supported sub-commands are:\n"
+                "#   create [TTL]\n"
+                "#   get SESSID\n"
+                "#   put SESSID\n"
+                "#   destroy SESSID\n"
+                , conn->stream);
+      return 0;
+    }
+
+  switch (gpg_err_code (err))
+    {
+    case GPG_ERR_LIMIT_REACHED:
+      errdesc = "Too many active sessions";
+      break;
+    case GPG_ERR_NOT_FOUND:
+      errdesc = "No such session or session timed out";
+      break;
+    case GPG_ERR_INV_NAME:
+      errdesc = "Invalid session id";
+      break;
+    default: errdesc = NULL;
+    }
+
+  if (err)
+    es_fprintf (conn->stream, "ERR %d (%s)\n",
+                err, errdesc? errdesc : gpg_strerror (err));
+  else
+    {
+      es_fprintf (conn->stream, "OK\n");
+      if (sessid)
+        es_fprintf (conn->stream, "_SESSID: %s\n", sessid);
+      for (kv = conn->dataitems; kv; kv = kv->next)
+        if (kv->name[0] >= 'A' && kv->name[0] < 'Z')
+          write_data_line (kv, conn->stream);
+    }
+  xfree (sessid);
+  return err;
+}
+
 
 \f
 /* The CARDTOKEN command creates a token for a card.  The following
@@ -448,8 +569,7 @@ reconvert_amount (int cents, int decdigits)
 
    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.
-
+   Live:      Set to 'f' in test mode or 't' in live mode.
  */
 static gpg_error_t
 cmd_cardtoken (conn_t conn, char *args)
@@ -527,15 +647,16 @@ cmd_cardtoken (conn_t conn, char *args)
    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 ro convey
+   Meta[NAME]: Meta data further described by NAME.  This is used to 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.
+   Live:       Set to 'f' in test mode or 't' in live mode.
    Currency:   The currency of the charge.
    Amount:     The charged amount with optional decimal fraction.
+   _timestamp: The timestamp as written to the journal
 
  */
 static gpg_error_t
@@ -769,7 +890,9 @@ connection_handler (conn_t conn)
     }
   es_fflush (conn->stream);
 
-  if ((cmdargs = has_leading_keyword (conn->command, "CARDTOKEN")))
+  if ((cmdargs = has_leading_keyword (conn->command, "SESSION")))
+    err = cmd_session (conn, cmdargs);
+  else 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);
index 0a2ee45..3af3d6e 100644 (file)
@@ -30,6 +30,7 @@
 #include <errno.h>
 #include <gpg-error.h>
 #include <npth.h>
+#include <gcrypt.h>
 
 #include "util.h"
 #include "logging.h"
 #include "tlssupport.h"
 #include "cred.h"
 #include "journal.h"
+#include "session.h"
 #include "payprocd.h"
 
 
 /* The name of the socket handling commands.  */
 #define SOCKET_NAME "/var/run/payproc/daemon"
 
-/* The interval in seconds for the housekeeping thread.  */
-#define TIMERTICK_INTERVAL  60
+/* The interval in seconds to check whether to do housekeeping.  */
+#define TIMERTICK_INTERVAL  30
+
+/* The interval in seconds to run the housekeeping thread.  */
+#define HOUSEKEEPING_INTERVAL  (120)
 
 /* Flag indicating that the socket shall shall be removed by
    cleanup.  */
@@ -100,6 +105,7 @@ static ARGPARSE_OPTS opts[] = {
 static void cleanup (void);
 static void launch_server (const char *logfile);
 static void server_loop (int fd);
+static void handle_tick (void);
 static void handle_signal (int signo);
 static void *connection_thread (void *arg);
 
@@ -188,6 +194,14 @@ main (int argc, char **argv)
       exit (1);
     }
 
+  /* Check that Libgcrypt is suitable.  */
+  gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
+  if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+    {
+      log_fatal ("%s is too old (need %s, have %s)\n", "libgcrypt",
+                 NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+    }
+
   /* Initialze processing subsystems.  */
   init_tls_subsystem ();
 
@@ -509,7 +523,7 @@ server_loop (int listen_fd)
   nfd = listen_fd;
 
   npth_clock_gettime (&abstime);
-  /* abstime.tv_sec += TIMERTICK_INTERVAL; */
+  abstime.tv_sec += TIMERTICK_INTERVAL;
 
   for (;;)
     {
@@ -530,7 +544,7 @@ server_loop (int listen_fd)
       if (!(npth_timercmp (&curtime, &abstime, <)))
         {
           /* Timeout.  */
-          /* handle_tick (); */
+          handle_tick ();
           npth_clock_gettime (&abstime);
           abstime.tv_sec += TIMERTICK_INTERVAL;
         }
@@ -604,6 +618,84 @@ server_loop (int listen_fd)
 }
 
 
+#if JNLIB_GCC_HAVE_PUSH_PRAGMA
+# pragma GCC push_options
+# pragma GCC optimize ("no-strict-overflow")
+#endif
+static int
+time_for_housekeeping_p (time_t now)
+{
+  static time_t last_housekeeping;
+
+  if (!last_housekeeping)
+    last_housekeeping = now;
+
+  if (last_housekeeping + HOUSEKEEPING_INTERVAL <= now
+      || last_housekeeping > now /*(be prepared for y2038)*/)
+    {
+      last_housekeeping = now;
+      return 1;
+    }
+  return 0;
+}
+#if JNLIB_GCC_HAVE_PUSH_PRAGMA
+# pragma GCC pop_options
+#endif
+
+
+/* Thread to do the housekeeping.  */
+static void *
+housekeeping_thread (void *arg)
+{
+  static int sentinel;
+
+  (void)arg;
+
+  if (sentinel)
+    {
+      log_info ("only one cleaning person at a time please\n");
+      return NULL;
+    }
+  sentinel++;
+  if (opt.verbose)
+    log_info ("starting housekeeping\n");
+
+  session_housekeeping ();
+
+  if (opt.verbose)
+    log_info ("finished with housekeeping\n");
+  sentinel--;
+  return NULL;
+
+}
+
+
+/* This is the worker for the ticker.  It is called every few seconds
+   and may only do fast operations. */
+static void
+handle_tick (void)
+{
+  if (time_for_housekeeping_p (time (NULL)))
+    {
+      npth_t thread;
+      npth_attr_t tattr;
+      int rc;
+
+      rc = npth_attr_init (&tattr);
+      if (rc)
+        log_error ("error preparing housekeeping thread: %s\n", strerror (rc));
+      else
+        {
+          npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
+          rc = npth_create (&thread, &tattr, housekeeping_thread, NULL);
+          if (rc)
+            log_error ("error spawning housekeeping thread: %s\n",
+                       strerror (rc));
+          npth_attr_destroy (&tattr);
+        }
+    }
+}
+
 
 /* The signal handler for payprocd.  It is expected to be run in its
    own thread and not in the context of a signal handler.  */
diff --git a/src/session.c b/src/session.c
new file mode 100644 (file)
index 0000000..d5c1e49
--- /dev/null
@@ -0,0 +1,467 @@
+/* session.c - Session management
+ * 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 <unistd.h>
+#include <time.h>
+#include <npth.h>
+#include <gcrypt.h>
+
+#include "util.h"
+#include "logging.h"
+#include "estream.h"
+#include "payprocd.h"
+#include "session.h"
+
+/* The default TTL for a session is 30 minutes.  Each access to
+   session data re-triggers this TTL. */
+#define DEFAULT_TTL 1800
+
+/* To inhibit people from using payproc as a cheap storage provider we
+   limit the entire lifetime of a session to 6 hours.  */
+#define MAX_SESSION_LIFETIME (6*3600)
+
+/* We put a limit on the number of active sessions.  2^16 seems to be
+   a reasonable value.  A session object without data requires about
+   64 byte and thus we need about 4MB to hold the session objects.
+   Assuming 1k of data on average per session and additional 64MB is
+   used for the data. */
+#define MAX_SESSIONS   65536
+
+/* We use 20 bytes for the session id.  Using the ZB32 encoder this
+   results in a 32 byte ascii string.  To avoid decoding of the
+   session string we store the ascii string .  */
+#define SESSID_RAW_LENGTH 20
+#define SESSID_LENGTH 32
+
+
+/* The object holding the session data.  */
+struct session_s
+{
+  session_t next;  /* The next item in the bucket.  */
+  unsigned int ttl;/* The session expires after this number of seconds
+                      without activity.  */
+  time_t created;  /* The time the session was created.  */
+  time_t accessed; /* The time the session was last used.  */
+
+  keyvalue_t dict; /* The dictionary with the session's data.  */
+
+  /* The session id as ZB32 encoded string. */
+  char sessid[SESSID_LENGTH+1];
+};
+
+
+/* A mutex used to protect the global session variables.  */
+static npth_mutex_t sessions_lock = NPTH_MUTEX_INITIALIZER;
+
+/* We store pointers to the session objects in 1024 buckets, indexed
+   by the first two ZB32 encoded characters of the session id.  This
+   requires 8k of memory for fast indexing which is not too much.  */
+static session_t sessions[32][32];
+
+/* Total number of sessions in use.  This counter is used to quickly
+   check whether we are allowed to create a new session.  */
+static unsigned int sessions_in_use;
+
+/* Because the session objects have a fixed size, it is easy to reuse
+   them.  */
+static session_t unused_sessions;
+
+
+
+\f
+static gpg_error_t
+lock_sessions (void)
+{
+  int res;
+
+  res = npth_mutex_lock (&sessions_lock);
+  if (res)
+    {
+      gpg_error_t err = gpg_error_from_errno (res);
+      log_error ("failed to acquire sessions lock: %s\n", gpg_strerror (err));
+      return err;
+    }
+
+  return 0;
+}
+
+
+static void
+unlock_sessions (void)
+{
+  int res;
+
+  res = npth_mutex_unlock (&sessions_lock);
+  if (res)
+    {
+      gpg_error_t err = gpg_error_from_errno (res);
+      log_error ("failed to release sessions lock: %s\n", gpg_strerror (err));
+    }
+}
+
+
+
+\f
+static int
+check_ttl (session_t sess, time_t now)
+{
+  if ((sess->ttl > 0 && sess->accessed + sess->ttl < now)
+      || (sess->created + MAX_SESSION_LIFETIME < now))
+    {
+      log_debug ("session '%s' expired\n", sess->sessid);
+      return 1;
+    }
+  return 0;
+}
+
+
+/* Housekeeping; i.e. time out sessions.  */
+void
+session_housekeeping (void)
+{
+  time_t now = time (NULL);
+  session_t prev, sess;
+  int a, b;
+
+  if (lock_sessions ())
+    return;
+
+  for (a=0; a < 32; a++)
+    for (b=0; b < 32; b++)
+      {
+      again:
+        for (sess = sessions[a][b], prev = NULL; sess;
+             prev = sess, sess = sess->next)
+          {
+            if (check_ttl (sess, now))
+              {
+                /* Remove the item from the hash table.  */
+                if (prev)
+                  prev->next = sess->next;
+                else
+                  sessions[a][b] = sess->next;
+                sessions_in_use--;
+
+                /* Remove the data.  */
+                keyvalue_release (sess->dict);
+                sess->dict = NULL;
+
+                /* Shove the item into the attic.  */
+                sess->next = unused_sessions;
+                unused_sessions = sess;
+
+                /* Scan this bucket again.  */
+                goto again;
+              }
+          }
+      }
+
+  unlock_sessions ();
+}
+
+
+
+\f
+/* Create a new session.  If TTL > 0 use that as TTL for the session.
+   DICT is an optional dictionary with the data to store in the
+   session.  On return a malloced string with the session-id is stored
+   at R_SESSID. */
+gpg_error_t
+session_create (int ttl, keyvalue_t dict, char **r_sessid)
+{
+  gpg_error_t err;
+  session_t sess = NULL;
+  int malloced = 0;
+  char nonce[SESSID_RAW_LENGTH];
+  keyvalue_t kv;
+  char *p;
+  int a, b;
+
+  *r_sessid = NULL;
+
+  /* Cap the TTL at the session lifetime.  */
+  if (ttl > MAX_SESSION_LIFETIME)
+    ttl = MAX_SESSION_LIFETIME;
+
+  err = lock_sessions ();
+  if (err)
+    return err;
+
+  if (unused_sessions)
+    {
+      sess = unused_sessions;
+      unused_sessions = sess->next;
+      sess->next = NULL;
+    }
+  else if (sessions_in_use < MAX_SESSIONS)
+    {
+      sess = xtrycalloc (1, sizeof *sess);
+      if (!sess)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+      malloced = 1;
+    }
+  else
+    {
+      err = gpg_error (GPG_ERR_LIMIT_REACHED);
+      goto leave;
+    }
+
+  gcry_create_nonce (nonce, sizeof nonce);
+  p = zb32_encode (nonce, 8*sizeof nonce);
+  if (!p)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  if (strlen (p) != SESSID_LENGTH)
+    BUG ();
+  strcpy (sess->sessid, p);
+  *r_sessid = p;
+
+  sess->created = sess->accessed = time (NULL);
+  sess->ttl = ttl > 0? ttl : DEFAULT_TTL;
+  sess->dict = NULL;  /* Just to be safe.  */
+
+  for (kv = dict; kv; kv = kv->next)
+    if (*kv->name)
+      {
+        err = keyvalue_put (&sess->dict, kv->name,
+                            (kv->value && *kv->value)? kv->value : NULL);
+        if (err)
+          goto leave;
+      }
+
+  /* Put the session into the hash table.  */
+  a = zb32_index (sess->sessid[0]);
+  b = zb32_index (sess->sessid[1]);
+  if ( a < 0 || a > 31 || b < 0 || b > 32)
+    BUG ();
+  sess->next = sessions[a][b];
+  sessions[a][b] = sess;
+  sess = NULL;
+  sessions_in_use++;
+
+ leave:
+  if (sess)
+    {
+      keyvalue_release (sess->dict);
+      sess->dict = NULL;
+
+      /* Push an unused session object back or release it.  */
+      if (malloced)
+        xfree (sess);
+      else
+        {
+          sess->next = unused_sessions;
+          unused_sessions = sess;
+        }
+    }
+  if (err)
+    {
+      xfree (*r_sessid);
+      *r_sessid = NULL;
+    }
+  unlock_sessions ();
+  return err;
+}
+
+
+/* Internal version of session_destroy.  */
+static gpg_error_t
+session_do_destroy (const char *sessid, int with_lock)
+{
+  gpg_error_t err;
+  session_t prev, sess;
+  int a, b;
+
+  if (strlen (sessid) != SESSID_LENGTH
+      || (a = zb32_index (sessid[0])) < 0
+      || (b = zb32_index (sessid[1])) < 0)
+    {
+      return gpg_error (GPG_ERR_INV_NAME);
+    }
+
+  if (with_lock)
+    {
+      err = lock_sessions ();
+      if (err)
+        return err;
+    }
+
+  for (sess = sessions[a][b], prev = NULL; sess; prev = sess, sess = sess->next)
+    if (!strcmp (sess->sessid, sessid))
+      break;
+  if (!sess)
+    {
+      err = gpg_error (GPG_ERR_NOT_FOUND);
+      goto leave;
+    }
+
+  /* Remove the item from the hash table.  */
+  if (prev)
+    prev->next = sess->next;
+  else
+    sessions[a][b] = sess->next;
+  sessions_in_use--;
+
+  /* Remove the data.  */
+  keyvalue_release (sess->dict);
+  sess->dict = NULL;
+
+  /* Shove the item into the attic.  */
+  sess->next = unused_sessions;
+  unused_sessions = sess;
+
+ leave:
+  if (with_lock)
+    unlock_sessions ();
+  return err;
+}
+
+
+/* Destroy the session SESSID.  */
+gpg_error_t
+session_destroy (const char *sessid)
+{
+  return session_do_destroy (sessid, 1);
+}
+
+
+\f
+/* Update the data for session SESSID using the dictionary DICT.  If
+   the value of a dictionary entry is the empty string, that entry is
+   removed from the session. */
+gpg_error_t
+session_put (const char *sessid, keyvalue_t dict)
+{
+  gpg_error_t err;
+  time_t now;
+  session_t sess;
+  keyvalue_t kv;
+  int a, b;
+
+  if (strlen (sessid) != SESSID_LENGTH
+      || (a = zb32_index (sessid[0])) < 0
+      || (b = zb32_index (sessid[1])) < 0)
+    {
+      return gpg_error (GPG_ERR_INV_NAME);
+    }
+
+  err = lock_sessions ();
+  if (err)
+    return err;
+
+  for (sess = sessions[a][b]; sess; sess = sess->next)
+    if (!strcmp (sess->sessid, sessid))
+      break;
+  if (!sess)
+    {
+      err = gpg_error (GPG_ERR_NOT_FOUND);
+      goto leave;
+    }
+
+  now = time (NULL);
+  if (check_ttl (sess, now))
+    {
+      session_do_destroy (sessid, 0);
+      err = gpg_error (GPG_ERR_NOT_FOUND);
+      goto leave;
+    }
+  sess->accessed = now;
+
+  /* Note: This is not an atomic operation.  If the put fails the
+     session dictionary may be only partly updated.  However, the only
+     reason for a failure is a memory shortage which is anyway a
+     deadend - at least for the session.  */
+  for (kv = dict; kv; kv = kv->next)
+    if (*kv->name)
+      {
+        err = keyvalue_put (&sess->dict, kv->name,
+                            (kv->value && *kv->value)? kv->value : NULL);
+        if (err)
+          goto leave;
+      }
+
+ leave:
+  unlock_sessions ();
+  return err;
+}
+
+
+\f
+/* Update the dictionary at address DICTP with the data from session
+   SESSID. */
+gpg_error_t
+session_get (const char *sessid, keyvalue_t *dictp)
+{
+  gpg_error_t err;
+  time_t now;
+  session_t sess;
+  keyvalue_t kv;
+  int a, b;
+
+  if (strlen (sessid) != SESSID_LENGTH
+      || (a = zb32_index (sessid[0])) < 0
+      || (b = zb32_index (sessid[1])) < 0)
+    {
+      return gpg_error (GPG_ERR_INV_NAME);
+    }
+
+  err = lock_sessions ();
+  if (err)
+    return err;
+
+  for (sess = sessions[a][b]; sess; sess = sess->next)
+    if (!strcmp (sess->sessid, sessid))
+      break;
+  if (!sess)
+    {
+      err = gpg_error (GPG_ERR_NOT_FOUND);
+      goto leave;
+    }
+
+  now = time (NULL);
+  if (check_ttl (sess, now))
+    {
+      session_do_destroy (sessid, 0);
+      err = gpg_error (GPG_ERR_NOT_FOUND);
+      goto leave;
+    }
+  sess->accessed = now;
+
+  for (kv = sess->dict; kv; kv = kv->next)
+    if (*kv->name)
+      {
+        err = keyvalue_put (dictp, kv->name,
+                            (kv->value && *kv->value)? kv->value : NULL);
+        if (err)
+          goto leave;
+      }
+
+ leave:
+  unlock_sessions ();
+  return err;
+}
diff --git a/src/session.h b/src/session.h
new file mode 100644 (file)
index 0000000..78953b8
--- /dev/null
@@ -0,0 +1,34 @@
+/* session.h - Definition for session management
+ * 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 SESSION_H
+#define SESSION_H
+
+struct session_s;
+typedef struct session_s *session_t;
+
+void session_housekeeping (void);
+
+gpg_error_t session_create (int ttl, keyvalue_t data, char **r_sessid);
+gpg_error_t session_destroy (const char *sessid);
+gpg_error_t session_put (const char *sessid, keyvalue_t dict);
+gpg_error_t session_get (const char *sessid, keyvalue_t *dictp);
+
+
+#endif /*SESSION_H*/
index 175d07b..d77ee59 100644 (file)
 # define DIMof(type,member)   DIM(((type *)0)->member)
 #endif
 
+#undef JNLIB_GCC_HAVE_PUSH_PRAGMA
 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )
 # define JNLIB_GCC_M_FUNCTION 1
 # define JNLIB_GCC_A_NR             __attribute__ ((noreturn))
 # if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4 )
+#   define JNLIB_GCC_HAVE_PUSH_PRAGMA 1
 #   define JNLIB_GCC_A_PRINTF( f, a ) \
                     __attribute__ ((format (__gnu_printf__,f,a)))
 #   define JNLIB_GCC_A_NR_PRINTF( f, a ) \