040c3ff6f9fd3accc4e1fa97f1f9ce0e1179e193
[fwknop.git] / client / fwknop.c
1 /*
2  *****************************************************************************
3  *
4  * File:    fwknop.c
5  *
6  * Author:  Damien S. Stuart
7  *
8  * Purpose: An implementation of an fwknop client.
9  *
10  * Copyright 2009-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 "fwknop.h"
32 #include "config_init.h"
33 #include "spa_comm.h"
34 #include "utils.h"
35 #include "getpasswd.h"
36
37 /* prototypes
38 */
39 static char * get_user_pw(fko_cli_options_t *options, const int crypt_op);
40 static void display_ctx(fko_ctx_t ctx);
41 static void errmsg(const char *msg, const int err);
42 static void show_last_command(void);
43 static void save_args(int argc, char **argv);
44 static void run_last_args(fko_cli_options_t *options);
45 static int set_message_type(fko_ctx_t ctx, fko_cli_options_t *options);
46 static int set_nat_access(fko_ctx_t ctx, fko_cli_options_t *options);
47 static int get_rand_port(fko_ctx_t ctx);
48 int resolve_ip_http(fko_cli_options_t *options);
49
50 int
51 main(int argc, char **argv)
52 {
53     fko_ctx_t           ctx, ctx2;
54     int                 res;
55     char               *spa_data, *version;
56     char                access_buf[MAX_LINE_LEN];
57
58     fko_cli_options_t   options;
59
60     /* Handle command line
61     */
62     config_init(&options, argc, argv);
63
64     /* Handle options that don't require a libfko context
65     */
66     if(options.run_last_command)
67         run_last_args(&options);
68     else if(options.show_last_command)
69         show_last_command();
70     else if (!options.no_save_args)
71         save_args(argc, argv);
72
73     /* Intialize the context
74     */
75     res = fko_new(&ctx);
76     if(res != FKO_SUCCESS)
77     {
78         errmsg("fko_new", res);
79         return(EXIT_FAILURE);
80     }
81
82     /* Display version info and exit.
83     */
84     if (options.version) {
85         fko_get_version(ctx, &version);
86
87         fprintf(stdout, "fwknop client %s, FKO protocol version %s\n",
88             MY_VERSION, version);
89
90         return(EXIT_SUCCESS);
91     }
92
93     /* Set client timeout
94     */
95     if(options.fw_timeout >= 0)
96     {
97         res = fko_set_spa_client_timeout(ctx, options.fw_timeout);
98         if(res != FKO_SUCCESS)
99         {
100             errmsg("fko_set_spa_client_timeout", res);
101             return(EXIT_FAILURE);
102         }
103     }
104
105     /* Set the SPA packet message type based on command line options
106     */
107     res = set_message_type(ctx, &options);
108     if(res != FKO_SUCCESS)
109     {
110         errmsg("fko_set_spa_message_type", res);
111         return(EXIT_FAILURE);
112     }
113
114     /* Adjust the SPA timestamp if necessary
115     */
116     if(options.time_offset_plus > 0)
117     {
118         res = fko_set_timestamp(ctx, options.time_offset_plus);
119         if(res != FKO_SUCCESS)
120         {
121             errmsg("fko_set_timestamp", res);
122             return(EXIT_FAILURE);
123         }
124     }
125     if(options.time_offset_minus > 0)
126     {
127         res = fko_set_timestamp(ctx, -options.time_offset_minus);
128         if(res != FKO_SUCCESS)
129         {
130             errmsg("fko_set_timestamp", res);
131             return(EXIT_FAILURE);
132         }
133     }
134
135     if(options.server_command[0] != 0x0)
136     {
137         /* Set the access message to a command that the server will
138          * execute
139         */
140         snprintf(access_buf, MAX_LINE_LEN, "%s%s%s",
141                 options.allow_ip_str, ",", options.server_command);
142     }
143     else
144     {
145         /* Resolve the client's public facing IP address if requestesd.
146          * if this fails, consider it fatal.
147         */
148         if (options.resolve_ip_http)
149             if(resolve_ip_http(&options) < 0)
150                 return(EXIT_FAILURE);
151
152         /* Set a message string by combining the allow IP and the
153          * port/protocol.  The fwknopd server allows no port/protocol
154          * to be specified as well, so in this case append the string
155          * "none/0" to the allow IP.
156         */
157         if(options.access_str[0] != 0x0)
158         {
159             snprintf(access_buf, MAX_LINE_LEN, "%s%s%s",
160                     options.allow_ip_str, ",", options.access_str);
161         }
162         else
163         {
164             snprintf(access_buf, MAX_LINE_LEN, "%s%s%s",
165                     options.allow_ip_str, ",", "none/0");
166         }
167     }
168     res = fko_set_spa_message(ctx, access_buf);
169     if(res != FKO_SUCCESS)
170     {
171         errmsg("fko_set_spa_message", res);
172         return(EXIT_FAILURE);
173     }
174
175     /* Set NAT access string
176     */
177     if (options.nat_local || options.nat_access_str[0] != 0x0)
178     {
179         res = set_nat_access(ctx, &options);
180         if(res != FKO_SUCCESS)
181         {
182             errmsg("fko_set_nat_access_str", res);
183             return(EXIT_FAILURE);
184         }
185     }
186
187     /* Set username
188     */
189     if(options.spoof_user[0] != 0x0)
190     {
191         res = fko_set_username(ctx, options.spoof_user);
192         if(res != FKO_SUCCESS)
193         {
194             errmsg("fko_set_username", res);
195             return(EXIT_FAILURE);
196         }
197     }
198
199     /* Set up for using GPG if specified.
200     */
201     if(options.use_gpg)
202     {
203         /* If use-gpg-agent was not specified, then remove the GPG_AGENT_INFO
204          * ENV variable if it exists.
205         */
206 #ifndef WIN32
207         if(!options.use_gpg_agent)
208             unsetenv("GPG_AGENT_INFO");
209 #endif
210
211         res = fko_set_spa_encryption_type(ctx, FKO_ENCRYPTION_GPG);
212         if(res != FKO_SUCCESS)
213         {
214             errmsg("fko_set_spa_encryption_type", res);
215             return(EXIT_FAILURE);
216         }
217
218         /* If a GPG home dir was specified, set it here.  Note: Setting
219          * this has to occur before calling any of the other GPG-related
220          * functions.
221         */
222         if(options.gpg_home_dir != NULL && strlen(options.gpg_home_dir) > 0)
223         {
224             res = fko_set_gpg_home_dir(ctx, options.gpg_home_dir);
225             if(res != FKO_SUCCESS)
226             {
227                 errmsg("fko_set_gpg_home_dir", res);
228                 return(EXIT_FAILURE);
229             }
230         }
231
232         res = fko_set_gpg_recipient(ctx, options.gpg_recipient_key);
233         if(res != FKO_SUCCESS)
234         {
235             errmsg("fko_set_gpg_recipient", res);
236
237             if(IS_GPG_ERROR(res))
238                 fprintf(stderr, "GPG ERR: %s\n", fko_gpg_errstr(ctx));
239             return(EXIT_FAILURE);
240         }
241
242         if(options.gpg_signer_key != NULL && strlen(options.gpg_signer_key))
243         {
244             res = fko_set_gpg_signer(ctx, options.gpg_signer_key);
245             if(res != FKO_SUCCESS)
246             {
247                 errmsg("fko_set_gpg_signer", res);
248
249                 if(IS_GPG_ERROR(res))
250                     fprintf(stderr, "GPG ERR: %s\n", fko_gpg_errstr(ctx));
251
252                 return(EXIT_FAILURE);
253             }
254         }
255     }
256
257     /* Set Digest type.
258     */
259     if(options.digest_type)
260     {
261         fko_set_spa_digest_type(ctx, options.digest_type);
262         if(res != FKO_SUCCESS)
263         {
264             errmsg("fko_set_spa_digest_type", res);
265             return(EXIT_FAILURE);
266         }
267     }
268
269     /* Finalize the context data (encrypt and encode the SPA data)
270     */
271     res = fko_spa_data_final(ctx, get_user_pw(&options, CRYPT_OP_ENCRYPT));
272     if(res != FKO_SUCCESS)
273     {
274         errmsg("fko_spa_data_final", res);
275
276         if(IS_GPG_ERROR(res))
277             fprintf(stderr, "GPG ERR: %s\n", fko_gpg_errstr(ctx));
278
279         return(EXIT_FAILURE);
280     }
281
282     /* Display the context data.
283     */
284     if (options.verbose || options.test)
285         display_ctx(ctx);
286
287     /* Save packet data payload if requested.
288     */
289     if (options.save_packet_file[0] != 0x0)
290         write_spa_packet_data(ctx, &options);
291
292     if (options.rand_port)
293         options.spa_dst_port = get_rand_port(ctx);
294
295     res = send_spa_packet(ctx, &options);
296     if(res < 0)
297     {
298         fprintf(stderr, "send_spa_packet: packet not sent.\n");
299         return(EXIT_FAILURE);
300     }
301     else
302     {
303         if(options.verbose)
304             fprintf(stderr, "send_spa_packet: bytes sent: %i\n", res);
305     }
306
307     /* Run through a decode cycle in test mode (--DSS XXX: This test/decode
308      * portion should be moved elsewhere).
309     */
310     if (options.test)
311     {
312         /************** Decoding now *****************/
313
314         /* Now we create a new context based on data from the first one.
315         */
316         res = fko_get_spa_data(ctx, &spa_data);
317         if(res != FKO_SUCCESS)
318         {
319             errmsg("fko_get_spa_data", res);
320             return(EXIT_FAILURE);
321         }
322
323         /* If gpg-home-dir is specified, we have to defer decrypting if we
324          * use the fko_new_with_data() function because we need to set the
325          * gpg home dir after the context is created, but before we attempt
326          * to decrypt the data.  Therefore we either pass NULL for the
327          * decryption key to fko_new_with_data() or use fko_new() to create
328          * an empty context, populate it with the encrypted data, set our
329          * options, then decode it.
330         */
331         res = fko_new_with_data(&ctx2, spa_data, NULL);
332         if(res != FKO_SUCCESS)
333         {
334             errmsg("fko_new_with_data", res);
335             return(EXIT_FAILURE);
336         }
337
338         /* See if we are using gpg and if we need to set the GPG home dir.
339         */
340         if(options.use_gpg)
341         {
342             if(options.gpg_home_dir != NULL && strlen(options.gpg_home_dir) > 0)
343             {
344                 res = fko_set_gpg_home_dir(ctx2, options.gpg_home_dir);
345                 if(res != FKO_SUCCESS)
346                 {
347                     errmsg("fko_set_gpg_home_dir", res);
348                     return(EXIT_FAILURE);
349                 }
350             }
351         }
352
353         res = fko_decrypt_spa_data(
354             ctx2, get_user_pw(&options, CRYPT_OP_DECRYPT)
355         );
356
357         if(res != FKO_SUCCESS)
358         {
359             errmsg("fko_decrypt_spa_data", res);
360
361             if(IS_GPG_ERROR(res)) {
362                 /* we most likely could not decrypt the gpg-encrypted data
363                  * because we don't have access to the private key associated
364                  * with the public key we used for encryption.  Since this is
365                  * expected, return 0 instead of an error condition (so calling
366                  * programs like the fwknop test suite don't interpret this as
367                  * an unrecoverable error), but print the error string for
368                  debugging purposes. */
369                 fprintf(stderr, "GPG ERR: %s\n%s\n", fko_gpg_errstr(ctx2),
370                     "No access to recipient private key?\n");
371                 return(EXIT_SUCCESS);
372             }
373
374             return(EXIT_FAILURE);
375         }
376
377         printf("\nDump of the Decoded Data\n");
378         display_ctx(ctx2);
379
380         fko_destroy(ctx2);
381     }
382
383     fko_destroy(ctx);
384
385     free_configs(&options);
386
387     return(EXIT_SUCCESS);
388 }
389
390 void
391 free_configs(fko_cli_options_t *opts)
392 {
393     if (opts->resolve_url != NULL)
394         free(opts->resolve_url);
395 }
396
397 static int
398 get_rand_port(fko_ctx_t ctx)
399 {
400     char *rand_val = NULL;
401     int   port     = 0;
402     int   res      = 0;
403
404     res = fko_get_rand_value(ctx, &rand_val);
405     if(res != FKO_SUCCESS)
406     {
407         errmsg("get_rand_port(), fko_get_rand_value", res);
408         exit(EXIT_FAILURE);
409     }
410
411     /* Convert to a random value between 1024 and 65535
412     */
413     port = (MIN_HIGH_PORT + (abs(atoi(rand_val)) % (MAX_PORT - MIN_HIGH_PORT)));
414
415     /* Force libfko to calculate a new random value since we don't want to
416      * given anyone a hint (via the port value) about the contents of the
417      * encrypted SPA data.
418     */
419     res = fko_set_rand_value(ctx, NULL);
420     if(res != FKO_SUCCESS)
421     {
422         errmsg("get_rand_port(), fko_get_rand_value", res);
423         exit(EXIT_FAILURE);
424     }
425
426     return port;
427 }
428
429 /* See if the string is of the format "<ipv4 addr>:<port>",
430  */
431 static int
432 ipv4_str_has_port(char *str)
433 {
434     int o1, o2, o3, o4, p;
435
436     /* Force the ':' (if any) to a ','
437     */
438     char *ndx = strchr(str, ':');
439     if(ndx != NULL)
440         *ndx = ',';
441
442     /* Check format and values.
443     */
444     if((sscanf(str, "%u.%u.%u.%u,%u", &o1, &o2, &o3, &o4, &p)) == 5
445         && o1 >= 0 && o1 <= 255
446         && o2 >= 0 && o2 <= 255
447         && o3 >= 0 && o3 <= 255
448         && o4 >= 0 && o4 <= 255
449         && p  >  0 && p  <  65536)
450     {
451         return 1;
452     }
453
454     return 0;
455 }
456
457 /* Set NAT access string
458 */
459 static int
460 set_nat_access(fko_ctx_t ctx, fko_cli_options_t *options)
461 {
462     char nat_access_buf[MAX_LINE_LEN] = "";
463     int nat_port = 0;
464
465     if (options->nat_rand_port)
466         nat_port = get_rand_port(ctx);
467     else if (options->nat_port)
468         nat_port = options->nat_port;
469     else
470         nat_port = DEFAULT_NAT_PORT;
471
472     if (options->nat_local && options->nat_access_str[0] == 0x0)
473     {
474         snprintf(nat_access_buf, MAX_LINE_LEN, "%s,%d",
475             options->spa_server_str, nat_port);
476     }
477
478     if (nat_access_buf[0] == 0x0 && options->nat_access_str[0] != 0x0)
479     {
480         if (ipv4_str_has_port(options->nat_access_str))
481         {
482             snprintf(nat_access_buf, MAX_LINE_LEN, "%s",
483                 options->nat_access_str);
484         }
485         else
486         {
487             snprintf(nat_access_buf, MAX_LINE_LEN, "%s,%d",
488                 options->nat_access_str, nat_port);
489         }
490     }
491
492     return fko_set_spa_nat_access(ctx, nat_access_buf);
493 }
494
495 static int
496 get_save_file(char *args_save_file)
497 {
498     char *homedir = NULL;
499     int rv = 0;
500
501     homedir = getenv("HOME");
502
503     if (homedir != NULL) {
504         snprintf(args_save_file, MAX_PATH_LEN, "%s%s%s",
505             homedir, "/", ".fwknop.run");
506         rv = 1;
507     }
508
509     return rv;
510 }
511
512 /* Show the last command that was executed
513 */
514 static void
515 show_last_command(void)
516 {
517     char args_save_file[MAX_PATH_LEN];
518     char args_str[MAX_LINE_LEN] = "";
519     FILE *args_file_ptr = NULL;
520
521 #ifdef WIN32
522     /* Not sure what the right thing is here on Win32, just exit
523      * for now.
524     */
525     fprintf(stderr, "--show-last not implemented on Win32 yet.");
526     exit(EXIT_FAILURE);
527 #endif
528
529     if (get_save_file(args_save_file)) {
530         if ((args_file_ptr = fopen(args_save_file, "r")) == NULL) {
531             fprintf(stderr, "Could not open args file: %s\n",
532                 args_save_file);
533             exit(EXIT_FAILURE);
534         }
535         if ((fgets(args_str, MAX_LINE_LEN, args_file_ptr)) != NULL) {
536             printf("Last fwknop client command line: %s", args_str);
537         } else {
538             printf("Could not read line from file: %s\n", args_save_file);
539         }
540         fclose(args_file_ptr);
541     }
542
543     exit(EXIT_SUCCESS);
544 }
545
546 /* Get the command line arguments from the previous invocation
547 */
548 static void
549 run_last_args(fko_cli_options_t *options)
550 {
551     FILE           *args_file_ptr = NULL;
552
553     int             current_arg_ctr = 0;
554     int             argc_new = 0;
555     int             i = 0;
556
557     char            args_save_file[MAX_PATH_LEN] = {0};
558     char            args_str[MAX_LINE_LEN] = {0};
559     char            arg_tmp[MAX_LINE_LEN]  = {0};
560     char           *argv_new[200];  /* should be way more than enough */
561
562
563 #ifdef WIN32
564     /* Not sure what the right thing is here on Win32, just return
565      * for now.
566     */
567     return;
568 #endif
569
570     if (get_save_file(args_save_file))
571     {
572         if ((args_file_ptr = fopen(args_save_file, "r")) == NULL)
573         {
574             fprintf(stderr, "Could not open args file: %s\n",
575                 args_save_file);
576             exit(EXIT_FAILURE);
577         }
578         if ((fgets(args_str, MAX_LINE_LEN, args_file_ptr)) != NULL)
579         {
580             args_str[MAX_LINE_LEN-1] = '\0';
581             if (options->verbose)
582                 printf("Executing: %s\n", args_str);
583             for (i=0; i < (int)strlen(args_str); i++)
584             {
585                 if (!isspace(args_str[i]))
586                 {
587                     arg_tmp[current_arg_ctr] = args_str[i];
588                     current_arg_ctr++;
589                 }
590                 else
591                 {
592                     arg_tmp[current_arg_ctr] = '\0';
593                     argv_new[argc_new] = malloc(strlen(arg_tmp)+1);
594                     if (argv_new[argc_new] == NULL)
595                     {
596                         fprintf(stderr, "malloc failure for cmd line arg.\n");
597                         exit(EXIT_FAILURE);
598                     }
599                     strlcpy(argv_new[argc_new], arg_tmp, strlen(arg_tmp)+1);
600                     current_arg_ctr = 0;
601                     argc_new++;
602                 }
603             }
604         }
605         fclose(args_file_ptr);
606
607         /* Reset the options index so we can run through them again.
608         */
609         optind = 0;
610
611         config_init(options, argc_new, argv_new);
612     }
613
614     return;
615 }
616
617 /* Save our command line arguments
618 */
619 static void
620 save_args(int argc, char **argv)
621 {
622     char args_save_file[MAX_PATH_LEN];
623     char args_str[MAX_LINE_LEN] = "";
624     FILE *args_file_ptr = NULL;
625     int i = 0, args_str_len = 0;
626
627 #ifdef WIN32
628     /* Not sure what the right thing is here on Win32, just return
629      * for now.
630     */
631     return;
632 #endif
633
634
635     if (get_save_file(args_save_file)) {
636         if ((args_file_ptr = fopen(args_save_file, "w")) == NULL) {
637             fprintf(stderr, "Could not open args file: %s\n",
638                 args_save_file);
639             exit(EXIT_FAILURE);
640         }
641         for (i=0; i < argc; i++) {
642             args_str_len += strlen(argv[i]);
643             if (args_str_len >= MAX_PATH_LEN) {
644                 fprintf(stderr, "argument string too long, exiting.\n");
645                 exit(EXIT_FAILURE);
646             }
647             strlcat(args_str, argv[i], MAX_PATH_LEN);
648             strlcat(args_str, " ", MAX_PATH_LEN);
649         }
650         fprintf(args_file_ptr, "%s\n", args_str);
651         fclose(args_file_ptr);
652     }
653     return;
654 }
655
656 /* Set the SPA packet message type
657 */
658 static int
659 set_message_type(fko_ctx_t ctx, fko_cli_options_t *options)
660 {
661     short message_type;
662
663     if(options->server_command[0] != 0x0)
664     {
665         message_type = FKO_COMMAND_MSG;
666     }
667     else if(options->nat_local)
668     {
669         if (options->fw_timeout >= 0)
670             message_type = FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG;
671         else
672             message_type = FKO_LOCAL_NAT_ACCESS_MSG;
673     }
674     else if(options->nat_access_str[0] != 0x0)
675     {
676         if (options->fw_timeout >= 0)
677             message_type = FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG;
678         else
679             message_type = FKO_NAT_ACCESS_MSG;
680     }
681     else
682     {
683         if (options->fw_timeout >= 0)
684             message_type = FKO_CLIENT_TIMEOUT_ACCESS_MSG;
685         else
686             message_type = FKO_ACCESS_MSG;
687     }
688
689     return fko_set_spa_message_type(ctx, message_type);
690 }
691
692 /* Prompt for and receive a user password.
693 */
694 static char*
695 get_user_pw(fko_cli_options_t *options, const int crypt_op)
696 {
697     char        *pw_ptr = NULL;
698     static char *no_pw  = "";
699
700     /* First of all if we are using GPG and GPG_AGENT
701      * then there is no password to return.
702     */
703     if(options->use_gpg
704       && (options->use_gpg_agent
705            || (crypt_op == CRYPT_OP_ENCRYPT && options->gpg_signer_key == NULL)))
706         return(no_pw);
707
708     /* If --get-key file was specified grab the key/password from it.
709     */
710     if (options->get_key_file[0] != 0x0)
711     {
712         pw_ptr = getpasswd_file(options->get_key_file, options->spa_server_str);
713     }
714     else if (options->use_gpg)
715     {
716         if(crypt_op == CRYPT_OP_DECRYPT)
717             pw_ptr = getpasswd("Enter passphrase for secret key: ");
718         else if(options->gpg_signer_key && strlen(options->gpg_signer_key))
719             pw_ptr = getpasswd("Enter passphrase for signing: ");
720         else
721             pw_ptr = no_pw;
722     }
723     else
724     {
725         if(crypt_op == CRYPT_OP_ENCRYPT)
726             pw_ptr = getpasswd("Enter encryption password: ");
727         else if(crypt_op == CRYPT_OP_DECRYPT)
728             pw_ptr = getpasswd("Enter decryption password: ");
729         else
730             pw_ptr = getpasswd("Enter password: ");
731     }
732
733     /* Empty password is allowed, NULL password is not.
734     */
735     if (pw_ptr == NULL)
736     {
737         fprintf(stderr, "Received no password data, exiting.\n");
738         exit(EXIT_FAILURE);
739     }
740
741     return pw_ptr;
742 }
743
744 /* Display an FKO error message.
745 */
746 void
747 errmsg(const char *msg, const int err) {
748     fprintf(stderr, "%s: %s: Error %i - %s\n",
749         MY_NAME, msg, err, fko_errstr(err));
750 }
751
752 /* Show the fields of the FKO context.
753 */
754 static void
755 display_ctx(fko_ctx_t ctx)
756 {
757     char       *rand_val        = NULL;
758     char       *username        = NULL;
759     char       *version         = NULL;
760     char       *spa_message     = NULL;
761     char       *nat_access      = NULL;
762     char       *server_auth     = NULL;
763     char       *enc_data        = NULL;
764     char       *spa_digest      = NULL;
765     char       *spa_data        = NULL;
766
767     time_t      timestamp       = 0;
768     short       msg_type        = -1;
769     short       digest_type     = -1;
770     int         client_timeout  = -1;
771
772     /* Should be checking return values, but this is temp code. --DSS
773     */
774     fko_get_rand_value(ctx, &rand_val);
775     fko_get_username(ctx, &username);
776     fko_get_timestamp(ctx, &timestamp);
777     fko_get_version(ctx, &version);
778     fko_get_spa_message_type(ctx, &msg_type);
779     fko_get_spa_message(ctx, &spa_message);
780     fko_get_spa_nat_access(ctx, &nat_access);
781     fko_get_spa_server_auth(ctx, &server_auth);
782     fko_get_spa_client_timeout(ctx, &client_timeout);
783     fko_get_spa_digest_type(ctx, &digest_type);
784     fko_get_encoded_data(ctx, &enc_data);
785     fko_get_spa_digest(ctx, &spa_digest);
786     fko_get_spa_data(ctx, &spa_data);
787
788     printf("\nFKO Field Values:\n=================\n\n");
789     printf("   Random Value: %s\n", rand_val == NULL ? "<NULL>" : rand_val);
790     printf("       Username: %s\n", username == NULL ? "<NULL>" : username);
791     printf("      Timestamp: %u\n", (unsigned int) timestamp);
792     printf("    FKO Version: %s\n", version == NULL ? "<NULL>" : version);
793     printf("   Message Type: %i\n", msg_type);
794     printf(" Message String: %s\n", spa_message == NULL ? "<NULL>" : spa_message);
795     printf("     Nat Access: %s\n", nat_access == NULL ? "<NULL>" : nat_access);
796     printf("    Server Auth: %s\n", server_auth == NULL ? "<NULL>" : server_auth);
797     printf(" Client Timeout: %u\n", client_timeout);
798     printf("    Digest Type: %u\n", digest_type);
799     printf("\n   Encoded Data: %s\n", enc_data == NULL ? "<NULL>" : enc_data);
800     printf("\nSPA Data Digest: %s\n", spa_digest == NULL ? "<NULL>" : spa_digest);
801     printf("\nFinal Packed/Encrypted/Encoded Data:\n\n%s\n\n", spa_data);
802 }
803
804 /***EOF***/