diff --git a/YourChat.pro b/YourChat.pro index e4b5a40..b0563cf 100644 --- a/YourChat.pro +++ b/YourChat.pro @@ -2,5 +2,33 @@ TEMPLATE = app CONFIG += console c++11 CONFIG -= app_bundle CONFIG -= qt +TARGET = yourchat -SOURCES += main.cpp +SOURCES += main.cpp \ + config.cpp \ + server.cpp \ + room.cpp \ + user.cpp \ + tools.cpp + +DISTFILES += \ + config/chatconfig.json + +HEADERS += \ + config.h \ + server.h \ + room.h \ + user.h \ + tools.h + +LIBS += -ljsoncpp \ + -lpthread + +bin.path = /opt/yourchat +bin.files += yourchat + +config.path = /etc/yourpart +config.files += config/chatconfig.json + +INSTALLS += bin \ + config diff --git a/config.cpp b/config.cpp index 9e0287b..f7c65c7 100644 --- a/config.cpp +++ b/config.cpp @@ -1,12 +1,31 @@ #include "config.h" +#include +#include +#include + namespace Yc { -namespace Lib { + namespace Lib { -Config::Config() -{ + Config::Config() { + loadConfig(); + } -} + void Config::loadConfig() { + std::ifstream configStream("/etc/yourpart/chatconfig.json", std::ifstream::binary); + configStream >> jsonConfig; + } -} // namespace Lib + Json::Value Config::value(std::string groupName, std::string field) { + if (jsonConfig[groupName].isNull()) { + return ""; + } + return jsonConfig[groupName][field]; + } + + Json::Value Config::group(std::string groupName) { + return jsonConfig[groupName]; + } + + } // namespace Lib } // namespace Yc diff --git a/config.h b/config.h index 306b12c..07c1097 100644 --- a/config.h +++ b/config.h @@ -1,6 +1,8 @@ #ifndef YC_LIB_CONFIG_H #define YC_LIB_CONFIG_H +#include +#include namespace Yc { namespace Lib { @@ -9,9 +11,14 @@ class Config { public: Config(); + void loadConfig(); + Json::Value value(std::string group, std::string field); + Json::Value group(std::string groupName); +private: + Json::Value jsonConfig; }; } // namespace Lib } // namespace Yc -#endif // YC_LIB_CONFIG_H \ No newline at end of file +#endif // YC_LIB_CONFIG_H diff --git a/config/chatconfig.json b/config/chatconfig.json index e69de29..ff0cc45 100644 --- a/config/chatconfig.json +++ b/config/chatconfig.json @@ -0,0 +1,17 @@ +{ + "server": { + "port": 1235 + }, + "database": { + "user": "yourpart", + "password": "r3EMWJ5p", + "connectstring": "tsschulz.de:1521/yourpart" + }, + "rooms": [ + { + "name": "Halle", + "password": "", + "allowed": [] + } + ] +} diff --git a/main.cpp b/main.cpp index b5b73c5..7f67efb 100644 --- a/main.cpp +++ b/main.cpp @@ -1,9 +1,10 @@ -#include +#include "config.h" +#include "server.h" -using namespace std; - -int main(int argc, char *argv[]) +int main(int, char **) { - cout << "Hello World!" << endl; + Yc::Lib::Config *config = new Yc::Lib::Config(); + Yc::Lib::Server server(config); + server.run(); return 0; } diff --git a/room.cpp b/room.cpp index de52a12..16d2023 100644 --- a/room.cpp +++ b/room.cpp @@ -1,12 +1,93 @@ #include "room.h" +#include +#include +#include +#include + namespace Yc { -namespace Lib { + namespace Lib { -Room::Room() -{ + Room::Room(Server *parent, std::string name, std::string password, std::vector allowedUsers) : + _parent(parent), + _name(name), + _password(password), + _allowedUsers(allowedUsers), + _blocked(false), + _stop(false) { + thread = new std::thread(&Room::run, this); + } -} + void Room::run() { + while (!_stop) { + if (_msgQueue.size() > 0 && !_blocked) { + _blocked = true; + while (_msgQueue.size() > 0) { + Message message = _msgQueue.front(); + _msgQueue.pop(); + for (auto &user: _users) { + user.sendMsg(message.type, message.messageTr, message.userName, message.color); + } + } + _blocked = false; + } + usleep(50000); + } + } -} // namespace Lib + bool Room::addUser(std::string _userName, std::string color, std::string _password, int socket) { + if (_password != "" && _password == _password && std::find(std::begin(_allowedUsers), std::end(_allowedUsers), _userName) == std::end(_allowedUsers)) { + return false; + } + User newUser(this, _userName, color, socket); + _users.push_back(newUser); + newUser.sendMsg(User::roomList, _parent->jsonRoomList(), "", ""); + addMessage(User::system, "room_entered", newUser.name(), newUser.color()); + return true; + } + + bool Room::addUser(User user, std::string password) { + if (password == _password) { + _users.push_back(user); + return true; + } + return false; + } + + bool Room::userNameExists(std::string userName) { + for (auto &user: _users) { + if (user.name() == userName) { + return true; + } + } + return false; + } + + void Room::removeUser(std::string _token) { + for (std::vector::iterator user = _users.begin(); user != _users.end(); ++user) { + if (user->validateToken(_token)) { + _users.erase(user); + } + } + } + + void Room::setStop() { + _stop = true; + } + + void Room::addMessage(User::MsgType type, std::string messageText, std::string userName, std::string color) + { + Message message; + message.type = type; + message.messageTr = messageText; + message.userName = userName; + message.color = color; + _msgQueue.push(message); + } + + std::string Room::name() { + return _name; + } + + } // namespace Lib } // namespace Yc diff --git a/room.h b/room.h index 63d89a1..4d59676 100644 --- a/room.h +++ b/room.h @@ -1,17 +1,49 @@ #ifndef YC_LIB_ROOM_H #define YC_LIB_ROOM_H +#include +#include +#include +#include +#include namespace Yc { -namespace Lib { + namespace Lib { -class Room -{ -public: - Room(); -}; + class Server; -} // namespace Lib + class Room + { + public: + Room(Server *parent, std::string name, std::string password = "", std::vector allowedUsers = std::vector()); + void run(); + std::string name(); + bool addUser(std::string userName, std::string color, std::string password, int socket); + bool addUser(User user, std::string password); + bool userNameExists(std::string userName); + void removeUser(std::string _token); + void setStop(); + void addMessage(User::MsgType type, std::string message, std::string userName = "", std::string color = ""); + private: + struct Message { + User::MsgType type; + std::string messageTr; + std::string userName; + std::string color; + }; + + Server *_parent; + std::string _name; + std::string _password; + std::vector _allowedUsers; + std::vector _users; + bool _blocked; + bool _stop; + std::queue _msgQueue; + std::thread *thread; + }; + + } // namespace Lib } // namespace Yc -#endif // YC_LIB_ROOM_H \ No newline at end of file +#endif // YC_LIB_ROOM_H diff --git a/server.cpp b/server.cpp index 6ee1a4a..88b2b5b 100644 --- a/server.cpp +++ b/server.cpp @@ -1,12 +1,149 @@ #include "server.h" -namespace Yp { -namespace Lib { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -Server::Server() -{ +namespace Yc { + namespace Lib { -} + Server::Server(Yc::Lib::Config *config) : + _config(config), + _stop(false) { + struct sockaddr_in serverAddr; + int opt = true; + _socket = socket(AF_INET, SOCK_STREAM, 0); + setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)); + int flags = 1; + setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags)); + serverAddr.sin_family = AF_INET; + serverAddr.sin_addr.s_addr = INADDR_ANY; + serverAddr.sin_port = htons(1235); + if (bind(_socket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) { + std::cout << "bind not possible" << std::endl; + exit(-1); + } + createRooms(config->group("rooms")); + } -} // namespace Lib + void Server::run() { + if (listen(_socket, 5) < 0) { + std::cout << "listen not possible" << std::endl; + exit(-1); + } + timeval origTv; + origTv.tv_sec = 5; + origTv.tv_usec = 0; + int _maxSd = _socket; + while (!_stop) { + timeval tv(origTv); + fd_set fd; + FD_ZERO(&fd); + FD_SET(_socket, &fd); + if (select(_maxSd + 1, &fd, NULL, NULL, &tv) > 0) { + std::async(std::launch::async, &Server::handleRequest, this); + } + } + } + + std::vector Server::roomList() { + std::vector list; + for (auto &room: _rooms) { + list.push_back(room->name()); + } + return list; + } + + Json::Value Server::jsonRoomList() { + Json::Value list; + for (auto &room: _rooms) { + list.append(room->name()); + } + return list; + } + + void Server::createRooms(Json::Value roomList) { + for (auto &room: roomList) { + std::vector allowedUsers; + for (auto &user: room["allowed"]) { + allowedUsers.push_back(user.asString()); + } + Room *newRoom = new Room(this, room["name"].asString(), room["password"].asString(), allowedUsers); + _rooms.push_back(newRoom); + } + } + + void Server::handleRequest() { + struct sockaddr_in sockAddr; + socklen_t sockAddrLen = sizeof(sockAddr); + int userSock = accept(_socket, (struct sockaddr *)&sockAddr, &sockAddrLen); + if (userSock < 0) { + return; + } + int flags = 1; + setsockopt(userSock, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags)); + std::string msg(""); + char buffer[256]; + bzero(buffer, 256); + while (int received = recv(userSock, buffer, 255, 0) > 0) { + msg += std::string(buffer); + if (received < 255) { + break; + } + } + if (msg == "") { + return; + } + inputSwitcher(userSock, msg); + } + + void Server::inputSwitcher(int userSocket, std::string input) { + Json::Value inputTree; + Json::CharReaderBuilder rbuilder; + std::unique_ptr const reader(rbuilder.newCharReader()); + JSONCPP_STRING inputJsonString(input); + reader->parse(inputJsonString.data(), inputJsonString.data() + inputJsonString.size(), &inputTree, NULL); + if (inputTree["type"] == "init") { + initUser(userSocket, inputTree); + } + } + + bool Server::userExists(std::string userName) { + for (auto &room: _rooms) { + if (room->userNameExists(userName)) { + return true; + } + } + return false; + } + + void Server::initUser(int userSocket, Json::Value data) { + if (userExists(data["name"].asString())) { + close(userSocket); + return; + } + std::string room = data["room"].asString(); + bool added(false); + for (auto &room: _rooms) { + if (room->name() == data["room"].asString()) { + if (room->addUser(data["name"].asString(), data["color"].asString(), data["password"].asString(), userSocket)) { + added = true; + break; + } + } + } + if (!added) { + close(userSocket); + } + } + } // namespace Lib } // namespace Yp diff --git a/server.h b/server.h index 3417c70..dc24d27 100644 --- a/server.h +++ b/server.h @@ -1,17 +1,34 @@ #ifndef YP_LIB_SERVER_H #define YP_LIB_SERVER_H +#include "config.h" +#include +#include "room.h" +#include -namespace Yp { -namespace Lib { +namespace Yc { + namespace Lib { -class Server -{ -public: - Server(); -}; + class Server + { + public: + Server(Yc::Lib::Config *config); + void run(); + std::vector roomList(); + Json::Value jsonRoomList(); + private: + int _socket; + Yc::Lib::Config *_config; + bool _stop; + std::vector _rooms; + void createRooms(Json::Value roomList); + void handleRequest(); + void inputSwitcher(int userSocket, std::string input); + bool userExists(std::string userName); + void initUser(int userSocket, Json::Value data); + }; -} // namespace Lib + } // namespace Lib } // namespace Yp -#endif // YP_LIB_SERVER_H \ No newline at end of file +#endif // YP_LIB_SERVER_H diff --git a/tools.cpp b/tools.cpp index 30c619d..ec2d5da 100644 --- a/tools.cpp +++ b/tools.cpp @@ -1,12 +1,33 @@ #include "tools.h" -namespace Yp { -namespace Lib { +#include +#include -Tools::Tools() -{ +namespace Yc { + namespace Lib { -} + Tools::Tools() { -} // namespace Lib + } + + std::string Tools::generateRandomString(size_t length) { + std::string choices( + "0123456789" + "`~!@#$%^&*()-_=+[{]}|\\:;<,>./?" + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + ); + + std::random_device random; + std::mt19937 generator(random()); + std::uniform_int_distribution distribution(0, choices.size()); + + std::string result(length, '0'); + for (size_t i = 0; i < length; ++i) { + result[i] = choices[distribution(generator)]; + } + + return result; + } + + } // namespace Lib } // namespace Yp diff --git a/tools.h b/tools.h index 99e3b92..45de509 100644 --- a/tools.h +++ b/tools.h @@ -1,17 +1,19 @@ #ifndef YP_LIB_TOOLS_H #define YP_LIB_TOOLS_H +#include -namespace Yp { -namespace Lib { +namespace Yc { + namespace Lib { -class Tools -{ -public: - Tools(); -}; + class Tools + { + public: + Tools(); + static std::string generateRandomString(size_t length = 16); + }; -} // namespace Lib + } // namespace Lib } // namespace Yp -#endif // YP_LIB_TOOLS_H \ No newline at end of file +#endif // YP_LIB_TOOLS_H diff --git a/user.cpp b/user.cpp index 4516463..6e7603f 100644 --- a/user.cpp +++ b/user.cpp @@ -1,12 +1,91 @@ #include "user.h" +#include +#include +#include +#include +#include +#include +#include "room.h" +#include +#include +#include +#include + namespace Yc { -namespace Lib { + namespace Lib { -User::User() -{ + User::User(Room *parent, std::string name, std::string color, int socket) : + _parent(parent), + _name(name), + _color(color), + _socket(socket), + _stop(false) { + _token = Yc::Lib::Tools::generateRandomString(32); + sendMsg(token, _token, "", ""); + std::async(std::bind(&User::checkerTask, this)); + } -} + std::string User::name() const { + return _name; + } -} // namespace Lib + bool User::validateToken(std::string token) { + return (token == _token); + } + + bool User::isUser(User *toValidate) { + return (toValidate == this); + } + + void User::sendMsg(MsgType type, std::string message , std::string userName, std::string color) { + Json::Value sendMessage; + sendMessage["type"] = type; + sendMessage["message"] = message; + sendMessage["userName"] = userName; + sendMessage["color"] = color; + std::string outString; + std::stringstream outStream; + outStream << sendMessage; + outString = outStream.str(); + write(_socket, outString.c_str(), outString.length()); + } + + void User::sendMsg(User::MsgType type, Json::Value message, std::string userName, std::string color) { + Json::Value sendMessage; + sendMessage["type"] = type; + sendMessage["message"] = message; + sendMessage["userName"] = userName; + sendMessage["color"] = color; + std::string outString; + std::stringstream outStream; + outStream << sendMessage; + outString = outStream.str(); + write(_socket, outString.c_str(), outString.length()); + } + + void User::checkerTask() { + while (!_stop) { + fd_set readSd; + FD_ZERO(&readSd); + FD_SET(_socket, &readSd); + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100; + if (select(_socket + 1, &readSd, NULL, NULL, &tv) < 0 && errno != ETIMEDOUT) { + _parent->removeUser(_token); + _stop = true; + } + } + } + + void User::stop() { + _stop = true; + } + + std::string User::color() const { + return _color; + } + + } // namespace Lib } // namespace Yc diff --git a/user.h b/user.h index 4c1c68d..c6b5019 100644 --- a/user.h +++ b/user.h @@ -1,17 +1,43 @@ #ifndef YC_LIB_USER_H #define YC_LIB_USER_H +#include +#include namespace Yc { -namespace Lib { + namespace Lib { -class User -{ -public: - User(); -}; + class Room; -} // namespace Lib + class User { + public: + enum MsgType { + token = 1, + userListe = 2, + roomList = 3, + message = 4, + system = 5 + }; + + User(Room *parent, std::string name, std::string color, int socket); + std::string name() const; + bool validateToken(std::string token); + bool isUser(User *toValidate); + void sendMsg(MsgType type, std::string message, std::string userName, std::string color); + void sendMsg(MsgType type, Json::Value message, std::string userName, std::string color); + void checkerTask(); + void stop(); + std::string color() const; + private: + Room *_parent; + std::string _name; + std::string _color; + int _socket; + std::string _token; + bool _stop; + }; + + } // namespace Lib } // namespace Yc -#endif // YC_LIB_USER_H \ No newline at end of file +#endif // YC_LIB_USER_H