基於USBDeview的自制USB設備監管系統實現(3)——USB S/N Checker

在本系列文章的第一章已經說明了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的大體運行流程:

  1. 首先獲取當前時間,由於UDL是在插入U盤時被usbdeview激活,因此可以認爲UDL啓動運行的時間就是U盤插入的時間;
  2. 隨後調用Win32 API中的GetComputerName函數獲取當前主機名,這是需要被記入日誌的重要元素;
  3. 從磁盤文件(默認爲legal_sn.txt)中加載合法的U盤序列號清單,這個序列號清單文件是文本文件,每個序列號佔用一行;
  4. 從命令行參數中獲取當前插入的USB設備的序列號,調用自定義函數checkSerialNumber檢查本次插入的USB設備的序列號是否在合法序列號清單中;
  5. 如果在合法序列號清單中未找到當前序列號,則使用/stop_by_serial命令調用usbdeview程序禁用這個USB設備,並通知USB Management System(本文命名爲NTMS);
  6. 將本次USB設備插入事件記錄到本地日誌中;
  7. 結束。

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編譯通過,並已經過簡單測試。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章