Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #12108 from noahpistilli/kd-check-mail
IOS/KD: Implement NWC24_CHECK_MAIL_NOW
  • Loading branch information
AdmiralCurtiss committed Sep 3, 2023
2 parents 26e9294 + 2154941 commit 143a136
Show file tree
Hide file tree
Showing 16 changed files with 568 additions and 17 deletions.
2 changes: 2 additions & 0 deletions Source/Core/Common/CMakeLists.txt
Expand Up @@ -29,6 +29,8 @@ add_library(common
Crypto/bn.h
Crypto/ec.cpp
Crypto/ec.h
Crypto/HMAC.cpp
Crypto/HMAC.h
Crypto/SHA1.cpp
Crypto/SHA1.h
Debug/MemoryPatches.cpp
Expand Down
27 changes: 27 additions & 0 deletions Source/Core/Common/Crypto/HMAC.cpp
@@ -0,0 +1,27 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <mbedtls/hmac_drbg.h>

#include "Common/Crypto/HMAC.h"
#include "Common/ScopeGuard.h"

namespace Common::HMAC
{
bool HMACWithSHA1(std::span<const u8> key, std::span<const u8> msg, u8* out)
{
mbedtls_md_context_t ctx;
Common::ScopeGuard guard{[&ctx] { mbedtls_md_free(&ctx); }};
mbedtls_md_init(&ctx);
if (mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), 1))
return false;

if (mbedtls_md_hmac_starts(&ctx, key.data(), key.size()) ||
mbedtls_md_hmac_update(&ctx, msg.data(), msg.size()) || mbedtls_md_hmac_finish(&ctx, out))
{
return false;
}

return true;
}
} // namespace Common::HMAC
14 changes: 14 additions & 0 deletions Source/Core/Common/Crypto/HMAC.h
@@ -0,0 +1,14 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "Common/CommonTypes.h"

#include <span>

namespace Common::HMAC
{
// HMAC with the SHA1 message digest. Excepted output length is 20 bytes.
bool HMACWithSHA1(std::span<const u8> key, std::span<const u8> msg, u8* out);
} // namespace Common::HMAC
37 changes: 37 additions & 0 deletions Source/Core/Common/HttpRequest.cpp
Expand Up @@ -27,6 +27,7 @@ class HttpRequest::Impl final
explicit Impl(std::chrono::milliseconds timeout_ms, ProgressCallback callback);

bool IsValid() const;
std::string GetHeaderValue(std::string_view name) const;
void SetCookies(const std::string& cookies);
void UseIPv4();
void FollowRedirects(long max);
Expand All @@ -41,6 +42,7 @@ class HttpRequest::Impl final
private:
static inline std::once_flag s_curl_was_initialized;
ProgressCallback m_callback;
Headers m_response_headers;
std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> m_curl{nullptr, curl_easy_cleanup};
std::string m_error_string;
};
Expand Down Expand Up @@ -82,6 +84,11 @@ s32 HttpRequest::GetLastResponseCode() const
return m_impl->GetLastResponseCode();
}

std::string HttpRequest::GetHeaderValue(std::string_view name) const
{
return m_impl->GetHeaderValue(name);
}

HttpRequest::Response HttpRequest::Get(const std::string& url, const Headers& headers,
AllowedReturnCodes codes)
{
Expand Down Expand Up @@ -173,6 +180,17 @@ void HttpRequest::Impl::FollowRedirects(long max)
curl_easy_setopt(m_curl.get(), CURLOPT_MAXREDIRS, max);
}

std::string HttpRequest::Impl::GetHeaderValue(std::string_view name) const
{
for (const auto& [key, value] : m_response_headers)
{
if (key == name)
return value.value();
}

return {};
}

std::string HttpRequest::Impl::EscapeComponent(const std::string& string)
{
char* escaped = curl_easy_escape(m_curl.get(), string.c_str(), static_cast<int>(string.size()));
Expand All @@ -190,10 +208,26 @@ static size_t CurlWriteCallback(char* data, size_t size, size_t nmemb, void* use
return actual_size;
}

static size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata)
{
auto* headers = static_cast<HttpRequest::Headers*>(userdata);
std::string_view full_buffer = std::string_view{buffer, nitems};
const size_t colon_pos = full_buffer.find(':');
if (colon_pos == std::string::npos)
return nitems * size;

const std::string_view key = full_buffer.substr(0, colon_pos);
const std::string_view value = StripWhitespace(full_buffer.substr(colon_pos + 1));

headers->emplace(std::string{key}, std::string{value});
return nitems * size;
}

HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method method,
const Headers& headers, const u8* payload,
size_t size, AllowedReturnCodes codes)
{
m_response_headers.clear();
curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST);
curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str());
if (method == Method::POST)
Expand All @@ -215,6 +249,9 @@ HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method me
}
curl_easy_setopt(m_curl.get(), CURLOPT_HTTPHEADER, list);

curl_easy_setopt(m_curl.get(), CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_setopt(m_curl.get(), CURLOPT_HEADERDATA, static_cast<void*>(&m_response_headers));

std::vector<u8> buffer;
curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, CurlWriteCallback);
curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, &buffer);
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Common/HttpRequest.h
Expand Up @@ -40,6 +40,7 @@ class HttpRequest final
void FollowRedirects(long max = 1);
s32 GetLastResponseCode() const;
std::string EscapeComponent(const std::string& string);
std::string GetHeaderValue(std::string_view name) const;
Response Get(const std::string& url, const Headers& headers = {},
AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
Response Post(const std::string& url, const std::vector<u8>& payload, const Headers& headers = {},
Expand Down
5 changes: 5 additions & 0 deletions Source/Core/Common/StringUtil.cpp
Expand Up @@ -693,4 +693,9 @@ bool CaseInsensitiveEquals(std::string_view a, std::string_view b)
return std::equal(a.begin(), a.end(), b.begin(),
[](char ca, char cb) { return Common::ToLower(ca) == Common::ToLower(cb); });
}

std::string BytesToHexString(std::span<const u8> bytes)
{
return fmt::format("{:02x}", fmt::join(bytes, ""));
}
} // namespace Common
2 changes: 2 additions & 0 deletions Source/Core/Common/StringUtil.h
Expand Up @@ -11,6 +11,7 @@
#include <iomanip>
#include <limits>
#include <locale>
#include <span>
#include <sstream>
#include <string>
#include <type_traits>
Expand Down Expand Up @@ -313,4 +314,5 @@ std::string GetEscapedHtml(std::string html);
void ToLower(std::string* str);
void ToUpper(std::string* str);
bool CaseInsensitiveEquals(std::string_view a, std::string_view b);
std::string BytesToHexString(std::span<const u8> bytes);
} // namespace Common
3 changes: 3 additions & 0 deletions Source/Core/Core/CMakeLists.txt
Expand Up @@ -378,6 +378,9 @@ add_library(core
IOS/Network/KD/VFF/VFFUtil.cpp
IOS/Network/KD/VFF/VFFUtil.h
IOS/Network/KD/WC24File.h
IOS/Network/KD/Mail/MailCommon.h
IOS/Network/KD/Mail/WC24Send.cpp
IOS/Network/KD/Mail/WC24Send.h
IOS/Network/MACUtils.cpp
IOS/Network/MACUtils.h
IOS/Network/NCD/Manage.cpp
Expand Down
74 changes: 74 additions & 0 deletions Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h
@@ -0,0 +1,74 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "Common/CommonTypes.h"

#include <array>

namespace IOS::HLE::NWC24::Mail
{
constexpr u32 MAIL_LIST_MAGIC = 0x57635466; // WcTf

#pragma pack(push, 1)
struct MailListHeader final
{
u32 magic; // 'WcTf' 0x57635466
u32 version; // 4 in Wii Menu 4.x
u32 number_of_mail;
u32 total_entries;
u32 total_size_of_messages;
u32 filesize;
u32 next_entry_id;
u32 next_entry_offset;
u32 unk1;
u32 vff_free_space;
std::array<u8, 48> unk2;
std::array<char, 40> mail_flag;
};
static_assert(sizeof(MailListHeader) == 128);

struct MultipartEntry final
{
u32 offset;
u32 size;
};

struct MailListEntry final
{
u32 id;
u32 flag;
u32 msg_size;
u32 app_id;
u32 header_length;
u32 tag;
u32 wii_cmd;
// Never validated
u32 crc32;
u64 from_friend_code;
u32 minutes_since_1900;
u32 padding;
u8 always_1;
u8 number_of_multipart_entries;
u16 app_group;
u32 packed_from;
u32 packed_to;
u32 packed_subject;
u32 packed_charset;
u32 packed_transfer_encoding;
u32 message_offset;
// Set to message_length if content transfer encoding is not base64.
u32 encoded_length;
std::array<MultipartEntry, 2> multipart_entries;
std::array<u32, 2> multipart_sizes;
std::array<u32, 2> multipart_content_types;
u32 message_length;
u32 dwc_id;
u32 always_0x80000000;
u32 padding3;
};
static_assert(sizeof(MailListEntry) == 128);

#pragma pack(pop)
} // namespace IOS::HLE::NWC24::Mail
68 changes: 68 additions & 0 deletions Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp
@@ -0,0 +1,68 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "Core/IOS/Network/KD/Mail/WC24Send.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/Uids.h"

namespace IOS::HLE::NWC24::Mail
{
constexpr const char SEND_LIST_PATH[] = "/" WII_WC24CONF_DIR "/mbox"
"/wc24send.ctl";

WC24SendList::WC24SendList(std::shared_ptr<FS::FileSystem> fs) : m_fs{std::move(fs)}
{
ReadSendList();
}

void WC24SendList::ReadSendList()
{
const auto file = m_fs->OpenFile(PID_KD, PID_KD, SEND_LIST_PATH, FS::Mode::Read);
if (!file || !file->Read(&m_data, 1))
return;

if (file->GetStatus()->size != SEND_LIST_SIZE)
{
ERROR_LOG_FMT(IOS_WC24, "The WC24 Send list file is not the correct size.");
return;
}

const s32 file_error = CheckSendList();
if (!file_error)
ERROR_LOG_FMT(IOS_WC24, "There is an error in the Send List for WC24 mail");
}

void WC24SendList::WriteSendList() const
{
constexpr FS::Modes public_modes{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite};
m_fs->CreateFullPath(PID_KD, PID_KD, SEND_LIST_PATH, 0, public_modes);
const auto file = m_fs->CreateAndOpenFile(PID_KD, PID_KD, SEND_LIST_PATH, public_modes);

if (!file || !file->Write(&m_data, 1))
ERROR_LOG_FMT(IOS_WC24, "Failed to open or write WC24 Send list file");
}

bool WC24SendList::CheckSendList() const
{
// 'WcTf' magic
if (Common::swap32(m_data.header.magic) != MAIL_LIST_MAGIC)
{
ERROR_LOG_FMT(IOS_WC24, "Send List magic mismatch ({} != {})",
Common::swap32(m_data.header.magic), MAIL_LIST_MAGIC);
return false;
}

if (Common::swap32(m_data.header.version) != 4)
{
ERROR_LOG_FMT(IOS_WC24, "Send List version mismatch");
return false;
}

return true;
}

std::string_view WC24SendList::GetMailFlag() const
{
return {m_data.header.mail_flag.data(), m_data.header.mail_flag.size()};
}
} // namespace IOS::HLE::NWC24::Mail
52 changes: 52 additions & 0 deletions Source/Core/Core/IOS/Network/KD/Mail/WC24Send.h
@@ -0,0 +1,52 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <string_view>

#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/Swap.h"
#include "Core/IOS/Network/KD/Mail/MailCommon.h"
#include "Core/IOS/Network/KD/NWC24Config.h"

namespace IOS::HLE
{
namespace FS
{
class FileSystem;
}
namespace NWC24::Mail
{

constexpr const char SEND_BOX_PATH[] = "/" WII_WC24CONF_DIR "/mbox"
"/wc24send.mbx";
class WC24SendList final
{
public:
explicit WC24SendList(std::shared_ptr<FS::FileSystem> fs);

void ReadSendList();
bool CheckSendList() const;
void WriteSendList() const;

std::string_view GetMailFlag() const;

private:
static constexpr u32 MAX_ENTRIES = 127;
static constexpr u32 SEND_LIST_SIZE = 16384;

#pragma pack(push, 1)
struct SendList final
{
MailListHeader header;
std::array<MailListEntry, MAX_ENTRIES> entries;
};
static_assert(sizeof(SendList) == SEND_LIST_SIZE);
#pragma pack(pop)

SendList m_data;
std::shared_ptr<FS::FileSystem> m_fs;
};
} // namespace NWC24::Mail
} // namespace IOS::HLE
12 changes: 12 additions & 0 deletions Source/Core/Core/IOS/Network/KD/NWC24Config.cpp
Expand Up @@ -216,4 +216,16 @@ void NWC24Config::SetEmail(const char* email)
strncpy(m_data.email, email, MAX_EMAIL_LENGTH);
m_data.email[MAX_EMAIL_LENGTH - 1] = '\0';
}

std::string_view NWC24Config::GetMlchkid() const
{
const size_t size = strnlen(m_data.mlchkid, MAX_MLCHKID_LENGTH);
return {m_data.mlchkid, size};
}

std::string NWC24Config::GetCheckURL() const
{
const size_t size = strnlen(m_data.http_urls[1], MAX_URL_LENGTH);
return {m_data.http_urls[1], size};
}
} // namespace IOS::HLE::NWC24

0 comments on commit 143a136

Please sign in to comment.