From 5a09fb48d553b461f401e8d0cc99fadba7cc9ded Mon Sep 17 00:00:00 2001 From: micio Date: Tue, 12 Oct 2010 23:08:09 +0000 Subject: [PATCH] Bazaar/Protect : Almost completed web authentication package git-svn-id: svn://ultimatepp.org/upp/trunk@2773 f0d560ea-af0d-0410-9eb7-867de7ffcac7 --- bazaar/Protect/Protect.upp | 6 +- bazaar/Protect/ProtectClient.cpp | 89 ++--- bazaar/Protect/ProtectClient.h | 13 +- bazaar/Protect/ProtectDB.cpp | 149 +++++++ bazaar/Protect/ProtectDB.h | 61 +++ bazaar/Protect/ProtectDB.sch | 15 + bazaar/Protect/ProtectServer.cpp | 370 ++++++++++++++---- bazaar/Protect/ProtectServer.h | 52 ++- bazaar/Protect/ProtectStatus.cpp | 9 +- bazaar/Protect/ProtectStatus.h | 11 +- bazaar/Protect/init | 2 + .../ProtectClientDemo/ProtectClientDemo.cpp | 25 +- .../ProtectClientDemo/ProtectClientDemo.lay | 44 ++- bazaar/ProtectServerDemo/main.cpp | 15 + 14 files changed, 693 insertions(+), 168 deletions(-) create mode 100644 bazaar/Protect/ProtectDB.cpp create mode 100644 bazaar/Protect/ProtectDB.h create mode 100644 bazaar/Protect/ProtectDB.sch diff --git a/bazaar/Protect/Protect.upp b/bazaar/Protect/Protect.upp index 5878dd545..fe162da1a 100644 --- a/bazaar/Protect/Protect.upp +++ b/bazaar/Protect/Protect.upp @@ -3,13 +3,17 @@ description "Software copy protection module\377"; uses Core, ScgiServer, - Cypher; + Cypher, + MySql; file ProtectStatus.h, ProtectStatus.cpp, Protect.h, Protect.cpp, + ProtectDB.sch, + ProtectDB.h, + ProtectDB.cpp, ProtectServer.h, ProtectServer.cpp, ProtectClient.h, diff --git a/bazaar/Protect/ProtectClient.cpp b/bazaar/Protect/ProtectClient.cpp index 647a4e15a..fc43ab05c 100644 --- a/bazaar/Protect/ProtectClient.cpp +++ b/bazaar/Protect/ProtectClient.cpp @@ -4,12 +4,8 @@ NAMESPACE_UPP ProtectClient::ProtectClient() { - // unconnected at startup - connected = false; - // generates client id - dword rnd = Random(); - clientID = HexString((byte const *)&rnd, sizeof(dword)); + clientID = -1; // defaults to Snow2 Cypher cypher = new Snow2; @@ -38,7 +34,6 @@ VectorMap ProtectClient::SendMap(VectorMap const & // copy vectormap adding client id and a magic number VectorMapdataMap(v, 1); dataMap.Add("APPID", "ProtectClient"); - dataMap.Add("CLIENTID", clientID); // sets cypher key (and create a random IV) cypher->SetKey(key); @@ -54,21 +49,8 @@ VectorMap ProtectClient::SendMap(VectorMap const & // if contents start with "ERROR", just fetch the error desc // and put it in result map VectorMap resMap; - if(contents.StartsWith("ERROR=")) - { - int i = contents.Find("\r"); - String s; - if(i >= 0) - s = contents.Mid(6, i - 6); - else - s = contents.Mid(6); - int errCode = atoi(~s); - resMap.Add("ERROR", errCode); - resMap.Add("ERRORMSG", ProtectMessage(errCode)); - return resMap; - } - // otherwise, if lastContents don't start with IV field - // signals it + + // if contents don't start with IV field signals it if(!contents.StartsWith("IV=")) { resMap.Add("ERROR", PROTECT_MISSING_IV); @@ -110,22 +92,27 @@ bool ProtectClient::Connect(void) lastError = 0; // if already connected, disconnect first - if(!Disconnect()) - return false; + Disconnect(); // send a connect packet to server VectorMapv; - v.Add("REASON", ProtectReasonStr(PROTECT_CONNECT)); + v.Add("REASON", PROTECT_CONNECT); + v.Add("EMAIL", userEMail); VectorMap res = SendMap(v); // check for errors if(res.Find("ERROR") >= 0) { lastError = res.Get("ERROR"); + clientID = -1; return false; } - connected = true; + int i = res.Find("CLIENTID"); + if(i < 0) + clientID = -1; + else + clientID = (int)res[i]; return true; } @@ -134,22 +121,22 @@ bool ProtectClient::Disconnect(void) { lastError = 0; - if(!connected) - return true; - // sends a disconnect packet to server VectorMapv; - v.Add("REASON", ProtectReasonStr(PROTECT_DISCONNECT)); + v.Add("REASON", PROTECT_DISCONNECT); + v.Add("CLIENTID", (int)clientID); VectorMap res = SendMap(v); + // we're probably disconnected anyways.... + clientID = -1; + // check for errors if(res.Find("ERROR") >= 0) { lastError = res.Get("ERROR"); return false; } - - connected = false; + return true; } @@ -160,13 +147,15 @@ bool ProtectClient::Refresh(void) // sends a refresh packet to server VectorMapv; - v.Add("REASON", ProtectReasonStr(PROTECT_REFRESH)); + v.Add("REASON", PROTECT_REFRESH); + v.Add("CLIENTID", (int)clientID); VectorMap res = SendMap(v); // check for errors if(res.Find("ERROR") >= 0) { lastError = res.Get("ERROR"); + clientID = -1; return false; } @@ -180,7 +169,8 @@ String ProtectClient::GetKey(void) // sends a getkey packet to server VectorMapv; - v.Add("REASON", ProtectReasonStr(PROTECT_GETKEY)); + v.Add("REASON", PROTECT_GETKEY); + v.Add("CLIENTID", (int)clientID); VectorMap res = SendMap(v); // check for errors @@ -204,7 +194,8 @@ bool ProtectClient::GetLicenseInfo(void) // sends a getinfo packet to server VectorMapv; - v.Add("REASON", ProtectReasonStr(PROTECT_GETLICENSEINFO)); + v.Add("REASON", PROTECT_GETLICENSEINFO); + v.Add("CLIENTID", (int)clientID); VectorMap res = SendMap(v); // check for errors @@ -214,13 +205,14 @@ bool ProtectClient::GetLicenseInfo(void) return false; } - if(res.Find("EMAIL") >= 0) userEMail = res.Get("USEREMAIL"); - if(res.Find("USERNAME") >= 0) userName = res.Get("USERNAME"); - if(res.Find("USERADDRESS") >= 0) userAddress = res.Get("USERADDRESS"); - if(res.Find("USERCOUNTRY") >= 0) userCountry = res.Get("USERCOUNTRY"); - if(res.Find("USERPHONE") >= 0) userPhone = res.Get("USERPHONE"); - if(res.Find("USERFAX") >= 0) userFax = res.Get("USERFAX"); - if(res.Find("USERCELL") >= 0) userCell = res.Get("USERCELL"); + if(res.Find("EMAIL") >= 0) userEMail = res.Get("EMAIL"); + if(res.Find("USERNAME") >= 0) userName = res.Get("NAME"); + if(res.Find("ADDRESS") >= 0) userAddress = res.Get("ADDRESS"); + if(res.Find("COUNTRY") >= 0) userCountry = res.Get("COUNTRY"); + if(res.Find("ZIP") >= 0) userZIP = res.Get("ZIP"); + if(res.Find("PHONE") >= 0) userPhone = res.Get("PHONE"); + if(res.Find("FAX") >= 0) userFax = res.Get("FAX"); + if(res.Find("CELL") >= 0) userCell = res.Get("CELL"); if(res.Find("EXPIRETIME") >= 0) expireTime = res.Get("EXPIRETIME"); if(res.Find("NUMLICENSES") >= 0) numLicenses = res.Get("NUMLICENSES"); @@ -234,14 +226,15 @@ bool ProtectClient::Register(void) // sends a register packet to server VectorMapv; - v.Add("REASON", ProtectReasonStr(PROTECT_REGISTER)); + v.Add("REASON", PROTECT_REGISTER); v.Add("EMAIL", userEMail); - v.Add("USERNAME", userName); - v.Add("USERADDRESS", userAddress); - v.Add("USERCOUNTRY", userCountry); - v.Add("USERPHONE", userPhone); - v.Add("USERFAX", userFax); - v.Add("USERCELL", userCell); + v.Add("NAME", userName); + v.Add("ADDRESS", userAddress); + v.Add("COUNTRY", userCountry); + v.Add("ZIP", userZIP); + v.Add("PHONE", userPhone); + v.Add("FAX", userFax); + v.Add("CELL", userCell); VectorMap res = SendMap(v); // check for errors diff --git a/bazaar/Protect/ProtectClient.h b/bazaar/Protect/ProtectClient.h index 53a377aa3..c0207717f 100644 --- a/bazaar/Protect/ProtectClient.h +++ b/bazaar/Protect/ProtectClient.h @@ -22,14 +22,8 @@ class ProtectClient // in Cypher package Onecypher; - // flag stating a succesfull connection - bool connected; - - // client id -- generated as random dword on creation - String clientID; - - // license ID -- got from server upon registration - String licenseID; + // client id -- got from server upon connection + dword clientID; // user data String userEMail; @@ -78,9 +72,6 @@ class ProtectClient int GetLastError(void) { return lastError; } String GetLastErrorMsg(void) { return ProtectMessage(lastError); } - // checks whether we're connected to server - bool IsConnected(void) { return connected; } - // create a persistent link to server bool Connect(void); diff --git a/bazaar/Protect/ProtectDB.cpp b/bazaar/Protect/ProtectDB.cpp new file mode 100644 index 000000000..2a1c27acd --- /dev/null +++ b/bazaar/Protect/ProtectDB.cpp @@ -0,0 +1,149 @@ +#include "ProtectDB.h" + +NAMESPACE_UPP + +#include +#include + +ProtectDB::ProtectDB() +{ + connected = false; + upgraded = false; +} + +ProtectDB::~ProtectDB() +{ + if(connected) + Disconnect(); +} + +// connect/disconnect +bool ProtectDB::Connect(void) +{ + if(!session.Connect(userName, password, dbName, host)) + return false; + connected = true; + SQL = session; + + // upgrade database on first connect + if(!upgraded) + { + SqlSchema sch(MY_SQL); + All_Tables(sch); + SqlPerformScript(sch.Upgrade()); + SqlPerformScript(sch.Attributes()); + SQL.ClearError(); + upgraded = true; + } + + return true; +} + +bool ProtectDB::Disconnect(void) +{ + if(!connected) + return true; + session.Close(); + connected = false; + return true; +} + +// get data -- email is the key -- non repeatable +VectorMap ProtectDB::Get(String const &mail) +{ + VectorMap res = Default(); + + SQL * Select(SqlAll()).From(USERS).Where(EMAIL == mail); + // we've unique key, so... + if(SQL.Fetch()) + for(int i = 0; i < res.GetCount(); i++) + res[i] = SQL[res.GetKey(i)]; + else + res.Clear(); + + return res; +} + +// set/append data +bool ProtectDB::Set(VectorMap const &d) +{ + String eMail = d.Get("EMAIL"); + + // fill missing fields with default values + VectorMapdata = Default(d); + + SQL * Select(SqlAll()).From(USERS).Where(EMAIL == eMail); + if(SQL.Fetch()) + { + SQL * Update(USERS) + (NAME , data.Get("NAME")) + (ADDRESS , data.Get("ADDRESS")) + (COUNTRY , data.Get("COUNTRY")) + (ZIP , data.Get("ZIP")) + (PHONE , data.Get("PHONE")) + (FAX , data.Get("FAX")) + (CELL , data.Get("CELL")) + (LICENSES , data.Get("LICENSES")) + (EXPIRATION , data.Get("EXPIRATION")) + (ACTIVATIONKEY , data.Get("ACTIVATIONKEY")) + (ACTIVATIONSENT , data.Get("ACTIVATIONSENT")) + (ACTIVATED , data.Get("ACTIVATED")) + .Where(EMAIL == eMail); + } + else + { + SQL * Insert(USERS) + (EMAIL , eMail) + (NAME , data.Get("NAME")) + (ADDRESS , data.Get("ADDRESS")) + (COUNTRY , data.Get("COUNTRY")) + (ZIP , data.Get("ZIP")) + (PHONE , data.Get("PHONE")) + (FAX , data.Get("FAX")) + (CELL , data.Get("CELL")) + (LICENSES , data.Get("LICENSES")) + (EXPIRATION , data.Get("EXPIRATION")) + (ACTIVATIONKEY , data.Get("ACTIVATIONKEY")) + (ACTIVATIONSENT , data.Get("ACTIVATIONSENT")) + (ACTIVATED , data.Get("ACTIVATED")) + ; + } +} + +// get default record +VectorMap ProtectDB::Default(VectorMap const &base) +{ + VectorMap res(base, 1); + res.FindAdd("EMAIL", ""); + res.FindAdd("NAME", ""); + res.FindAdd("ADDRESS", ""); + res.FindAdd("COUNTRY", ""); + res.FindAdd("ZIP", ""); + res.FindAdd("PHONE", ""); + res.FindAdd("FAX", ""); + res.FindAdd("CELL", ""); + res.FindAdd("LICENSES", 1); + res.FindAdd("EXPIRATION", GetSysTime() + 24*60*60*30); + dword r = Random(); + res.FindAdd("ACTIVATIONKEY", HexString((byte *)&r, sizeof(r))); + res.FindAdd("ACTIVATIONSENT", GetSysTime()); + res.FindAdd("ACTIVATED", false); + + return res; +} + +/* + MySqlSession session; + // substitute your 'username' and 'password' here: + if(!session.Connect("username", "password", "mysql", "localhost")) { + printf("Can't connect with MySql\n"); + return; + } + Sql sql(session); + sql.Execute("use mysql"); + sql.Execute("show tables"); + while(sql.Fetch()) + Cout() << (String)sql[0] << '\n'; +*/ + +END_UPP_NAMESPACE diff --git a/bazaar/Protect/ProtectDB.h b/bazaar/Protect/ProtectDB.h new file mode 100644 index 000000000..7627757c8 --- /dev/null +++ b/bazaar/Protect/ProtectDB.h @@ -0,0 +1,61 @@ +#ifndef _Protect_ProtectDB_h_ +#define _Protect_ProtectDB_h_ + +#include + +NAMESPACE_UPP + +#define SCHEMADIALECT +#define MODEL +#include "Sql/sch_header.h" + +// encapsulation of Protect database +class ProtectDB +{ + private: + + // host, db name, username and password + String host; + String dbName; + String userName; + String password; + + // connected flag + bool connected; + + // database upgraded flag + bool upgraded; + + // database session + MySqlSession session; + + protected: + + public: + + ProtectDB(); + ~ProtectDB(); + + // setting of connection data + ProtectDB &SetHost(String const &_host) { host = _host; return *this; } + ProtectDB &SetDBName(String const &_dbName) { dbName = _dbName; return *this; } + ProtectDB &SetUserName(String const &_name) { userName = _name; return *this; } + ProtectDB &SetPassword(String const &_pwd) { password = _pwd; return *this; } + + // connect/disconnect + bool Connect(void); + bool Disconnect(void); + bool IsConnected(void) { return connected; } + + // get data -- email is the key -- non repeatable + VectorMapGet(String const &mail); + + // set/append data + bool Set(VectorMap const &data); + + // get default record + VectorMap Default(VectorMap const &base = VectorMap()); +}; + +END_UPP_NAMESPACE +#endif diff --git a/bazaar/Protect/ProtectDB.sch b/bazaar/Protect/ProtectDB.sch new file mode 100644 index 000000000..2dbb0bffd --- /dev/null +++ b/bazaar/Protect/ProtectDB.sch @@ -0,0 +1,15 @@ +TABLE_(USERS) + STRING_ (EMAIL, 50) PRIMARY_KEY + STRING_ (NAME, 200) + STRING_ (ADDRESS, 200) + STRING_ (COUNTRY, 50) + STRING_ (ZIP, 20) + STRING_ (PHONE, 50) + STRING_ (FAX, 50) + STRING_ (CELL, 50) + TIME_ (EXPIRATION) + INT_ (LICENSES) + STRING_ (ACTIVATIONKEY, 200) + DATE_ (ACTIVATIONSENT) + BOOL_ (ACTIVATED) +END_TABLE diff --git a/bazaar/Protect/ProtectServer.cpp b/bazaar/Protect/ProtectServer.cpp index 9ce29e7bc..ce4ec298c 100644 --- a/bazaar/Protect/ProtectServer.cpp +++ b/bazaar/Protect/ProtectServer.cpp @@ -2,6 +2,65 @@ NAMESPACE_UPP +// email validation helper +bool ValidateEMail(const char *address) +{ + int count = 0; + const char *c, *domain; + static const char *rfc822_specials = "()<>@,;:\\\"[]"; + + // first we validate the name portion (name@domain) + for (c = address; *c; c++) + { + if (*c == '\"' && (c == address || *(c - 1) == '.' || *(c - 1) == '\"')) + { + while (*++c) + { + if (*c == '\"') + break; + if (*c == '\\' && (*++c == ' ')) + continue; + if (*c < ' ' || *c >= 127) + return false; + } + if (!*c++) + return false; + if (*c == '@') + break; + if (*c != '.') + return false; + continue; + } + if (*c == '@') + break; + if (*c <= ' ' || *c >= 127) + return false; + if (strchr(rfc822_specials, *c)) + return false; + } + if (c == address || *(c - 1) == '.') + return false; + + // next we validate the domain portion (name@domain) + if (!*(domain = ++c)) + return false; + do + { + if (*c == '.') + { + if (c == domain || *(c - 1) == '.') + return false; + count++; + } + if (*c <= ' ' || *c >= 127) + return false; + if (strchr(rfc822_specials, *c)) + return false; + } + while (*++c); + return (count >= 1); +} + // constructor - defaults to port 8787 and connection timeout of 5 minutes (300 sec.) ProtectServer::ProtectServer(int port, int timeout) : ScgiServer(port) { @@ -12,45 +71,73 @@ ProtectServer::ProtectServer(int port, int timeout) : ScgiServer(port) clientTimeout = timeout; } -void ProtectServer::SendError(int msg) -{ - clientSock.Write("ERROR=" + FormatInt(msg) + "\r\n"); -} - // polls clients and remove those which connection has expired void ProtectServer::CheckExpiredClients(void) { - int64 curTime = GetSysTime().Get(); - for(int iClient = clients.GetCount() - 1; iClient >= 0; iClient--) - if(clients[iClient].Get() < curTime) - clients.Remove(iClient); + Time curTime = GetSysTime(); + for(int iClient = clientLinks.GetCount() - 1; iClient >= 0; iClient--) + if(clientLinks[iClient].time < curTime) + { + String eMail = clientLinks[iClient].eMail; + clientLinks.Remove(iClient); + registeredConnections.Get(eMail)--; + if(registeredConnections.Get(eMail) <= 0) + registeredConnections.RemoveKey(eMail); + } } // checks client list to see if a client is connected -bool ProtectServer::IsClientConnected(String const &clientID) +// and refresh its expire time on the way +bool ProtectServer::IsClientConnected(dword clientID) { - int iClient = clients.Find(clientID); + int iClient = clientLinks.Find(clientID); if(iClient >= 0) { - clients[iClient] = GetSysTime() + clientTimeout; + clientLinks[iClient].time = GetSysTime() + clientTimeout; return true; } return false; } -// connects a client or refresh its expiration time -void ProtectServer::ConnectClient(String const &clientID) +// connects a client -- returns a new clientID +// doesn't check anything, that one must be done elsewhere +dword ProtectServer::ConnectClient(String const &eMail) { - int i = clients.FindAdd(clientID); - clients[i] = GetSysTime() + clientTimeout; + // generates an unique random clientID + dword id; + do + { + id = Random(); + } + while(clientLinks.Find(id) >= 0); + + // adds to clientLinks and to registered emails count + clientLinks.Add(id, TimeMail(GetSysTime() + clientTimeout, eMail)); + int i = registeredConnections.Find(eMail); + if(i >= 0) + registeredConnections[i]++; + else + registeredConnections.Add(eMail, 1); + + return id; } // disconnects a client or refresh its expiration time -void ProtectServer::DisconnectClient(String const &clientID) +void ProtectServer::DisconnectClient(dword clientID) { - int i = clients.Find(clientID); + int i = clientLinks.Find(clientID); if(i >= 0) - clients.Remove(i); + { + String eMail = clientLinks[i].eMail; + clientLinks.Remove(i); + i = registeredConnections.Find(eMail); + if(i >= 0) + { + registeredConnections[i]--; + if(registeredConnections[i] <= 0) + registeredConnections.Remove(i); + } + } } @@ -61,38 +148,35 @@ void ProtectServer::OnAccepted() void ProtectServer::OnRequest() { + int err = 0; + VectorMap data; + VectorMap results; + clientSock.Write("Content-Type: text/plain\r\n\r\n"); // ignore requests without post data if(!HasPostData()) - { - SendError(PROTECT_BAD_REQUEST); - return; - } - + err = PROTECT_BAD_REQUEST; // post data should contain at least an IV field and a DATA field - if(post.Find("IV") < 0 || post.Find("DATA") < 0) + else if(post.Find("IV") < 0 || post.Find("DATA") < 0) + err = PROTECT_MISSING_IV; + else { - SendError(PROTECT_MISSING_IV); - return; - } - - // gets IV and setup the cypher to decrypt DATA field - String IV = ScanHexString(post.GetString("IV")); - cypher->SetKey(key, IV); - - // decrypts DATA field and build its vectormap - VectorMap data; - String decoded; - try - { - decoded = (*cypher)(ScanHexString(post.GetString("DATA"))); - LoadFromXML(data, decoded); - } - catch(...) - { - SendError(PROTECT_BAD_DATA); - return; + // gets IV and setup the cypher to decrypt DATA field + String IV = ScanHexString(post.GetString("IV")); + cypher->SetKey(key, IV); + + // decrypts DATA field and build its vectormap + String decoded; + try + { + decoded = (*cypher)(ScanHexString(post.GetString("DATA"))); + LoadFromXML(data, decoded); + } + catch(...) + { + err = PROTECT_BAD_DATA; + } } // Get request reason and process it @@ -103,28 +187,19 @@ void ProtectServer::OnRequest() // GETKEY gets application key // REGISTER registers app for timed demo // GETLICENSEINFO gets info about license (name, expiration date, app version....) - if(data.Find("REASON") < 0 || data.Find("CLIENTID") < 0) + if(!err && data.Find("REASON") < 0) + err = PROTECT_MISSING_REASON; + if(!err) + results = ProcessRequest(data.Get("REASON"), data); + + // if previous error, sets it into result packet + if(err) { - SendError(PROTECT_BAD_DATA); - return; + results.Add("ERROR", err); + results.Add("ERRORMSG", ProtectMessage(err)); } - String ClientID = data.Get("CLIENTID"); - int reason = ProtectReason(data.Get("REASON")); - if(reason < PROTECT_CONNECT || reason > PROTECT_GETLICENSEINFO) - { - SendError(PROTECT_BAD_DATA); - return; - } - if(reason != PROTECT_CONNECT && reason != PROTECT_REGISTER && !IsClientConnected(ClientID)) - { - SendError(PROTECT_NOT_CONNECTED); - return; - } - VectorMap results = ProcessRequest(reason, data); - if(reason == PROTECT_CONNECT && results.Find("ERROR") < 0) - ConnectClient(ClientID); - else if(reason == PROTECT_DISCONNECT) - DisconnectClient(ClientID); + else if(results.Find("ERROR") >= 0) + results.Add("ERRORMSG", ProtectMessage(results.Get("ERROR"))); // encodes results and send back to client cypher->SetKey(key); @@ -144,19 +219,182 @@ void ProtectServer::OnClosed() VectorMap ProtectServer::ProcessRequest(int reason, VectorMap const &v) { VectorMap res; + dword clientID; + VectorMap userRec; + String eMail; + int numConnections; + switch(reason) { case PROTECT_REGISTER: + { + // we need an eMail to register + if(v.Find("EMAIL") < 0) + { + res.Add("ERROR", PROTECT_MISSING_EMAIL); + return res; + } + eMail = v.Get("EMAIL"); + if(!ValidateEMail(~eMail)) + { + res.Add("ERROR", PROTECT_INVALID_EMAIL); + return res; + } + + // avoid hacking of source packet stripping eventually + // added licenseinfo, expiration, etc + VectorMapvs(v, 1); + vs.RemoveKey("LICENSES"); + vs.RemoveKey("EXPIRATION"); + vs.RemoveKey("ACTIVATIONKEY"); + vs.RemoveKey("ACTIVATIONSENT"); + vs.RemoveKey("ACTIVATED"); + + // try to get user record from database, if any + userRec = db.Get(eMail); + + // if not found, new registration + if(!userRec.GetCount()) + { + // new registration + db.Set(vs); + if(SendActivationMail(userRec)) + res.Add("STATUS", "ACTIVATION RESENT"); + else + res.Add("ERROR", PROTECT_MAIL_SEND_ERROR); + } + // otherwise, we shall check if activated + else if(!userRec.Get("ACTIVATED")) + { + // already registered but still not activated + // resend activation mail + if(SendActivationMail(userRec)) + res.Add("STATUS", "ACTIVATION RESENT"); + else + res.Add("ERROR", PROTECT_MAIL_SEND_ERROR); + } + // otherwise we shall check if license expired + else if(userRec.Get("EXPIRATION") <= GetSysTime()) + { + // signals expiration and leave + res.Add("ERROR", PROTECT_MAIL_ALREADY_USED); + } + // otherwise, just tell user that's already registered + else + { + res.Add("STATUS", "ALREADY REGISTERED"); + } + } + break; + case PROTECT_CONNECT: + // we need email to connect + if(v.Find("EMAIL") < 0) + { + res.Add("ERROR", PROTECT_MISSING_EMAIL); + return res; + } + eMail = v.Get("EMAIL"); + // get the user data from database + userRec = db.Get(eMail); + + // if no mail data, product is unregistered + if(!userRec.GetCount()) + { + res.Add("ERROR", PROTECT_UNREGISTERED); + return res; + } + + // product is registered, now we check if activation mail was sent + if(!userRec.Get("ACTIVATED")) + { + res.Add("ERROR", PROTECT_LICENSE_NOT_ACTIVATED); + SendActivationMail(userRec); + return res; + } + + // ok, product registered and activated... is license expired ? + if(userRec.Get("EXPIRATION") < GetSysTime()) + { + res.Add("ERROR", PROTECT_LICENSE_EXPIRED); + return res; + } + + // all ok, we shall finally check if number of licenses has overrun + // first, we get number of current connections with same email + numConnections = registeredConnections.Find(eMail); + if(numConnections < 0) + numConnections = 0; + else + numConnections = registeredConnections[numConnections]; + if(numConnections >= (int)userRec.Get("LICENSES")) + { + res.Add("ERROR", PROTECT_LICENSES_NUMBER_EXCEEDED); + return res; + } + + // all OK, connect client and return connection ID + clientID = ConnectClient(eMail); + res.Add("CLIENTID", (int)clientID); + + break; + + case PROTECT_DISCONNECT : + // we need the conneciton ID + if(v.Find("CLIENTID") < 0) + { + res.Add("ERROR", PROTECT_MISSING_CLIENTID); + return res; + } + clientID = (int)v.Get("CLIENTID"); + DisconnectClient(clientID); break; case PROTECT_GETKEY: - res.Add("KEY", "THIS IS A DUMMY KEY"); + // we need the conneciton ID + if(v.Find("CLIENTID") < 0) + { + res.Add("ERROR", PROTECT_MISSING_CLIENTID); + return res; + } + // we shall be connected + clientID = (int)v.Get("CLIENTID"); + if(!IsClientConnected(clientID)) + { + res.Add("ERROR", PROTECT_NOT_CONNECTED); + return res; + } + // all ok, return the key + res.Add("KEY", appKey); break; default: + res.Add("ERROR", PROTECT_UNKNOWN_REASON); break; } return res; } +// sends activation mail to user +bool ProtectServer::SendActivationMail(VectorMap const &userData) +{ + smtp.To(userData.Get("EMAIL")); + smtp.Subject("APPLICATION ACTIVATION"); + smtp.Text("http://www.timberstruct.it?DATA=" + (String)userData.Get("ACTIVATIONKEY")); + return smtp.Send(); +} + +// runs the server +void ProtectServer::Run(void) +{ + // try to connect to db + if(!db.Connect()) + { + Cout() << "Error connecting to database\n"; + return; + } + + // runs the SCGI server + ScgiServer::Run(); +} + END_UPP_NAMESPACE diff --git a/bazaar/Protect/ProtectServer.h b/bazaar/Protect/ProtectServer.h index 0d00c8282..f4d4a428f 100644 --- a/bazaar/Protect/ProtectServer.h +++ b/bazaar/Protect/ProtectServer.h @@ -6,6 +6,7 @@ #include #include "ProtectStatus.h" +#include "ProtectDB.h" NAMESPACE_UPP @@ -13,11 +14,28 @@ class ProtectServer : public ScgiServer { private: + struct TimeMail : Moveable + { + Time time; + String eMail; + TimeMail(Time const &t, String const &m) : time(t), eMail(m) {}; + TimeMail() {}; + }; + + // database for user auth + ProtectDB db; + + // mailer for activation mails + SmtpMail smtp; + // clients timeout value -- defaults to 5 minutes (300 seconds) int clientTimeout; - // list of all connected clients with their expiration dates - VectorMap clients; + // list of all connected clients with their expiration dates and emails + VectorMap clientLinks; + + // number of succesful connections per registered email + VectorMap registeredConnections; // encryption engine One cypher; @@ -34,21 +52,24 @@ class ProtectServer : public ScgiServer // checks client list to see if a client is connected // as a side effect, refresh its connection time - bool IsClientConnected(String const &clientID); + bool IsClientConnected(dword clientID); - // connects a client or refresh its expiration time - void ConnectClient(String const &clientID); + // connects a client -- return an ID + dword ConnectClient(String const &eMail); // disconnects a client or refresh its expiration time - void DisconnectClient(String const &clientID); - - // sends error message - void SendError(int msg); + void DisconnectClient(dword clientID); // process client request // takes a VectorMap on input from client // produces a response VectorMap to be returned virtual VectorMap ProcessRequest(int reason, VectorMap const &v); + + // Application key to be returned on auth success + String appKey; + + // sends activation mail to user + bool SendActivationMail(VectorMap const &userData); public: @@ -61,6 +82,19 @@ public: // sets encryption key ProtectServer &SetKey(String const &_key) { key = _key; return *this; } + + // gets database + ProtectDB &GetDB(void) { return db; } + + // gets mailer + SmtpMail &GetSmtp(void) { return smtp; } + + // sets application key (to be returned on auth success) + ProtectServer &SetAppKey(String const &k) { appKey = k; return *this; } + String GetAppKey(void) { return appKey; } + + // runs the server + void Run(void); }; END_UPP_NAMESPACE diff --git a/bazaar/Protect/ProtectStatus.cpp b/bazaar/Protect/ProtectStatus.cpp index a7504ce64..36258d46d 100644 --- a/bazaar/Protect/ProtectStatus.cpp +++ b/bazaar/Protect/ProtectStatus.cpp @@ -9,14 +9,21 @@ const char *__ProtectMessages[] = tt_("HTTP SERVER ERROR"), tt_("BAD REQUEST"), tt_("MISSING INITIALIZATION VECTOR ON DATA"), + tt_("MISSING EMAIL ON DATA"), + tt_("ILL-FORMED EMAIL ADDRESS"), + tt_("MISSING CONNECTION REASON ON DATA"), + tt_("UNKNOWN CONNECTION REASON"), + tt_("MISSING CLIENT ID ON DATA"), tt_("BAD REQUEST DATA"), tt_("NOT CONNECTED TO SERVER"), tt_("SERVER CONNECTION EXPIRED"), tt_("TESTING LICENSE EXPIRED"), + tt_("LICENSE STILL NOT ACTIVATED, RE-SENDING ACTIVATION EMAIL"), tt_("UNREGISTERED APPLICATION"), tt_("LICENSES NUMBER EXCEEDED"), tt_("EMAIL ALREADY USED AND EXPIRED"), - tt_("INVALID APPLICATION VERSION, PLEASE UPGRADE") + tt_("INVALID APPLICATION VERSION, PLEASE UPGRADE"), + tt_("ERROR SENDING ACTIVATION EMAIL") }; String ProtectMessage(int m) diff --git a/bazaar/Protect/ProtectStatus.h b/bazaar/Protect/ProtectStatus.h index 8a0dec0df..a3c788946 100644 --- a/bazaar/Protect/ProtectStatus.h +++ b/bazaar/Protect/ProtectStatus.h @@ -11,14 +11,21 @@ typedef enum { PROTECT_HTTP_ERROR, // error on HTTP communication with server PROTECT_BAD_REQUEST, // missing POST data on request PROTECT_MISSING_IV, // missing Initialization Vector on POST data + PROTECT_MISSING_EMAIL, // missing mandatory EMAIL field in POST DATA + PROTECT_INVALID_EMAIL, // ill-formed email address + PROTECT_MISSING_REASON, // missing connection's reason + PROTECT_UNKNOWN_REASON, // unknown connection's reason + PROTECT_MISSING_CLIENTID, // missing client connection ID in POST DATA PROTECT_BAD_DATA, // missing mandatory fields in POST DATA PROTECT_NOT_CONNECTED, // not connected to server -- must connect first PROTECT_CONNECTION_EXPIRED, // server connection timeout -- should refresh more often PROTECT_LICENSE_EXPIRED, // testing license expired + PROTECT_LICENSE_NOT_ACTIVATED, // license requested but still not activated by user PROTECT_UNREGISTERED, // product unregistered PROTECT_LICENSES_NUMBER_EXCEEDED, // number of product licenses exceeded (too many apps running) PROTECT_MAIL_ALREADY_USED, // e-mail already used (and expired), can't register - PROTECT_INVALID_APP_VERSION // your license isn't valid for this APP version, should upgrade + PROTECT_INVALID_APP_VERSION, // your license isn't valid for this APP version, should upgrade + PROTECT_MAIL_SEND_ERROR // smtp error sending email } ProtectStatus; // gets human-readable error message @@ -26,7 +33,7 @@ extern String ProtectMessage(int m); // server request reasons typedef enum { - PROTECT_UNKNOWN_REASON, // internal error + PROTECT_BAD_REASON, // internal error PROTECT_CONNECT, // establish connection to server PROTECT_DISCONNECT, // frees server connection PROTECT_REFRESH, // refreshes server connection (to restart timeout) diff --git a/bazaar/Protect/init b/bazaar/Protect/init index 7cf31ff7a..a110a890d 100644 --- a/bazaar/Protect/init +++ b/bazaar/Protect/init @@ -1,5 +1,7 @@ #ifndef _Protect_icpp_init_stub #define _Protect_icpp_init_stub #include "Core/init" +#include "ScgiServer/init" #include "Cypher/init" +#include "MySql/init" #endif diff --git a/bazaar/ProtectClientDemo/ProtectClientDemo.cpp b/bazaar/ProtectClientDemo/ProtectClientDemo.cpp index ce3732016..0e091ba91 100644 --- a/bazaar/ProtectClientDemo/ProtectClientDemo.cpp +++ b/bazaar/ProtectClientDemo/ProtectClientDemo.cpp @@ -34,11 +34,7 @@ ProtectClientDemo::ProtectClientDemo() quitButton <<= Breaker(); -// urlEdit <<= "www.timberstruct.it/scgi/testing"; - urlEdit <<= "localhost/scgi/testing"; - keyEdit <<= "aabbccddeeff00112233445566778899"; - emailEdit <<= "demo@timberstruct.it"; - licenseIDEdit <<= "21309602655597975"; + emailEdit <<= "test@testme.net"; } ProtectClientDemo::~ProtectClientDemo() @@ -51,14 +47,19 @@ void ProtectClientDemo::onAction(int reason) responseText <<= ""; // setup client url and key - client.SetURL(urlEdit); - client.SetKey(ScanHexString(keyEdit)); + client.SetURL("localhost/scgi/testing"); +// client.SetURL("www.timberstruct.it/scgi/testing"); + client.SetKey(ScanHexString("aabbccddeeff00112233445566778899")); - // builds data vector map - VectorMap userData; - - userData.Add("EMAIL", ~emailEdit); - userData.Add("LICENSEID", ~licenseIDEdit); + // sets user data + client.SetUserName(~nameEdit); + client.SetUserAddress(~addressEdit); + client.SetUserCountry(~countryEdit); + client.SetUserZip(~zipEdit); + client.SetUserPhone(~phoneEdit); + client.SetUserFax(~faxEdit); + client.SetUserCell(cellEdit); + client.SetUserEMail(~emailEdit); String res; String key; diff --git a/bazaar/ProtectClientDemo/ProtectClientDemo.lay b/bazaar/ProtectClientDemo/ProtectClientDemo.lay index c41c0f709..b0ee6e1c4 100644 --- a/bazaar/ProtectClientDemo/ProtectClientDemo.lay +++ b/bazaar/ProtectClientDemo/ProtectClientDemo.lay @@ -1,20 +1,28 @@ -LAYOUT(ProtectClientDemoLayout, 552, 416) - ITEM(LineEdit, responseText, SetEditable(false).SetFrame(NullFrame()).LeftPosZ(4, 544).TopPosZ(136, 276)) - ITEM(EditString, keyEdit, LeftPosZ(116, 432).TopPosZ(32, 19)) - ITEM(Button, quitButton, SetLabel(t_("Quit")).LeftPosZ(480, 68).TopPosZ(84, 20)) - ITEM(Label, dv___3, SetLabel(t_("Communication Key:")).LeftPosZ(4, 108).TopPosZ(32, 20)) - ITEM(EditString, urlEdit, LeftPosZ(116, 432).TopPosZ(8, 19)) - ITEM(Label, dv___5, SetLabel(t_("Server response :")).LeftPosZ(4, 256).TopPosZ(112, 20)) - ITEM(Label, dv___6, SetLabel(t_("Server Url :")).LeftPosZ(4, 108).TopPosZ(8, 20)) - ITEM(EditString, licenseIDEdit, LeftPosZ(344, 204).TopPosZ(56, 19)) - ITEM(Label, dv___8, SetLabel(t_("LicenseID :")).HSizePosZ(272, 212).TopPosZ(56, 20)) - ITEM(Label, dv___9, SetLabel(t_("EMail:")).LeftPosZ(4, 40).TopPosZ(56, 20)) - ITEM(EditString, emailEdit, LeftPosZ(52, 200).TopPosZ(56, 19)) - ITEM(Button, refreshButton, SetLabel(t_("Refresh")).LeftPosZ(156, 68).TopPosZ(84, 20)) - ITEM(Button, disconnectButton, SetLabel(t_("Disconnect")).LeftPosZ(232, 68).TopPosZ(84, 20)) - ITEM(Button, getInfoButton, SetLabel(t_("Get Info")).HSizePosZ(384, 100).TopPosZ(84, 20)) - ITEM(Button, registerButton, SetLabel(t_("Register")).LeftPosZ(4, 68).TopPosZ(84, 20)) - ITEM(Button, getKeyButton, SetLabel(t_("Get Key")).HSizePosZ(308, 176).TopPosZ(84, 20)) - ITEM(Button, connectButton, SetLabel(t_("Connect")).LeftPosZ(80, 68).TopPosZ(84, 20)) +LAYOUT(ProtectClientDemoLayout, 552, 456) + ITEM(Label, dv___0, SetLabel(t_("Name:")).LeftPosZ(4, 48).TopPosZ(4, 20)) + ITEM(Label, dv___1, SetLabel(t_("Address:")).LeftPosZ(4, 48).TopPosZ(28, 20)) + ITEM(Label, dv___2, SetLabel(t_("Country:")).LeftPosZ(344, 52).TopPosZ(28, 20)) + ITEM(Label, dv___3, SetLabel(t_("Zip:")).LeftPosZ(344, 52).TopPosZ(52, 20)) + ITEM(Label, dv___4, SetLabel(t_("Phone:")).HSizePosZ(4, 500).TopPosZ(76, 20)) + ITEM(Label, dv___5, SetLabel(t_("Fax:")).LeftPosZ(176, 48).TopPosZ(76, 20)) + ITEM(Label, dv___6, SetLabel(t_("Cell:")).LeftPosZ(344, 48).TopPosZ(76, 20)) + ITEM(Label, dv___7, SetLabel(t_("EMail:")).LeftPosZ(4, 40).TopPosZ(100, 20)) + ITEM(Label, dv___8, SetLabel(t_("Server response :")).LeftPosZ(4, 256).TopPosZ(152, 20)) + ITEM(EditString, nameEdit, LeftPosZ(56, 492).TopPosZ(4, 19)) + ITEM(LineEdit, addressEdit, LeftPosZ(56, 280).TopPosZ(28, 44)) + ITEM(EditString, countryEdit, LeftPosZ(400, 148).TopPosZ(28, 19)) + ITEM(EditString, zipEdit, LeftPosZ(400, 148).TopPosZ(52, 19)) + ITEM(EditString, phoneEdit, LeftPosZ(56, 112).TopPosZ(76, 19)) + ITEM(EditString, faxEdit, LeftPosZ(228, 108).TopPosZ(76, 19)) + ITEM(EditString, cellEdit, LeftPosZ(400, 148).TopPosZ(76, 19)) + ITEM(EditString, emailEdit, LeftPosZ(56, 200).TopPosZ(100, 19)) + ITEM(Button, registerButton, SetLabel(t_("Register")).LeftPosZ(4, 68).TopPosZ(124, 20)) + ITEM(Button, connectButton, SetLabel(t_("Connect")).LeftPosZ(80, 68).TopPosZ(124, 20)) + ITEM(Button, refreshButton, SetLabel(t_("Refresh")).LeftPosZ(156, 68).TopPosZ(124, 20)) + ITEM(Button, disconnectButton, SetLabel(t_("Disconnect")).LeftPosZ(232, 68).TopPosZ(124, 20)) + ITEM(Button, getKeyButton, SetLabel(t_("Get Key")).HSizePosZ(308, 176).TopPosZ(124, 20)) + ITEM(Button, getInfoButton, SetLabel(t_("Get Info")).HSizePosZ(384, 100).TopPosZ(124, 20)) + ITEM(Button, quitButton, SetLabel(t_("Quit")).LeftPosZ(480, 68).TopPosZ(124, 20)) + ITEM(LineEdit, responseText, SetEditable(false).SetFrame(NullFrame()).LeftPosZ(4, 544).TopPosZ(176, 276)) END_LAYOUT diff --git a/bazaar/ProtectServerDemo/main.cpp b/bazaar/ProtectServerDemo/main.cpp index f54f8ff9e..65ae811c9 100644 --- a/bazaar/ProtectServerDemo/main.cpp +++ b/bazaar/ProtectServerDemo/main.cpp @@ -6,6 +6,21 @@ CONSOLE_APP_MAIN { ProtectServer server; + // setup database + server + .SetAppKey(ScanHexString("AABBCCDDEEFF00112233445566778899")) + .GetDB() + .SetHost("localhost") + .SetDBName("protect_demo") + .SetUserName("protect_user") + .SetPassword("protect") + ; + server + .GetSmtp() + .Host("localhost") + .Port(25) + ; + // setup key server.SetKey(ScanHexString("aabbccddeeff00112233445566778899"));