static int parse_username(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx) { if((*t_size = strcspn(*ndx, ":")) < 1) return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_MISSING); if (*t_size > MAX_SPA_USERNAME_SIZE) return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_TOOBIG); strlcpy(tbuf, *ndx, *t_size+1); if(ctx->username != NULL) free(ctx->username); ctx->username = calloc(1, *t_size+1); /* Yes, more than we need */ if(ctx->username == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); if(b64_decode(tbuf, (unsigned char*)ctx->username) < 0) return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_DECODEFAIL); if(validate_username(ctx->username) != FKO_SUCCESS) return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_VALIDFAIL); *ndx += *t_size + 1; return FKO_SUCCESS; }
/* Get or Set the username for the fko context spa data. */ int fko_set_username(fko_ctx_t ctx, const char * const spoof_user) { char *username = NULL; int res = FKO_SUCCESS, is_user_heap_allocated=0; #if HAVE_LIBFIU fiu_return_on("fko_set_username_init", FKO_ERROR_CTX_NOT_INITIALIZED); #endif /* Must be initialized */ if(!CTX_INITIALIZED(ctx)) return FKO_ERROR_CTX_NOT_INITIALIZED; /* If spoof_user was not passed in, check for a SPOOF_USER enviroment * variable. If it is set, use its value. */ if(spoof_user != NULL && spoof_user[0] != '\0') { #if HAVE_LIBFIU fiu_return_on("fko_set_username_strdup", FKO_ERROR_MEMORY_ALLOCATION); #endif username = strdup(spoof_user); if(username == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); is_user_heap_allocated = 1; } else username = getenv("SPOOF_USER"); /* Try to get the username from the system. */ if(username == NULL) { /* Since we've already tried looking at an env variable, try * LOGNAME next (and the cuserid() man page recommends this) */ if((username = getenv("LOGNAME")) == NULL) { #ifdef _XOPEN_SOURCE /* cuserid will return the effective user (i.e. su or setuid). */ username = cuserid(NULL); #else username = getlogin(); #endif /* if we still didn't get a username, continue falling back */ if(username == NULL) { if((username = getenv("USER")) == NULL) { username = strdup("NO_USER"); if(username == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); is_user_heap_allocated = 1; } } } } /* Truncate the username if it is too long. */ if(strnlen(username, MAX_SPA_USERNAME_SIZE) == MAX_SPA_USERNAME_SIZE) *(username + MAX_SPA_USERNAME_SIZE - 1) = '\0'; if((res = validate_username(username)) != FKO_SUCCESS) { if(is_user_heap_allocated == 1) free(username); #if HAVE_LIBFIU fiu_return_on("fko_set_username_valuser", FKO_ERROR_INVALID_DATA); #endif return res; } /* Just in case this is a subsquent call to this function. We * do not want to be leaking memory. */ if(ctx->username != NULL) free(ctx->username); ctx->username = strdup(username); ctx->state |= FKO_DATA_MODIFIED; if(is_user_heap_allocated == 1) free(username); if(ctx->username == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); return(FKO_SUCCESS); }
/* Decode the encoded SPA data. */ int fko_decode_spa_data(fko_ctx_t ctx) { char *tbuf, *ndx, *tmp; int t_size, i, is_err; if (! is_valid_encoded_msg_len(ctx->encoded_msg_len)) return(FKO_ERROR_INVALID_DATA_DECODE_MSGLEN_VALIDFAIL); /* Make sure there are no non-ascii printable chars */ for (i=0; i < (int)strnlen(ctx->encoded_msg, MAX_SPA_ENCODED_MSG_SIZE); i++) if(isprint(ctx->encoded_msg[i]) == 0) return(FKO_ERROR_INVALID_DATA_DECODE_NON_ASCII); /* Make sure there are enough fields in the SPA packet * delimited with ':' chars */ ndx = ctx->encoded_msg; for (i=0; i < MAX_SPA_FIELDS; i++) { if ((tmp = strchr(ndx, ':')) == NULL) break; ndx = tmp; ndx++; } if (i < MIN_SPA_FIELDS) return(FKO_ERROR_INVALID_DATA_DECODE_LT_MIN_FIELDS); t_size = strnlen(ndx, SHA512_B64_LEN+1); switch(t_size) { case MD5_B64_LEN: ctx->digest_type = FKO_DIGEST_MD5; ctx->digest_len = MD5_B64_LEN; break; case SHA1_B64_LEN: ctx->digest_type = FKO_DIGEST_SHA1; ctx->digest_len = SHA1_B64_LEN; break; case SHA256_B64_LEN: ctx->digest_type = FKO_DIGEST_SHA256; ctx->digest_len = SHA256_B64_LEN; break; case SHA384_B64_LEN: ctx->digest_type = FKO_DIGEST_SHA384; ctx->digest_len = SHA384_B64_LEN; break; case SHA512_B64_LEN: ctx->digest_type = FKO_DIGEST_SHA512; ctx->digest_len = SHA512_B64_LEN; break; default: /* Invalid or unsupported digest */ return(FKO_ERROR_INVALID_DIGEST_TYPE); } if (ctx->encoded_msg_len - t_size < 0) return(FKO_ERROR_INVALID_DATA_DECODE_ENC_MSG_LEN_MT_T_SIZE); if(ctx->digest != NULL) free(ctx->digest); /* Copy the digest into the context and terminate the encoded data * at that point so the original digest is not part of the * encoded string. */ ctx->digest = strdup(ndx); if(ctx->digest == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); /* Zero out the rest of the encoded_msg bucket... */ bzero((ndx-1), t_size); ctx->encoded_msg_len -= t_size+1; /* Make a tmp bucket for processing base64 encoded data and * other general use. */ tbuf = malloc(FKO_ENCODE_TMP_BUF_SIZE); if(tbuf == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); /* Can now verify the digest. */ switch(ctx->digest_type) { case FKO_DIGEST_MD5: md5_base64(tbuf, (unsigned char*)ctx->encoded_msg, ctx->encoded_msg_len); break; case FKO_DIGEST_SHA1: sha1_base64(tbuf, (unsigned char*)ctx->encoded_msg, ctx->encoded_msg_len); break; case FKO_DIGEST_SHA256: sha256_base64(tbuf, (unsigned char*)ctx->encoded_msg, ctx->encoded_msg_len); break; case FKO_DIGEST_SHA384: sha384_base64(tbuf, (unsigned char*)ctx->encoded_msg, ctx->encoded_msg_len); break; case FKO_DIGEST_SHA512: sha512_base64(tbuf, (unsigned char*)ctx->encoded_msg, ctx->encoded_msg_len); break; } /* We give up here if the computed digest does not match the * digest in the message data. */ if(constant_runtime_cmp(ctx->digest, tbuf, t_size) != 0) { free(tbuf); return(FKO_ERROR_DIGEST_VERIFICATION_FAILED); } /* Now we will work through the encoded data and extract (and base64- * decode where necessary), the SPA data fields and populate the context. */ ndx = ctx->encoded_msg; /* The rand val data */ if((t_size = strcspn(ndx, ":")) < FKO_RAND_VAL_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_RAND_MISSING); } if(ctx->rand_val != NULL) free(ctx->rand_val); ctx->rand_val = calloc(1, FKO_RAND_VAL_SIZE+1); if(ctx->rand_val == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } ctx->rand_val = strncpy(ctx->rand_val, ndx, FKO_RAND_VAL_SIZE); /* Jump to the next field (username). We need to use the temp buffer * for the base64 decode step. */ ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_MISSING); } if (t_size > MAX_SPA_USERNAME_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_TOOBIG); } strlcpy(tbuf, ndx, t_size+1); if(ctx->username != NULL) free(ctx->username); ctx->username = malloc(t_size+1); /* Yes, more than we need */ if(ctx->username == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } if(b64_decode(tbuf, (unsigned char*)ctx->username) < 0) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_DECODEFAIL); } if(validate_username(ctx->username) != FKO_SUCCESS) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_VALIDFAIL); } /* Extract the timestamp value. */ ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMESTAMP_MISSING); } if (t_size > MAX_SPA_TIMESTAMP_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMESTAMP_TOOBIG); } strlcpy(tbuf, ndx, t_size+1); ctx->timestamp = (unsigned int) strtol_wrapper(tbuf, 0, -1, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMESTAMP_DECODEFAIL); } /* Extract the version string. */ ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_VERSION_MISSING); } if (t_size > MAX_SPA_VERSION_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_VERSION_TOOBIG); } if(ctx->version != NULL) free(ctx->version); ctx->version = malloc(t_size+1); if(ctx->version == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } strlcpy(ctx->version, ndx, t_size+1); /* Extract the message type value. */ ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_MSGTYPE_MISSING); } if (t_size > MAX_SPA_MESSAGE_TYPE_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_MSGTYPE_TOOBIG); } strlcpy(tbuf, ndx, t_size+1); ctx->message_type = strtol_wrapper(tbuf, 0, FKO_LAST_MSG_TYPE, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_MSGTYPE_DECODEFAIL); } /* Extract the SPA message string. */ ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_MESSAGE_MISSING); } if (t_size > MAX_SPA_MESSAGE_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_MESSAGE_TOOBIG); } strlcpy(tbuf, ndx, t_size+1); if(ctx->message != NULL) free(ctx->message); ctx->message = malloc(t_size+1); /* Yes, more than we need */ if(ctx->message == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } if(b64_decode(tbuf, (unsigned char*)ctx->message) < 0) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_MESSAGE_DECODEFAIL); } if(ctx->message_type == FKO_COMMAND_MSG) { /* Require a message similar to: 1.2.3.4,<command> */ if(validate_cmd_msg(ctx->message) != FKO_SUCCESS) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_MESSAGE_VALIDFAIL); } } else { /* Require a message similar to: 1.2.3.4,tcp/22 */ if(validate_access_msg(ctx->message) != FKO_SUCCESS) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_ACCESS_VALIDFAIL); } } /* Extract nat_access string if the message_type indicates so. */ if( ctx->message_type == FKO_NAT_ACCESS_MSG || ctx->message_type == FKO_LOCAL_NAT_ACCESS_MSG || ctx->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG || ctx->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG) { ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_NATACCESS_MISSING); } if (t_size > MAX_SPA_MESSAGE_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_NATACCESS_TOOBIG); } strlcpy(tbuf, ndx, t_size+1); if(ctx->nat_access != NULL) free(ctx->nat_access); ctx->nat_access = malloc(t_size+1); /* Yes, more than we need */ if(ctx->nat_access == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } if(b64_decode(tbuf, (unsigned char*)ctx->nat_access) < 0) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_NATACCESS_DECODEFAIL); } if(validate_nat_access_msg(ctx->nat_access) != FKO_SUCCESS) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_NATACCESS_VALIDFAIL); } } /* Now look for a server_auth string. */ ndx += t_size + 1; if((t_size = strlen(ndx)) > 0) { if (t_size > MAX_SPA_MESSAGE_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_SRVAUTH_MISSING); } /* There is data, but what is it? * If the message_type does not have a timeout, assume it is a * server_auth field. */ if( ctx->message_type != FKO_CLIENT_TIMEOUT_ACCESS_MSG && ctx->message_type != FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG && ctx->message_type != FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG) { strlcpy(tbuf, ndx, t_size+1); if(ctx->server_auth != NULL) free(ctx->server_auth); ctx->server_auth = malloc(t_size+1); /* Yes, more than we need */ if(ctx->server_auth == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } if(b64_decode(tbuf, (unsigned char*)ctx->server_auth) < 0) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_SRVAUTH_DECODEFAIL); } /* At this point we should be done. */ free(tbuf); /* Call the context initialized. */ ctx->initval = FKO_CTX_INITIALIZED; FKO_SET_CTX_INITIALIZED(ctx); return(FKO_SUCCESS); } /* If we are here then we may still have a server_auth string, * or a timeout, or both. So we look for a ':' delimiter. If * it is there we have both, if not we check the message_type * again. */ if(strchr(ndx, ':')) { t_size = strcspn(ndx, ":"); if (t_size > MAX_SPA_MESSAGE_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_EXTRA_TOOBIG); } /* Looks like we have both, so assume this is the */ strlcpy(tbuf, ndx, t_size+1); if(ctx->server_auth != NULL) free(ctx->server_auth); ctx->server_auth = malloc(t_size+1); /* Yes, more than we need */ if(ctx->server_auth == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } if(b64_decode(tbuf, (unsigned char*)ctx->server_auth) < 0) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_EXTRA_DECODEFAIL); } ndx += t_size + 1; } /* Now we look for a timeout value if one is supposed to be there. */ if( ctx->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG || ctx->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG || ctx->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG) { if((t_size = strlen(ndx)) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMEOUT_MISSING); } if (t_size > MAX_SPA_MESSAGE_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMEOUT_TOOBIG); } /* Should be a number only. */ if(strspn(ndx, "0123456789") != t_size) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMEOUT_VALIDFAIL); } ctx->client_timeout = (unsigned int) strtol_wrapper(ndx, 0, (2 << 15), NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMEOUT_DECODEFAIL); } } } /* Done with the tmp buffer. */ free(tbuf); /* Call the context initialized. */ ctx->initval = FKO_CTX_INITIALIZED; FKO_SET_CTX_INITIALIZED(ctx); return(FKO_SUCCESS); }
/* Set the SPA encryption type. */ int fko_encode_spa_data(fko_ctx_t ctx) { int res, offset = 0; char *tbuf; /* Must be initialized */ if(!CTX_INITIALIZED(ctx)) return(FKO_ERROR_CTX_NOT_INITIALIZED); /* Check prerequisites. * --DSS XXX: Needs review. Also, we could make this more robust (or * (at leaset expand the error reporting for the missing * data). */ if( validate_username(ctx->username) != FKO_SUCCESS || ctx->version == NULL || strnlen(ctx->version, MAX_SPA_VERSION_SIZE) == 0 || ctx->message == NULL || strnlen(ctx->message, MAX_SPA_MESSAGE_SIZE) == 0) { return(FKO_ERROR_INCOMPLETE_SPA_DATA); } if(ctx->message_type == FKO_NAT_ACCESS_MSG) { if(ctx->nat_access == NULL || strnlen(ctx->nat_access, MAX_SPA_MESSAGE_SIZE) == 0) return(FKO_ERROR_INCOMPLETE_SPA_DATA); } /* Allocate our initial tmp buffer. */ tbuf = calloc(1, FKO_ENCODE_TMP_BUF_SIZE); if(tbuf == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); /* Put it together a piece at a time, starting with the rand val. */ strlcpy(tbuf, ctx->rand_val, FKO_ENCODE_TMP_BUF_SIZE); /* Add the base64-encoded username. */ strlcat(tbuf, ":", FKO_ENCODE_TMP_BUF_SIZE); if((res = append_b64(tbuf, ctx->username)) != FKO_SUCCESS) { free(tbuf); return(res); } /* Add the timestamp. */ offset = strlen(tbuf); sprintf(((char*)tbuf+offset), ":%u:", (unsigned int) ctx->timestamp); /* Add the version string. */ strlcat(tbuf, ctx->version, FKO_ENCODE_TMP_BUF_SIZE); /* Before we add the message type value, we will once again * check for whether or not a client_timeout was specified * since the message_type was set. If this is the case, then * we want to adjust the message_type first. The easy way * to do this is simply call fko_set_spa_client_timeout and set * it to its current value. This will force a re-check and * possible reset of the message type. * */ fko_set_spa_client_timeout(ctx, ctx->client_timeout); /* Add the message type value. */ offset = strlen(tbuf); sprintf(((char*)tbuf+offset), ":%i:", ctx->message_type); /* Add the base64-encoded SPA message. */ if((res = append_b64(tbuf, ctx->message)) != FKO_SUCCESS) { free(tbuf); return(res); } /* If a nat_access message was given, add it to the SPA * message. */ if(ctx->nat_access != NULL) { strlcat(tbuf, ":", FKO_ENCODE_TMP_BUF_SIZE); if((res = append_b64(tbuf, ctx->nat_access)) != FKO_SUCCESS) { free(tbuf); return(res); } } /* If we have a server_auth field set. Add it here. * */ if(ctx->server_auth != NULL) { strlcat(tbuf, ":", FKO_ENCODE_TMP_BUF_SIZE); if((res = append_b64(tbuf, ctx->server_auth)) != FKO_SUCCESS) { free(tbuf); return(res); } } /* If a client timeout is specified and we are not dealing with a * SPA command message, add the timeout here. */ if(ctx->client_timeout > 0 && ctx->message_type != FKO_COMMAND_MSG) { offset = strlen(tbuf); sprintf(((char*)tbuf+offset), ":%i", ctx->client_timeout); } /* If encoded_msg is not null, then we assume it needs to * be freed before re-assignment. */ if(ctx->encoded_msg != NULL) free(ctx->encoded_msg); /* Copy our encoded data into the context. */ ctx->encoded_msg = strdup(tbuf); if(ctx->encoded_msg == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } /* At this point we can compute the digest for this SPA data. */ if((res = fko_set_spa_digest(ctx)) != FKO_SUCCESS) { free(tbuf); return(res); } /* Here we can clear the modified flags on the SPA data fields. */ FKO_CLEAR_SPA_DATA_MODIFIED(ctx); free(tbuf); return(FKO_SUCCESS); }