From 4fe95cc1e7412e309d026e53cd44239bd3ef031d Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Sat, 16 Sep 2017 16:11:15 -0400 Subject: Wii can now send a POST request to a website The extractor now uses libfat to read a config file off the root of the SD card, a model for which is included in the repository. The extractor is capable of negotiating an HTTPS connection, but it requires a download of the target site's root CA certificate to be on the SD card and pointed at by the config file. TLS/SSL functionality is provided by wolfSSL. I had to make a couple of minor changes to wolfSSL for it to work properly, and those changes are located in the devkitpro branch of my fork, hatkirby/wolfssl. The Wii program now arranges some of the information that the GBA sends it into a JSON object, which is then sent off (along with some authentication information from the config file) to the endpoint defined in the config file. The code used to maintain the network connection is from the WiiTweet project, which was licensed under the GPL. Some of the code used to send the HTTP request also comes from said project. --- source/http.c | 359 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 source/http.c (limited to 'source/http.c') diff --git a/source/http.c b/source/http.c new file mode 100644 index 0000000..1979e22 --- /dev/null +++ b/source/http.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2008 Tantric + * Copyright (C) 2012 Pedro Aguiar + * Copyright (C) 2017 hatkirby + * + * Originally part of the WiiTweet project, which was distributed under the + * GPL. + */ +#include "http.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TCP_CONNECT_TIMEOUT 4000 +#define TCP_SEND_SIZE (32 * 1024) +#define TCP_RECV_SIZE (32 * 1024) +#define TCP_BLOCK_RECV_TIMEOUT 4000 +#define TCP_BLOCK_SEND_TIMEOUT 4000 +#define TCP_BLOCK_SIZE 1024 +#define HTTP_TIMEOUT 10000 +#define IOS_O_NONBLOCK 0x04 + +static int tcpWrite( + bool secure, + void* firstParam, + const u8* buffer, + u32 length) +{ + int left = length; + int sent = 0; + int step = 0; + + u64 t = gettime(); + while (left) + { + if (ticks_to_millisecs(diff_ticks(t, gettime())) > TCP_BLOCK_SEND_TIMEOUT) + { + return HTTPR_ERR_TIMEOUT; + } + + int block = left; + if (block > TCP_SEND_SIZE) + { + block = TCP_SEND_SIZE; + } + + int result; + if (secure) + { + result = wolfSSL_write(*(WOLFSSL**)firstParam, buffer, block); + } else { + result = net_write(*(s32*)firstParam, buffer, block); + } + + if ((result == 0) || (result == -56)) + { + usleep(20 * 1000); + + continue; + } + + if (result < 0) + { + break; + } + + sent += result; + left -= result; + buffer += result; + usleep(100); + + if ((sent / TCP_BLOCK_SIZE) > step) + { + t = gettime(); + step++; + } + } + + if (left == 0) + { + return HTTPR_OK; + } else { + return HTTPR_ERR_WRITE; + } +} + +static int tcpRead( + bool secure, + void* firstParam, + char* buffer, + u16 maxLength) +{ + u64 startTime = gettime(); + u16 cur = 0; + + while (cur < maxLength) + { + if (ticks_to_millisecs(diff_ticks(startTime, gettime())) > HTTP_TIMEOUT) + { + return HTTPR_ERR_TIMEOUT; + } + + u32 result; + if (secure) + { + result = wolfSSL_read(*(WOLFSSL**)firstParam, &buffer[cur], 1); + } else { + result = net_read(*(s32*)firstParam, &buffer[cur], 1); + } + + if (result == -EAGAIN) + { + usleep(20 * 1000); + + continue; + } + + if (result <= 0) + { + break; + } + + if ((cur > 0) && (buffer[cur-1] == '\r') && (buffer[cur] == '\n')) + { + buffer[cur-1] = 0; + + return HTTPR_OK; + } + + cur++; + startTime = gettime(); + usleep(100); + } + + return HTTPR_ERR_RECEIVE; +} + +int submitToApi( + const char* endpoint, + WOLFSSL_CTX* sslContext, + cJSON* data, + const char* username, + const char* password) +{ + // Form request body. + cJSON* jRequestBody = cJSON_CreateObject(); + + cJSON_AddItemToObject(jRequestBody, "game", data); + + cJSON_AddItemToObject( + jRequestBody, + "username", + cJSON_CreateString(username)); + + cJSON_AddItemToObject( + jRequestBody, + "password", + cJSON_CreateString(password)); + + const char* requestBody = cJSON_PrintUnformatted(jRequestBody); + + // Gather data from endpoint URL. + char httpHost[64]; + char httpPath[256]; + int httpPort = 80; + bool secure = false; + + if (!strncasecmp(endpoint, "https://", 8)) + { + endpoint += 8; + secure = true; + httpPort = 443; + } else if (!strncasecmp(endpoint, "http://", 7)) + { + endpoint += 7; + } else { + return 10; + } + + char* solidus = strchr(endpoint, '/'); + if ((solidus == NULL) || (solidus[0] == 0)) + { + return 11; + } + + char* colon = strchr(endpoint, ':'); + if (colon != NULL) + { + snprintf(httpHost, colon - endpoint + 1, "%s", endpoint); + sscanf(colon + 1, "%d", &httpPort); + } else { + snprintf(httpHost, solidus - endpoint + 1, "%s", endpoint); + } + + strcpy(httpPath, solidus); + + // Connect to endpoint. + printf("Connecting...\n"); + + s32 socket = net_socket(PF_INET, SOCK_STREAM, IPPROTO_IP); + if (socket < 0) + { + return HTTPR_ERR_CONNECT; + } + + if (!secure) + { + int result = net_fcntl(socket, F_GETFL, 0); + if (result < 0) + { + net_close(socket); + + return HTTPR_ERR_CONNECT; + } + + if (net_fcntl(socket, F_SETFL, result | IOS_O_NONBLOCK)) + { + net_close(socket); + + return HTTPR_ERR_CONNECT; + } + } + + struct sockaddr_in sa; + memset(&sa, 0, sizeof(struct sockaddr_in)); + sa.sin_family = PF_INET; + sa.sin_len = sizeof(struct sockaddr_in); + sa.sin_port = htons(httpPort); + + struct in_addr inAddr; + if ((strlen(httpHost) < 16) && (inet_aton(httpHost, &inAddr))) + { + sa.sin_addr.s_addr = inAddr.s_addr; + } else { + struct hostent* hp = net_gethostbyname(httpHost); + if (!hp || (hp->h_addrtype != PF_INET)) + { + return HTTPR_ERR_CONNECT; + } + + memcpy( + (char*) &sa.sin_addr, + hp->h_addr_list[0], + hp->h_length); + } + + u64 time1 = ticks_to_secs(gettime()); + s32 connectResult; + do + { + connectResult = net_connect( + socket, + (struct sockaddr*) &sa, + sizeof(sa)); + + if (ticks_to_secs(gettime()) - time1 > TCP_CONNECT_TIMEOUT*1000) + { + break; + } + } while (connectResult != -EISCONN); + + if (connectResult != -EISCONN) + { + net_close(socket); + + return HTTPR_ERR_CONNECT; + } + + // Initialize SSL, if necessary. + WOLFSSL* sslHandle = 0; + if (secure) + { + sslHandle = wolfSSL_new(sslContext); + if (sslHandle == NULL) + { + net_close(socket); + + return HTTPR_ERR_SSL; + } + + wolfSSL_set_fd(sslHandle, socket); + } + + // Send request. + printf("Sending...\n"); + + void* firstParam = 0; + + if (secure) + { + firstParam = &sslHandle; + } else { + firstParam = &socket; + } + + char requestHeader[1024]; + char* r = requestHeader; + r += sprintf(r, "POST %s HTTP/1.1\r\n", httpPath); + r += sprintf(r, "Host: %s\r\n", httpHost); + r += sprintf(r, "Content-Type: application/json\r\n"); + r += sprintf(r, "Content-Length: %d\r\n\r\n", strlen(requestBody)); + + int result = tcpWrite( + secure, + firstParam, + (u8*) requestHeader, + strlen(requestHeader)); + if (result != HTTPR_OK) + { + return result; + } + + result = tcpWrite( + secure, + firstParam, + (u8*) requestBody, + strlen(requestBody)); + + if (result != HTTPR_OK) + { + return result; + } + + // Read response. + char line[1024]; + int httpStatus = 404; + while (!tcpRead(secure, firstParam, line, 1024)) + { + if (!line[0]) + { + break; + } + + sscanf(line, "HTTP/1.%*u %u", &httpStatus); + } + + if (httpStatus != 200) + { + return HTTPR_ERR_STATUS; + } + + // Clean up. + if (secure) + { + wolfSSL_free(sslHandle); + } + + net_close(socket); + + cJSON_Delete(jRequestBody); + + return HTTPR_OK; +} -- cgit 1.4.1