diff --git a/ChangeLog b/ChangeLog index 7108bd85..51445259 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,11 +1,23 @@ +2009-07-21 Michael Rash + * Got forward and local NAT modes working with the --nat-access, + --nat-local, --nat-port, and --nat-randport options. All NAT modes + are now passing the fwknop test suite. + * Added the --server-command option to build an SPA packet with a command + for the server to execute. + * Added the --fw-timeout option for client side timeouts to be specified. + * Added the --time-offset-plus and --time-offset-minus options to allow + the user to influence the timestamp associated with an SPA packet. + * Added the --rand-port option so that the SPA packet destination port can + be randomized. + 2009-07-16 Michael Rash - * Added the ability to send SPA packets over valid HTTP requests with + * Added the ability to send SPA packets over valid HTTP requests with the fwknop-c client. - * Added support for transmitting SPA packets over IPv6 via TCP and UDP + * Added support for transmitting SPA packets over IPv6 via TCP and UDP sockets, and also via HTTP. - * Added GnuPG 'hQ' base64 encoded prefix handling (this prefix is + * Added GnuPG 'hQ' base64 encoded prefix handling (this prefix is stripped out of encrypted SPA packet data). - * Added hostname resolution support to the fwknop-c client if the SPA + * Added hostname resolution support to the fwknop-c client if the SPA server is specified as a hostname instead of an IP address. 2008-12-21 Damien Stuart diff --git a/src/config_init.c b/src/config_init.c index 1e710160..6a8ec1d7 100644 --- a/src/config_init.c +++ b/src/config_init.c @@ -73,6 +73,56 @@ get_char_val(const char *var_name, char *dest, char *lptr) return 1; } +/* Parse any time offset from the command line +*/ +static int +parse_time_offset(char *offset_str) +{ + int offset = 0, i, j, offset_type = TIME_OFFSET_SECONDS; + char offset_digits[MAX_TIME_STR_LEN]; + + j=0; + for (i=0; i < strlen(offset_str); i++) { + if (isdigit(offset_str[i])) { + offset_digits[j] = offset_str[i]; + j++; + } else if (offset_str[i] == 'm' || offset_str[i] == 'M') { + offset_type = TIME_OFFSET_MINUTES; + break; + } else if (offset_str[i] == 'h' || offset_str[i] == 'H') { + offset_type = TIME_OFFSET_HOURS; + break; + } else if (offset_str[i] == 'd' || offset_str[i] == 'D') { + offset_type = TIME_OFFSET_DAYS; + break; + } + } + offset_digits[j] = '\0'; + if (j < 1) { + fprintf(stderr, "[*] Invalid time offset: %s", offset_str); + exit(EXIT_FAILURE); + } + + offset = atoi(offset_digits); + if (offset < 0) { + fprintf(stderr, "[*] Invalid time offset: %s", offset_str); + exit(EXIT_FAILURE); + } + + switch (offset_type) { + case TIME_OFFSET_MINUTES: + offset *= 60; + break; + case TIME_OFFSET_HOURS: + offset *= 60 * 60; + break; + case TIME_OFFSET_DAYS: + offset *= 60 * 60 * 24; + break; + } + return offset; +} + /* Parse the config file... */ static void @@ -97,7 +147,7 @@ parse_config_file(fko_cli_options_t *options, struct opts_track* ot) { fprintf(stderr, "[*] Could not open config file: %s\n", options->config_file); - exit(1); + exit(EXIT_FAILURE); } fprintf(stderr, @@ -106,7 +156,7 @@ parse_config_file(fko_cli_options_t *options, struct opts_track* ot) return; } - + if ((cfile_ptr = fopen(options->config_file, "r")) == NULL) { fprintf(stderr, "[*] Could not open config file: %s\n", @@ -159,7 +209,7 @@ validate_options(fko_cli_options_t *options) { fprintf(stderr, "[*] Must use --destination unless --test mode is used\n"); - exit(1); + exit(EXIT_FAILURE); } /* If we are using gpg, we must at least have the recipient set. @@ -171,7 +221,7 @@ validate_options(fko_cli_options_t *options) { fprintf(stderr, "[*] Must specify --gpg-recipient-key when GPG is used.\n"); - exit(1); + exit(EXIT_FAILURE); } } @@ -184,9 +234,7 @@ validate_options(fko_cli_options_t *options) void config_init(fko_cli_options_t *options, int argc, char **argv) { - int cmd_arg, index; - //unsigned int tmpint; - + int cmd_arg, index, i; struct opts_track ot; /* Zero out options and opts_track. @@ -199,10 +247,10 @@ config_init(fko_cli_options_t *options, int argc, char **argv) */ options->spa_proto = FKO_DEFAULT_PROTO; options->spa_dst_port = FKO_DEFAULT_PORT; - strlcpy(options->spa_dst_port_str, FKO_DEFAULT_PORT_STR, MAX_PORT_STR_LEN); + options->fw_timeout = -1; while ((cmd_arg = getopt_long(argc, argv, - "a:A:bB:D:gG:hm:np:P:qQ:S:TU:vV", cmd_opts, &index)) != -1) { + "a:A:bB:C:D:f:gG:hIm:nN:p:P:qQ:rS:TU:vV", cmd_opts, &index)) != -1) { switch(cmd_arg) { case 'a': @@ -217,9 +265,19 @@ config_init(fko_cli_options_t *options, int argc, char **argv) case 'B': strlcpy(options->save_packet_file, optarg, MAX_PATH_LEN); break; + case 'C': + strlcpy(options->server_command, optarg, MAX_LINE_LEN); + break; case 'D': strlcpy(options->spa_server_str, optarg, MAX_SERVER_STR_LEN); break; + case 'f': + options->fw_timeout = atoi(optarg); + if (options->fw_timeout < 0) { + fprintf(stderr, "[*] --fw-timeout must be >= 0\n"); + exit(EXIT_FAILURE); + } + break; case 'g': case GPG_ENCRYPTION: options->use_gpg = 1; @@ -229,7 +287,7 @@ config_init(fko_cli_options_t *options, int argc, char **argv) break; case 'h': usage(); - exit(0); + exit(EXIT_SUCCESS); case 'm': case FKO_DIGEST_NAME: if(strncasecmp(optarg, "md5", 3) == 0) @@ -241,18 +299,20 @@ config_init(fko_cli_options_t *options, int argc, char **argv) else { fprintf(stderr, "* Invalid digest type: %s\n", optarg); - exit(1); + exit(EXIT_FAILURE); } break; case 'n': options->no_save = 1; break; + case 'N': + strlcpy(options->nat_access_str, optarg, MAX_LINE_LEN); + break; case 'p': - strlcpy(options->spa_dst_port_str, optarg, MAX_PORT_STR_LEN); options->spa_dst_port = atoi(optarg); if (options->spa_dst_port < 0 || options->spa_dst_port > 65535) { fprintf(stderr, "[*] Unrecognized port: %s\n", optarg); - exit(1); + exit(EXIT_FAILURE); } break; case 'P': @@ -268,7 +328,7 @@ config_init(fko_cli_options_t *options, int argc, char **argv) options->spa_proto = FKO_PROTO_HTTP; else { fprintf(stderr, "[*] Unrecognized protocol: %s\n", optarg); - exit(1); + exit(EXIT_FAILURE); } break; case 'q': @@ -277,12 +337,14 @@ config_init(fko_cli_options_t *options, int argc, char **argv) case 'Q': strlcpy(options->spoof_ip_src_str, optarg, MAX_IP_STR_LEN); break; + case 'r': + options->rand_port = 1; + break; case 'S': - strlcpy(options->spa_src_port_str, optarg, MAX_PORT_STR_LEN); options->spa_src_port = atoi(optarg); if (options->spa_src_port < 0 || options->spa_src_port > 65535) { fprintf(stderr, "[*] Unrecognized port: %s\n", optarg); - exit(1); + exit(EXIT_FAILURE); } break; case 'T': @@ -313,9 +375,28 @@ config_init(fko_cli_options_t *options, int argc, char **argv) options->use_gpg = 1; options->use_gpg_agent = 1; break; + case NAT_LOCAL: + options->nat_local = 1; + break; + case NAT_RAND_PORT: + options->nat_rand_port = 1; + break; + case NAT_PORT: + options->nat_port = atoi(optarg); + if (options->nat_port < 0 || options->nat_port > 65535) { + fprintf(stderr, "[*] Unrecognized port: %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case TIME_OFFSET_PLUS: + options->time_offset_plus = parse_time_offset(optarg); + break; + case TIME_OFFSET_MINUS: + options->time_offset_minus = parse_time_offset(optarg); + break; default: usage(); - exit(1); + exit(EXIT_FAILURE); } } @@ -349,6 +430,8 @@ usage(void) " -a, --allow-ip - Specify IP address to allow within the SPA\n" " packet.\n" " -D, --destination - Specify the IP address of the fwknop server.\n" + " -N, --nat-access - Gain NAT access to an internal service\n" + " protected by the fwknop server.\n" " -p, --server-port - Set the destination port for outgoing SPA\n" " packet.\n" " -P, --server-proto - Set the protocol (udp, tcp, tcpraw, icmp) for\n" @@ -360,17 +443,29 @@ usage(void) " -U, --spoof-user - Set the username within outgoing SPA packet.\n" " -q, --quiet - Perform fwknop functions quietly.\n" " -G, --get-key - Load an encryption key/password from a file.\n" + " -r, --rand-port - Send the SPA packet over a randomly assigned\n" + " port (requires a broader pcap filter on the\n" + " server side than the default of udp 62201).\n" " -T, --test - Build the SPA packet but do not send it over\n" " the network.\n" " -v, --verbose - Set verbose mode.\n" " -V, --version - Print version number.\n" " -m, --digest-type - Speciy the message digest algorithm to use.\n" " (md5, sha1, or sha256 (default)).\n" + " -f, --fw-timeout - Specify SPA server firewall timeout from the\n" + " client side.\n" " --gpg-encryption - Use GPG encyrption (default is Rijndael).\n" " --gpg-recipient-key - Specify the recipient GPG key name or ID.\n" " --gpg-signer-key - Specify the signer's GPG key name or ID.\n" " --gpg-home-dir - Specify the GPG home directory.\n" " --gpg-agent - Use GPG agent if available.\n" + " --nat-local - Use GPG agent if available.\n" + " --nat-port - Use GPG agent if available.\n" + " --nat-rand-port - Use GPG agent if available.\n" + " --nat-local - Use GPG agent if available.\n" + " --time-offset-plus - Add time to outgoing SPA packet timestamp.\n" + " --time-offset-minus - Subtract time from outgoing SPA packet\n" + " timestamp.\n" "\n" ); diff --git a/src/config_init.h b/src/config_init.h index 8cc3992d..4ed6e89f 100644 --- a/src/config_init.h +++ b/src/config_init.h @@ -33,6 +33,11 @@ */ enum { FKO_DIGEST_NAME = 0x100, + NAT_LOCAL, + NAT_PORT, + NAT_RAND_PORT, + TIME_OFFSET_MINUS, + TIME_OFFSET_PLUS, /* Put GPG-related items below the following line */ GPG_ENCRYPTION = 0x200, GPG_RECIP_KEY, @@ -50,8 +55,10 @@ static struct option cmd_opts[] = {"access", 1, NULL, 'A'}, {"save-packet-append", 0, NULL, 'b'}, {"save-packet", 1, NULL, 'B'}, + {"server-command", 1, NULL, 'C'}, {"digest-type", 1, NULL, FKO_DIGEST_NAME}, {"destination", 1, NULL, 'D'}, + {"fw-timeout", 1, NULL, 'f'}, {"gpg-encryption", 0, NULL, 'g'}, {"gpg-recipient-key", 1, NULL, GPG_RECIP_KEY }, {"gpg-signer-key", 1, NULL, GPG_SIGNER_KEY }, @@ -60,12 +67,19 @@ static struct option cmd_opts[] = {"get-key", 1, NULL, 'G'}, {"help", 0, NULL, 'h'}, {"no-save", 0, NULL, 'n'}, + {"nat-access", 1, NULL, 'N'}, + {"nat-local", 0, NULL, NAT_LOCAL}, + {"nat-port", 1, NULL, NAT_PORT}, + {"nat-rand-port", 0, NULL, NAT_RAND_PORT}, {"server-port", 1, NULL, 'p'}, {"server-proto", 1, NULL, 'P'}, {"quiet", 0, NULL, 'q'}, + {"rand-port", 0, NULL, 'r'}, {"spoof-src", 1, NULL, 'Q'}, {"source-port", 1, NULL, 'S'}, {"test", 0, NULL, 'T'}, + {"time-offset-plus", 1, NULL, TIME_OFFSET_PLUS}, + {"time-offset-minus", 1, NULL, TIME_OFFSET_MINUS}, {"spoof-user", 1, NULL, 'U'}, {"verbose", 0, NULL, 'v'}, {"version", 0, NULL, 'V'}, diff --git a/src/fwknop.c b/src/fwknop.c index 39d61693..c28993a8 100644 --- a/src/fwknop.c +++ b/src/fwknop.c @@ -33,7 +33,11 @@ */ char* get_user_pw(fko_cli_options_t *options, int crypt_op); static void display_ctx(fko_ctx_t ctx); -void errmsg(char *msg, int err); +void errmsg(char *msg, int err); +static int set_message_type(fko_ctx_t ctx, fko_cli_options_t *options); +static int set_nat_access(fko_ctx_t ctx, fko_cli_options_t *options); +static int get_rand_port(fko_ctx_t ctx); +static void dump_transmit_options(fko_cli_options_t *options); int main(int argc, char **argv) @@ -69,6 +73,84 @@ main(int argc, char **argv) return(0); } + /* Set client timeout + */ + if(options.fw_timeout >= 0) + { + res = fko_set_spa_client_timeout(ctx, options.fw_timeout); + if(res != FKO_SUCCESS) + { + errmsg("fko_set_spa_client_timeout", res); + return(1); + } + } + + /* Set the SPA packet message type based on command line options + */ + res = set_message_type(ctx, &options); + if(res != FKO_SUCCESS) + { + errmsg("fko_set_spa_message_type", res); + return(1); + } + + if(options.server_command[0] != 0x0) + { + /* Set the access message to a command that the server will + * execute + */ + snprintf(access_buf, MAX_LINE_LEN, "%s%s%s", + options.allow_ip_str, ",", options.server_command); + } + else + { + /* Set a message string by combining the allow IP and the + * port/protocol. The fwknopd server allows no port/protocol + * to be specified as well, so in this case append the string + * "none/0" to the allow IP. + */ + if(options.access_str[0] != 0x0) + { + snprintf(access_buf, MAX_LINE_LEN, "%s%s%s", + options.allow_ip_str, ",", options.access_str); + } + else + { + snprintf(access_buf, MAX_LINE_LEN, "%s%s%s", + options.allow_ip_str, ",", "none/0"); + } + } + res = fko_set_spa_message(ctx, access_buf); + if(res != FKO_SUCCESS) + { + errmsg("fko_set_spa_message", res); + return(1); + } + + /* Set NAT access string + */ + if (options.nat_local || options.nat_access_str[0] != 0x0) + { + res = set_nat_access(ctx, &options); + if(res != FKO_SUCCESS) + { + errmsg("fko_set_nat_access_str", res); + return(1); + } + } + + /* Set username + */ + if(options.spoof_user[0] != 0x0) + { + res = fko_set_username(ctx, options.spoof_user); + if(res != FKO_SUCCESS) + { + errmsg("fko_set_username", res); + return(1); + } + } + /* Set up for using GPG if specified. */ if(options.use_gpg) @@ -127,17 +209,6 @@ main(int argc, char **argv) } } - /* Set a message string by combining the allow IP and the port/protocol - */ - snprintf(access_buf, MAX_LINE_LEN, "%s%s%s", - options.allow_ip_str, ",", options.access_str); - res = fko_set_spa_message(ctx, access_buf); - if(res != FKO_SUCCESS) - { - errmsg("fko_set_spa_message", res); - return(1); - } - /* Set Digest type. */ if(options.digest_type) @@ -173,6 +244,12 @@ main(int argc, char **argv) if (options.save_packet_file[0] != 0x0) write_spa_packet_data(ctx, &options); + if (options.rand_port) + options.spa_dst_port = get_rand_port(ctx); + + if (options.verbose) + dump_transmit_options(&options); + /* If not in test mode, send the SPA data across the wire with a * protocol/port specified on the command line (default is UDP/62201). * Otherwise, run through a decode cycle (--DSS XXX: This test/decode @@ -270,6 +347,168 @@ main(int argc, char **argv) return(0); } +static void +print_proto(int proto) +{ + switch (proto) { + case FKO_PROTO_UDP: + printf("udp"); + break; + case FKO_PROTO_TCP_RAW: + printf("tcpraw"); + break; + case FKO_PROTO_TCP: + printf("tcp"); + break; + case FKO_PROTO_ICMP: + printf("icmp"); + break; + case FKO_PROTO_HTTP: + printf("http"); + break; + } + return; +} + +static +int get_rand_port(fko_ctx_t ctx) +{ + char *rand_val = NULL; + int port = 0; + int res = 0; + + res = fko_get_rand_value(ctx, &rand_val); + if(res != FKO_SUCCESS) + { + errmsg("get_rand_port(), fko_get_rand_value", res); + exit(EXIT_FAILURE); + } + + /* convert to a random value between 1024 and 65535 + */ + return (MIN_HIGH_PORT + (atoi(rand_val) % (MAX_PORT - MIN_HIGH_PORT))); +} + +static void +dump_transmit_options(fko_cli_options_t *options) +{ + printf("[+] Generating SPA packet:\n protocol: "); + print_proto(options->spa_proto), + printf("\n port: %d\n", options->spa_dst_port); + return; +} + +/* See if the string is of the format ":", + * e.g. "123.1.2.3,12345" - this needs work. +*/ +static int ipv4_str_has_port(char *str) +{ + int rv = 0, i; + + for (i=0; i < strlen(str); i++) { + if (str[i] == ',' || str[i] == ':') { + str[i] = ','; /* force "," format */ + rv = 1; + continue; + } + if (rv && ! isdigit(str[i])) { + rv = 0; + break; + } + } + + return rv; +} + +/* Set NAT access string +*/ +static int +set_nat_access(fko_ctx_t ctx, fko_cli_options_t *options) +{ + char nat_access_buf[MAX_LINE_LEN] = ""; + int nat_port = 0; + + if (options->nat_rand_port) + nat_port = get_rand_port(ctx); + else if (options->nat_port) + nat_port = options->nat_port; + else + nat_port = DEFAULT_NAT_PORT; + + if (options->nat_local && options->nat_access_str[0] == 0x0) + { + snprintf(nat_access_buf, MAX_LINE_LEN, "%s,%d", + options->spa_server_str, nat_port); + } + + if (nat_access_buf[0] == 0x0 && options->nat_access_str[0] != 0x0) + { + if (ipv4_str_has_port(options->nat_access_str)) + { + snprintf(nat_access_buf, MAX_LINE_LEN, "%s", + options->nat_access_str); + } + else + { + snprintf(nat_access_buf, MAX_LINE_LEN, "%s,%d", + options->nat_access_str, nat_port); + } + } + + return fko_set_spa_nat_access(ctx, nat_access_buf); +} + +/* Set the SPA packet message type +*/ +static int +set_message_type(fko_ctx_t ctx, fko_cli_options_t *options) +{ + short message_type; + + if(options->server_command[0] != 0x0) + { + message_type = FKO_COMMAND_MSG; + } + else if(options->nat_access_str[0] != 0x0) + { + if (options->nat_local) + { + if (options->fw_timeout >= 0) + { + message_type = FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG; + } + else + { + message_type = FKO_LOCAL_NAT_ACCESS_MSG; + } + } + else + { + if (options->fw_timeout >= 0) + { + message_type = FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG; + } + else + { + message_type = FKO_NAT_ACCESS_MSG; + } + } + } + else + { + if (options->fw_timeout >= 0) + { + message_type = FKO_CLIENT_TIMEOUT_ACCESS_MSG; + } + else + { + message_type = FKO_ACCESS_MSG; + } + } +printf("....setting message type to: %d\n", message_type); + return fko_set_spa_message_type(ctx, message_type); +} + /* Prompt for and receive a user password. */ char* diff --git a/src/fwknop_common.h b/src/fwknop_common.h index ba01cf63..404097bb 100644 --- a/src/fwknop_common.h +++ b/src/fwknop_common.h @@ -91,8 +91,11 @@ enum { */ #define FKO_DEFAULT_PROTO FKO_PROTO_UDP #define FKO_DEFAULT_PORT 62201 -#define FKO_DEFAULT_PORT_STR "62201" +#define DEFAULT_NAT_PORT 55000 +#define MIN_HIGH_PORT 1024 +#define MAX_PORT 65535 #define MAX_PORT_STR_LEN 6 +#define MAX_PROTO_STR_LEN 6 #define MAX_IP_STR_LEN 16 #define MAX_SERVER_STR_LEN 50 @@ -101,12 +104,23 @@ enum { #define MAX_GPG_KEY_ID 128 #define MAX_USERNAME_LEN 30 +#define MAX_TIME_STR_LEN 9 +enum { + TIME_OFFSET_SECONDS, + TIME_OFFSET_MINUTES, + TIME_OFFSET_HOURS, + TIME_OFFSET_DAYS +}; + +#define RAND_FILE "/dev/urandom" + /* fwkop client configuration parameters and values */ typedef struct fko_cli_options { char config_file[MAX_PATH_LEN]; char access_str[MAX_PATH_LEN]; + char server_command[MAX_LINE_LEN]; char get_key_file[MAX_LINE_LEN]; char save_packet_file[MAX_LINE_LEN]; int save_packet_file_append; @@ -114,15 +128,23 @@ typedef struct fko_cli_options char allow_ip_str[MAX_IP_STR_LEN]; char spoof_ip_src_str[MAX_IP_STR_LEN]; char spoof_user[MAX_USERNAME_LEN]; + int rand_port; char gpg_recipient_key[MAX_GPG_KEY_ID]; char gpg_signer_key[MAX_GPG_KEY_ID]; char gpg_home_dir[MAX_PATH_LEN]; + /* NAT access + */ + char nat_access_str[MAX_PATH_LEN]; + int nat_local; + int nat_port; + int nat_rand_port; + + /* SPA packet transmission port and protocol + */ int spa_proto; unsigned int spa_dst_port; - char spa_dst_port_str[MAX_PORT_STR_LEN]; unsigned int spa_src_port; /* only used with --source-port */ - char spa_src_port_str[MAX_PORT_STR_LEN]; /* --source-port */ unsigned int digest_type; @@ -134,6 +156,9 @@ typedef struct fko_cli_options unsigned char test; unsigned char use_gpg; unsigned char use_gpg_agent; + int time_offset_plus; + int time_offset_minus; + int fw_timeout; //char config_file[MAX_PATH_LEN]; diff --git a/src/spa_comm.c b/src/spa_comm.c index b4e380c4..60d7ca6d 100644 --- a/src/spa_comm.c +++ b/src/spa_comm.c @@ -76,6 +76,7 @@ send_spa_packet_tcp_or_udp(char *spa_data, int sd_len, fko_cli_options_t *option { int sock, res, error; struct addrinfo *result, *rp, hints; + char port_str[MAX_PORT_STR_LEN]; memset(&hints, 0, sizeof(struct addrinfo)); @@ -97,8 +98,9 @@ send_spa_packet_tcp_or_udp(char *spa_data, int sd_len, fko_cli_options_t *option hints.ai_protocol = IPPROTO_TCP; } - error = getaddrinfo(options->spa_server_str, - options->spa_dst_port_str, &hints, &result); + sprintf(port_str, "%d", options->spa_dst_port); + + error = getaddrinfo(options->spa_server_str, port_str, &hints, &result); if (error != 0) {