[server] replay attack detection memory leak bug fix
[fwknop.git] / server / incoming_spa.c
1 /*
2  *****************************************************************************
3  *
4  * File:    incoming_spa.c
5  *
6  * Author:  Damien S. Stuart
7  *
8  * Purpose: Process an incoming SPA data packet for fwknopd.
9  *
10  * Copyright 2010 Damien Stuart (dstuart@dstuart.org)
11  *
12  *  License (GNU Public License):
13  *
14  *  This program is free software; you can redistribute it and/or
15  *  modify it under the terms of the GNU General Public License
16  *  as published by the Free Software Foundation; either version 2
17  *  of the License, or (at your option) any later version.
18  *
19  *  This program is distributed in the hope that it will be useful,
20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  *  GNU General Public License for more details.
23  *
24  *  You should have received a copy of the GNU General Public License
25  *  along with this program; if not, write to the Free Software
26  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
27  *  USA
28  *
29  *****************************************************************************
30 */
31 #include "fwknopd_common.h"
32 #include "netinet_common.h"
33
34 #if HAVE_SYS_WAIT_H
35   #include <sys/wait.h>
36 #endif
37
38 #include "incoming_spa.h"
39 #include "access.h"
40 #include "extcmd.h"
41 #include "log_msg.h"
42 #include "utils.h"
43 #include "fw_util.h"
44 #include "fwknopd_errors.h"
45 #include "replay_cache.h"
46
47 /* Validate and in some cases preprocess/reformat the SPA data.  Return an
48  * error code value if there is any indication the data is not valid spa data.
49 */
50 static int
51 preprocess_spa_data(fko_srv_options_t *opts, const char *src_ip)
52 {
53     spa_pkt_info_t *spa_pkt = &(opts->spa_pkt);
54
55     char    *ndx = (char *)&(spa_pkt->packet_data);
56     int      pkt_data_len = spa_pkt->packet_data_len;
57     int      i;
58
59     /* At this point, we can reset the packet data length to 0.  This is our
60      * indicator to the rest of the program that we do not have a current
61      * spa packet to process (after this one that is).
62     */
63     spa_pkt->packet_data_len = 0;
64
65     /* Detect and parse out SPA data from an HTTP request. If the SPA data
66      * starts with "GET /" and the user agent starts with "Fwknop", then
67      * assume it is a SPA over HTTP request.
68     */
69     if(strncasecmp(opts->config[CONF_ENABLE_SPA_OVER_HTTP], "N", 1) == 0
70       && strncasecmp(ndx, "GET /", 5) == 0
71       && strstr(ndx, "User-Agent: Fwknop") != NULL)
72     {
73         /* This looks like an HTTP request, so let's see if we are
74          * configured to accept such request and if so, find the SPA
75          * data.
76         */
77
78         /* Now extract, adjust (convert characters translated by the fwknop
79          * client), and reset the SPA message itself.
80         */
81         strlcpy((char *)spa_pkt->packet_data, ndx+5, pkt_data_len);
82
83         for(i=0; i<pkt_data_len; i++)
84         {
85             if(isspace(*ndx)) /* The first space marks the end of the req */
86             {
87                 *ndx = '\0';
88                 break;
89             }
90             else if(*ndx == '-') /* Convert '-' to '+' */
91                 *ndx = '+';
92             else if(*ndx == '_') /* Convert '_' to '/' */
93                 *ndx = '/';
94
95             ndx++;
96         }
97     }
98
99     /* Require base64-encoded data
100     */
101     if(! is_base64(spa_pkt->packet_data, pkt_data_len))
102         return(SPA_MSG_NOT_SPA_DATA);
103
104
105     /* --DSS:  Are there other checks we can do here ??? */
106
107     /* If we made it here, we have no reason to assume this is not SPA data
108      * (at least until we come up with more checks).
109     */
110     return(FKO_SUCCESS);
111 }
112
113 /* For replay attack detection
114 */
115 static int
116 get_raw_digest(char **digest, char *pkt_data)
117 {
118     fko_ctx_t    ctx = NULL;
119     char        *tmp_digest = NULL;
120     int          res = FKO_SUCCESS;
121
122     /* initialize an FKO context with no decryption key just so
123      * we can get the outer message digest
124     */
125     res = fko_new_with_data(&ctx, (char *)pkt_data, NULL);
126     if(res != FKO_SUCCESS)
127     {
128         log_msg(LOG_WARNING, "Error initializing FKO context from SPA data: %s",
129             fko_errstr(res));
130         fko_destroy(ctx);
131         return(SPA_MSG_FKO_CTX_ERROR);
132     }
133
134     res = fko_set_raw_spa_digest_type(ctx, FKO_DEFAULT_DIGEST);
135     if(res != FKO_SUCCESS)
136     {
137         log_msg(LOG_WARNING, "Error setting digest type for SPA data: %s",
138             fko_errstr(res));
139         fko_destroy(ctx);
140         return(SPA_MSG_DIGEST_ERROR);
141     }
142
143     res = fko_set_raw_spa_digest(ctx);
144     if(res != FKO_SUCCESS)
145     {
146         log_msg(LOG_WARNING, "Error setting digest for SPA data: %s",
147             fko_errstr(res));
148         fko_destroy(ctx);
149         return(SPA_MSG_DIGEST_ERROR);
150     }
151
152     res = fko_get_raw_spa_digest(ctx, &tmp_digest);
153     if(res != FKO_SUCCESS)
154     {
155         log_msg(LOG_WARNING, "Error getting digest from SPA data: %s",
156             fko_errstr(res));
157         fko_destroy(ctx);
158         return(SPA_MSG_DIGEST_ERROR);
159     }
160
161     *digest = strdup(tmp_digest);
162
163     if (digest == NULL)
164         return SPA_MSG_ERROR;
165
166     fko_destroy(ctx);
167
168     return res;
169 }
170
171
172 /* Popluate a spa_data struct from an initialized (and populated) FKO context.
173 */
174 static int
175 get_spa_data_fields(fko_ctx_t ctx, spa_data_t *spdat)
176 {
177     int res = FKO_SUCCESS;
178
179     res = fko_get_username(ctx, &(spdat->username));
180     if(res != FKO_SUCCESS)
181         return(res);
182
183     res = fko_get_timestamp(ctx, &(spdat->timestamp));
184     if(res != FKO_SUCCESS)
185         return(res);
186
187     res = fko_get_version(ctx, &(spdat->version));
188     if(res != FKO_SUCCESS)
189         return(res);
190
191     res = fko_get_spa_message_type(ctx, &(spdat->message_type));
192     if(res != FKO_SUCCESS)
193         return(res);
194
195     res = fko_get_spa_message(ctx, &(spdat->spa_message));
196     if(res != FKO_SUCCESS)
197         return(res);
198
199     res = fko_get_spa_nat_access(ctx, &(spdat->nat_access));
200     if(res != FKO_SUCCESS)
201         return(res);
202
203     res = fko_get_spa_server_auth(ctx, &(spdat->server_auth));
204     if(res != FKO_SUCCESS)
205         return(res);
206
207     res = fko_get_spa_client_timeout(ctx, (int *)&(spdat->client_timeout));
208     if(res != FKO_SUCCESS)
209         return(res);
210
211     return(res);
212 }
213
214 /* Check for access.conf stanza SOURCE match based on SPA packet
215  * source IP
216 */
217 static int
218 is_src_match(acc_stanza_t *acc, const uint32_t ip)
219 {
220     while (acc)
221     {
222         if(compare_addr_list(acc->source_list, ip))
223             return 1;
224
225         acc = acc->next;
226     }
227     return 0;
228 }
229
230 /* Process the SPA packet data
231 */
232 void
233 incoming_spa(fko_srv_options_t *opts)
234 {
235     /* Always a good idea to initialize ctx to null if it will be used
236      * repeatedly (especially when using fko_new_with_data()).
237     */
238     fko_ctx_t       ctx = NULL;
239
240     char            *spa_ip_demark, *gpg_id, *raw_digest = NULL;
241     time_t          now_ts;
242     int             res, status, ts_diff, enc_type, stanza_num=0;
243     int             added_replay_digest = 0;
244
245     spa_pkt_info_t *spa_pkt = &(opts->spa_pkt);
246
247     /* This will hold our pertinent SPA data.
248     */
249     spa_data_t spadat;
250
251     /* Loop through all access stanzas looking for a match
252     */
253     acc_stanza_t    *acc = opts->acc_stanzas;
254
255     inet_ntop(AF_INET, &(spa_pkt->packet_src_ip),
256         spadat.pkt_source_ip, sizeof(spadat.pkt_source_ip));
257
258     /* At this point, we want to validate and (if needed) preprocess the
259      * SPA data and/or to be reasonably sure we have a SPA packet (i.e
260      * try to eliminate obvious non-spa packets).
261     */
262     res = preprocess_spa_data(opts, spadat.pkt_source_ip);
263     if(res != FKO_SUCCESS)
264     {
265         if(opts->verbose > 1)
266             log_msg(LOG_INFO, "preprocess_spa_data() returned error %i: '%s' for incoming packet.",
267                 res, get_errstr(res));
268         return;
269     }
270
271     if (is_src_match(opts->acc_stanzas, ntohl(spa_pkt->packet_src_ip)))
272     {
273         if(strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0)
274             /* Check for a replay attack
275             */
276             res = get_raw_digest(&raw_digest, (char *)spa_pkt->packet_data);
277             if(res != FKO_SUCCESS)
278             {
279                 if (raw_digest != NULL)
280                     free(raw_digest);
281                 return;
282             }
283             if (raw_digest == NULL)
284                 return;
285
286             if (is_replay(opts, raw_digest) != SPA_MSG_SUCCESS)
287             {
288                 free(raw_digest);
289                 return;
290             }
291     }
292     else
293     {
294         log_msg(LOG_WARNING,
295             "No access data found for source IP: %s", spadat.pkt_source_ip
296         );
297         return;
298     }
299
300     /* Now that we know there is a matching access.conf stanza and the
301      * incoming SPA packet is not a replay, see if we should grant any
302      * access
303     */
304     while(acc)
305     {
306         stanza_num++;
307
308         /* Check for a match for the SPA source IP and the access stanza
309         */
310         if(! compare_addr_list(acc->source_list, ntohl(spa_pkt->packet_src_ip)))
311         {
312             acc = acc->next;
313             continue;
314         }
315
316         log_msg(LOG_INFO, "(stanza #%d) SPA Packet from IP: %s received with access source match",
317             stanza_num, spadat.pkt_source_ip);
318
319         if(opts->verbose > 1)
320             log_msg(LOG_INFO, "SPA Packet: '%s'\n", spa_pkt->packet_data);
321
322         /* Make sure this access stanza has not expired
323         */
324         if(acc->access_expire_time > 0)
325         {
326             if(acc->expired)
327             {
328                 acc = acc->next;
329                 continue;
330             }
331             else
332             {
333                 if(time(NULL) > acc->access_expire_time)
334                 {
335                     log_msg(LOG_INFO, "(stanza #%d) Access stanza has expired",
336                         stanza_num);
337                     acc->expired = 1;
338                     acc = acc->next;
339                     continue;
340                 }
341             }
342         }
343
344         /* Get encryption type and try its decoding routine first (if the key
345          * for that type is set)
346         */
347         enc_type = fko_encryption_type((char *)spa_pkt->packet_data);
348
349         if(enc_type == FKO_ENCRYPTION_RIJNDAEL)
350         {
351             if(acc->key != NULL)
352                 res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, acc->key);
353             else
354             {
355                 log_msg(LOG_ERR,
356                     "(stanza #%d) No KEY for RIJNDAEL encrypted messages",
357                     stanza_num
358                 );
359                 acc = acc->next;
360                 continue;
361             }
362         }
363         else if(enc_type == FKO_ENCRYPTION_GPG)
364         {
365             /* For GPG we create the new context without decrypting on the fly
366              * so we can set some  GPG parameters first.
367             */
368             if(acc->gpg_decrypt_pw != NULL)
369             {
370                 res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, NULL);
371                 if(res != FKO_SUCCESS)
372                 {
373                     log_msg(LOG_WARNING,
374                         "(stanza #%d) Error creating fko context (before decryption): %s",
375                         stanza_num, fko_errstr(res)
376                     );
377                     acc = acc->next;
378                     continue;
379                 }
380
381                 /* Set whatever GPG parameters we have.
382                 */
383                 if(acc->gpg_home_dir != NULL)
384                     res = fko_set_gpg_home_dir(ctx, acc->gpg_home_dir);
385                     if(res != FKO_SUCCESS)
386                     {
387                         log_msg(LOG_WARNING,
388                             "(stanza #%d) Error setting GPG keyring path to %s: %s",
389                             stanza_num, acc->gpg_home_dir, fko_errstr(res)
390                         );
391                         acc = acc->next;
392                         continue;
393                     }
394
395                 if(acc->gpg_decrypt_id != NULL)
396                     fko_set_gpg_recipient(ctx, acc->gpg_decrypt_id);
397
398                 /* If GPG_REQUIRE_SIG is set for this acc stanza, then set
399                  * the FKO context accordingly and check the other GPG Sig-
400                  * related parameters. This also applies when REMOTE_ID is
401                  * set.
402                 */
403                 if(acc->gpg_require_sig)
404                 {
405                     fko_set_gpg_signature_verify(ctx, 1);
406
407                     /* Set whether or not to ignore signature verification errors.
408                     */
409                     fko_set_gpg_ignore_verify_error(ctx, acc->gpg_ignore_sig_error);
410                 }
411                 else
412                 {
413                     fko_set_gpg_signature_verify(ctx, 0);
414                     fko_set_gpg_ignore_verify_error(ctx, 1);
415                 }
416
417                 /* Now decrypt the data.
418                 */
419                 res = fko_decrypt_spa_data(ctx, acc->gpg_decrypt_pw);
420
421             }
422             else
423             {
424                 log_msg(LOG_ERR,
425                     "(stanza #%d) No GPG_DECRYPT_PW for GPG encrypted messages",
426                     stanza_num
427                 );
428                 acc = acc->next;
429                 continue;
430             }
431         }
432         else
433         {
434             log_msg(LOG_ERR, "(stanza #%d) Unable to determing encryption type. Got type=%i.",
435                 stanza_num, enc_type);
436             acc = acc->next;
437             continue;
438         }
439
440         /* Do we have a valid FKO context?  Did the SPA decrypt properly?
441         */
442         if(res != FKO_SUCCESS)
443         {
444             log_msg(LOG_WARNING, "(stanza #%d) Error creating fko context: %s",
445                 stanza_num, fko_errstr(res));
446
447             if(IS_GPG_ERROR(res))
448                 log_msg(LOG_WARNING, "(stanza #%d) - GPG ERROR: %s",
449                     stanza_num, fko_gpg_errstr(ctx));
450
451             if(ctx != NULL)
452                 fko_destroy(ctx);
453             acc = acc->next;
454             continue;
455         }
456
457         /* Add this SPA packet into the replay detection cache
458         */
459         if (! added_replay_digest)
460         {
461             res = add_replay(opts, raw_digest);
462             if (res != SPA_MSG_SUCCESS)
463             {
464                 log_msg(LOG_WARNING, "(stanza #%d) Could not add digest to replay cache",
465                     stanza_num);
466                 if(ctx != NULL)
467                     fko_destroy(ctx);
468                 acc = acc->next;
469                 continue;
470             }
471             added_replay_digest = 1;
472         }
473
474         /* At this point, we assume the SPA data is valid.  Now we need to see
475          * if it meets our access criteria.
476         */
477         if(opts->verbose > 1)
478             log_msg(LOG_INFO, "(stanza #%d) SPA Decode (res=%i):\n%s",
479                 stanza_num, res, dump_ctx(ctx));
480
481         /* First, if this is a GPG message, and GPG_REMOTE_ID list is not empty,
482          * then we need to make sure this incoming message is signer ID matches
483          * an entry in the list.
484         */
485         if(enc_type == FKO_ENCRYPTION_GPG && acc->gpg_require_sig)
486         {
487             res = fko_get_gpg_signature_id(ctx, &gpg_id);
488             if(res != FKO_SUCCESS)
489             {
490                 log_msg(LOG_WARNING, "(stanza #%d) Error pulling the GPG signature ID from the context: %s",
491                     stanza_num, fko_gpg_errstr(ctx));
492                 if(ctx != NULL)
493                     fko_destroy(ctx);
494                 acc = acc->next;
495                 continue;
496             }
497
498             if(opts->verbose)
499                 log_msg(LOG_INFO, "(stanza #%d) Incoming SPA data signed by '%s'.",
500                     stanza_num, gpg_id);
501
502             if(acc->gpg_remote_id != NULL && !acc_check_gpg_remote_id(acc, gpg_id))
503             {
504                 log_msg(LOG_WARNING,
505                     "(stanza #%d) Incoming SPA packet signed by ID: %s, but that ID is not the GPG_REMOTE_ID list.",
506                     stanza_num, gpg_id);
507                 if(ctx != NULL)
508                     fko_destroy(ctx);
509                 acc = acc->next;
510                 continue;
511             }
512         }
513
514         /* Populate our spa data struct for future reference.
515         */
516         res = get_spa_data_fields(ctx, &spadat);
517
518         /* Figure out what our timeout will be. If it is specified in the SPA
519          * data, then use that.  If not, try the FW_ACCESS_TIMEOUT from the
520          * access.conf file (if there is one).  Otherwise use the default.
521         */
522         if(spadat.client_timeout > 0)
523             spadat.fw_access_timeout = spadat.client_timeout;
524         else if(acc->fw_access_timeout > 0)
525             spadat.fw_access_timeout = acc->fw_access_timeout;
526         else
527             spadat.fw_access_timeout = DEF_FW_ACCESS_TIMEOUT;
528
529         if(res != FKO_SUCCESS)
530         {
531             log_msg(LOG_ERR, "(stanza #%d) Unexpected error pulling SPA data from the context: %s",
532                 stanza_num, fko_errstr(res));
533
534             if(ctx != NULL)
535                 fko_destroy(ctx);
536             acc = acc->next;
537             continue;
538         }
539
540         /* Check packet age if so configured.
541         */
542         if(strncasecmp(opts->config[CONF_ENABLE_SPA_PACKET_AGING], "Y", 1) == 0)
543         {
544             time(&now_ts);
545
546             ts_diff = abs(now_ts - spadat.timestamp);
547
548             if(ts_diff > atoi(opts->config[CONF_MAX_SPA_PACKET_AGE]))
549             {
550                 log_msg(LOG_WARNING, "(stanza #%d) SPA data time difference is too great (%i seconds).",
551                     stanza_num, ts_diff);
552
553                 if(ctx != NULL)
554                     fko_destroy(ctx);
555                 acc = acc->next;
556                 continue;
557             }
558         }
559
560         /* At this point, we have enough to check the embedded (or packet source)
561          * IP address against the defined access rights.  We start by splitting
562          * the spa msg source IP from the remainder of the message.
563         */
564         spa_ip_demark = strchr(spadat.spa_message, ',');
565         if(spa_ip_demark == NULL)
566         {
567             log_msg(LOG_WARNING, "(stanza #%d) Error parsing SPA message string: %s",
568                 stanza_num, fko_errstr(res));
569
570             if(ctx != NULL)
571                 fko_destroy(ctx);
572             acc = acc->next;
573             continue;
574         }
575
576         strlcpy(spadat.spa_message_src_ip,
577             spadat.spa_message, (spa_ip_demark-spadat.spa_message)+1);
578
579         if(strnlen(spadat.spa_message_src_ip,
580                 MIN_IPV4_STR_LEN) < MIN_IPV4_STR_LEN)
581         {
582             log_msg(LOG_WARNING, "(stanza #%d) Invalid source IP in SPA message, ignoring SPA packet",
583                 stanza_num, fko_errstr(res));
584
585             if(ctx != NULL)
586                 fko_destroy(ctx);
587             acc = acc->next;
588             break;
589         }
590
591         strlcpy(spadat.spa_message_remain, spa_ip_demark+1, 1024);
592
593         /* If use source IP was requested (embedded IP of 0.0.0.0), make sure it
594          * is allowed.
595         */
596         if(strcmp(spadat.spa_message_src_ip, "0.0.0.0") == 0)
597         {
598             if(acc->require_source_address)
599             {
600                 log_msg(LOG_WARNING,
601                     "(stanza #%d) Got 0.0.0.0 when valid source IP was required.",
602                     stanza_num
603                 );
604
605                 if(ctx != NULL)
606                     fko_destroy(ctx);
607                 acc = acc->next;
608                 continue;
609             }
610
611             spadat.use_src_ip = spadat.pkt_source_ip;
612         }
613         else
614             spadat.use_src_ip = spadat.spa_message_src_ip;
615
616         /* If REQUIRE_USERNAME is set, make sure the username in this SPA data
617          * matches.
618         */
619         if(acc->require_username != NULL)
620         {
621             if(strcmp(spadat.username, acc->require_username) != 0)
622             {
623                 log_msg(LOG_WARNING,
624                     "(stanza #%d) Username in SPA data (%s) does not match required username: %s",
625                     stanza_num, spadat.username, acc->require_username
626                 );
627
628                 if(ctx != NULL)
629                     fko_destroy(ctx);
630                 acc = acc->next;
631                 continue;
632             }
633         }
634
635         /* Take action based on SPA message type.
636         */
637         if(spadat.message_type == FKO_LOCAL_NAT_ACCESS_MSG
638               || spadat.message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG
639               || spadat.message_type == FKO_NAT_ACCESS_MSG
640               || spadat.message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG)
641         {
642 #if FIREWALL_IPTABLES
643             if(strncasecmp(opts->config[CONF_ENABLE_IPT_FORWARDING], "Y", 1)!=0)
644             {
645                 log_msg(LOG_WARNING,
646                     "(stanza #%d) SPA packet from %s requested NAT access, but is not enabled",
647                     stanza_num, spadat.pkt_source_ip
648                 );
649
650                 if(ctx != NULL)
651                     fko_destroy(ctx);
652                 acc = acc->next;
653                 continue;
654             }
655 #else
656             log_msg(LOG_WARNING,
657                 "(stanza #%d) SPA packet from %s requested unsupported NAT access",
658                 stanza_num, spadat.pkt_source_ip
659             );
660
661             if(ctx != NULL)
662                 fko_destroy(ctx);
663             acc = acc->next;
664             continue;
665 #endif
666         }
667
668         /* Command messages.
669         */
670         if(spadat.message_type == FKO_COMMAND_MSG)
671         {
672             if(!acc->enable_cmd_exec)
673             {
674                 log_msg(LOG_WARNING,
675                     "(stanza #%d) SPA Command message are not allowed in the current configuration.",
676                     stanza_num
677                 );
678
679                 if(ctx != NULL)
680                     fko_destroy(ctx);
681                 acc = acc->next;
682                 continue;
683             }
684             else
685             {
686                 log_msg(LOG_INFO,
687                     "(stanza #%d) Processing SPA Command message: command='%s'.",
688                     stanza_num, spadat.spa_message_remain
689                 );
690
691                 /* Do we need to become another user? If so, we call
692                  * run_extcmd_as and pass the cmd_exec_uid.
693                 */
694                 if(acc->cmd_exec_user != NULL && strncasecmp(acc->cmd_exec_user, "root", 4) != 0)
695                 {
696                     if(opts->verbose)
697                         log_msg(LOG_INFO, "(stanza #%d) Setting effective user to %s (UID=%i) before running command.",
698                             stanza_num, acc->cmd_exec_user, acc->cmd_exec_uid);
699
700
701                     res = run_extcmd_as(acc->cmd_exec_uid,
702                                         spadat.spa_message_remain, NULL, 0, 0);
703                 }
704                 else /* Just run it as we are (root that is). */
705                     res = run_extcmd(spadat.spa_message_remain, NULL, 0, 5);
706
707                 /* --DSS XXX: I have found that the status (and res for that
708                  *            matter) have been unreliable indicators of the
709                  *            actual exit status of some commands.  Not sure
710                  *            why yet.  For now, we will take what we get.
711                 */
712                 status = WEXITSTATUS(res);
713
714                 if(opts->verbose > 1)
715                     log_msg(LOG_WARNING,
716                         "(stanza #%d) CMD_EXEC: command returned %i",
717                         stanza_num, status);
718
719                 if(status != 0)
720                     res = SPA_MSG_COMMAND_ERROR;
721
722                 if(ctx != NULL)
723                     fko_destroy(ctx);
724
725                 /* we processed the command on a matching access stanza, so we
726                  * don't look for anything else to do with this SPA packet
727                 */
728                 break;
729             }
730         }
731
732         /* From this point forward, we have some kind of access message. So
733          * we first see if access is allowed by checking access against
734          * restrict_ports and open_ports.
735          *
736          *  --DSS TODO: We should add BLACKLIST support here as well.
737         */
738         if(! acc_check_port_access(acc, spadat.spa_message_remain))
739         {
740             log_msg(LOG_WARNING,
741                 "(stanza #%d) One or more requested protocol/ports was denied per access.conf.",
742                 stanza_num
743             );
744
745             if(ctx != NULL)
746                 fko_destroy(ctx);
747             acc = acc->next;
748             continue;
749         }
750
751         /* At this point, we process the SPA request and break out of the
752          * access stanza loop (first valid access stanza stops us looking
753          * for others).
754         */
755         process_spa_request(opts, acc, &spadat);
756         if(ctx != NULL)
757             fko_destroy(ctx);
758         break;
759     }
760
761     if (raw_digest != NULL)
762         free(raw_digest);
763
764     return;
765 }
766
767 /***EOF***/