Bazaar/Protect : Almost completed web authentication package

git-svn-id: svn://ultimatepp.org/upp/trunk@2773 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
micio 2010-10-12 23:08:09 +00:00
parent 278689a015
commit 5a09fb48d5
14 changed files with 693 additions and 168 deletions

View file

@ -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,

View file

@ -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<String, Value> ProtectClient::SendMap(VectorMap<String, Value> const &
// copy vectormap adding client id and a magic number
VectorMap<String, Value>dataMap(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<String, Value> ProtectClient::SendMap(VectorMap<String, Value> const &
// if contents start with "ERROR", just fetch the error desc
// and put it in result map
VectorMap<String, Value> 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
VectorMap<String, Value>v;
v.Add("REASON", ProtectReasonStr(PROTECT_CONNECT));
v.Add("REASON", PROTECT_CONNECT);
v.Add("EMAIL", userEMail);
VectorMap<String, Value> 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
VectorMap<String, Value>v;
v.Add("REASON", ProtectReasonStr(PROTECT_DISCONNECT));
v.Add("REASON", PROTECT_DISCONNECT);
v.Add("CLIENTID", (int)clientID);
VectorMap<String, Value> 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
VectorMap<String, Value>v;
v.Add("REASON", ProtectReasonStr(PROTECT_REFRESH));
v.Add("REASON", PROTECT_REFRESH);
v.Add("CLIENTID", (int)clientID);
VectorMap<String, Value> 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
VectorMap<String, Value>v;
v.Add("REASON", ProtectReasonStr(PROTECT_GETKEY));
v.Add("REASON", PROTECT_GETKEY);
v.Add("CLIENTID", (int)clientID);
VectorMap<String, Value> res = SendMap(v);
// check for errors
@ -204,7 +194,8 @@ bool ProtectClient::GetLicenseInfo(void)
// sends a getinfo packet to server
VectorMap<String, Value>v;
v.Add("REASON", ProtectReasonStr(PROTECT_GETLICENSEINFO));
v.Add("REASON", PROTECT_GETLICENSEINFO);
v.Add("CLIENTID", (int)clientID);
VectorMap<String, Value> 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
VectorMap<String, Value>v;
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<String, Value> res = SendMap(v);
// check for errors

View file

@ -22,14 +22,8 @@ class ProtectClient
// in Cypher package
One<Cypher>cypher;
// 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);

View file

@ -0,0 +1,149 @@
#include "ProtectDB.h"
NAMESPACE_UPP
#include <Sql/sch_schema.h>
#include <Sql/sch_source.h>
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<String, Value> ProtectDB::Get(String const &mail)
{
VectorMap<String, Value> 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<String, Value> const &d)
{
String eMail = d.Get("EMAIL");
// fill missing fields with default values
VectorMap<String, Value>data = 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<String, Value> ProtectDB::Default(VectorMap<String, Value> const &base)
{
VectorMap<String, Value> 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

View file

@ -0,0 +1,61 @@
#ifndef _Protect_ProtectDB_h_
#define _Protect_ProtectDB_h_
#include <MySql/MySql.h>
NAMESPACE_UPP
#define SCHEMADIALECT <MySql/MySqlSchema.h>
#define MODEL <Protect/ProtectDB.sch>
#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
VectorMap<String, Value>Get(String const &mail);
// set/append data
bool Set(VectorMap<String, Value> const &data);
// get default record
VectorMap<String, Value> Default(VectorMap<String, Value> const &base = VectorMap<String, Value>());
};
END_UPP_NAMESPACE
#endif

View file

@ -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

View file

@ -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<String, Value> data;
VectorMap<String, Value> 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<String, Value> 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<String, Value> 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<String, Value> ProtectServer::ProcessRequest(int reason, VectorMap<String, Value> const &v)
{
VectorMap<String, Value> res;
dword clientID;
VectorMap<String, Value> 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
VectorMap<String, Value>vs(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<String, Value> 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

View file

@ -6,6 +6,7 @@
#include <Cypher/Cypher.h>
#include "ProtectStatus.h"
#include "ProtectDB.h"
NAMESPACE_UPP
@ -13,11 +14,28 @@ class ProtectServer : public ScgiServer
{
private:
struct TimeMail : Moveable<TimeMail>
{
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<String, Time> clients;
// list of all connected clients with their expiration dates and emails
VectorMap<dword, TimeMail> clientLinks;
// number of succesful connections per registered email
VectorMap<String, int> registeredConnections;
// encryption engine
One<Cypher> 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<String, Value> on input from client
// produces a response VectorMap<String, Value> to be returned
virtual VectorMap<String, Value> ProcessRequest(int reason, VectorMap<String, Value> const &v);
// Application key to be returned on auth success
String appKey;
// sends activation mail to user
bool SendActivationMail(VectorMap<String, Value> 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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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<String, Value> 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;

View file

@ -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

View file

@ -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"));