campo-sirio/SSAservice/ESignService.cpp

544 lines
16 KiB
C++
Raw Blame History

#pragma region Includes
#include "ESignService.h"
#include "ThreadPool.h"
#include "esigner.h"
#include <cassert>
#include <zmq.h>
#pragma endregion
///////////////////////////////////////////////////////////
// Utility
///////////////////////////////////////////////////////////
static std::string GetIniString(const char* key)
{
char val[_MAX_PATH];
::GetPrivateProfileStringA("Default", key, "", val, sizeof(val), "./esigner.ini");
return val;
}
static int GetIniInteger(const char* key)
{
char val[_MAX_PATH];
::GetPrivateProfileStringA("Default", key, "", val, sizeof(val), "./esigner.ini");
return atoi(val);
}
///////////////////////////////////////////////////////////
// CEsigner
///////////////////////////////////////////////////////////
class CEsigner
{
HMODULE _hESigner;
ESignerSignProto _SignPDF;
ESignerVerifyProto _VerifyPDF;
bool m_bMark;
int m_nNum;
std::string m_strDLL, m_strPFX, m_strPEM, m_strCER, m_strEXT;
std::string m_strTSAurl, m_strTSAuser, m_strTSApwd, m_strTSApolicy, m_strTSAcoding;
std::string* _socket;
protected:
void Log(const char* fmt, ...) const;
bool EsitoZero(const CPathName& strResult) const;
public:
bool IsOk() const { return _hESigner != NULL; }
void SetOutputString(std::string& s) { _socket = &s; }
bool Sign(const CPathName& strInput, CPathName& strOutput, const CPathName& strBackup,
const std::string& strPin, const std::string& strExt) const;
CEsigner();
virtual ~CEsigner();
};
void CEsigner::Log(const char* fmt, ...) const
{
char buffer[512];
va_list ap;
va_start(ap, fmt);
vsprintf_s(buffer, sizeof(buffer), fmt, ap);
va_end(ap);
if (_socket != NULL)
{
*_socket += buffer;
*_socket += "\r\n";
}
else
printf("%s\n", buffer);
}
CEsigner::CEsigner() : m_bMark(false), m_nNum(0), _socket(NULL)
{
char full[_MAX_PATH]; ::GetModuleFileNameA(NULL, full, sizeof(full));
CPathName pn = full;
::SetCurrentDirectoryA(pn.Path().c_str());
_hESigner = ::LoadLibrary(TEXT("esigner.dll"));
if (_hESigner)
{
_SignPDF = (ESignerSignProto)::GetProcAddress(_hESigner, "Sign");
_VerifyPDF = (ESignerVerifyProto)::GetProcAddress(_hESigner, "Verify");
m_strCER = GetIniString("CER");
m_strDLL = GetIniString("DLL");
m_strPEM = GetIniString("PEM");
m_strPFX = GetIniString("PFX");
m_strEXT = GetIniString("EXT"); // PDF or P7M
if (m_strEXT.empty())
m_strEXT = "p7m";
const int nIdx = GetIniInteger("IDX");
if (nIdx > 0)
m_nNum = nIdx;
}
}
CEsigner::~CEsigner()
{
if (_hESigner)
::FreeLibrary(_hESigner);
}
bool CEsigner::EsitoZero(const CPathName& strResult) const
{
bool positive = false;
FILE* f = NULL;
if (fopen_s(&f, strResult.c_str(), "r") == 0)
{
char buffer[512];
while (!feof(f))
{
fgets(buffer, sizeof(buffer), f);
if (strstr(buffer, "ESITO=\"000\"") != NULL)
{
positive = true;
break;
}
}
fclose(f);
}
return positive;
}
bool CEsigner::Sign(const CPathName& strInput, CPathName& strOutput, const CPathName& strBackup,
const std::string& strPin, const std::string& strExt) const
{
if (_hESigner == NULL)
{
Log("Impossibile caricare esigner.dll");
return false;
}
std::string strMethod;
if (!m_strDLL.empty()) // Token
{
strMethod = "T";
Log("Firma tramite token");
} else
if (!m_strPFX.empty())
{
strMethod = "P";
Log("Firma tramite file PFX");
} else
if (!m_strCER.empty())
{
strMethod = "F";
Log("Firma tramite i file CER e PEM");
}
else
{
Log("Impossibile trovare certificati o token.\nVerificare i parametri in esigner.ini");
return false;
}
std::string strOperation = "S";
const bool bIsTSA = m_bMark && !m_strTSAurl.empty();
if (bIsTSA) // Firma con marcatura temporale
strOperation += "+T";
const bool bIsDir = strInput.IsDirectory();
if (bIsDir)
strOperation += "+D";
std::string strIndex;
strIndex << m_nNum;
char* operation = (char*)strOperation.c_str();
char* method = (char*)strMethod.c_str();
char* ext = (char*)(strExt.empty() ? m_strEXT.c_str() : strExt.c_str());
char* input = (char*)strInput.c_str();
char* output = (char*)strOutput.c_str();
char* cer = strMethod == "F" ? (char*)m_strCER.c_str() : NULL;
char* pem = strMethod == "F" ? (char*)m_strPEM.c_str() : NULL;
char* pfx = strMethod == "P" ? (char*)m_strPFX.c_str() : NULL;
char* idx = (char*)strIndex.c_str();
char* pin = (char*)strPin.c_str();
char* dll = strMethod == "T" ? (char*)m_strDLL.c_str() : NULL;
char* TSA_output = NULL;
char* TSA_url = NULL;
char* TSA_user = NULL;
char* TSA_pwd = NULL;
char* TSA_policy = NULL;
char* TSA_coding = NULL;
char* TSA_rootCA = NULL;
if (bIsTSA) // Firma con marcatura temporale
{
TSA_url = (char*)m_strTSAurl.c_str();
TSA_user = (char*)m_strTSAuser.c_str();
TSA_pwd = (char*)m_strTSApwd.c_str();
TSA_policy = (char*)m_strTSApolicy.c_str();
TSA_coding = (char*)m_strTSAcoding.c_str();
}
if (bIsDir)
{
if (strOutput.empty())
output = input;
}
else
{
//ext = strInput.Lower().EndsWith(".pdf") ? ".pdf.p7m" : ".p7m";
if (strOutput.empty())
{
strOutput = strInput;
if (strExt == "p7m")
strOutput += ".p7m";
}
::DeleteFileA(strOutput.c_str()); // Altrimenti la fantastica dll s'incazza come una biscia
while (!strOutput.Ext().empty())
strOutput.SetExt("");
output = (char*)strOutput.c_str();
}
int res = _SignPDF(operation, method,
input, output, NULL, ext,
cer, pem, pfx, dll, pin, NULL, idx,
TSA_url, TSA_user, TSA_pwd, TSA_policy, TSA_coding, NULL);
int nSigned = 0;
if (bIsDir) // +D only
{
std::vector<CPathName> infiles;
CPathName pnInput; pnInput.Set(strInput.c_str(), "*", "pdf");
const size_t n = pnInput.List(infiles);
if (n > 0)
{
Log("Trovati %d documenti da firmare in %s", n, strInput.c_str());
res = 0;
CPathName pnResult;
for (size_t i = 0; i < n; i++)
{
const CPathName& fi = infiles[i];
pnResult.Set(strOutput.c_str(), fi.FullName().c_str(), "result");
if (pnResult.IsFile() && EsitoZero(pnResult))
{
::DeleteFileA(pnResult.c_str());
nSigned++;
}
else
{
Log("Impossibile firmare il file %s", fi.c_str());
res = -99;
}
}
}
int mode = 0; // 0=nulla; 1=cancella; 2=backup
if (strBackup == "Cestino" || strBackup == "NULL" || strBackup == "Delete" ||
strBackup == "Basket" || strBackup == "Trash")
mode = 1; else
if (strBackup.IsDirectory())
mode = 2;
if (res == 0 && mode != 0 && n > 0)
{
std::string msg;
if (mode == 1)
{
msg = "Eliminazione file originali in ";
msg += strInput;
}
else
{
msg = "Spostamento file originali da ";
msg += strInput;
msg += " a ";
msg += strBackup;
}
for (size_t i = 0; i < n; i++)
{
const CPathName& fsrc = infiles[i];
CPathName fout = fsrc;
fout.SetPath(strBackup.c_str());
if (mode == 2)
::MoveFileA(fsrc.c_str(), fout.c_str());
else
::DeleteFileA(fsrc.c_str());
}
}
}
else
{
Log("Un documento da firmare in %s", strInput.c_str());
nSigned == res ? 0 : 1;
}
if (res == 0)
{
if (nSigned > 0)
Log("Sono stati firmati %d documenti in %s", nSigned, output);
else
Log("Non <20> stato firmato alcun documento");
}
else
{
const char* msg = NULL;
switch (res)
{
case -2: msg = "Errore di lettura del certificato"; break;
case -3: msg = "Errore di lettura della chiave privata"; break;
case -4: msg = "Errore di lettura file PFX"; break;
case -5: msg = "Errore recupero certificato da Token"; break;
case -6: msg = "Errore apertura file"; break;
case -7:
case -8:
case -9: msg = "Errore conatatto TSA"; break;
case -10: msg = "Accesso al token fallito"; break;
case -11: msg = "Impossibile trovare dll driver del token"; break;
case -14: msg = "Il file di output esiste gi<67>"; break;
case -15:
case -16: msg = "Errore di marcatura temporale"; break;
case -99: msg = "Impossibile trovare .result per tutti i file della cartella"; break;
default : msg = "Errore di accesso alle operazioni di firma"; break;
}
Log(msg);
}
return res == 0;
}
///////////////////////////////////////////////////////////
// CESignService
///////////////////////////////////////////////////////////
CESignService::CESignService(LPCTSTR pszServiceName, BOOL fCanStop, BOOL fCanShutdown, BOOL fCanPauseContinue)
: CServiceBase(pszServiceName, fCanStop, fCanShutdown, fCanPauseContinue)
{
m_fStopping = FALSE;
// Create a manual-reset event that is not signaled at first to indicate
// the stopped signal of the service.
m_hStoppedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (m_hStoppedEvent == NULL)
throw GetLastError();
}
CESignService::~CESignService(void)
{
if (m_hStoppedEvent)
{
CloseHandle(m_hStoppedEvent);
m_hStoppedEvent = NULL;
}
}
//
// FUNCTION: CESignService::OnStart(DWORD, LPWSTR *)
//
// PURPOSE: The function is executed when a Start command is sent to the
// service by the SCM or when the operating system starts (for a service
// that starts automatically). It specifies actions to take when the
// service starts. In this code sample, OnStart logs a service-start
// message to the Application log, and queues the main service function for
// execution in a thread pool worker thread.
//
// PARAMETERS:
// * dwArgc - number of command line arguments
// * lpszArgv - array of command line arguments
//
// NOTE: A service application is designed to be long running. Therefore,
// it usually polls or monitors something in the system. The monitoring is
// set up in the OnStart method. However, OnStart does not actually do the
// monitoring. The OnStart method must return to the operating system after
// the service's operation has begun. It must not loop forever or block. To
// set up a simple monitoring mechanism, one general solution is to create
// a timer in OnStart. The timer would then raise events in your code
// periodically, at which time your service could do its monitoring. The
// other solution is to spawn a new thread to perform the main service
// functions, which is demonstrated in this code sample.
//
void CESignService::OnStart(DWORD dwArgc, LPWSTR *lpszArgv)
{
// Log a service start message to the Application log.
WriteEventLogEntry(TEXT("ESignService Starting"));
// Queue the main service function for execution in a worker thread.
CThreadPool::QueueUserWorkItem(&CESignService::ServiceLoop, this);
}
char CESignService::ParseHex(const char* hex) const
{
int n = 0;
sscanf_s(hex, "%2X", &n);
return char(n);
}
const char* CESignService::ParseString(const char* equal, std::string& str) const
{
str = "";
const char* s = equal;
for (; *s != '&' && *s > ' '; s++)
{
if (*s == '%')
{
const char h = ParseHex(s+1);
str += h;
s += 2;
}
else
str += *s;
}
return s;
}
bool CESignService::ParseGET(const char* get, CPathName& src, CPathName& dst, CPathName& bck,
std::string& pin, std::string& ext) const
{
src = dst = bck = "";
pin = "";
ext = "";
while (get != NULL)
{
const char* equal = strchr(get, '=');
if (equal != NULL)
{
switch (*(equal-3))
{
case 'B': get = ParseString(equal+1, bck); break; // BCK
case 'D': get = ParseString(equal+1, dst); break; // DST
case 'E': get = ParseString(equal+1, ext); break; // EXT
case 'P': get = ParseString(equal+1, pin); break; // PIN
case 'S': get = ParseString(equal+1, src); break; // SRC
default : break;
}
}
else
get = equal;
}
return !src.empty();
}
//
// FUNCTION: CESignService::ServiceLoop(void)
//
// PURPOSE: The method performs the main function of the service. It runs
// on a thread pool worker thread.
//
void CESignService::ServiceLoop(void)
{
void *ctx = zmq_ctx_new ();
assert (ctx);
/* Create ZMQ_STREAM socket */
void *socket = zmq_socket (ctx, ZMQ_STREAM);
assert (socket);
int nPort = GetIniInteger("PORT");
if (nPort < 1000)
nPort = 8083;
char port[32]; sprintf_s(port, sizeof(port), "tcp://*:%d", nPort);
int rc = zmq_bind (socket, port);
assert (rc == 0);
const int timeout = 5000; // 5 secondi di timeout per verificare anche m_fStopping
zmq_setsockopt (socket, ZMQ_RCVTIMEO, &timeout, sizeof(timeout));
/* Data structure to hold the ZMQ_STREAM ID */
uint8_t id [256];
/* Data structure to hold the ZMQ_STREAM received data */
const size_t raw_max = 512;
char* raw = new char[raw_max];
while (!m_fStopping)
{
/* Get HTTP request; ID frame and then request */
memset(id, 0, sizeof(id));
const int id_size = zmq_recv (socket, id, sizeof(id), 0);
if (id_size <= 0)
continue;
memset(raw, 0, raw_max);
const size_t raw_size = zmq_recv (socket, raw, raw_max, 0);
CPathName src, dst, bck;
std::string pin, ext;
ParseGET(raw, src, dst, bck, pin, ext);
/* Sends the ID frame followed by the response */
zmq_send (socket, id, id_size, ZMQ_SNDMORE);
/* Prepares the response */
std::string log = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n";
CEsigner esigner;
esigner.SetOutputString(log);
esigner.Sign(src, dst, bck, pin, ext);
zmq_send (socket, log.c_str(),log.length(), ZMQ_SNDMORE);
/* Closes the connection by sending the ID frame followed by a zero response */
zmq_send (socket, id, id_size, ZMQ_SNDMORE);
zmq_send (socket, 0, 0, ZMQ_SNDMORE);
/* NOTE: If we don't use ZMQ_SNDMORE, then we won't be able to send more */
/* message to any client */
}
delete [] raw;
zmq_close (socket);
zmq_ctx_destroy (ctx);
// Signal the stopped event.
SetEvent(m_hStoppedEvent);
}
//
// FUNCTION: CESignService::OnStop(void)
//
// PURPOSE: The function is executed when a Stop command is sent to the
// service by SCM. It specifies actions to take when a service stops
// running. In this code sample, OnStop logs a service-stop message to the
// Application log, and waits for the finish of the main service function.
//
// COMMENTS:
// Be sure to periodically call ReportServiceStatus() with
// SERVICE_STOP_PENDING if the procedure is going to take long time.
//
void CESignService::OnStop()
{
// Log a service stop message to the Application log.
WriteEventLogEntry(L"ESignService Stopping");
// Indicate that the service is stopping and wait for the finish of the
// main service function (ServiceWorkerThread).
m_fStopping = TRUE;
if (WaitForSingleObject(m_hStoppedEvent, INFINITE) != WAIT_OBJECT_0)
throw GetLastError();
}