merged usage() information from master
authorMichael Rash <mbr@cipherdyne.org>
Mon, 9 Jul 2012 02:00:13 +0000 (22:00 -0400)
committerMichael Rash <mbr@cipherdyne.org>
Mon, 9 Jul 2012 02:00:13 +0000 (22:00 -0400)
CREDITS
lib/fko.h
lib/fko_context.h
lib/fko_digest.c
lib/fko_funcs.c
server/incoming_spa.c
server/replay_cache.c
server/replay_cache.h
test/conf/dual_key_usage_access.conf [new file with mode: 0644]
test/test-fwknop.pl

diff --git a/CREDITS b/CREDITS
index f912289..1d3c17a 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -18,3 +18,9 @@ Max Kastanas
 
 Ted Wynnychenko
     - Helped test fwknop PF support on OpenBSD.
+
+Andy Rowland
+    - Reported a bug where the same encryption key used for two stanzas in the
+      access.conf file would result in access requests that matched the second
+      stanza to always be treated as a replay attack.  This has been fixed for
+      the fwknop-2.0.1 release.
index e694a71..9ef8ae4 100644 (file)
--- a/lib/fko.h
+++ b/lib/fko.h
@@ -224,6 +224,8 @@ DLL_API int fko_set_spa_server_auth(fko_ctx_t ctx, const char *server_auth);
 DLL_API int fko_set_spa_client_timeout(fko_ctx_t ctx, const int timeout);
 DLL_API int fko_set_spa_digest_type(fko_ctx_t ctx, const short digest_type);
 DLL_API int fko_set_spa_digest(fko_ctx_t ctx);
+DLL_API int fko_set_raw_spa_digest_type(fko_ctx_t ctx, const short raw_digest_type);
+DLL_API int fko_set_raw_spa_digest(fko_ctx_t ctx);
 DLL_API int fko_set_spa_encryption_type(fko_ctx_t ctx, const short encrypt_type);
 DLL_API int fko_set_spa_encryption_mode(fko_ctx_t ctx, const int encrypt_mode);
 DLL_API int fko_set_spa_data(fko_ctx_t ctx, const char *enc_msg);
@@ -252,7 +254,9 @@ DLL_API int fko_get_spa_nat_access(fko_ctx_t ctx, char **nat_access);
 DLL_API int fko_get_spa_server_auth(fko_ctx_t ctx, char **server_auth);
 DLL_API int fko_get_spa_client_timeout(fko_ctx_t ctx, int *client_timeout);
 DLL_API int fko_get_spa_digest_type(fko_ctx_t ctx, short *spa_digest_type);
+DLL_API int fko_get_raw_spa_digest_type(fko_ctx_t ctx, short *raw_spa_digest_type);
 DLL_API int fko_get_spa_digest(fko_ctx_t ctx, char **spa_digest);
+DLL_API int fko_get_raw_spa_digest(fko_ctx_t ctx, char **raw_spa_digest);
 DLL_API int fko_get_spa_encryption_type(fko_ctx_t ctx, short *spa_enc_type);
 DLL_API int fko_get_spa_encryption_mode(fko_ctx_t ctx, int *spa_enc_mode);
 DLL_API int fko_get_spa_data(fko_ctx_t ctx, char **spa_data);
index 18d5a8a..52a1c6a 100644 (file)
@@ -69,6 +69,12 @@ struct fko_context {
     char           *version;
     char           *digest;
 
+    /* Digest of raw encrypted/base64 data - this is used
+     * for replay attack detection
+    */
+    char           *raw_digest;
+    short           raw_digest_type;
+
     /* Computed processed data (encodings, etc.) */
     char           *encoded_msg;
     char           *encrypted_msg;
index 6eaf126..f2e2577 100644 (file)
@@ -36,8 +36,9 @@
 
 /* Set the SPA digest type.
 */
-int
-fko_set_spa_digest_type(fko_ctx_t ctx, const short digest_type)
+static int
+set_spa_digest_type(fko_ctx_t ctx,
+    short *digest_type_field, const short digest_type)
 {
     /* Must be initialized
     */
@@ -47,13 +48,25 @@ fko_set_spa_digest_type(fko_ctx_t ctx, const short digest_type)
     if(digest_type < 1 || digest_type >= FKO_LAST_DIGEST_TYPE)
         return(FKO_ERROR_INVALID_DATA);
 
-    ctx->digest_type = digest_type;
+    *digest_type_field = digest_type;
 
     ctx->state |= FKO_DIGEST_TYPE_MODIFIED;
 
     return(FKO_SUCCESS);
 }
 
+int
+fko_set_spa_digest_type(fko_ctx_t ctx, const short digest_type)
+{
+    return set_spa_digest_type(ctx, &ctx->digest_type, digest_type);
+}
+
+int
+fko_set_raw_spa_digest_type(fko_ctx_t ctx, const short raw_digest_type)
+{
+    return set_spa_digest_type(ctx, &ctx->raw_digest_type, raw_digest_type);
+}
+
 /* Return the SPA digest type.
 */
 int
@@ -69,22 +82,27 @@ fko_get_spa_digest_type(fko_ctx_t ctx, short *digest_type)
     return(FKO_SUCCESS);
 }
 
+/* Return the SPA digest type.
+*/
 int
-fko_set_spa_digest(fko_ctx_t ctx)
+fko_get_raw_spa_digest_type(fko_ctx_t ctx, short *raw_digest_type)
 {
-    char    *md = NULL;
-
     /* Must be initialized
     */
     if(!CTX_INITIALIZED(ctx))
         return(FKO_ERROR_CTX_NOT_INITIALIZED);
 
-    /* Must have encoded message data to start with.
-    */
-    if(ctx->encoded_msg == NULL)
-        return(FKO_ERROR_MISSING_ENCODED_DATA);
+    *raw_digest_type = ctx->raw_digest_type;
+
+    return(FKO_SUCCESS);
+}
+
+static int
+set_digest(char *data, char **digest, short digest_type)
+{
+    char    *md = NULL;
 
-    switch(ctx->digest_type)
+    switch(digest_type)
     {
         case FKO_DIGEST_MD5:
             md = malloc(MD_HEX_SIZE(MD5_DIGEST_LENGTH)+1);
@@ -92,7 +110,7 @@ fko_set_spa_digest(fko_ctx_t ctx)
                 return(FKO_ERROR_MEMORY_ALLOCATION);
 
             md5_base64(md,
-                (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
+                (unsigned char*)data, strlen(data));
             break;
 
         case FKO_DIGEST_SHA1:
@@ -101,7 +119,7 @@ fko_set_spa_digest(fko_ctx_t ctx)
                 return(FKO_ERROR_MEMORY_ALLOCATION);
 
             sha1_base64(md,
-                (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
+                (unsigned char*)data, strlen(data));
             break;
 
         case FKO_DIGEST_SHA256:
@@ -110,7 +128,7 @@ fko_set_spa_digest(fko_ctx_t ctx)
                 return(FKO_ERROR_MEMORY_ALLOCATION);
 
             sha256_base64(md,
-                (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
+                (unsigned char*)data, strlen(data));
             break;
 
         case FKO_DIGEST_SHA384:
@@ -119,7 +137,7 @@ fko_set_spa_digest(fko_ctx_t ctx)
                 return(FKO_ERROR_MEMORY_ALLOCATION);
 
             sha384_base64(md,
-                (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
+                (unsigned char*)data, strlen(data));
             break;
 
         case FKO_DIGEST_SHA512:
@@ -128,7 +146,7 @@ fko_set_spa_digest(fko_ctx_t ctx)
                 return(FKO_ERROR_MEMORY_ALLOCATION);
 
             sha512_base64(md,
-                (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
+                (unsigned char*)data, strlen(data));
             break;
 
         default:
@@ -138,15 +156,49 @@ fko_set_spa_digest(fko_ctx_t ctx)
     /* Just in case this is a subsquent call to this function.  We
      * do not want to be leaking memory.
     */
-    if(ctx->digest != NULL)
-        free(ctx->digest);
+    if(*digest != NULL)
+        free(*digest);
 
-    ctx->digest = md;
+    *digest = md;
 
     return(FKO_SUCCESS);
 }
 
 int
+fko_set_spa_digest(fko_ctx_t ctx)
+{
+    /* Must be initialized
+    */
+    if(!CTX_INITIALIZED(ctx))
+        return(FKO_ERROR_CTX_NOT_INITIALIZED);
+
+    /* Must have encoded message data to start with.
+    */
+    if(ctx->encoded_msg == NULL)
+        return(FKO_ERROR_MISSING_ENCODED_DATA);
+
+    return set_digest(ctx->encoded_msg,
+        &ctx->digest, ctx->digest_type);
+}
+
+int
+fko_set_raw_spa_digest(fko_ctx_t ctx)
+{
+    /* Must be initialized
+    */
+    if(!CTX_INITIALIZED(ctx))
+        return(FKO_ERROR_CTX_NOT_INITIALIZED);
+
+    /* Must have encoded message data to start with.
+    */
+    if(ctx->encrypted_msg == NULL)
+        return(FKO_ERROR_MISSING_ENCODED_DATA);
+
+    return set_digest(ctx->encrypted_msg,
+        &ctx->raw_digest, ctx->raw_digest_type);
+}
+
+int
 fko_get_spa_digest(fko_ctx_t ctx, char **md)
 {
     /* Must be initialized
@@ -159,4 +211,17 @@ fko_get_spa_digest(fko_ctx_t ctx, char **md)
     return(FKO_SUCCESS);
 }
 
+int
+fko_get_raw_spa_digest(fko_ctx_t ctx, char **md)
+{
+    /* Must be initialized
+    */
+    if(!CTX_INITIALIZED(ctx))
+        return(FKO_ERROR_CTX_NOT_INITIALIZED);
+
+    *md = ctx->raw_digest;
+
+    return(FKO_SUCCESS);
+}
+
 /***EOF***/
index e6fe8a8..91c01a7 100644 (file)
@@ -261,6 +261,9 @@ fko_destroy(fko_ctx_t ctx)
         if(ctx->digest != NULL)
             free(ctx->digest);
 
+        if(ctx->raw_digest != NULL)
+            free(ctx->raw_digest);
+
         if(ctx->encoded_msg != NULL)
             free(ctx->encoded_msg);
 
index 2383219..254990e 100644 (file)
@@ -110,6 +110,64 @@ preprocess_spa_data(fko_srv_options_t *opts, const char *src_ip)
     return(FKO_SUCCESS);
 }
 
+/* For replay attack detection
+*/
+static int
+get_raw_digest(char **digest, char *pkt_data)
+{
+    fko_ctx_t    ctx = NULL;
+    char        *tmp_digest = NULL;
+    int          res = FKO_SUCCESS;
+
+    /* initialize an FKO context with no decryption key just so
+     * we can get the outer message digest
+    */
+    res = fko_new_with_data(&ctx, (char *)pkt_data, NULL);
+    if(res != FKO_SUCCESS)
+    {
+        log_msg(LOG_WARNING, "Error initializing FKO context from SPA data: %s",
+            fko_errstr(res));
+        fko_destroy(ctx);
+        return(SPA_MSG_FKO_CTX_ERROR);
+    }
+
+    res = fko_set_raw_spa_digest_type(ctx, FKO_DEFAULT_DIGEST);
+    if(res != FKO_SUCCESS)
+    {
+        log_msg(LOG_WARNING, "Error setting digest type for SPA data: %s",
+            fko_errstr(res));
+        fko_destroy(ctx);
+        return(SPA_MSG_DIGEST_ERROR);
+    }
+
+    res = fko_set_raw_spa_digest(ctx);
+    if(res != FKO_SUCCESS)
+    {
+        log_msg(LOG_WARNING, "Error setting digest for SPA data: %s",
+            fko_errstr(res));
+        fko_destroy(ctx);
+        return(SPA_MSG_DIGEST_ERROR);
+    }
+
+    res = fko_get_raw_spa_digest(ctx, &tmp_digest);
+    if(res != FKO_SUCCESS)
+    {
+        log_msg(LOG_WARNING, "Error getting digest from SPA data: %s",
+            fko_errstr(res));
+        fko_destroy(ctx);
+        return(SPA_MSG_DIGEST_ERROR);
+    }
+
+    *digest = strdup(tmp_digest);
+
+    if (digest == NULL)
+        return SPA_MSG_ERROR;
+
+    fko_destroy(ctx);
+    return res;
+}
+
+
 /* Popluate a spa_data struct from an initialized (and populated) FKO context.
 */
 static int
@@ -152,6 +210,22 @@ get_spa_data_fields(fko_ctx_t ctx, spa_data_t *spdat)
     return(res);
 }
 
+/* Check for access.conf stanza SOURCE match based on SPA packet
+ * source IP
+*/
+static int
+is_src_match(acc_stanza_t *acc, const uint32_t ip)
+{
+    while (acc)
+    {
+        if(compare_addr_list(acc->source_list, ip))
+            return 1;
+
+        acc = acc->next;
+    }
+    return 0;
+}
+
 /* Process the SPA packet data
 */
 void
@@ -162,9 +236,10 @@ incoming_spa(fko_srv_options_t *opts)
     */
     fko_ctx_t       ctx = NULL;
 
-    char            *spa_ip_demark, *gpg_id;
+    char            *spa_ip_demark, *gpg_id, *raw_digest = NULL;
     time_t          now_ts;
-    int             res, status, ts_diff, enc_type, found_acc_sip=0, stanza_num=0;
+    int             res, status, ts_diff, enc_type, stanza_num=0;
+    int             added_replay_digest = 0;
 
     spa_pkt_info_t *spa_pkt = &(opts->spa_pkt);
 
@@ -192,6 +267,36 @@ incoming_spa(fko_srv_options_t *opts)
         return;
     }
 
+    if (is_src_match(opts->acc_stanzas, ntohl(spa_pkt->packet_src_ip)))
+    {
+        if(strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0)
+            /* Check for a replay attack
+            */
+            res = get_raw_digest(&raw_digest, (char *)spa_pkt->packet_data);
+            if(res != FKO_SUCCESS)
+            {
+                if (raw_digest != NULL)
+                    free(raw_digest);
+                return;
+            }
+            if (raw_digest == NULL)
+                return;
+
+            if (is_replay(opts, raw_digest) != SPA_MSG_SUCCESS)
+                return;
+    }
+    else
+    {
+        log_msg(LOG_WARNING,
+            "No access data found for source IP: %s", spadat.pkt_source_ip
+        );
+        return;
+    }
+
+    /* Now that we know there is a matching access.conf stanza and the
+     * incoming SPA packet is not a replay, see if we should grant any
+     * access
+    */
     while(acc)
     {
         stanza_num++;
@@ -204,8 +309,6 @@ incoming_spa(fko_srv_options_t *opts)
             continue;
         }
 
-        found_acc_sip = 1;
-
         log_msg(LOG_INFO, "(stanza #%d) SPA Packet from IP: %s received with access source match",
             stanza_num, spadat.pkt_source_ip);
 
@@ -332,7 +435,7 @@ incoming_spa(fko_srv_options_t *opts)
             continue;
         }
 
-        /* Do we have a valid FKO context?
+        /* Do we have a valid FKO context?  Did the SPA decrypt properly?
         */
         if(res != FKO_SUCCESS)
         {
@@ -349,6 +452,23 @@ incoming_spa(fko_srv_options_t *opts)
             continue;
         }
 
+        /* Add this SPA packet into the replay detection cache
+        */
+        if (! added_replay_digest)
+        {
+            res = add_replay(opts, raw_digest);
+            if (res != SPA_MSG_SUCCESS)
+            {
+                log_msg(LOG_WARNING, "(stanza #%d) Could not add digest to replay cache",
+                    stanza_num);
+                if(ctx != NULL)
+                    fko_destroy(ctx);
+                acc = acc->next;
+                continue;
+            }
+            added_replay_digest = 1;
+        }
+
         /* At this point, we assume the SPA data is valid.  Now we need to see
          * if it meets our access criteria.
         */
@@ -389,20 +509,6 @@ incoming_spa(fko_srv_options_t *opts)
             }
         }
 
-        /* Check for replays if so configured.
-        */
-        if(strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0)
-        {
-            res = replay_check(opts, ctx);
-            if(res != 0) /* non-zero means we have seen this packet before. */
-            {
-                if(ctx != NULL)
-                    fko_destroy(ctx);
-                acc = acc->next;
-                continue;
-            }
-        }
-
         /* Populate our spa data struct for future reference.
         */
         res = get_spa_data_fields(ctx, &spadat);
@@ -636,12 +742,8 @@ incoming_spa(fko_srv_options_t *opts)
         break;
     }
 
-    if(! found_acc_sip)
-    {
-        log_msg(LOG_WARNING,
-            "No access data found for source IP: %s", spadat.pkt_source_ip
-        );
-    }
+    if (raw_digest != NULL)
+        free(raw_digest);
 
     return;
 }
index 77566af..2b164e9 100644 (file)
@@ -420,44 +420,45 @@ replay_db_cache_init(fko_srv_options_t *opts)
 #endif /* USE_FILE_CACHE */
 
 /* Take an fko context, pull the digest and use it as the key to check the
- * replay db (digest cache). Returns 1 if there was a match (a replay),
- * 0 for no match, and -1 on error.
+ * replay db (digest cache).
 */
 int
-replay_check(fko_srv_options_t *opts, fko_ctx_t ctx)
+is_replay(fko_srv_options_t *opts, char *digest)
 {
 #ifdef NO_DIGEST_CACHE
     return(-1);
 #else
 
 #if USE_FILE_CACHE
-    return replay_check_file_cache(opts, ctx);
+    return is_replay_file_cache(opts, digest);
 #else
-    return replay_check_dbm_cache(opts, ctx);
+    return is_replay_dbm_cache(opts, digest);
 #endif
 #endif /* NO_DIGEST_CACHE */
 }
 
-#if USE_FILE_CACHE
 int
-replay_check_file_cache(fko_srv_options_t *opts, fko_ctx_t ctx)
+add_replay(fko_srv_options_t *opts, char *digest)
 {
-    char       *digest = NULL;
-    char        src_ip[INET_ADDRSTRLEN+1] = {0};
-    char        dst_ip[INET_ADDRSTRLEN+1] = {0};
-    int         res = 0, digest_len = 0;
-    FILE       *digest_file_ptr = NULL;
+#ifdef NO_DIGEST_CACHE
+    return(-1);
+#else
 
-    struct digest_cache_list *digest_list_ptr = NULL, *digest_elm = NULL;
+#if USE_FILE_CACHE
+    return add_replay_file_cache(opts, digest);
+#else
+    return add_replay_dbm_cache(opts, digest);
+#endif
+#endif /* NO_DIGEST_CACHE */
+}
 
-    res = fko_get_spa_digest(ctx, &digest);
-    if(res != FKO_SUCCESS)
-    {
-        log_msg(LOG_WARNING, "Error getting digest from SPA data: %s",
-            fko_errstr(res));
+#if USE_FILE_CACHE
+int
+is_replay_file_cache(fko_srv_options_t *opts, char *digest)
+{
+    int         digest_len = 0;
 
-        return(SPA_MSG_DIGEST_ERROR);
-    }
+    struct digest_cache_list *digest_list_ptr = NULL;
 
     digest_len = strlen(digest);
 
@@ -474,11 +475,21 @@ replay_check_file_cache(fko_srv_options_t *opts, fko_ctx_t ctx)
             return(SPA_MSG_REPLAY);
         }
     }
+    return(SPA_MSG_SUCCESS);
+}
+
+int
+add_replay_file_cache(fko_srv_options_t *opts, char *digest)
+{
+    FILE       *digest_file_ptr = NULL;
+    int         digest_len = 0;
+    char        src_ip[INET_ADDRSTRLEN+1] = {0};
+    char        dst_ip[INET_ADDRSTRLEN+1] = {0};
+
+    struct digest_cache_list *digest_elm = NULL;
+
+    digest_len = strlen(digest);
 
-    /* If we make it here, then this is a new SPA packet that needs to be
-     * added to the cache.  We've already decrypted the data, so we know that
-     * the contents are valid.
-    */
     if ((digest_elm = calloc(1, sizeof(struct digest_cache_list))) == NULL)
     {
         log_msg(LOG_WARNING, "Error calloc() returned NULL for digest cache element",
@@ -537,7 +548,7 @@ replay_check_file_cache(fko_srv_options_t *opts, fko_ctx_t ctx)
 
 #if !USE_FILE_CACHE
 int
-replay_check_dbm_cache(fko_srv_options_t *opts, fko_ctx_t ctx)
+is_replay_dbm_cache(fko_srv_options_t *opts, char *digest)
 {
 #ifdef NO_DIGEST_CACHE
     return 0;
@@ -550,20 +561,11 @@ replay_check_dbm_cache(fko_srv_options_t *opts, fko_ctx_t ctx)
 #endif
     datum       db_key, db_ent;
 
-    char       *digest;
-    int         digest_len, res;
+    char       *digest = NULL;
+    int         digest_len, res = SPA_MSG_SUCCESS;
 
     digest_cache_info_t dc_info;
 
-    res = fko_get_spa_digest(ctx, &digest);
-    if(res != FKO_SUCCESS)
-    {
-        log_msg(LOG_WARNING, "Error getting digest from SPA data: %s",
-            fko_errstr(res));
-
-        return(SPA_MSG_DIGEST_ERROR);
-    }
-
     digest_len = strlen(digest);
 
     db_key.dptr = digest;
@@ -609,9 +611,65 @@ replay_check_dbm_cache(fko_srv_options_t *opts, fko_ctx_t ctx)
 #ifdef HAVE_LIBGDBM
         free(db_ent.dptr);
 #endif
-
         res = SPA_MSG_REPLAY;
-    } else {
+    }
+
+    MY_DBM_CLOSE(rpdb);
+
+    return(res);
+#endif /* NO_DIGEST_CACHE */
+}
+
+int
+add_replay_dbm_cache(fko_srv_options_t *opts, char *digest)
+{
+#ifdef NO_DIGEST_CACHE
+    return 0;
+#else
+
+#ifdef HAVE_LIBGDBM
+    GDBM_FILE   rpdb;
+#elif HAVE_LIBNDBM
+    DBM        *rpdb;
+#endif
+    datum       db_key, db_ent;
+
+    char       *digest = NULL;
+    int         digest_len, res = SPA_MSG_SUCCESS;
+
+    digest_cache_info_t dc_info;
+
+    digest_len = strlen(digest);
+
+    db_key.dptr = digest;
+    db_key.dsize = digest_len;
+
+    /* Check the db for the key
+    */
+#ifdef HAVE_LIBGDBM
+    rpdb = gdbm_open(
+         opts->config[CONF_DIGEST_DB_FILE], 512, GDBM_WRCREAT, S_IRUSR|S_IWUSR, 0
+    );
+#elif HAVE_LIBNDBM
+    rpdb = dbm_open(opts->config[CONF_DIGEST_DB_FILE], O_RDWR, 0);
+#endif
+
+    if(!rpdb)
+    {
+        log_msg(LOG_WARNING, "Error opening digest_cache: '%s': %s",
+            opts->config[CONF_DIGEST_DB_FILE],
+            MY_DBM_STRERROR(errno)
+        );
+
+        return(SPA_MSG_DIGEST_CACHE_ERROR);
+    }
+
+    db_ent = MY_DBM_FETCH(rpdb, db_key);
+
+    /* If the datum is null, we have a new entry.
+    */
+    if(db_ent.dptr == NULL)
+    {
         /* This is a new SPA packet that needs to be added to the cache.
         */
         dc_info.src_ip   = opts->spa_pkt.packet_src_ip;
@@ -636,12 +694,14 @@ replay_check_dbm_cache(fko_srv_options_t *opts, fko_ctx_t ctx)
 
         res = SPA_MSG_SUCCESS;
     }
+    else
+        res = SPA_MSG_DIGEST_CACHE_ERROR;
 
     MY_DBM_CLOSE(rpdb);
 
     return(res);
 #endif /* NO_DIGEST_CACHE */
-}
+
 #endif /* USE_FILE_CACHE */
 
 #if USE_FILE_CACHE
index 04ee11a..d523fa1 100644 (file)
@@ -59,14 +59,17 @@ struct digest_cache_list {
 /* Prototypes
 */
 int replay_cache_init(fko_srv_options_t *opts);
-int replay_check(fko_srv_options_t *opts, fko_ctx_t ctx);
+int is_replay(fko_srv_options_t *opts, char *digest);
+int add_replay(fko_srv_options_t *opts, char *digest);
 #ifdef USE_FILE_CACHE
 int replay_file_cache_init(fko_srv_options_t *opts);
-int replay_check_file_cache(fko_srv_options_t *opts, fko_ctx_t ctx);
+int is_replay_file_cache(fko_srv_options_t *opts, char *digest);
+int add_replay_file_cache(fko_srv_options_t *opts, char *digest);
 void free_replay_list(fko_srv_options_t *opts);
 #else
 int replay_db_cache_init(fko_srv_options_t *opts);
-int replay_check_dbm_cache(fko_srv_options_t *opts, fko_ctx_t ctx);
+int is_replay_dbm_cache(fko_srv_options_t *opts, char *digest);
+int add_replay_dbm_cache(fko_srv_options_t *opts, char *digest);
 #endif
 
 #endif  /* REPLAY_CACHE_H */
diff --git a/test/conf/dual_key_usage_access.conf b/test/conf/dual_key_usage_access.conf
new file mode 100644 (file)
index 0000000..0cc0d8e
--- /dev/null
@@ -0,0 +1,9 @@
+SOURCE: ANY;
+KEY: fwknoptest;
+OPEN_PORTS: tcp/22;
+FW_ACCESS_TIMEOUT:  2;
+
+SOURCE: ANY;
+KEY: fwknoptest;
+OPEN_PORTS: tcp/80;
+FW_ACCESS_TIMEOUT:  3;
index b919582..7ffd3a8 100755 (executable)
@@ -33,6 +33,7 @@ my $expired_epoch_access_conf = "$conf_dir/expired_epoch_stanza_access.conf";
 my $invalid_expire_access_conf = "$conf_dir/invalid_expire_access.conf";
 my $invalid_source_access_conf = "$conf_dir/invalid_source_access.conf";
 my $force_nat_access_conf = "$conf_dir/force_nat_access.conf";
+my $dual_key_usage_access_conf = "$conf_dir/dual_key_usage_access.conf";
 my $gpg_access_conf     = "$conf_dir/gpg_access.conf";
 my $default_digest_file = "$run_dir/digest.cache";
 my $default_pid_file    = "$run_dir/fwknopd.pid";
@@ -76,7 +77,11 @@ my $test_include = '';
 my @tests_to_include = ();
 my $test_exclude = '';
 my @tests_to_exclude = ();
+my %valgrind_flagged_fcns = ();
+my %valgrind_flagged_fcns_unique = ();
 my $list_mode = 0;
+my $diff_dir1 = '';
+my $diff_dir2 = '';
 my $loopback_intf = '';
 my $anonymize_results = 0;
 my $current_test_file = "$output_dir/init";
@@ -123,7 +128,10 @@ exit 1 unless GetOptions(
     'List-mode'         => \$list_mode,
     'enable-valgrind'   => \$use_valgrind,
     'valgrind-path=s'   => \$valgrindCmd,
+    'output-dir=s'      => \$output_dir,
     'diff'              => \$diff_mode,
+    'diff-dir1=s'       => \$diff_dir1,
+    'diff-dir2=s'       => \$diff_dir2,
     'help'              => \$help
 );
 
@@ -602,6 +610,25 @@ my @tests = (
     {
         'category' => 'Rijndael SPA',
         'subcategory' => 'client+server',
+        'detail'   => 'dual usage access key (tcp/80 http)',
+        'err_msg'  => 'could not complete SPA cycle',
+        'function' => \&spa_cycle,
+        'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopCmd -A tcp/80 -a $fake_ip -D $loopback_ip --get-key " .
+            "$local_key_file --verbose --verbose",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $default_conf -a $dual_key_usage_access_conf " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        ### check for the first stanza that does not allow tcp/80 - the
+        ### second stanza allows this
+        'server_positive_output_matches' => [qr/stanza #1\)\sOne\sor\smore\srequested\sprotocol\/ports\swas\sdenied/],
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
         'detail'   => 'packet aging (past) (tcp/22 ssh)',
         'err_msg'  => 'old SPA packet accepted',
         'function' => \&spa_cycle,
@@ -1317,7 +1344,6 @@ my @tests = (
         'fwknopd_cmdline'  => $default_server_gpg_args,
         'fatal'    => $NO
     },
-
     {
         'category' => 'GnuPG (GPG) SPA',
         'subcategory' => 'server',
@@ -1336,6 +1362,18 @@ my @tests = (
     },
 );
 
+if ($use_valgrind) {
+    push @tests,
+        {
+            'category' => 'valgrind output',
+            'subcategory' => 'flagged functions',
+            'detail'   => '',
+            'err_msg'  => 'could not parse flagged functions',
+            'function' => \&parse_valgrind_flagged_functions,
+            'fatal'    => $NO
+        };
+}
+
 my %test_keys = (
     'category'        => $REQUIRED,
     'subcategory'     => $OPTIONAL,
@@ -1431,7 +1469,8 @@ sub process_include_exclude() {
     if (@tests_to_include) {
         my $found = 0;
         for my $test (@tests_to_include) {
-            if ($msg =~ /$test/) {
+            if ($msg =~ /$test/ or ($use_valgrind
+                    and $msg =~ /valgrind\soutput/)) {
                 $found = 1;
                 last;
             }
@@ -1452,17 +1491,21 @@ sub process_include_exclude() {
 }
 
 sub diff_test_results() {
+
+    $diff_dir1 = "${output_dir}.last" unless $diff_dir2;
+    $diff_dir2 = $output_dir unless $diff_dir1;
+
     die "[*] Need results from a previous run before running --diff"
-        unless -d "${output_dir}.last";
-    die "[*] Current results set does not exist." unless -d $output_dir;
+        unless -d $diff_dir2;
+    die "[*] Current results set does not exist." unless -d $diff_dir1;
 
     my %current_tests  = ();
     my %previous_tests = ();
 
     ### Only diff results for matching tests (parse the logfile to see which
     ### test numbers match across the two test cycles).
-    &build_results_hash(\%current_tests, $output_dir);
-    &build_results_hash(\%previous_tests, "${output_dir}.last");
+    &build_results_hash(\%current_tests, $diff_dir1);
+    &build_results_hash(\%previous_tests, $diff_dir2);
 
     for my $test_msg (sort {$current_tests{$a}{'num'} <=> $current_tests{$b}{'num'}}
                 keys %current_tests) {
@@ -1493,25 +1536,25 @@ sub diff_results() {
     ### remove CMD timestamps
     my $cmd_search_re = qr/^\S+\s.*?\s\d{4}\sCMD\:/;
 
-    for my $file ("${output_dir}.last/${previous_num}.test",
-        "${output_dir}.last/${previous_num}_fwknopd.test",
-        "${output_dir}/${current_num}.test",
-        "${output_dir}/${current_num}_fwknopd.test",
+    for my $file ("$diff_dir1/${previous_num}.test",
+        "$diff_dir1/${previous_num}_fwknopd.test",
+        "$diff_dir2/${current_num}.test",
+        "$diff_dir2/${current_num}_fwknopd.test",
     ) {
         system qq{perl -p -i -e 's|$valgrind_search_re||' $file} if -e $file;
         system qq{perl -p -i -e 's|$cmd_search_re|CMD:|' $file} if -e $file;
     }
 
-    if (-e "${output_dir}.last/${previous_num}.test"
-            and -e "${output_dir}/${current_num}.test") {
-        system "diff -u ${output_dir}.last/${previous_num}.test " .
-            "${output_dir}/${current_num}.test";
+    if (-e "$diff_dir1/${previous_num}.test"
+            and -e "$diff_dir2/${current_num}.test") {
+        system "diff -u $diff_dir1/${previous_num}.test " .
+            "$diff_dir2/${current_num}.test";
     }
 
-    if (-e "${output_dir}.last/${previous_num}_fwknopd.test"
-            and -e "${output_dir}/${current_num}_fwknopd.test") {
-        system "diff -u ${output_dir}.last/${previous_num}_fwknopd.test " .
-            "${output_dir}/${current_num}_fwknopd.test";
+    if (-e "$diff_dir1/${previous_num}_fwknopd.test"
+            and -e "$diff_dir2/${current_num}_fwknopd.test") {
+        system "diff -u $diff_dir1/${previous_num}_fwknopd.test " .
+            "$diff_dir2/${current_num}_fwknopd.test";
     }
 
     return;
@@ -2722,6 +2765,42 @@ sub identify_loopback_intf() {
     return;
 }
 
+sub parse_valgrind_flagged_functions() {
+    for my $file (glob("$output_dir/*.test")) {
+        my $type = 'server';
+        $type = 'client' if $file =~ /\d\.test/;
+        open F, "< $file" or die $!;
+        while (<F>) {
+            ### ==30969==    by 0x4E3983A: fko_set_username (fko_user.c:65)
+            if (/^==.*\sby\s\S+\:\s(\S+)\s(.*)/) {
+                $valgrind_flagged_fcns{$type}{"$1 $2"}++;
+                $valgrind_flagged_fcns_unique{$type}{$1}++;
+            }
+        }
+        close F;
+    }
+
+    open F, ">> $current_test_file" or die $!;
+    for my $type ('client', 'server') {
+        print F "\n[+] fwknop $type functions (unique view):\n";
+        next unless defined $valgrind_flagged_fcns_unique{$type};
+        for my $fcn (sort {$valgrind_flagged_fcns_unique{$type}{$b}
+                <=> $valgrind_flagged_fcns_unique{$type}{$a}}
+                keys %{$valgrind_flagged_fcns_unique{$type}}) {
+            printf F "    %5d : %s\n", $valgrind_flagged_fcns_unique{$type}{$fcn}, $fcn;
+        }
+        print F "\n[+] fwknop $type functions (with call line numbers):\n";
+        for my $fcn (sort {$valgrind_flagged_fcns{$type}{$b}
+                <=> $valgrind_flagged_fcns{$type}{$a}} keys %{$valgrind_flagged_fcns{$type}}) {
+            printf F "    %5d : %s\n", $valgrind_flagged_fcns{$type}{$fcn}, $fcn;
+        }
+        next unless defined $valgrind_flagged_fcns{$type};
+
+    }
+    close F;
+    return 1;
+}
+
 sub is_fw_rule_active() {
     my $test_hr = shift;
 
@@ -2839,5 +2918,42 @@ sub logr() {
 }
 
 sub usage() {
-    return;
+    print <<_HELP_;
+
+[+] $0 <options>
+
+    -A   --Anonymize-results      - Prepare anonymized results at:
+                                    $tarfile
+    --diff                        - Compare the results of one test run to
+                                    another.  By default this compares output
+                                    in ${output_dir}.last to $output_dir
+    --diff-dir1=<path>            - Left hand side of diff directory path,
+                                    default is: ${output_dir}.last
+    --diff-dir2=<path>            - Right hand side of diff directory path,
+                                    default is: $output_dir
+    --include=<regex>             - Specify a regex to be used over test
+                                    names that must match.
+    --exclude=<regex>             - Specify a regex to be used over test
+                                    names that must not match.
+    --enable-recompile            - Recompile fwknop sources and look for
+                                    compilation warnings.
+    --enable-valgrind             - Run every test underneath valgrind.
+    --List                        - List test names.
+    --loopback-intf=<intf>        - Specify loopback interface name (default
+                                    depends on the OS where the test suite
+                                    is executed).
+    --output-dir=<path>           - Path to output directory, default is:
+                                    $output_dir
+    --fwknop-path=<path>          - Path to fwknop binary, default is:
+                                    $fwknopCmd
+    --fwknopd-path=<path>         - Path to fwknopd binary, default is:
+                                    $fwknopdCmd
+    --libfko-path=<path>          - Path to libfko, default is:
+                                    $libfko_bin
+    --valgrind-path=<path>        - Path to valgrind, default is:
+                                    $valgrindCmd
+    -h   --help                   - Display usage on STDOUT and exit.
+
+_HELP_
+    exit 0;
 }