From f71f1c9b11c3739cca7821e0f3022b14d7877376 Mon Sep 17 00:00:00 2001 From: pete Date: Tue, 24 Jun 2025 15:09:33 -0700 Subject: [PATCH] Initial commit Add .gitignore to exclude VSCode workspace files and build artifacts --- .gitignore | 34 ++++++++++ finger.code-workspace | 111 +++++++++++++++++++++++++++++++++ handler.cpp | 27 ++++++++ handler.hpp | 15 +++++ main.cpp | 63 +++++++++++++++++++ meson.build | 24 +++++++ test_handler.cpp | 141 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 415 insertions(+) create mode 100644 .gitignore create mode 100644 finger.code-workspace create mode 100644 handler.cpp create mode 100644 handler.hpp create mode 100644 main.cpp create mode 100644 meson.build create mode 100644 test_handler.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1dd3b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# VSCode workspace files +*.code-workspace + +# Build directories +builddir/ +build/ + +# Compiled binaries +finger +test_handler + +# Object files +*.o +*.obj + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Temporary files +*~ +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/finger.code-workspace b/finger.code-workspace new file mode 100644 index 0000000..0b69013 --- /dev/null +++ b/finger.code-workspace @@ -0,0 +1,111 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "C_Cpp.default.compileCommands": "/home/pmb/code/cpp/finger/builddir/compile_commands.json", + "C_Cpp.default.configurationProvider": "mesonbuild.mesonbuild", + "files.associations": { + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "strstream": "cpp", + "barrier": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "cfenv": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "coroutine": "cpp", + "csetjmp": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cuchar": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "expected": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "source_location": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "flat_map": "cpp", + "flat_set": "cpp", + "format": "cpp", + "fstream": "cpp", + "future": "cpp", + "generator": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "latch": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "print": "cpp", + "queue": "cpp", + "ranges": "cpp", + "scoped_allocator": "cpp", + "semaphore": "cpp", + "shared_mutex": "cpp", + "span": "cpp", + "spanstream": "cpp", + "sstream": "cpp", + "stack": "cpp", + "stacktrace": "cpp", + "stdexcept": "cpp", + "stdfloat": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "syncstream": "cpp", + "text_encoding": "cpp", + "thread": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "valarray": "cpp", + "variant": "cpp" + } + } +} \ No newline at end of file diff --git a/handler.cpp b/handler.cpp new file mode 100644 index 0000000..eeded02 --- /dev/null +++ b/handler.cpp @@ -0,0 +1,27 @@ +#include "handler.hpp" + +std::string process(const std::string &username){ + try{ + // Check for directory traversal patterns + if (username.find("../") != std::string::npos || + username.find("..\\") != std::string::npos || + username.find("%2e%2e%2f") != std::string::npos || + username.find("%2e%2e%5c") != std::string::npos || + username.find("%2E%2E%2F") != std::string::npos || + username.find("%2E%2E%5C") != std::string::npos || + username.find("..%2f") != std::string::npos || + username.find("..%5c") != std::string::npos || + username.find("..%2F") != std::string::npos || + username.find("..%5C") != std::string::npos) { + throw InvalidInput("Directory traversal detected in username"); + } + + if (username.find("/") != std::string::npos){ + throw InvalidInput("Path detected in username"); + } + } + catch(InvalidInput&e){ + return std::string("InvalidInput: ") + e.what() + std::string("\r\n"); + } + return username; +} diff --git a/handler.hpp b/handler.hpp new file mode 100644 index 0000000..9e6fae6 --- /dev/null +++ b/handler.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include + +class InvalidInput : public std::runtime_error +{ +public: + InvalidInput(const std::string& what = "") : std::runtime_error(what) {} +}; + + +std::string process(const std::string &username); \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..927e217 --- /dev/null +++ b/main.cpp @@ -0,0 +1,63 @@ + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "handler.hpp" + +using boost::asio::awaitable; +using boost::asio::co_spawn; +using boost::asio::deferred; +using boost::asio::detached; +using boost::asio::ip::tcp; +namespace this_coro = boost::asio::this_coro; + +awaitable dofinger(const std::string &username) { + co_return process(username); + //co_return std::string("this is some finger information for ") + username + std::string("\r\n"); +} + +awaitable echo(tcp::socket socket) { + try { + char data[1024]; + co_await socket.async_read_some(boost::asio::buffer(data), deferred); + auto response = co_await dofinger(std::string(data)); + co_await async_write(socket, boost::asio::buffer(response), deferred); + co_return; + } + catch (std::exception &e) { + std::printf("echo Exception: %s\n", e.what()); + } +} + +awaitable listener() { + auto executor = co_await this_coro::executor; + tcp::acceptor acceptor(executor, {tcp::v4(), 79}); + for (;;) { + tcp::socket socket = co_await acceptor.async_accept(deferred); + co_spawn(executor, echo(std::move(socket)), detached); + } +} + +int main() { + try { + boost::asio::io_context io_context(1); + + boost::asio::signal_set signals(io_context, SIGINT, SIGTERM); + signals.async_wait([&](auto, auto) { io_context.stop(); }); + + co_spawn(io_context, listener(), detached); + + io_context.run(); + } catch (std::exception &e) { + std::printf("Exception: %s\n", e.what()); + } +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..052cad1 --- /dev/null +++ b/meson.build @@ -0,0 +1,24 @@ +project('finger', 'cpp', + version : '0.1.0', + default_options : ['warning_level=3', + 'cpp_std=c++20']) + +# Find Boost dependencies - prefer static libraries +boost_dep = dependency('boost', modules : ['system'], static : true) + +# Find Google Test dependency +gtest_dep = dependency('gtest', main : true, required : true) + +executable('finger', + 'main.cpp','handler.cpp', + dependencies : [boost_dep], + link_args : ['-static', '-static-libgcc', '-static-libstdc++'], + install : true) + +# Test executable +test_exe = executable('test_handler', + 'test_handler.cpp', 'handler.cpp', + dependencies : [boost_dep, gtest_dep]) + +# Register the test +test('handler_tests', test_exe) diff --git a/test_handler.cpp b/test_handler.cpp new file mode 100644 index 0000000..8178825 --- /dev/null +++ b/test_handler.cpp @@ -0,0 +1,141 @@ +#include "handler.hpp" +#include + +// Test fixture for process function tests +class ProcessTest : public ::testing::Test { +protected: + void SetUp() override {} + void TearDown() override {} +}; + +// Valid username tests +TEST_F(ProcessTest, ValidUsernameSimple) { + std::string result = process("john"); + EXPECT_EQ(result, "john"); +} + +TEST_F(ProcessTest, ValidUsernameWithNumbers) { + std::string result = process("user123"); + EXPECT_EQ(result, "user123"); +} + +TEST_F(ProcessTest, ValidUsernameWithUnderscore) { + std::string result = process("user_name"); + EXPECT_EQ(result, "user_name"); +} + +TEST_F(ProcessTest, ValidUsernameWithHyphen) { + std::string result = process("user-name"); + EXPECT_EQ(result, "user-name"); +} + +TEST_F(ProcessTest, ValidUsernameEmptyString) { + std::string result = process(""); + EXPECT_EQ(result, ""); +} + +// Directory traversal tests +TEST_F(ProcessTest, DirectoryTraversalBasicDotDotSlash) { + std::string result = process("user../file"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Directory traversal detected") != std::string::npos); +} + +TEST_F(ProcessTest, DirectoryTraversalBasicDotDotBackslash) { + std::string result = process("user..\\file"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Directory traversal detected") != std::string::npos); +} + +TEST_F(ProcessTest, DirectoryTraversalURLEncodedLowercase2e2e2f) { + std::string result = process("user%2e%2e%2ffile"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Directory traversal detected") != std::string::npos); +} + +TEST_F(ProcessTest, DirectoryTraversalURLEncodedLowercase2e2e5c) { + std::string result = process("user%2e%2e%5cfile"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Directory traversal detected") != std::string::npos); +} + +TEST_F(ProcessTest, DirectoryTraversalURLEncodedUppercase2E2E2F) { + std::string result = process("user%2E%2E%2Ffile"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Directory traversal detected") != std::string::npos); +} + +TEST_F(ProcessTest, DirectoryTraversalURLEncodedUppercase2E2E5C) { + std::string result = process("user%2E%2E%5Cfile"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Directory traversal detected") != std::string::npos); +} + +TEST_F(ProcessTest, DirectoryTraversalMixedDotDot2f) { + std::string result = process("user..%2ffile"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Directory traversal detected") != std::string::npos); +} + +TEST_F(ProcessTest, DirectoryTraversalMixedDotDot5c) { + std::string result = process("user..%5cfile"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Directory traversal detected") != std::string::npos); +} + +TEST_F(ProcessTest, DirectoryTraversalMixedDotDot2F) { + std::string result = process("user..%2Ffile"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Directory traversal detected") != std::string::npos); +} + +TEST_F(ProcessTest, DirectoryTraversalMixedDotDot5C) { + std::string result = process("user..%5Cfile"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Directory traversal detected") != std::string::npos); +} + +// Path detection tests +TEST_F(ProcessTest, PathDetectionForwardSlash) { + std::string result = process("user/name"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Path detected") != std::string::npos); +} + +TEST_F(ProcessTest, PathDetectionForwardSlashAtStart) { + std::string result = process("/username"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Path detected") != std::string::npos); +} + +TEST_F(ProcessTest, PathDetectionForwardSlashAtEnd) { + std::string result = process("username/"); + EXPECT_TRUE(result.find("InvalidInput:") == 0); + EXPECT_TRUE(result.find("Path detected") != std::string::npos); +} + +// Edge cases +TEST_F(ProcessTest, SingleDot) { + std::string result = process("."); + EXPECT_EQ(result, "."); +} + +TEST_F(ProcessTest, DoubleDotWithoutSlash) { + std::string result = process(".."); + EXPECT_EQ(result, ".."); +} + +TEST_F(ProcessTest, ContainsDotButNotTraversal) { + std::string result = process("user.name"); + EXPECT_EQ(result, "user.name"); +} + +TEST_F(ProcessTest, BackslashWithoutDots) { + std::string result = process("user\\name"); + EXPECT_EQ(result, "user\\name"); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}