在本系列文章的第一章已經說明了USB S/N Checker程序的作用,本章將詳細說明這個程序的編制思路,並附完整的源代碼。
由於歷史原因,USB S/N Checker在實現時被命名爲usbdevicelogger(以下簡稱UDL)。
UDL用C/C++語言寫成,其main函數如下:
int main(int argc, char* argv[])
{
loadConfig(__configFilename, &config);
/* 獲取USB設備插入時間和主機名 */
time_t currTime = time(NULL);
struct tm *plugTime = localtime(&currTime);
DWORD hostNameLength = sizeof(computerName) / sizeof(TCHAR); // windows platform only
GetComputerName((LPTSTR)computerName, &hostNameLength); // windows platform only
int serialNumberCount = loadSerialNumber(config.serialNumberFilename);
char *currSerialNumber = argv[idx_serialNumber];
bool found = checkSerialNumber(currSerialNumber, serialNumberCount);
if (!found)
{ // the serial number of the plugging device is illegal.
#ifdef __STOP_DEVICE__
// stop the illegal USB device.
sprintf(sysCmd, "usbdeview /stop_by_serial %s", currSerialNumber);
system(sysCmd);
#endif // __STOP_DEVICE__
// call webservice of NTMS to record the illegal action.
notifyToNtms(plugTime, computerName, argc, argv);
}
saveDeviceInfo(plugTime, computerName, argc, argv, found);
return found ? SUCCESS : ERROR_SN_ILLEGAL;
}
main函數揭示了UDL的大體運行流程:
- 首先獲取當前時間,由於UDL是在插入U盤時被usbdeview激活,因此可以認爲UDL啓動運行的時間就是U盤插入的時間;
- 隨後調用Win32 API中的GetComputerName函數獲取當前主機名,這是需要被記入日誌的重要元素;
- 從磁盤文件(默認爲legal_sn.txt)中加載合法的U盤序列號清單,這個序列號清單文件是文本文件,每個序列號佔用一行;
- 從命令行參數中獲取當前插入的USB設備的序列號,調用自定義函數checkSerialNumber檢查本次插入的USB設備的序列號是否在合法序列號清單中;
- 如果在合法序列號清單中未找到當前序列號,則使用/stop_by_serial命令調用usbdeview程序禁用這個USB設備,並通知USB Management System(本文命名爲NTMS);
- 將本次USB設備插入事件記錄到本地日誌中;
- 結束。
main函數的第一條語句是調用loadConfig來裝載UDL配置文件udl.ini,這個文件樣式如下:
deviceLog=C:\Windows\usbdevice.log
agentName=ntmsAgent.exe
serialNumber=.\legal_sn.txt
debugLog=.\debug.log
baseUrl="http://ntms.company.com/ntms/illegalusb"
這個文件指定了UDL的運行時屬性,每個參數佔用一行,採用“key=value”的樣式。注意:key和value之間用“=”字符分隔,'='字符前後不能有空格。key不區分大小寫。value中的URL、文件路徑最好用半角雙引號包圍起來。配置文件屬性說明如下:
- deviceLog屬性指定了日誌文件的文件名(完整絕對路徑);
- agentName屬性指定了NTMS服務代理程序,UDL通過這個代理程序向NTMS發送消息;
- serialNumber屬性指定了UDL需要依據的合法序列號文件,可以是相對路徑或絕對路徑;
- debugLog屬性指定了UDL調試信息記錄在哪個文件裏;
- baseUrl屬性指定了URL可以通過瀏覽器向NTMS傳遞USB插入信息的URL(UDL可以通過條件編譯被編譯成繞過NTMS Agent直接調用瀏覽器向NTMS發送數據的模式)。
下面對UDL的部分函數做個說明。
- checkSerialNumber函數
bool checkSerialNumber(char currSerialNumber[], int rangeCount)
{
bool found = false;
for(int index = 0; index < rangeCount; index ++)
{
if (strlen(currSerialNumber) == 0) // for the device that has not serial number.
{
found = true;
break;
}
int result = strcmp(currSerialNumber, legalSerianNumber[index]);
if (result == 0)
{
found = true;
break;
}
}
return found;
}
注意裏面有一段
if (strlen(currSerialNumber) == 0) // for the device that has not serial number.
{
found = true;
break;
}
這是判斷當前插入的USB設備的序列號是否爲空,若爲空,則表示這個設備不是U盤,一般是USB key、USB鍵鼠之類的,這樣的設備可以認爲是合法設備,不用管它。
- notifyToNtms函數
void notifyToNtms(struct tm *plugTime, char hostName[], int argc, char *argv[])
{ // Call NTMS agent to transfer the detail of USB device to NTMS.
int errorCode = 0;
char *dateTimeStr = dateTime2String(plugTime);
sprintf(sysCmd, "%s \"%s\" \"%s\"", config.ntmsAgent, dateTimeStr, hostName);
for(int argIndex = 1; argIndex < argc; argIndex ++)
{
strcat(sysCmd, " \"");
strcat(sysCmd, argv[argIndex]);
strcat(sysCmd, "\"");
}
free(dateTimeStr);
#ifdef __DEBUG_LOG__
logDebug(plugTime, sysCmd);
#endif // __DEBUG_LOG__
#ifdef __INVOKE_NTMSAGENT__
errorCode = system(sysCmd);
if (errorCode == -1)
{
char errMsg[128];
sprintf(errMsg, "system() error code: %d\n", errno);
logDebug(plugTime, errMsg);
}
#endif // __INVOKE_NTMSAGENT__
return;
}
notifyToNtms函數會合成一個調用ntmsAgent代理程序的命令行,存入sysCmd字符數組中。完成命令行構造之後,notifyToNtms函數調用system(sysCmd)來執行通知NTMS的動作。
最後,列出UDL的完整源代碼如下:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#ifdef __cplusplus
#include <string>
using namespace std;
#endif // __cplusplus
/**
關於條件編譯宏定義的說明:
__cplusplus__ 編譯器內置宏。當使用C++編譯器以C++方式編譯此文件時,該宏被編譯器定義。
__STOP_DEVICE__ 若定義了此宏,則插入非法USB設備時,usbdevicelogger會停用(斷開)該設備,在usbdeview
中將看不到此設備。當插入的設備爲USB存儲設備時,其對應的盤符會消失。UDL正式部署前,
應該啓用該宏定義,並重新編譯UDL後部署。
__SAVE_XML__ 若定義了此宏,則插入USB設備時,usbdevicelogger會調用usbdeview將當前接入到計算機中的
所有USB設備詳情保存到usbdevice.xml文件中。如果不需要調用usbdeview保存USB設備記錄,則
無需啓用該宏定義。
__USE_WEBSERVICE__ 若定義了此宏,則插入非法USB設備時,usbdevicelogger會調用NTMS agent將非法設備信息通過
WS接口傳遞給NTMS;否則,將調用系統默認瀏覽器,以URL方式傳遞給NTMS。
__INVOKE_NTMSAGENT__ 若定義了此宏,則會將非法插入設備的信息傳遞給NTMS,傳遞方法由__USE_WEBSERVICE__宏定義
決定。UDL正式部署前,應啓用該宏定義,並重新編譯UDL後部署。
__HIDE_WINDOW__ 若定義了此宏,則usbdevicelogger的窗口不會閃現。
__DEBUG_LOG__ 若定義了此宏,則usbdevicelogger運行時會生成調試日誌,保存在debug.log文件中。當發現UDL
運行異常時,可啓用該宏定義,並重新編譯UDL後部署。
*/
#define __STOP_DEVICE__
// #define __SAVE_XML__
#define __USE_WEBSERVICE__
#define __INVOKE_NTMSAGENT__
#define __USE_CONFIG_FILE__
// #define __HIDE_WINDOW__
#define __DEBUG_LOG__
/** 以下是常數宏定義 */
#define __LEGAL_SN_COUNT__ 1024
#define __SN_LENGTH__ 64
#define __CMDLINE_LENGTH__ 16384
#define __MAX_URL_LENGTH__ 16384
const int SUCCESS = 0;
const int ERROR_SN_ILLEGAL = 1;
const char __configFilename[] = "udl.ini";
const char __deviceLogKey[] = "DEVICELOG";
const char __agentNameKey[] = "AGENTNAME";
const char __debugLogKey[] = "DEBUGLOG";
const char __baseUrlKey[] = "BASEURL";
const char __serialNumberKey[] = "SERIALNUMBER";
const char splitChar = '|';
const char urlParamNames[][32] =
{
"plugTime",
"hostName",
"deviceDescr",
"serialNumber",
"deviceType",
"serviceName",
"deviceClass",
"deviceMfg",
"driverFile",
"driverVersion",
"firmwareVersion",
"productName",
"vendorName",
"legalFlag"
};
typedef struct __tagConfig
{
char logFilename[MAX_PATH + 1];
char ntmsAgent[MAX_PATH + 1];
char serialNumberFilename[MAX_PATH + 1];
char debugLogFilename[MAX_PATH + 1];
char baseUrl[__MAX_URL_LENGTH__ + 1];
} Config;
/*
char logFilename[MAX_PATH + 1] = "C:\\Windows\\usbdevice.log";
char ntmsAgent[MAX_PATH + 1] = "ntmsagent";
char serialNumberFilename[MAX_PATH + 1] = "legal_sn.txt";
char debugLogFilename[MAX_PATH + 1] = "debug.log";
char baseUrl[__MAX_URL_LENGTH__ + 1] = "http://ntms.803.sast.casc/ntms/illegalusb";
*/
Config config =
{
"C:\\Windows\\usbdevice.log",
".\\ntmsagent",
".\\legal_sn.txt",
".\\debug.log",
"http://ntms.803.sast.casc/ntms/illegalusb"
};
char legalSerianNumber[__LEGAL_SN_COUNT__][__SN_LENGTH__ + 1]; // 可以存放__LEGAL_COUNT__個合法序列號,每個序列號256個字節。
char computerName[MAX_COMPUTERNAME_LENGTH + 1];
char sysCmd[__CMDLINE_LENGTH__ + 1];
char ntmsUrl[__MAX_URL_LENGTH__ + 1];
typedef enum __tagDeviceParamsIndex // 用於指示命令行參數中設備參數信息所在的位置索引。
{
idx_deviceDescr = 1,
idx_serialNumber = 2,
idx_deviceType = 3,
idx_serviceName = 4,
idx_deviceClass = 5,
idx_deviceMfg = 6,
idx_driverFile = 7,
idx_driverVersion = 8,
idx_firmwareVersion = 9,
idx_productName = 10,
idx_vendorName = 11
} DeviceParamsIndex;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
WINBASEAPI HWND WINAPI GetConsoleWindow();
int loadConfig(const char configFile[], Config *config);
void saveDeviceInfo(struct tm *plugTime, char hostName[], int argc, char* argv[], bool legal); // 保存當前插入的USB設備的詳細信息到文件中。
int loadSerialNumber(const char fileName[]); // 讀入合法的USB設備序列號,返回值爲讀入的序列號個數。
bool checkSerialNumber(char currSerialNumber[], int rangeCount); // 檢查插入的USB設備序列號是否合法。合法則返回true,非法則返回false。
void notifyToNtms(struct tm *plugTime, char hostName[], int argc, char *argv[]); // 將非法USB插入記錄提交給NTMS。
void composeUrl(struct tm *plugTime, char hostName[], int argc, char *argv[], char url[]);
char *dateTime2String(struct tm *dateTime);
int replaceSpace(char src[], char dest[]);
void strupr(char str[]);
void logDebug(struct tm *plugTime, char logStr[]);
#ifdef __cplusplus
}
#endif // __cplusplus
int main(int argc, char* argv[])
{
#ifdef __HIDE_WINDOW__
HWND consoleWindowHandle = GetConsoleWindow();
ShowWindow(consoleWindowHandle, SW_HIDE);
#endif // __HIDE_WINDOW__
loadConfig(__configFilename, &config);
/* 獲取USB設備插入時間和主機名 */
time_t currTime = time(NULL);
struct tm *plugTime = localtime(&currTime);
DWORD hostNameLength = sizeof(computerName) / sizeof(TCHAR); // windows platform only
GetComputerName((LPTSTR)computerName, &hostNameLength); // windows platform only
int serialNumberCount = loadSerialNumber(config.serialNumberFilename);
char *currSerialNumber = argv[idx_serialNumber];
bool found = checkSerialNumber(currSerialNumber, serialNumberCount);
if (!found)
{ // the serial number of the plugging device is illegal.
#ifdef __STOP_DEVICE__
// stop the illegal USB device.
sprintf(sysCmd, "usbdeview /stop_by_serial %s", currSerialNumber);
system(sysCmd);
#endif // __STOP_DEVICE__
// call webservice of NTMS to record the illegal action.
notifyToNtms(plugTime, computerName, argc, argv);
}
saveDeviceInfo(plugTime, computerName, argc, argv, found);
return found ? SUCCESS : ERROR_SN_ILLEGAL;
}
int loadConfig(const char configFile[], Config *config)
{
int loadCode = 0;
#ifdef __USE_CONFIG_FILE__
const char splitter = '=';
char *valuePtr;
char key[32];
char buffer[__MAX_URL_LENGTH__ + 1];
FILE *cfgFp = fopen(configFile, "r");
if (cfgFp != NULL)
{
loadCode = 0;
do {
fscanf(cfgFp, "%s", buffer);
valuePtr = strchr(buffer, splitter);
if (valuePtr == NULL)
{
loadCode = -2;
break;
}
*valuePtr = '\0';
valuePtr ++;
// strncpy(key, buffer, strlen(buffer) - strlen(valuePtr) + 1);
strcpy(key, buffer);
strupr(key);
if (!strcmp(key, __deviceLogKey))
{
strcpy(config->logFilename, valuePtr);
}
else if (!strcmp(key, __agentNameKey))
{
strcpy(config->ntmsAgent, valuePtr);
}
else if (!strcmp(key, __serialNumberKey))
{
strcpy(config->serialNumberFilename, valuePtr);
}
else if (!strcmp(key, __baseUrlKey))
{
strcpy(config->baseUrl, valuePtr);
}
else if (!strcmp(key, __debugLogKey))
{
strcpy(config->debugLogFilename, valuePtr);
}
else
{
loadCode = -2;
break;
}
} while(!feof(cfgFp));
fclose(cfgFp);
}
else
{
loadCode = -1;
}
#endif // __USE_CONFIG_FILE__
return loadCode;
}
void saveDeviceInfo(struct tm *plugTime, char hostName[], int argc, char* argv[], bool legal)
{
FILE *ofp = fopen(config.logFilename, "a+");
// record the date-time the USB device was plugged..
fprintf(ofp, "%04d-%02d-%02d %02d:%02d:%02d",
plugTime->tm_year + 1900, plugTime->tm_mon + 1, plugTime->tm_mday,
plugTime->tm_hour, plugTime->tm_min, plugTime->tm_sec);
// record the computer name.
fprintf(ofp, "%c%s", splitChar, hostName);
// record the detail of the plugged USB device.
for(int argIdx = 1; argIdx < argc; argIdx ++)
{
fprintf(ofp, "%c%s", splitChar, argv[argIdx]);
}
fprintf(ofp, "%c%c", splitChar, legal ? 'Y' : 'N');
fprintf(ofp, "\n");
fclose(ofp);
#ifdef __SAVE_XML__
system("usbdeview /sxml usbdevice.xml"); // optional //
#endif // __SAVE_XML__
}
int loadSerialNumber(const char fileName[])
{
int count = 0;
FILE *ifp = fopen(fileName, "r");
if (ifp != NULL)
{
do {
fscanf(ifp, "%s", legalSerianNumber[count]);
count ++;
} while(!feof(ifp));
fclose(ifp);
}
return count;
}
bool checkSerialNumber(char currSerialNumber[], int rangeCount)
{
bool found = false;
for(int index = 0; index < rangeCount; index ++)
{
if (strlen(currSerialNumber) == 0) // for the device that has not serial number.
{
found = true;
break;
}
int result = strcmp(currSerialNumber, legalSerianNumber[index]);
if (result == 0)
{
found = true;
break;
}
}
return found;
}
void notifyToNtms(struct tm *plugTime, char hostName[], int argc, char *argv[])
{ // Call NTMS agent to transfer the detail of USB device to NTMS.
int errorCode = 0;
char *dateTimeStr = dateTime2String(plugTime);
sprintf(sysCmd, "%s \"%s\" \"%s\"", config.ntmsAgent, dateTimeStr, hostName);
for(int argIndex = 1; argIndex < argc; argIndex ++)
{
strcat(sysCmd, " \"");
strcat(sysCmd, argv[argIndex]);
strcat(sysCmd, "\"");
}
free(dateTimeStr);
#ifdef __DEBUG_LOG__
logDebug(plugTime, sysCmd);
#endif // __DEBUG_LOG__
#ifdef __INVOKE_NTMSAGENT__
errorCode = system(sysCmd);
if (errorCode == -1)
{
char errMsg[128];
sprintf(errMsg, "system() error code: %d\n", errno);
logDebug(plugTime, errMsg);
}
#endif // __INVOKE_NTMSAGENT__
return;
}
void logDebug(struct tm *plugTime, char logStr[])
{
FILE *logFp = fopen(config.debugLogFilename, "a");
char *dtStr = dateTime2String(plugTime);
fprintf(logFp, "%s%c%s\n", dtStr, splitChar, logStr);
free(dtStr);
fclose(logFp);
// system("pause");
}
char *dateTime2String(struct tm *dateTime)
{
char *dateTimeString = (char *)malloc(64);
if (dateTime != NULL)
{ // if convert success, the memory space of dateTimeString will not be freed.
sprintf(dateTimeString, "%04d-%02d-%02d %02d:%02d:%02d",
dateTime->tm_year + 1900, dateTime->tm_mon + 1, dateTime->tm_mday,
dateTime->tm_hour, dateTime->tm_min, dateTime->tm_sec);
}
else
{
free(dateTimeString);
dateTimeString = NULL;
}
return dateTimeString;
}
void strupr(char str[])
{
int length = strlen(str);
for(int index = 0; index < length; index ++)
{
if ((str[index] >= 'a') && (str[index] <= 'z'))
{
str[index] -= 0x20;
}
}
}
以上代碼用Code::Blocks + MinGW 5.10編譯通過,並已經過簡單測試。