#pragma region Includes #include "ESignService.h" #include "ThreadPool.h" #include "esigner.h" #include #include #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 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 è 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à"; 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(); }