adding many new files, mostly from old projects, register handling with validation, loading and connect to mysql

This commit is contained in:
Dario 2019-09-26 11:26:39 +02:00
parent e7ca744da0
commit f7744ee2c1
26 changed files with 760 additions and 501 deletions

View File

@ -8,19 +8,28 @@ include_directories(
"dependencies"
"dependencies/tinf/src/"
"dependencies/iroha-ed25519/include"
#"dependencies/mariadb-connector-c/build/include"
#"dependencies/mariadb-connector-c/include"
"import/mariadb/include"
)
FILE(GLOB TINF "dependencies/tinf/src/*.c" "dependencies/tinf/src/*.h")
FILE(GLOB HTTPInterface "src/cpp/HTTPInterface/*.h" "src/cpp/HTTPInterface/*.cpp")
FILE(GLOB SINGLETON_MANAGER "src/cpp/SingletonManager/*.h" "src/cpp/SingletonManager/*.cpp")
FILE(GLOB MODEL "src/cpp/model/*.h" "src/cpp/model/*.cpp")
FILE(GLOB CRYPTO "src/cpp/Crypto/*.h" "src/cpp/Crypto/*.cpp")
FILE(GLOB MAIN "src/cpp/*.cpp" "src/cpp/*.c" "src/cpp/*.h")
SET(LOCAL_SRCS ${TINF} ${MAIN} ${HTTPInterface} ${CRYPTO})
FILE(GLOB MYSQL "src/cpp/MySQL/*.cpp" "src/cpp/MySQL/Poco/*.h")
SET(LOCAL_SRCS ${TINF} ${MAIN} ${HTTPInterface} ${CRYPTO} ${MODEL} ${SINGLETON_MANAGER} ${MYSQL})
aux_source_directory("src/cpp" LOCAL_SRCS)
if(MSVC)
# src
source_group("tinf" FILES ${TINF})
source_group("crypto" FILES ${CRYPTO})
source_group("model" FILES ${MODEL})
source_group("mysql" FILES ${MYSQL})
source_group("SingletonManager" FILES ${SINGLETON_MANAGER})
source_group("HTTP-Interface" FILES ${HTTPInterface})
endif(MSVC)
@ -28,16 +37,28 @@ endif(MSVC)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
#add_subdirectory("dependencies/curl")
#add_subdirectory("dependencies/mariadb-connector-c")
add_executable(Gradido_LoginServer ${LOCAL_SRCS})
if(WIN32)
find_library(MYSQL_LIBRARIES mariadbclient.lib PATHS "import/mariadb/lib/release")
#find_library(MYSQL_LIBRARIES_DEBUG mariadbclient.lib PATHS "import/mariadb/lib/debug")
find_library(MARIADB_CLIENT_DEBUG mariadbclient PATHS "dependencies/mariadb-connector-c/build/libmariadb/Debug" REQUIRED)
find_library(IROHA_ED25519 ed25519 PATHS "dependencies/iroha-ed25519/build/Debug" REQUIRED)
set(MYSQL_INCLUDE_DIR "import/mariadb/include")
else(WIN32)
endif(WIN32)
target_link_libraries(Gradido_LoginServer ${CONAN_LIBS} ${IROHA_ED25519})
target_link_libraries(Gradido_LoginServer ${CONAN_LIBS} ${IROHA_ED25519} ${MARIADB_CLIENT})
if(WIN32)
TARGET_LINK_LIBRARIES(Gradido_LoginServer optimized ${MYSQL_LIBRARIES} Shlwapi)
TARGET_LINK_LIBRARIES(Gradido_LoginServer debug ${MARIADB_CLIENT_DEBUG} Shlwapi)
else(WIN32)
endif(WIN32)

View File

@ -6,3 +6,5 @@ protobuf/3.9.1@bincrafters/stable
[generators]
cmake

View File

@ -0,0 +1,91 @@
#include "KeyPair.h"
#include <memory.h>
#include <string.h>
#define STR_BUFFER_SIZE 25
KeyPair::KeyPair()
: mPrivateKey(nullptr), mSodiumSecret(nullptr)
{
}
KeyPair::~KeyPair()
{
if (mPrivateKey) {
delete mPrivateKey;
}
if (mSodiumSecret) {
delete mSodiumSecret;
}
}
bool KeyPair::generateFromPassphrase(const char* passphrase, Mnemonic* word_source)
{
// libsodium doc: https://libsodium.gitbook.io/doc/advanced/hmac-sha2
// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
//crypto_auth_hmacsha512_keygen
unsigned long word_indices[PHRASE_WORD_COUNT];
//DHASH key = DRMakeStringHash(passphrase);
size_t pass_phrase_size = strlen(passphrase);
char acBuffer[STR_BUFFER_SIZE]; memset(acBuffer, 0, STR_BUFFER_SIZE);
size_t buffer_cursor = 0;
// get word indices for hmac key
unsigned char word_cursor = 0;
for (size_t i = 0; i < pass_phrase_size; i++) {
if (passphrase[i] == ' ') {
word_indices[word_cursor] = word_source->getWordIndex(acBuffer);
word_cursor++;
memset(acBuffer, 0, STR_BUFFER_SIZE);
buffer_cursor = 0;
}
else {
acBuffer[buffer_cursor++] = passphrase[i];
}
}
sha_context state;
unsigned char hash[SHA_512_SIZE];
//crypto_auth_hmacsha512_state state;
size_t word_index_size = sizeof(word_indices);
//crypto_auth_hmacsha512_init(&state, (unsigned char*)word_indices, sizeof(word_indices));
sha512_init(&state);
sha512_update(&state, (unsigned char*)word_indices, sizeof(word_indices));
sha512_update(&state, (unsigned char*)passphrase, pass_phrase_size);
//crypto_auth_hmacsha512_update(&state, (unsigned char*)passphrase, pass_phrase_size);
sha512_final(&state, hash);
//crypto_auth_hmacsha512_final(&state, hash);
//ed25519_create_keypair(public_key, private_key, hash);
private_key_t prv_key_t;
memcpy(prv_key_t.data, hash, 32);
public_key_t pbl_key_t;
ed25519_derive_public_key(&prv_key_t, &pbl_key_t);
//memcpy(private_key, prv_key_t.data, 32);
if (mPrivateKey) {
delete mPrivateKey;
}
mPrivateKey = new ObfusArray(ed25519_privkey_SIZE, prv_key_t.data);
memcpy(mPublicKey, pbl_key_t.data, ed25519_pubkey_SIZE);
unsigned char sodium_secret[crypto_sign_SECRETKEYBYTES];
crypto_sign_seed_keypair(mSodiumPublic, sodium_secret, *mPrivateKey);
if(mSodiumSecret) {
delete mSodiumSecret;
}
mSodiumSecret = new ObfusArray(crypto_sign_SECRETKEYBYTES, sodium_secret);
// using
return true;
}

View File

@ -0,0 +1,27 @@
#ifndef GRADIDO_LOGIN_SERVER_CRYPTO_KEY_PAIR
#define GRADIDO_LOGIN_SERVER_CRYPTO_KEY_PAIR
#include "Obfus_array.h"
#include "mnemonic.h"
#include "ed25519/ed25519.h"
#include <sodium.h>
class KeyPair
{
public:
KeyPair();
~KeyPair();
bool generateFromPassphrase(const char* passphrase, Mnemonic* word_source);
protected:
private:
ObfusArray* mPrivateKey;
ObfusArray* mSodiumSecret;
unsigned char mPublicKey[ed25519_pubkey_SIZE];
unsigned char mSodiumPublic[crypto_sign_PUBLICKEYBYTES];
};
#endif //GRADIDO_LOGIN_SERVER_CRYPTO_KEY_PAIR

View File

@ -0,0 +1,28 @@
#include "Obfus_array.h"
#include <sodium.h>
#include <memory.h>
#include <math.h>
ObfusArray::ObfusArray(size_t size, const unsigned char * data)
: m_arraySize(0), m_offsetSize(0), m_dataSize(size), m_Data(nullptr)
{
m_arraySize = randombytes_random() % (int)roundf(size + size*0.25f);
m_Data = (unsigned char*)malloc(m_arraySize);
m_offsetSize = randombytes_random() % (int)roundf((m_arraySize - m_dataSize) * 0.8f);
for (size_t i = 0; i < (size_t)floorf(m_arraySize / 4.0f); i++) {
uint32_t* d = (uint32_t*)m_Data[i];
*d = randombytes_random();
}
uint32_t* d = (uint32_t*)(m_Data + (m_arraySize - 4));
*d = randombytes_random();
memcpy(&m_Data[m_offsetSize], data, size);
}
ObfusArray::~ObfusArray()
{
if (m_Data) {
free(m_Data);
}
}

View File

@ -0,0 +1,26 @@
#ifndef GRADIDO_LOGIN_SERVER_CRYPTO_OBFUS_ARRAY
#define GRADIDO_LOGIN_SERVER_CRYPTO_OBFUS_ARRAY
class ObfusArray
{
public:
ObfusArray(size_t size, const unsigned char * data);
~ObfusArray();
operator const unsigned char*() {
return &m_Data[m_offsetSize];
}
size_t size() {
return m_dataSize;
}
private:
size_t m_arraySize;
size_t m_offsetSize;
size_t m_dataSize;
unsigned char* m_Data;
};
#endif //GRADIDO_LOGIN_SERVER_CRYPTO_OBFUS_ARRAY

View File

@ -13,6 +13,7 @@
#include "DRHashList.h"
#define PHRASE_WORD_COUNT 24
class Mnemonic

View File

@ -1,9 +1,15 @@
#include "Gradido_LoginServer.h"
#include "ServerConfig.h"
#include "HTTPInterface/PageRequestHandlerFactory.h"
#include "SingletonManager/ConnectionManager.h"
#include "SingletonManager/SessionManager.h"
#include "Poco/Util/HelpFormatter.h"
#include "Poco/Net/ServerSocket.h"
#include "Poco/Net/HTTPServer.h"
#include "MySQL/Poco/Connector.h"
#include <sodium.h>
@ -65,6 +71,22 @@ int Gradido_LoginServer::main(const std::vector<std::string>& args)
{
unsigned short port = (unsigned short)config().getInt("HTTPServer.port", 9980);
// load word lists
ServerConfig::loadMnemonicWordLists();
// load up connection configs
// register MySQL connector
Poco::Data::MySQL::Connector::registerConnector();
//Poco::Data::MySQL::Connector::KEY;
auto conn = ConnectionManager::getInstance();
//conn->setConnection()
conn->setConnectionsFromConfig(config(), CONNECTION_MYSQL_LOGIN_SERVER);
conn->setConnectionsFromConfig(config(), CONNECTION_MYSQL_PHP_SERVER);
SessionManager::getInstance()->init();
// put urandom on linux servers
//srand();
// set-up a server socket
Poco::Net::ServerSocket svs(port);
// set-up a HTTPServer instance

View File

@ -27,7 +27,14 @@ void HandleFileRequest::handleRequest(Poco::Net::HTTPServerRequest& request, Poc
}
std::string path = "data" + uri;
printf("file path: %s\n", path.data());
response.sendFile(path, mediaType);
try {
response.sendFile(path, mediaType);
}
catch (...) {
std::ostream& _responseStream = response.send();
_responseStream << "Error, file not found";
}
/*
bool _compressResponse(request.hasToken("Accept-Encoding", "gzip"));
if (_compressResponse) response.set("Content-Encoding", "gzip");

View File

@ -1 +1,31 @@
#include "ServerConfig.h"
#include "ServerConfig.h"
#include "Crypto/mnemonic_german.h"
#include "Crypto/mnemonic_bip0039.h"
namespace ServerConfig {
Mnemonic g_Mnemonic_WordLists[MNEMONIC_MAX];
void loadMnemonicWordLists()
{
for (int i = 0; i < MNEMONIC_MAX; i++) {
int iResult = 0;
switch (i) {
case MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER:
iResult = g_Mnemonic_WordLists[i].init(populate_mnemonic_german, g_mnemonic_german_original_size, g_mnemonic_german_compressed_size);
if (iResult) {
printf("[%s] error init german mnemonic set, error nr: %d\n", __FUNCTION__, iResult);
//return -1;
}
break;
case MNEMONIC_BIP0039_SORTED_ORDER:
iResult = g_Mnemonic_WordLists[i].init(populate_mnemonic_bip0039, g_mnemonic_bip0039_original_size, g_mnemonic_bip0039_compressed_size);
if (iResult) {
printf("[%s] error init bip0039 mnemonic set, error nr: %d\n", __FUNCTION__, iResult);
}
break;
default: printf("[%s] unknown MnemonicType\n", __FUNCTION__);
}
}
}
}

View File

@ -0,0 +1,14 @@
#include "Crypto/mnemonic.h"
namespace ServerConfig {
enum Mnemonic_Types {
MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER,
MNEMONIC_BIP0039_SORTED_ORDER,
MNEMONIC_MAX
};
extern Mnemonic g_Mnemonic_WordLists[MNEMONIC_MAX];
void loadMnemonicWordLists();
}

View File

@ -1,405 +1,65 @@
#include "ConnectionManager.h"
#include "../Connections/MysqlConnection.h"
#include <sstream>
ConnectThread::ConnectThread(ConnectionManager* parent)
: Thread(true), initMysqlThread(false), mParent(parent)
{
}
ConnectThread::~ConnectThread()
{
}
int ConnectThread::ThreadFunction()
{
if (!initMysqlThread) {
if(mysql_thread_init()) {
printf("error by calling mysql thread init\n");
}
initMysqlThread = true;
}
mDataMutex.lock();
// clean up not longer used connections
while (mConnectionWaitingOnDestroy.size() > 0) {
auto con = mConnectionWaitingOnDestroy.front();
mConnectionWaitingOnDestroy.pop();
mDataMutex.unlock();
delete con;
mDataMutex.lock();
}
// creating new connections
while(mConnectionsWaitingOnConnectCall.size() > 0) {
auto con = mConnectionsWaitingOnConnectCall.front();
mConnectionsWaitingOnConnectCall.pop();
mDataMutex.unlock();
if(!con->connect()) {
//printf("[ConnectThread::ThreadFunction] error connecting with type: %s\n",
//Connection::getConnectionTypeName(con->getType()));
};
mParent->markAsAvailable(con);
mParent->condSignal();
mDataMutex.lock();
}
mDataMutex.unlock();
return 0;
}
void ConnectThread::cleanUpInThread()
{
if (initMysqlThread) {
mysql_thread_end();
initMysqlThread = false;
}
}
void ConnectThread::addConnectionConnect(Connection* connection)
{
mDataMutex.lock();
mConnectionsWaitingOnConnectCall.push(connection);
mDataMutex.unlock();
}
void ConnectThread::addConnectionDestroy(Connection* connection)
{
mDataMutex.lock();
mConnectionWaitingOnDestroy.push(connection);
mDataMutex.unlock();
}
// -----------------------------------------------------------------
// ConnectionManager
// -----------------------------------------------------------------
ConnectionManager* ConnectionManager::getInstance()
{
static ConnectionManager only;
return &only;
}
ConnectionManager::ConnectionManager()
: mConnectionEstablishThread(this), mInitalized(true)
{
}
ConnectionManager::~ConnectionManager()
{
if (mInitalized) {
deinitalize();
}
}
void ConnectionManager::deinitalize()
bool ConnectionManager::setConnectionsFromConfig(const Poco::Util::LayeredConfiguration& config, ConnectionType type)
{
lock();
mInitalized = false;
unlock();
mConnectionEstablishThread.exitThread();
for (auto it = mConnections.begin(); it != mConnections.end(); it++)
{
auto freeConnections = it->second->mFreeConnections;
while (freeConnections.size() > 0) {
delete freeConnections.top();
freeConnections.pop();
}
auto cfg_ptr = it->second->cfg_ptr;
cfg_ptr->removeRef();
if (cfg_ptr->getRef() <= 0) {
delete cfg_ptr;
}
}
mConnections.clear();
mInitalized = false;
}
bool ConnectionManager::addConnectionPool(DRSimpleResourcePtr<Config>* cfg_ptr)
{
if (!mInitalized) {
printf("[ConnectionManager::%s] not initialized any more\n", __FUNCTION__);
return false;
}
if ((*cfg_ptr)->name == std::string("")) {
// getConnectionTypeName
ConnectionConfig* cfg = static_cast<ConnectionConfig*>((Config*)(*cfg_ptr));
(*cfg_ptr)->name = "Connect_";
(*cfg_ptr)->name += Connection::getConnectionTypeName(cfg->type);
if (cfg->type == CONN_MYSQL) {
DBConnectionConfig* db_cfg = static_cast<DBConnectionConfig*>((Config*)(*cfg_ptr));
(*cfg_ptr)->name += "_";
(*cfg_ptr)->name += db_cfg->db;
}
}
DHASH id = (*cfg_ptr)->getHash();
// check if collision
lock();
if (!mInitalized) {
printf("[ConnectionManager::%s] not initialized any more\n", __FUNCTION__);
return false;
}
auto it = mConnections.find(id);
if (it != mConnections.end()) {
unlock();
if ((*it->second->cfg_ptr)->name == (*cfg_ptr)->name) {
printf("connection %s already in there: %s\n", (*cfg_ptr)->name.data(), __FUNCTION__);
return false;
}
else {
printf("Hash Collision detected with %s and %s in %s\n",
(*cfg_ptr)->name.data(), (*it->second->cfg_ptr)->name.data(), __FUNCTION__);
return false;
}
}
unlock();
// try to create connection object
auto con = createConnection(cfg_ptr);
if (!con) {
printf("couldn't create connection with cfg: %s\n", (*cfg_ptr)->name.data());
return false;
}
// create pool
auto pool = new ConnectionPool;
//pool->cfg = cfg;
pool->cfg_ptr = cfg_ptr;
pool->cfg_ptr->addRef();
printf("create new connection pool for connection: %s\n", (*cfg_ptr)->name.data());
lock();
if (!mInitalized) {
printf("[ConnectionManager::%s] not initialized any more\n", __FUNCTION__);
return false;
}
mConnections.insert(std::pair<int, ConnectionPool*>(id, pool));
unlock();
// start connection
if(con) {
mConnectionEstablishThread.addConnectionConnect(con);
mConnectionEstablishThread.condSignal();
}
return true;
}
bool ConnectionManager::markAsAvailable(Connection* con)
{
if (!mInitalized) {
addError(new Error(__FUNCTION__, "not initialized any more"));
return false;
}
DHASH id = con->getHash();
lock();
if (!mInitalized) {
addError(new Error(__FUNCTION__, "not initialized any more"));
return false;
}
auto it = mConnections.find(id);
if (it == mConnections.end()) {
addError(new ParamError(__FUNCTION__, "couldn't find connection pool for id:", id));
delete con;
unlock();
return false;
}
else if (!con->isOpen()) {
if (con->errorCount() > 0) {
it->second->mErrorConnectingAttempts++;
addError(new ParamError(__FUNCTION__, "connection wasn't open and has errors, failed connecting attempt: ", it->second->mErrorConnectingAttempts));
getErrors(con);
unlock();
return false;
}
}
it->second->mFreeConnections.push(con);
checkTime(it->second);
unlock();
return true;
}
void ConnectionManager::checkTime(ConnectionPool* pool)
{
if (!mInitalized) {
addError(new Error(__FUNCTION__, "not initialized any more"));
return;
}
auto currentSize = pool->mFreeConnections.size();
// (re)start timer if at least three unused connections reached
if (pool->mFreeConnectionsCount < 3 && currentSize >= 3) {
pool->mConnectionFreeTimeout.reset();
}
if (currentSize >= 3 && pool->mConnectionFreeTimeout.seconds() > UNUSED_CONNECTION_TIMEOUT_SECONDS) {
// clean one connection and reset timer
auto con = pool->mFreeConnections.top();
pool->mFreeConnections.pop();
currentSize = pool->mFreeConnections.size();
mConnectionEstablishThread.addConnectionDestroy(con);
mConnectionEstablishThread.condSignal();
pool->mConnectionFreeTimeout.reset();
}
pool->mFreeConnectionsCount = currentSize;
}
Connection* ConnectionManager::createConnection(DRSimpleResourcePtr<Config>* cfg_ptr)
{
auto con_cfg = static_cast<ConnectionConfig*>((Config*)(*cfg_ptr));
switch (con_cfg->type) {
case CONN_DEFAULT: return new Connection(cfg_ptr);
case CONN_MYSQL: return new MysqlConnection(cfg_ptr);
}
return nullptr;
}
bool ConnectionManager::isConnectionPool(DHASH id)
{
if (!mInitalized) {
addError(new Error(__FUNCTION__, "not initialized any more"));
return false;
}
lock();
if (!mInitalized) {
addError(new Error(__FUNCTION__, "not initialized any more"));
return false;
}
auto it = mConnections.find(id);
if (it == mConnections.end()) {
unlock();
return false;
}
unlock();
return true;
}
Connection* ConnectionManager::getConnection(DHASH id)
{
if (!mInitalized) {
addError(new Error(__FUNCTION__, "not initialized any more"));
return nullptr;
}
lock();
if (!mInitalized) {
unlock();
addError(new Error(__FUNCTION__, "not initialized any more"));
return nullptr;
}
auto it = mConnections.find(id);
if (it == mConnections.end()) {
addError(new ParamError(__FUNCTION__, "couldn't find connection pool for id:", id));
unlock();
return nullptr;
}
Connection* result = nullptr;
auto pool = it->second;
if (pool->mFreeConnections.size() > 0) {
result = pool->mFreeConnections.top();
pool->mFreeConnections.pop();
checkTime(pool);
}
unlock();
// if pool is empty, create new connection
if (pool->mFreeConnections.size() == 0) {
auto con = createConnection(pool->cfg_ptr);
if (!con) {
addError(new ParamError(__FUNCTION__, "couldn't create connection with cfg:", (*pool->cfg_ptr)->name));
return nullptr;
}
mConnectionEstablishThread.addConnectionConnect(con);
mConnectionEstablishThread.condSignal();
}
// we have get a connection, return
if (result) {
return result;
}
// we haven't get a connection, so we wait
std::unique_lock<std::mutex> lk(mConnectCondMutex);
mConnectCond.wait(lk, [] {return 1; });
lock();
if (!mInitalized) {
unlock();
addError(new Error(__FUNCTION__, "not initialized any more"));
return nullptr;
}
if (pool->mFreeConnections.size() == 0) {
addError(new Error(__FUNCTION__, "no free connection after wait :/"));
unlock();
return nullptr;
}
result = pool->mFreeConnections.top();
pool->mFreeConnections.pop();
checkTime(pool);
unlock();
return result;
}
// int ConnectionManager::connectMysql(
// const char* db,
// const char* username /* = "root"*/,
// const char* pwd /* = nullptr*/,
// const char* url /* = "127.0.0.1" */,
// int port /*= 3306*/ )
// {
//mysqlx://mike:s3cr3t!@localhost:13009
/*std::stringstream urlStream;
urlStream << username;
if (pwd) {
urlStream << ":" << pwd;
}
urlStream << "@" << url;
urlStream << ":" << port;
*/
/*
mysqlx::Session* mysqlSession = nullptr;
try {
mysqlSession = new mysqlx::Session(url, port, username, pwd, db);
}
catch (const char* e) {
printf("[ConnectionManager::connectMysql] catch e: %s\n", e);
return -1;
}
phpServer.url = 127.0.0.1:80/gradido_php
phpServer.db.host = localhost
phpServer.db.name = cake_gradido_node
phpServer.db.user = root
phpServer.db.password =
phpServer.db.port = 3306
mWorkingMutex.lock();
loginServer.url =
loginServer.db.host = localhost
loginServer.db.name = gradido_login
loginServer.db.user = gradido_login
loginServer.db.password = hj2-sk28sKsj8(u_ske
loginServer.db.port = 3306
*/
int handle = mMysqlConnections.size();
mMysqlConnections.insert(std::pair<int, mysqlx::Session*>(handle, mysqlSession));
return handle;
return 0;
}
/*
mysqlx::Session* ConnectionManager::getMysqlConnection(int handle)
{
mysqlx::Session* session = nullptr;
mWorkingMutex.lock();
auto it = mMysqlConnections.find(handle);
if (it != mMysqlConnections.end()) {
session = it->second;
/*
connectionString example: host=localhost;port=3306;db=mydb;user=alice;password=s3cr3t;compress=true;auto-reconnect=true
*/
std::string firstKeyPart;
switch (type) {
case CONNECTION_MYSQL_LOGIN_SERVER: firstKeyPart = "loginServer"; break;
case CONNECTION_MYSQL_PHP_SERVER: firstKeyPart = "phpServer"; break;
default: addError(new Error(__FUNCTION__, "type invalid")); return false;
}
mWorkingMutex.unlock();
return session;
}
*/
std::stringstream dbConfig;
dbConfig << "host=" << config.getString(firstKeyPart + ".db.host", "localhost") << ";";
dbConfig << "port=" << config.getInt(firstKeyPart + ".db.port", 3306) << ";";
std::string dbName = config.getString(firstKeyPart + ".db.name", "");
if (dbName == "") {
addError(new Error(__FUNCTION__, "no db name given"));
return false;
}
dbConfig << "db=" << dbName << ";";
dbConfig << "user=" << config.getString(firstKeyPart + ".db.user", "root") << ";";
dbConfig << "password=" << config.getString(firstKeyPart + ".db.password", "") << ";";
dbConfig << "auto-reconnect=true";
setConnection(dbConfig.str(), type);
return true;
}

View File

@ -1,104 +1,57 @@
/*!
*
* \author: einhornimmond
*
* \date: 28.02.19
*
* \brief: manage Connections like mysql or socket connections to another server
*/
#ifndef GRADIDO_LOGIN_SERVER_SINGLETON_MANAGER_CONNECTION_MANAGER_INCLUDE
#define GRADIDO_LOGIN_SERVER_SINGLETON_MANAGER_CONNECTION_MANAGER_INCLUDE
#ifndef DR_LUA_WEB_MODULE_CONNECTION_MANAGER_H
#define DR_LUA_WEB_MODULE_CONNECTION_MANAGER_H
#include "../Crypto/DRHashList.h"
#include <string>
#include "../LuaWebModule.h"
#include "../Connections/Connection.h"
#include "../CoreLib/Profiler.h"
#include "../CoreLib/Thread.h"
#include "../CoreLib/DRResourcePtr.h"
#include <queue>
#include <map>
#include "Poco/Util/LayeredConfiguration.h"
#include "Poco/Data/SessionPoolContainer.h"
#include "Poco/Data/MySQL/Connector.h"
#define UNUSED_CONNECTION_TIMEOUT_SECONDS 2
#include "../Model/ErrorList.h"
class ConnectionManager;
class ConnectThread : public Thread
{
public:
ConnectThread(ConnectionManager* parent);
virtual ~ConnectThread();
void addConnectionConnect(Connection* connection);
void addConnectionDestroy(Connection* connection);
protected:
std::mutex mDataMutex;
std::queue<Connection*> mConnectionsWaitingOnConnectCall;
std::queue<Connection*> mConnectionWaitingOnDestroy;
virtual int ThreadFunction();
virtual void cleanUpInThread();
bool initMysqlThread;
ConnectionManager* mParent;
enum ConnectionType {
CONNECTION_MYSQL_LOGIN_SERVER,
CONNECTION_MYSQL_PHP_SERVER,
CONNECTION_MAX
};
class ConnectionManager : public ErrorList
{
friend ConnectThread;
public:
public:
~ConnectionManager();
static ConnectionManager* getInstance();
bool addConnectionPool(DRSimpleResourcePtr<Config>* cfg);
bool markAsAvailable(Connection* con);
bool setConnectionsFromConfig(const Poco::Util::LayeredConfiguration& config, ConnectionType type);
bool isConnectionPool(DHASH id);
static Connection* createConnection(DRSimpleResourcePtr<Config>* cfg);
inline Connection* getConnection(const char* name) { return getConnection(DRMakeStringHash(name)); }
//! \param connectionString example: host=localhost;port=3306;db=mydb;user=alice;password=s3cr3t;compress=true;auto-reconnect=true
inline void setConnection(std::string connectionString, ConnectionType type) {
if (type == CONNECTION_MYSQL_LOGIN_SERVER || CONNECTION_MYSQL_PHP_SERVER) {
mSessionPoolNames[type] = Poco::Data::Session::uri(Poco::Data::MySQL::Connector::KEY, connectionString);
mSessionPools.add(Poco::Data::MySQL::Connector::KEY, connectionString);
//mConnectionData[type] = connectionString;
}
}
Connection* getConnection(DHASH id);
inline void lock() { mWorkingMutex.lock(); }
inline void unlock() { mWorkingMutex.unlock(); }
void deinitalize();
inline Poco::Data::Session getConnection(ConnectionType type) {
switch (type)
{
case CONNECTION_MYSQL_LOGIN_SERVER:
break;
case CONNECTION_MYSQL_PHP_SERVER:
break;
default:
break;
}
}
protected:
ConnectionManager();
struct ConnectionPool {
ConnectionPool(): mFreeConnectionsCount(0), mErrorConnectingAttempts(0){}
DRSimpleResourcePtr<Config>* cfg_ptr;
std::stack<Connection*> mFreeConnections;
// used to measure how long at least two connections not used
Profiler mConnectionFreeTimeout;
// only for calculating connection timeout
int mFreeConnectionsCount;
int mErrorConnectingAttempts;
};
void checkTime(ConnectionPool* pool);
// connection Pool
std::map<int, ConnectionPool*> mConnections;
// access mutex
std::mutex mWorkingMutex;
inline void condSignal() { mConnectCond.notify_one(); }
// creating and destroying connections thread
ConnectThread mConnectionEstablishThread;
std::condition_variable mConnectCond;
std::mutex mConnectCondMutex;
bool mInitalized;
private:
std::string mSessionPoolNames[CONNECTION_MAX];
Poco::Data::SessionPoolContainer mSessionPools;
};
#endif //DR_LUA_WEB_MODULE_CONNECTION_MANAGER_H
#endif //GRADIDO_LOGIN_SERVER_SINGLETON_MANAGER_CONNECTION_MANAGER_INCLUDE

View File

@ -15,8 +15,8 @@
#include <list>
#include <map>
#include <cstring>
#include "../Error/Error.h"
#include "../CoreLib/DRHash.hpp"
#include "../Model/Error.h"
#include "../Crypto/DRHash.h"
class ErrorManager : public IErrorCollection
{

View File

@ -1,5 +1,8 @@
#include "SessionManager.h"
#include <sodium.h>
SessionManager* SessionManager::getInstance()
{
static SessionManager only;
@ -7,7 +10,7 @@ SessionManager* SessionManager::getInstance()
}
SessionManager::SessionManager()
: mInitalized(true)
: mInitalized(false)
{
}
@ -19,6 +22,26 @@ SessionManager::~SessionManager()
}
}
bool SessionManager::init()
{
mWorkingMutex.lock();
for (int i = 0; i < VALIDATE_MAX; i++) {
switch (i) {
//case VALIDATE_NAME: mValidations[i] = new Poco::RegularExpression("/^[a-zA-Z_ -]{3,}$/"); break;
case VALIDATE_NAME: mValidations[i] = new Poco::RegularExpression("^[a-zA-Z]{3,}$"); break;
case VALIDATE_EMAIL: mValidations[i] = new Poco::RegularExpression("^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$"); break;
case VALIDATE_PASSWORD: mValidations[i] = new Poco::RegularExpression("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[@$!%*?&+-])[A-Za-z0-9@$!%*?&+-]{8,}$"); break;
case VALIDATE_PASSPHRASE: mValidations[i] = new Poco::RegularExpression("^(?:[a-z]* ){23}[a-z]*\s*$"); break;
default: printf("[SessionManager::%s] unknown validation type\n", __FUNCTION__);
}
}
mInitalized = true;
mWorkingMutex.unlock();
return true;
}
void SessionManager::deinitalize()
{
@ -32,13 +55,26 @@ void SessionManager::deinitalize()
delete it->second;
}
mRequestSessionMap.clear();
for (int i = 0; i < VALIDATE_MAX; i++) {
delete mValidations[i];
}
mInitalized = false;
mWorkingMutex.unlock();
}
bool SessionManager::isValid(const std::string& subject, SessionValidationTypes validationType)
{
if (validationType >= VALIDATE_MAX) {
return false;
}
return *mValidations[validationType] == subject;
}
MysqlSession* SessionManager::getNewMysqlSession(int* handle)
Session* SessionManager::getNewSession(int* handle)
{
if (!mInitalized) {
printf("[SessionManager::%s] not initialized any more\n", __FUNCTION__);
@ -53,7 +89,7 @@ MysqlSession* SessionManager::getNewMysqlSession(int* handle)
mEmptyRequestStack.pop();
auto resultIt = mRequestSessionMap.find(local_handle);
if (resultIt != mRequestSessionMap.end()) {
MysqlSession* result = resultIt->second;
Session* result = resultIt->second;
mWorkingMutex.unlock();
if (handle) {
@ -64,9 +100,23 @@ MysqlSession* SessionManager::getNewMysqlSession(int* handle)
}
else {
// else create new RequestSession Object
int newHandle = mRequestSessionMap.size();
auto requestSession = new MysqlSession(newHandle);
mRequestSessionMap.insert(std::pair<int, MysqlSession*>(newHandle, requestSession));
// calculate random handle
// check if already exist, if get new
int newHandle = 0;
int maxTrys = 0;
do {
newHandle = randombytes_random();
maxTrys++;
} while (mRequestSessionMap.find(newHandle) != mRequestSessionMap.end() && maxTrys < 100);
if (maxTrys >= 100 || 0 == newHandle) {
printf("[SessionManager::%s] can't find new handle, have already: %d",
__FUNCTION__, mRequestSessionMap.size());
return nullptr;
}
auto requestSession = new Session(newHandle);
mRequestSessionMap.insert(std::pair<int, Session*>(newHandle, requestSession));
mWorkingMutex.unlock();
if (handle) {
@ -79,7 +129,7 @@ MysqlSession* SessionManager::getNewMysqlSession(int* handle)
return nullptr;
}
bool SessionManager::releseMysqlSession(int requestHandleSession)
bool SessionManager::releseSession(int requestHandleSession)
{
if (!mInitalized) {
printf("[SessionManager::%s] not initialized any more\n", __FUNCTION__);
@ -115,13 +165,13 @@ bool SessionManager::isExist(int requestHandleSession)
return result;
}
MysqlSession* SessionManager::getMysqlSession(int handle)
Session* SessionManager::getSession(int handle)
{
if (!mInitalized) {
printf("[SessionManager::%s] not initialized any more\n", __FUNCTION__);
return nullptr;
}
MysqlSession* result = nullptr;
Session* result = nullptr;
mWorkingMutex.lock();
auto it = mRequestSessionMap.find(handle);
if (it != mRequestSessionMap.end()) {

View File

@ -11,12 +11,22 @@
#define DR_LUA_WEB_MODULE_SESSION_MANAGER_H
#include "../Session/MysqlSession.h"
#include "../Model/Session.h"
#include "Poco/RegularExpression.h"
#include <mutex>
#include <map>
#include <stack>
enum SessionValidationTypes {
VALIDATE_NAME,
VALIDATE_EMAIL,
VALIDATE_PASSWORD,
VALIDATE_PASSPHRASE,
VALIDATE_MAX
};
class SessionManager
{
public:
@ -24,15 +34,19 @@ public:
static SessionManager* getInstance();
MysqlSession* getNewMysqlSession(int* handle = nullptr);
inline bool releseMysqlSession(MysqlSession* requestSession) {
return releseMysqlSession(requestSession->getHandle());
Session* getNewSession(int* handle = nullptr);
inline bool releseSession(Session* requestSession) {
return releseSession(requestSession->getHandle());
}
bool releseMysqlSession(int requestHandleSession);
bool releseSession(int requestHandleSession);
bool isExist(int requestHandleSession);
MysqlSession* getMysqlSession(int handle);
Session* getSession(int handle);
bool init();
void deinitalize();
bool isValid(const std::string& subject, SessionValidationTypes validationType);
protected:
SessionManager();
@ -41,10 +55,13 @@ protected:
std::mutex mWorkingMutex;
// sessions storage
std::map<int, MysqlSession*> mRequestSessionMap;
std::stack<int> mEmptyRequestStack;
std::map<int, Session*> mRequestSessionMap;
std::stack<int> mEmptyRequestStack;
bool mInitalized;
// validations
Poco::RegularExpression* mValidations[VALIDATE_MAX];
};
#endif //DR_LUA_WEB_MODULE_SESSION_MANAGER_H

View File

@ -19,11 +19,25 @@ std::string Error::getString()
return ss.str();
}
std::string Error::getHtmlString()
{
std::stringstream ss;
ss << mFunctionName << ": " << mMessage << std::endl;
return ss.str();
}
std::string ParamError::getString()
{
std::stringstream ss;
ss << mFunctionName << ": " << mMessage << " " << mParam << std::endl;
return ss.str();
}
std::string ParamError::getHtmlString()
{
std::stringstream ss;
ss << mFunctionName << ": " << mMessage << " " << mParam << std::endl;
return ss.str();
}

View File

@ -22,6 +22,7 @@ public:
const char* getFunctionName() { return mFunctionName.data(); }
const char* getMessage() { return mMessage.data(); }
virtual std::string getString();
virtual std::string getHtmlString();
protected:
std::string mFunctionName;
@ -44,6 +45,7 @@ public:
}
virtual std::string getString();
virtual std::string getHtmlString();
protected:
std::string mParam;
};

View File

@ -63,4 +63,20 @@ void ErrorList::printErrors()
printf(error->getString().data());
delete error;
}
}
std::string ErrorList::getErrorsHtml()
{
std::string res;
res = "<ul class='grd-no-style'>";
while (mErrorStack.size() > 0) {
auto error = mErrorStack.top();
mErrorStack.pop();
res += "<li class='grd-error'>";
res += error->getHtmlString();
res += "</li>";
delete error;
}
res += "</ul>";
return res;
}

View File

@ -36,6 +36,7 @@ public:
int getErrors(ErrorList* send);
void printErrors();
std::string getErrorsHtml();
protected:
std::stack<Error*> mErrorStack;

View File

@ -1,4 +1,6 @@
#include "Session.h"
#include "Poco/RegularExpression.h"
#include "../SingletonManager/SessionManager.h"
Session::Session(int handle)
: mHandleId(handle)
@ -11,3 +13,37 @@ Session::~Session()
}
void Session::reset()
{
}
bool Session::createUser(const std::string& name, const std::string& email, const std::string& password, const std::string& passphrase)
{
auto sm = SessionManager::getInstance();
if (!sm->isValid(name, VALIDATE_NAME)) {
addError(new Error("Vorname", "Bitte gebe einen Namen an. Mindestens 3 Zeichen, keine Sonderzeichen oder Zahlen."));
return false;
}
if (!sm->isValid(email, VALIDATE_EMAIL)) {
addError(new Error("E-Mail", "Bitte gebe eine g&uuml;ltige E-Mail Adresse an."));
return false;
}
if (!sm->isValid(password, VALIDATE_PASSWORD)) {
addError(new Error("Password", "Bitte gebe ein g&uuml;ltiges Password ein mit mindestens 8 Zeichen, Gro&szlig;- und Kleinbuchstaben, mindestens einer Zahl und einem Sonderzeichen"));
return false;
}
if (passphrase.size() > 0 && !sm->isValid(passphrase, VALIDATE_PASSPHRASE)) {
addError(new Error("Merksatz", "Der Merksatz ist nicht g&uuml;ltig, er besteht aus 24 W&ouml;rtern, mit Komma getrennt."));
return false;
}
mSessionUser = new User(email.data(), name.data(), password.data(), passphrase.size() ? passphrase.data() : nullptr);
return true;
}
bool Session::loadUser(const std::string& email, const std::string& password)
{
return true;
}

View File

@ -10,7 +10,8 @@
#ifndef DR_LUA_WEB_MODULE_SESSION_SESSION_H
#define DR_LUA_WEB_MODULE_SESSION_SESSION_H
#include "../Error/ErrorList.h"
#include "ErrorList.h"
#include "User.h"
class Session : public ErrorList
{
@ -18,11 +19,15 @@ public:
Session(int handle);
~Session();
bool createUser(const std::string& name, const std::string& email, const std::string& password, const std::string& passphrase);
bool loadUser(const std::string& email, const std::string& password);
inline int getHandle() { return mHandleId; }
virtual void reset() = 0;
void reset();
protected:
int mHandleId;
User* mSessionUser;
};

View File

@ -0,0 +1,135 @@
#include "User.h"
#include <sodium.h>
#include "ed25519/ed25519.h"
#include "Poco/Util/Application.h"
NewUser::NewUser(User* user, const char* password, const char* passphrase)
: mUser(user), mPassword(password), mPassphrase(passphrase)
{
}
NewUser::~NewUser()
{
}
void NewUser::run()
{
// create crypto key
if (!mUser->hasCryptoKey()) {
mUser->createCryptoKey(mUser->getEmail(), mPassword.data());
}
// generate
}
// ------------------------------------------------------------------------------------
LoginUser::LoginUser(User* user, const char* password)
: mUser(user), mPassword(password)
{
// auto app = Poco::Util::Application::instance();
}
LoginUser::~LoginUser()
{
}
void LoginUser::run()
{
}
// *******************************************************************************
User::User(const char* email, const char* name, const char* password, const char* passphrase)
: mEmail(email), mFirstName(name), mCryptoKey(nullptr)
{
}
User::User(const char* email, const char* password)
: mEmail(email)
{
}
User::~User()
{
if (mCryptoKey) {
delete mCryptoKey;
}
}
std::string User::generateNewPassphrase(Mnemonic* word_source)
{
unsigned int random_indices[PHRASE_WORD_COUNT];
unsigned int str_sizes[PHRASE_WORD_COUNT];
unsigned int phrase_buffer_size = 0;
for (int i = 0; i < PHRASE_WORD_COUNT; i++) {
random_indices[i] = randombytes_random() % 2048;
str_sizes[i] = strlen(word_source->getWord(random_indices[i]));
phrase_buffer_size += str_sizes[i];
}
phrase_buffer_size += PHRASE_WORD_COUNT + 1;
std::string phrase_buffer(phrase_buffer_size, '\0');
int phrase_buffer_cursor = 0;
for (int i = 0; i < PHRASE_WORD_COUNT; i++) {
memcpy(&phrase_buffer[phrase_buffer_cursor], word_source->getWord(random_indices[i]), str_sizes[i]);
phrase_buffer_cursor += str_sizes[i];
phrase_buffer[phrase_buffer_cursor++] = ' ';
}
return phrase_buffer;
}
void User::createCryptoKey(const char* username, const char* password)
{
// TODO: put it in secure location
static const unsigned char app_secret[] = { 0x21, 0xff, 0xbb, 0xc6, 0x16, 0xfe };
size_t username_size = strlen(username);
size_t password_size = strlen(password);
sha_context context_sha512;
//unsigned char* hash512 = (unsigned char*)malloc(SHA_512_SIZE);
if (SHA_512_SIZE < crypto_pwhash_SALTBYTES) {
addError(new Error(__FUNCTION__, "sha512 is to small for libsodium pwhash saltbytes"));
return;
}
unsigned char hash512_salt[SHA_512_SIZE]; // need at least crypto_pwhash_SALTBYTES 16U
sha512_init(&context_sha512);
sha512_update(&context_sha512, (const unsigned char*)username, username_size);
sha512_update(&context_sha512, app_secret, 6);
sha512_final(&context_sha512, hash512_salt);
unsigned char* key = (unsigned char *)malloc(crypto_box_SEEDBYTES); // 32U
if (crypto_pwhash(key, crypto_box_SEEDBYTES, password, password_size, hash512_salt, 2U, 8388608, 2) != 0) {
addError(new ParamError(__FUNCTION__, " error creating pwd hash, maybe to much memory requestet? error:", strerror(errno)));
//printf("[User::%s] error creating pwd hash, maybe to much memory requestet? error: %s\n", __FUNCTION__, strerror(errno));
//printf("pwd: %s\n", pwd);
return ;
}
lock();
mCryptoKey = new ObfusArray(crypto_box_SEEDBYTES, key);
unlock();
free(key);
// mCryptoKey
}

View File

@ -0,0 +1,72 @@
#ifndef GRADIDO_LOGIN_SERVER_MODEL_USER_INCLUDE
#define GRADIDO_LOGIN_SERVER_MODEL_USER_INCLUDE
#include "../Crypto/KeyPair.h"
#include <string>
#include "ErrorList.h"
#include "Poco/Thread.h"
class NewUser;
class User : public ErrorList
{
friend NewUser;
public:
// new user
User(const char* email, const char* name, const char* password, const char* passphrase);
// existing user
User(const char* email, const char* password);
~User();
static std::string generateNewPassphrase(Mnemonic* word_source);
inline bool hasCryptoKey() { lock(); bool bRet = mCryptoKey != nullptr; unlock(); return bRet; }
inline const char* getEmail() { return mEmail.data(); }
protected:
void createCryptoKey(const char* email, const char* password);
inline void lock() { mWorkingMutex->lock(); }
inline void unlock() { mWorkingMutex->unlock(); }
private:
std::string mEmail;
std::string mFirstName;
// crypto key as obfus array
ObfusArray* mCryptoKey;
Poco::Mutex* mWorkingMutex;
};
class NewUser : public Poco::Runnable
{
public:
NewUser(User* user, const char* password, const char* passphrase);
~NewUser();
virtual void run();
protected:
User* mUser;
std::string mPassword;
std::string mPassphrase;
};
class LoginUser : public Poco::Runnable
{
public:
LoginUser(User* user, const char* password);
~LoginUser();
virtual void run();
protected:
User* mUser;
std::string mPassword;
};
#endif //GRADIDO_LOGIN_SERVER_MODEL_USER_INCLUDE

View File

@ -21,8 +21,8 @@
<legend>Login</legend>
<p>Bitte gebe deine Zugangsdaten ein um dich einzuloggen.</p>
<p class="grd_small">
<label for="login-username">Benutzernamen</label>
<input id="login-username" type="text" name="login-username"/>
<label for="login-email">E-Mail</label>
<input id="login-email" type="text" name="login-email"/>
</p>
<p class="grd_small">
<label for="login-password">Passwort</label>

View File

@ -1,8 +1,20 @@
<%@ page class="RegisterPage" %>
<%@ page form="true" %>
<%@ page compressed="true" %>
<%!
<%!
#include "../SingletonManager/SessionManager.h"
%>
<%
auto session = SessionManager::getInstance()->getNewSession();
bool userReturned = false;
if(!form.empty()) {
userReturned = session->createUser(
form.get("register-name"),
form.get("register-email"),
form.get("register-password"),
form.get("register-key-existing")
);
}
%>
<!DOCTYPE html>
<html>
@ -12,17 +24,34 @@
<title>Gradido Login Server: Register</title>
<!--<link rel="stylesheet" type="text/css" href="css/styles.min.css">-->
<link rel="stylesheet" type="text/css" href="https://gradido2.dario-rekowski.de/css/styles.css">
<style type="text/css" >
input:not([type='radio']) {
width:200px;
}
label:not(.grd_radio_label) {
width:80px;
display:inline-block;
}
</style>
</head>
<body>
<h1>Login</h1>
<h1>Einen neuen Account anlegen</h1>
<form method="POST">
<div class="grd_container">
<% if(!form.empty() && !userReturned) {%>
<%= session->getErrorsHtml() %>
<%} %>
<fieldset class="grd_container_small">
<legend>Account anlegen</legend>
<p>Bitte gebe deine Daten um einen Account anzulegen</p>
<p class="grd_small">
<label for="register-username">Benutzernamen</label>
<input id="register-username" type="text" name="register-username"/>
<label for="register-name">Vorname</label>
<input id="register-name" type="text" name="register-name" value="<%= !form.empty() ? form.get("register-name") : "" %>"/>
</p>
<p class="grd_small">
<label for="register-email">E-Mail</label>
<input id="register-email" type="email" name="register-email" value="<%= !form.empty() ? form.get("register-email") : "" %>"/>
</p>
<p class="grd_small">
<label for="register-password">Passwort</label>
@ -31,13 +60,13 @@
<p>Hast du bereits schonmal ein Gradido Konto besessen?</p>
<p class="grd_small">
<input id="register-key-new-yes" type="radio" name="register-key" value="yes" checked/>
<label for="register-key-new-yes">Nein, bitte ein neues erstellen!</label>
<label class="grd_radio_label" for="register-key-new-yes">Nein, bitte ein neues erstellen!</label>
</p>
<p class="grd_small">
<input id="register-key-new-no" type="radio" name="register-key" value="no"/>
<label for="register-key-new-no">Ja, bitte wiederherstellen!</label>
<label class="grd_radio_label" for="register-key-new-no">Ja, bitte wiederherstellen!</label>
</p>
<textarea style="width:100%;height:100px" name="register-key-existing"></textarea>
<textarea style="width:100%;height:100px" name="register-key-existing"><%= !form.empty() ? form.get("register-key-existing") : "" %></textarea>
</fieldset>
<input class="grd_bn_succeed" type="submit" name="submit" value="Einloggen">
</div>