From 37fe98373855e96c4eca457f30bac7b1800d8898 Mon Sep 17 00:00:00 2001 From: JDierkse Date: Fri, 28 Apr 2017 18:45:26 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 + Makefile | 51 +++++++++++++ base64.cpp | 123 +++++++++++++++++++++++++++++++ base64.h | 5 ++ connection.cpp | 59 +++++++++++++++ connection.h | 37 ++++++++++ httpClient.cpp | 57 +++++++++++++++ httpClient.h | 59 +++++++++++++++ inputImage.cpp | 120 +++++++++++++++++++++++++++++++ inputImage.h | 50 +++++++++++++ main.cpp | 49 +++++++++++++ message.cpp | 188 ++++++++++++++++++++++++++++++++++++++++++++++++ message.h | 57 +++++++++++++++ mimeImage.cpp | 30 ++++++++ mimeImage.h | 23 ++++++ outputImage.cpp | 64 +++++++++++++++++ outputImage.h | 26 +++++++ protocol.cpp | 111 ++++++++++++++++++++++++++++ protocol.h | 63 ++++++++++++++++ server.cpp | 75 +++++++++++++++++++ server.h | 34 +++++++++ util.cpp | 17 +++++ util.h | 18 +++++ 23 files changed, 1319 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 base64.cpp create mode 100644 base64.h create mode 100644 connection.cpp create mode 100644 connection.h create mode 100644 httpClient.cpp create mode 100644 httpClient.h create mode 100644 inputImage.cpp create mode 100644 inputImage.h create mode 100644 main.cpp create mode 100644 message.cpp create mode 100644 message.h create mode 100644 mimeImage.cpp create mode 100644 mimeImage.h create mode 100644 outputImage.cpp create mode 100644 outputImage.h create mode 100644 protocol.cpp create mode 100644 protocol.h create mode 100644 server.cpp create mode 100644 server.h create mode 100644 util.cpp create mode 100644 util.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c097947 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +.*.swp +AlarmServer diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..01bc8a6 --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ +CC := g++ +CFLAGS := -O3 +LFLAGS := -L/opt/lib -lpthread -lcrypto -lcurl -lboost_regex -lboost_filesystem -lboost_system -ljpeg + +ifeq ($(BUILD),debug) +# Debug flags +CFLAGS += -O0 -g +endif + +ifeq ($(BUILD),release) +# Optimization flags valid for Intel Atom (DS1813+) +CFLAGS += -march=prescott -mtune=pentium -mfpmath=sse -O3 -s -DNDEBUG +endif + +%.o: %.cpp + $(CC) -c $(CFLAGS) $< -o $@ + +AlarmServer: main.cpp server.o connection.o protocol.o message.o mimeImage.o outputImage.o inputImage.o util.o httpClient.o base64.o + g++ -o AlarmServer main.cpp server.o connection.o protocol.o message.o mimeImage.o outputImage.o inputImage.o util.o httpClient.o base64.o $(LFLAGS) $(LDFLAGS) $(CFLAGS) + +server.o: server.cpp server.h connection.h protocol.h util.h httpClient.h + +connection.o: connection.cpp connection.h protocol.h util.h httpClient.h + +protocol.o: protocol.cpp protocol.h util.h httpClient.h + +message.o: message.cpp message.h mimeImage.h outputImage.h inputImage.h util.h httpClient.h base64.h + +mimeImage.o: mimeImage.cpp mimeImage.h outputImage.h inputImage.h base64.h + +outputImage.o: outputImage.cpp outputImage.h inputImage.h + +inputImage.o: inputImage.cpp inputImage.h + +util.o: util.cpp util.h httpClient.h + +httpClient.o: httpClient.cpp httpClient.h + +base64.o: base64.cpp base64.h + +all: AlarmServer + +debug: + make "BUILD=debug" + +release: + make "BUILD=release" + +clean: + -rm *.o AlarmServer + diff --git a/base64.cpp b/base64.cpp new file mode 100644 index 0000000..f0efe95 --- /dev/null +++ b/base64.cpp @@ -0,0 +1,123 @@ +/* + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include "base64.h" + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + + } + + return ret; + +} + +std::string base64_decode(std::string const& encoded_string) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +} + diff --git a/base64.h b/base64.h new file mode 100644 index 0000000..5f87aec --- /dev/null +++ b/base64.h @@ -0,0 +1,5 @@ +#include + +std::string base64_encode(unsigned char const* , unsigned int len); +std::string base64_decode(std::string const& s); + diff --git a/connection.cpp b/connection.cpp new file mode 100644 index 0000000..1dd46e4 --- /dev/null +++ b/connection.cpp @@ -0,0 +1,59 @@ +#include +#include + +#include "connection.h" + + +connection::connection(boost::asio::io_service& ioService, const context& context) : + m_ioService(ioService), + m_socket(ioService), + m_protocol(context) +{ +} + +boost::asio::ip::tcp::socket& connection::socket() +{ + return m_socket; +} + +void connection::start() +{ + start_write(m_protocol.openConnection(m_socket.remote_endpoint().address().to_string()).second); +} + +void connection::start_read() +{ + m_socket.async_read_some(boost::asio::buffer(m_data), boost::bind(&connection::handle_read, this, _1, _2)); +} + +void connection::handle_read(const boost::system::error_code& ec, std::size_t length) +{ + if (!ec) + { + protocol::result result = m_protocol.processMessage(std::string(m_data.begin(), m_data.begin() + length)); + if (result.second.size() > 0) + start_write(result.second); + else + start_read(); + + if (result.first == protocol::state::disconnected) + m_socket.close(); + } +} + +void connection::start_write(const std::string& message) +{ + std::copy(message.begin(), message.end(), m_data.data()); + + size_t length = message.size() + 1; + m_data[message.size()] = '\n'; + + boost::asio::async_write(m_socket, boost::asio::buffer(m_data, length), boost::bind(&connection::handle_write, this, _1)); +} + +void connection::handle_write(const boost::system::error_code& ec) +{ + if (!ec) + start_read(); +} + diff --git a/connection.h b/connection.h new file mode 100644 index 0000000..83c07a4 --- /dev/null +++ b/connection.h @@ -0,0 +1,37 @@ +#ifndef CONNECTION_H +#define CONNECTION_H + +#include +#include +#include +#include +#include +#include +#include "protocol.h" +#include "util.h" + + +class connection : private boost::noncopyable +{ +public: + connection(boost::asio::io_service& ioService, const context& context); + + boost::asio::ip::tcp::socket& socket(); + + void start(); + +private: + void start_read(); + void handle_read(const boost::system::error_code& ec, std::size_t length); + void start_write(const std::string& message); + void handle_write(const boost::system::error_code& ec); + + boost::asio::io_service& m_ioService; + boost::asio::ip::tcp::socket m_socket; + boost::array m_data; + + protocol m_protocol; +}; + +#endif // CONNECTION_H + diff --git a/httpClient.cpp b/httpClient.cpp new file mode 100644 index 0000000..a1a6c9e --- /dev/null +++ b/httpClient.cpp @@ -0,0 +1,57 @@ +#include "httpClient.h" + + +httpClient::httpClient() +{ + curl_global_init(CURL_GLOBAL_ALL); + + m_userAgents.push_back("Opera/9.80 (X11; Linux i686; U; en) Presto/2.7.62 Version/11.00"); + + initialize_locks(); +} + +httpClient::~httpClient() +{ + free_locks(); + curl_global_cleanup(); +} + +std::string httpClient::GetUrlContents(const std::string& url) const +{ + std::string buffer; + + CURL* curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_USERAGENT, m_userAgents[rand() % m_userAgents.size()].c_str()); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + + if (code != 200) + { + std::stringstream error; + error << "Error encountered while retrieving " << url; + throw std::runtime_error(error.str()); + } + + return buffer; +} + +size_t httpClient::WriteCallback(char* data, size_t size, size_t nmemb, std::string* writerData) +{ + if (writerData == NULL) + return 0; + + writerData->append(data, size * nmemb); + return size * nmemb; +} + diff --git a/httpClient.h b/httpClient.h new file mode 100644 index 0000000..dda065c --- /dev/null +++ b/httpClient.h @@ -0,0 +1,59 @@ +#ifndef HTTPCLIENT_H +#define HTTPCLIENT_H + +#include +#include +#include +#include +#include +#include +#include + + +static std::vector > g_mutexes; + +static void lock_callback(int mode, int type, const char *file, int line) +{ + if (mode & CRYPTO_LOCK) + g_mutexes[type]->lock(); + else + g_mutexes[type]->unlock(); +} + +static unsigned long thread_id(void) +{ + return (unsigned long)pthread_self(); +} + +static void initialize_locks(void) +{ + g_mutexes.resize(CRYPTO_num_locks()); + for (int i = 0; i < CRYPTO_num_locks(); ++i) + g_mutexes[i] = boost::shared_ptr(new boost::mutex()); + + CRYPTO_set_id_callback(thread_id); + CRYPTO_set_locking_callback(lock_callback); +} + +static void free_locks(void) +{ + CRYPTO_set_locking_callback(NULL); +} + +class httpClient : boost::noncopyable +{ +public: + httpClient(); + ~httpClient(); + + std::string GetUrlContents(const std::string& url) const; + +private: + static size_t WriteCallback(char* data, size_t size, size_t nmemb, std::string* writerData); + +private: + std::vector m_userAgents; +}; + +#endif // HTTPCLIENT_H + diff --git a/inputImage.cpp b/inputImage.cpp new file mode 100644 index 0000000..1c2b9eb --- /dev/null +++ b/inputImage.cpp @@ -0,0 +1,120 @@ +#include "inputImage.h" + + +const static JOCTET EOI_BUFFER[1] = { JPEG_EOI }; + +inputImage::inputImage() +{ +} + +void inputImage::setImageData(const std::string& data) +{ + decompressImage(data.c_str(), data.size()); +} + +unsigned int inputImage::width() const +{ + return m_width; +} + +unsigned int inputImage::height() const +{ + return m_height; +} + +unsigned int inputImage::strideX() const +{ + return m_strideX; +} + +unsigned int inputImage::strideY() const +{ + return m_strideY; +} + +const std::vector& inputImage::data() const +{ + return m_data; +} + +void inputImage::init_source(j_decompress_ptr pCinfo) +{ +} + +boolean inputImage::fill_input_buffer(j_decompress_ptr pCinfo) +{ + source_mgr_ptr pSrc = reinterpret_cast(pCinfo->src); + pSrc->pub.next_input_byte = EOI_BUFFER; + pSrc->pub.bytes_in_buffer = 1; + return TRUE; +} + +void inputImage::skip_input_data(j_decompress_ptr pCinfo, long numBytes) +{ + source_mgr_ptr pSrc = reinterpret_cast(pCinfo->src); + if (pSrc->pub.bytes_in_buffer < numBytes) + { + pSrc->pub.next_input_byte = EOI_BUFFER; + pSrc->pub.bytes_in_buffer = 1; + } + else + { + pSrc->pub.next_input_byte += numBytes; + pSrc->pub.bytes_in_buffer -= numBytes; + } +} + +void inputImage::term_source(j_decompress_ptr pCinfo) +{ +} + +void inputImage::setSourceMgr(j_decompress_ptr pCinfo, const char* pData, size_t len) const +{ + source_mgr_ptr pSrc; + if (pCinfo->src == 0) + pCinfo->src = (struct jpeg_source_mgr *)(*pCinfo->mem->alloc_small)(reinterpret_cast(pCinfo), JPOOL_PERMANENT, sizeof(source_mgr)); + + pSrc = reinterpret_cast(pCinfo->src); + pSrc->pub.init_source = inputImage::init_source; + pSrc->pub.fill_input_buffer = inputImage::fill_input_buffer; + pSrc->pub.skip_input_data = inputImage::skip_input_data; + pSrc->pub.resync_to_restart = jpeg_resync_to_restart; + pSrc->pub.term_source = inputImage::term_source; + + pSrc->data = (const JOCTET *)pData; + pSrc->len = len; + pSrc->pub.bytes_in_buffer = len; + pSrc->pub.next_input_byte = pSrc->data; +} + +void inputImage::decompressImage(const char* pData, size_t len) +{ + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + + jerr.trace_level = 0; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + setSourceMgr(&cinfo, pData, len); + + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + m_width = cinfo.image_width; + m_height = cinfo.image_height; + m_strideX = cinfo.num_components; + m_strideY = m_width * cinfo.num_components; + + m_data.resize(m_height * m_strideY); + + JSAMPROW scanlines[1]; + while (cinfo.output_scanline < cinfo.image_height) + { + scanlines[0] = reinterpret_cast(&m_data[cinfo.output_scanline * m_strideY]); + jpeg_read_scanlines(&cinfo, scanlines, 1); + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); +} + diff --git a/inputImage.h b/inputImage.h new file mode 100644 index 0000000..b813a1d --- /dev/null +++ b/inputImage.h @@ -0,0 +1,50 @@ +#ifndef INPUTIMAGE_H +#define INPUTIMAGE_H + +#include +#include +#include + +class inputImage +{ +public: + inputImage(); + + void setImageData(const std::string& data); + + unsigned int width() const; + unsigned int height() const; + unsigned int strideX() const; + unsigned int strideY() const; + const std::vector& data() const; + +private: + struct source_mgr + { + struct jpeg_source_mgr pub; + const JOCTET *data; + size_t len; + }; + typedef source_mgr* source_mgr_ptr; + +private: + static void init_source(j_decompress_ptr pCinfo); + static boolean fill_input_buffer(j_decompress_ptr pCinfo); + static void skip_input_data(j_decompress_ptr pCinfo, long numBytes); + static void term_source(j_decompress_ptr pCinfo); + + void setSourceMgr(j_decompress_ptr pCinfo, const char* pData, size_t len) const; + + void decompressImage(const char* pData, size_t len); + +private: + unsigned int m_width; + unsigned int m_height; + unsigned int m_strideX; + unsigned int m_strideY; + + std::vector m_data; +}; + +#endif // INPUTIMAGE_H + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..529ad14 --- /dev/null +++ b/main.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include "server.h" + + +int main(int argc, char* argv[]) +{ + try + { + /* error checking elided for brevity */ + int fd = ::open("/dev/null", O_WRONLY); + ::dup2(fd, 2); + ::close(fd); + + unsigned int port; + std::string path; + std::string url; + + if (argc == 3) + { + port = std::atoi(argv[1]); + path = std::string(argv[2]); + } + else if (argc == 4) + { + port = std::atoi(argv[1]); + path = std::string(argv[2]); + url = std::string(argv[3]); + } + else + { + std::cout << "Usage: AlarmServer [url]\n"; + return 1; + } + + boost::asio::io_service io_service; + + server s(io_service, port, path, url); + + io_service.run(); + } + catch (std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + } +} + diff --git a/message.cpp b/message.cpp new file mode 100644 index 0000000..19f9f34 --- /dev/null +++ b/message.cpp @@ -0,0 +1,188 @@ +#include +#include +#include +#include +#include +#include "outputImage.h" +#include "base64.h" +#include "message.h" + + +message::message(const std::string& address, const context& context, const std::string& message) : + m_address(address), + m_context(context), + m_mimeBoundary("-_-InvalidBoundary-_-"), + m_contentEncoding("none"), + m_currentImage(0) +{ + processMessage(message); +} + +std::string Decode(const std::string& data) +{ + std::string result; + if (boost::istarts_with(data, "=?utf-8?")) + result = base64_decode(std::string(data.begin() + 10, data.begin() + data.find_last_of('?'))); + else + result = base64_decode(data); + + return result; +} + +void message::processMessage(const std::string& message) +{ + message::state::type state = message::state::header; + std::string line; + std::istringstream stream(message); + while (std::getline(stream, line, '\r')) + { + boost::erase_all(line, "\n"); + switch (state) + { + case state::header: + state = processHeaderLine(line); + break; + case state::data: + state = processDataLine(line); + break; + case state::mimeBoundary: + state = processMimeBoundary(line); + break; + case state::image: + state = processImage(line); + break; + } + } + + if (boost::algorithm::contains(m_contentEncoding, "base64")) + { + std::string decoded = Decode(m_subject); + if (decoded.size() > 0) + m_subject = decoded; + } + + outputImage image(m_images); + + boost::filesystem::path path(m_context.path); + path = boost::filesystem::canonical(path); + path /= m_address; + + if (!boost::filesystem::exists(path)) + boost::filesystem::create_directory(path); + + std::string fileName(getTimeString()); + fileName.append(".jpg"); + path /= fileName; + + image.save(path.string()); + + if (!m_context.url.empty()) + { + std::stringstream url; + url << m_context.url << "?ip=" << m_address << "&file=" << fileName; + m_context.client.GetUrlContents(url.str()); + } +} + +message::state::type message::processHeaderLine(const std::string& line) +{ + if (line.empty()) + return message::state::data; + + if (boost::istarts_with(line, "from:")) + { + m_sender = std::string(line.begin() + line.find_first_of(' ') + 1, line.end()); + return message::state::header; + } + if (boost::istarts_with(line, "to:")) + { + m_receiver = std::string(line.begin() + line.find_first_of(' ') + 1, line.end()); + return message::state::header; + } + if (boost::istarts_with(line, "subject:")) + { + m_subject = std::string(line.begin() + line.find_first_of(' ') + 1, line.end()); + return message::state::header; + } + if (boost::istarts_with(line, "date")) + { + m_date = std::string(line.begin() + line.find_first_of(' ') + 1, line.end()); + return message::state::header; + } + if (boost::istarts_with(line, "content-type:")) + { + m_contentType = std::string(line.begin() + line.find_first_of(' ') + 1, line.end()); + return message::state::header; + } + if (boost::istarts_with(line, "boundary") || boost::istarts_with(line, "\tboundary") || boost::istarts_with(line, " boundary")) + { + m_mimeBoundary = std::string("--").append(std::string(line.begin() + line.find_first_of('"') + 1, line.begin() + line.find_last_of('"'))); + return message::state::header; + } + if (boost::istarts_with(line, "content-transfer-encoding:")) + { + m_contentEncoding = std::string(line.begin() + line.find_first_of(' ') + 1, line.end()); + return message::state::header; + } + + return message::state::header; +} + +message::state::type message::processDataLine(const std::string& line) +{ + if (boost::istarts_with(line, m_mimeBoundary)) + { + m_contentType = ""; + return message::state::mimeBoundary; + } + + return message::state::data; +} + +message::state::type message::processMimeBoundary(const std::string& line) +{ + if (line.empty()) + { + if (boost::istarts_with(m_contentType, "image/jpeg") || boost::istarts_with(m_contentType, "image/jpg")) + { + m_mimeImages.push_back(mimeImage(m_imageName)); + m_imageName = ""; + return message::state::image; + } + return message::state::data; + } + if (boost::istarts_with(line, "content-type:")) + { + m_contentType = std::string(line.begin() + line.find_first_of(' ') + 1, line.end()); + return message::state::mimeBoundary; + } + if (boost::istarts_with(line, "name") || boost::istarts_with(line, "\tname")) + { + m_imageName = "image.jpg"; + //m_imageName = Decode(std::string(line.begin() + line.find_first_of('"') + 1, line.begin() + line.find_last_of('"'))); + return message::state::mimeBoundary; + } + if (boost::istarts_with(line, "content-transfer-encoding:")) + { + m_contentEncoding = std::string(line.begin() + line.find_first_of(' ') + 1, line.end()); + return message::state::mimeBoundary; + } + + return message::state::mimeBoundary; +} + +message::state::type message::processImage(const std::string& line) +{ + if (boost::istarts_with(line, m_mimeBoundary)) + { + m_images.push_back(m_mimeImages[m_currentImage].decodeImageData()); + ++m_currentImage; + m_contentType = ""; + return message::state::mimeBoundary; + } + + m_mimeImages[m_currentImage].appendData(line); + + return message::state::image; +} + diff --git a/message.h b/message.h new file mode 100644 index 0000000..fddbc19 --- /dev/null +++ b/message.h @@ -0,0 +1,57 @@ +#ifndef MESSAGE_H +#define MESSAGE_H + +#include +#include +#include "mimeImage.h" +#include "inputImage.h" +#include "util.h" + + +class message +{ +public: + message(const std::string& address, const context& context, const std::string& message); + +private: + class state + { + public: + enum type + { + header, + data, + mimeBoundary, + image + }; + }; + +private: + void processMessage(const std::string& message); + state::type processHeaderLine(const std::string& line); + state::type processDataLine(const std::string& line); + state::type processMimeBoundary(const std::string& line); + state::type processImage(const std::string& line); + +private: + std::string m_address; + const context& m_context; + + std::string m_sender; + std::string m_receiver; + std::string m_subject; + std::string m_date; + std::string m_mimeBoundary; + + std::string m_contentType; + std::string m_contentEncoding; + + size_t m_currentImage; + std::string m_imageName; + + std::vector m_mimeImages; + std::vector m_images; +}; + +#endif // MESSAGE_H + diff --git a/mimeImage.cpp b/mimeImage.cpp new file mode 100644 index 0000000..e115ee1 --- /dev/null +++ b/mimeImage.cpp @@ -0,0 +1,30 @@ +#include +#include +#include "base64.h" +#include "mimeImage.h" + + +mimeImage::mimeImage() +{ +} + +mimeImage::mimeImage(const std::string& name) : + m_name(name) +{ +} + +void mimeImage::appendData(const std::string& data) +{ + if (data.empty()) + return; + + m_imageData.append(data); +} + +const inputImage& mimeImage::decodeImageData() +{ + boost::erase_all(m_imageData, "\r"); + m_inputImage.setImageData(base64_decode(m_imageData)); + return m_inputImage; +} + diff --git a/mimeImage.h b/mimeImage.h new file mode 100644 index 0000000..10a1ad6 --- /dev/null +++ b/mimeImage.h @@ -0,0 +1,23 @@ +#ifndef MIMEIMAGE_H +#define MIMEIMAGE_H + +#include "inputImage.h" + + +class mimeImage +{ +public: + mimeImage(); + mimeImage(const std::string& name); + + void appendData(const std::string& data); + const inputImage& decodeImageData(); + +private: + std::string m_name; + std::string m_imageData; + inputImage m_inputImage; +}; + +#endif // MIMEIMAGE_H + diff --git a/outputImage.cpp b/outputImage.cpp new file mode 100644 index 0000000..1776c54 --- /dev/null +++ b/outputImage.cpp @@ -0,0 +1,64 @@ +#include +#include "outputImage.h" + + +outputImage::outputImage(const std::vector& images) : + m_width(0), + m_height(0), + m_strideX(0), + m_strideY(0) +{ + if (images.size() == 0) + return; + + m_width = images[0].width(); + m_strideX = images[0].strideX(); + m_strideY = images[0].strideY(); + + size_t height = images[0].height(); + m_height = height * images.size(); + m_imageData.resize(m_strideY * m_height); + + std::vector::iterator outIterator = m_imageData.begin(); + for (std::vector::const_iterator it = images.begin(); it != images.end(); ++it) + outIterator = std::copy(it->data().begin(), it->data().end(), outIterator); +} + +void outputImage::save(const std::string& fileName) +{ + if (m_width == 0 || m_height == 0 || m_strideX == 0 || m_strideY == 0) + return; + + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + + FILE* outFile = fopen(fileName.c_str(), "wb"); + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, outFile); + + cinfo.image_width = m_width; + cinfo.image_height = m_height; + cinfo.input_components = m_strideX; + cinfo.in_color_space = JCS_RGB; + + jpeg_set_defaults( &cinfo ); + cinfo.num_components = 3; + cinfo.dct_method = JDCT_FLOAT; + jpeg_set_quality(&cinfo, 80, TRUE); + + jpeg_start_compress( &cinfo, TRUE ); + + JSAMPROW scanlines[1]; + while (cinfo.next_scanline < cinfo.image_height) + { + scanlines[0] = reinterpret_cast(&m_imageData[cinfo.next_scanline * m_strideY]); + jpeg_write_scanlines(&cinfo, scanlines, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + fclose(outFile); +} + diff --git a/outputImage.h b/outputImage.h new file mode 100644 index 0000000..446f9f3 --- /dev/null +++ b/outputImage.h @@ -0,0 +1,26 @@ +#ifndef OUTPUTIMAGE_H +#define OUTPUTIMAGE_H + +#include +#include +#include "inputImage.h" + + +class outputImage +{ +public: + outputImage(const std::vector& images); + + void save(const std::string& fileName); + +private: + size_t m_width; + size_t m_height; + size_t m_strideX; + size_t m_strideY; + + std::vector m_imageData; +}; + +#endif // OUTPUTIMAGE_H + diff --git a/protocol.cpp b/protocol.cpp new file mode 100644 index 0000000..327b522 --- /dev/null +++ b/protocol.cpp @@ -0,0 +1,111 @@ +#include +#include +#include "message.h" +#include "protocol.h" + +protocol::protocol(const context& context) : + m_internalState(internalState::none), + m_context(context) +{ +} + +protocol::result protocol::openConnection(const std::string& address) +{ + m_address = address; + m_internalState = internalState::connect; + //std::cout << "Connection from " << address << std::endl; + return answer(protocol::state::connected, "220 AlarmServer"); +} + +protocol::result protocol::processMessage(const std::string& message) +{ + if (m_internalState == internalState::data) + { + appendMessage(message); + + if (boost::iends_with(message, ".\r\n")) + { + ::message msg(m_address, m_context, m_message); + m_internalState = internalState::helo; + return answer(protocol::state::connected, "250 Ok"); + } + + return answer(protocol::state::connected, ""); + } + + //std::cout << "-> " << message << std::endl; + + if (m_internalState == internalState::auth_login_user) + { + m_internalState = internalState::auth_login_pass; + return answer(protocol::state::connected, "334 UGFzc3dvcmQ6"); + } + if (m_internalState == internalState::auth_login_pass) + { + m_internalState = internalState::helo; + return answer(protocol::state::connected, "235 2.7.0 Authentication successful"); + } + + if (boost::iequals(message, "quit\r\n")) + { + m_internalState = internalState::disconnect; + return answer(protocol::state::disconnected, "221 Bye"); + } + if (boost::istarts_with(message, "helo")) + { + m_internalState = internalState::helo; + return answer(protocol::state::connected, "250-AlarmServer\r\n250 AUTH LOGIN"); + } + if (boost::istarts_with(message, "ehlo")) + { + m_internalState = internalState::helo; + return answer(protocol::state::connected, "250-AlarmServer\r\n250 AUTH LOGIN"); + } + if (boost::istarts_with(message, "auth login")) + { + if (m_internalState != internalState::helo) + return answer(protocol::state::connected, "503 Bad sequence of commands"); + m_internalState = internalState::auth_login_user; + return answer(protocol::state::connected, "334 VXNlcm5hbWU6"); + } + if (boost::istarts_with(message, "mail from:")) + { + if (m_internalState != internalState::helo) + return answer(protocol::state::connected, "503 Bad sequence of commands"); + m_internalState = internalState::mail_from; + return answer(protocol::state::connected, "250 Ok"); + } + if (boost::istarts_with(message, "rcpt to:")) + { + if (m_internalState != internalState::mail_from) + return answer(protocol::state::connected, "503 Bad sequence of commands"); + m_internalState = internalState::rcpt_to; + return answer(protocol::state::connected, "250 Ok"); + } + if (boost::iequals(message, "data\r\n")) + { + if (m_internalState != internalState::rcpt_to) + return answer(protocol::state::connected, "503 Bad sequence of commands"); + m_internalState = internalState::data; + return answer(protocol::state::connected, "354 Go ahead"); + } + + return answer(protocol::state::connected, "502 Error"); +} + +protocol::result protocol::answer(protocol::state::type state, std::string answer) const +{ +/* + if (answer.size() > 0) + std::cout << "<- " << answer << std::endl; +*/ + + return result(state, answer); +} + +void protocol::appendMessage(const std::string& message) +{ + m_message.append(message); + m_message.append("\n"); +} + diff --git a/protocol.h b/protocol.h new file mode 100644 index 0000000..4b2b55e --- /dev/null +++ b/protocol.h @@ -0,0 +1,63 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include +#include +#include "util.h" + + +class protocol +{ +public: + class state + { + public: + enum type + { + connected, + disconnected + }; + }; + + typedef std::pair result; + +public: + explicit protocol(const context& context); + + result openConnection(const std::string& address); + result processMessage(const std::string& message); + +private: + class internalState + { + public: + enum type + { + none, + connect, + helo, + auth_login_user, + auth_login_pass, + mail_from, + rcpt_to, + data, + mime_oundary, + image_data, + disconnect + }; + }; + +private: + result answer(state::type state, std::string answer) const; + void appendMessage(const std::string& message); + +private: + internalState::type m_internalState; + std::string m_address; + std::string m_message; + + const context& m_context; +}; + +#endif // PROTOCOL_H + diff --git a/server.cpp b/server.cpp new file mode 100644 index 0000000..e7b4e97 --- /dev/null +++ b/server.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include + +#include "server.h" + +server::server(boost::asio::io_service& ioService, unsigned short port, const std::string& path, const std::string& url) : + m_ioService(ioService), + m_signal(ioService, SIGCHLD), + m_acceptor(ioService, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)), + m_socket(ioService) +{ + m_context.path = path; + m_context.url = url; + + start_signal_wait(); + start_accept(); +} + +void server::start_signal_wait() +{ + m_signal.async_wait(boost::bind(&server::handle_signal_wait, this)); +} + +void server::handle_signal_wait() +{ + // Only the parent process should check for this signal. We can determine + // whether we are in the parent by checking if the acceptor is still open. + if (m_acceptor.is_open()) + { + // Reap completed child processes so that we don't end up with zombies. + int status = 0; + while (waitpid(-1, &status, WNOHANG) > 0) {} + + start_signal_wait(); + } +} + +void server::start_accept() +{ + m_connection.reset(new connection(m_ioService, m_context)); + m_acceptor.async_accept(m_connection->socket(), boost::bind(&server::handle_accept, this, _1)); +} + +void server::handle_accept(const boost::system::error_code& ec) +{ + if (!ec) + { + m_ioService.notify_fork(boost::asio::io_service::fork_prepare); + + if (fork() == 0) + { + m_ioService.notify_fork(boost::asio::io_service::fork_child); + m_acceptor.close(); + m_signal.cancel(); + + m_connection->start(); + } + else + { + m_ioService.notify_fork(boost::asio::io_service::fork_parent); + + m_connection.reset(); + m_socket.close(); + start_accept(); + } + } + else + { + std::cout << "Accept error: " << ec.message() << std::endl; + start_accept(); + } +} + diff --git a/server.h b/server.h new file mode 100644 index 0000000..f448406 --- /dev/null +++ b/server.h @@ -0,0 +1,34 @@ +#ifndef SERVER_H +#define SERVER_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "util.h" + + +class server +{ +public: + server(boost::asio::io_service& ioService, unsigned short port, const std::string& path, const std::string& url); + +private: + void start_signal_wait(); + void handle_signal_wait(); + void start_accept(); + void handle_accept(const boost::system::error_code& ec); + + boost::asio::io_service& m_ioService; + boost::asio::signal_set m_signal; + boost::asio::ip::tcp::acceptor m_acceptor; + boost::asio::ip::tcp::socket m_socket; + boost::shared_ptr m_connection; + + context m_context; +}; + +#endif // SERVER_H + diff --git a/util.cpp b/util.cpp new file mode 100644 index 0000000..facea1a --- /dev/null +++ b/util.cpp @@ -0,0 +1,17 @@ +#include +#include +#include "util.h" + +std::string getTimeString() +{ + time_t rawtime; + struct tm* timeinfo; + char buffer[80]; + + time (&rawtime); + timeinfo = localtime(&rawtime); + + strftime(buffer, 80, "%Y%m%d-%H%M%S", timeinfo); + return std::string(buffer); +} + diff --git a/util.h b/util.h new file mode 100644 index 0000000..eefe826 --- /dev/null +++ b/util.h @@ -0,0 +1,18 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include "httpClient.h" + +struct context +{ +public: + std::string path; + httpClient client; + std::string url; +}; + +std::string getTimeString(); + +#endif // UTIL_H +