commit 37fe98373855e96c4eca457f30bac7b1800d8898 Author: JDierkse Date: Fri Apr 28 18:45:26 2017 +0200 Initial commit 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 +