在本系列文章的第一章已经说明了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编译通过,并已经过简单测试。