IP 通告(通过电子邮件)
通过电子邮件将本地计算机的网络信息发送到远程计算机。
引言
此工具用于通过执行命令来收集系统信息,然后通过网络(在本版本中是通过电子邮件通过 SMTP)将信息发送到特定目的地,即使是穿越互联网。有关 SMTP 的信息,请参考:RFC821。
背景
您是否曾打电话给朋友,请他帮忙调试您的 PC,但因为他不愿花费时间教您什么是 IP 地址,而您也无法让他连接到您的电脑而放弃?您是否曾联系 IT 支持工程师解决一个简单问题,但他却想让您了解大量无用的信息?
您是否曾被叫去支持那些对计算机知之甚少的朋友、父母、客户或老板?也许他们唯一的电脑使用经验就是阅读和撰写电子邮件。当您离他们很远,而他们却说“你是他们唯一信任的人”时,这种情况是否经常发生?因此,您可能希望通过连接到他们的计算机来解决问题。当您尝试这样做时,解释什么是 IP 地址是否经常让您感到沮丧?
您是否曾想监控计算机的启动时间?也许您也希望在用户登录后,通过网络(例如电子邮件)发送通知,而不是查看那些枯燥的事件日志。您是否希望通知包含更多有用的信息,例如它从 DHCP 获取的 IP 地址?您是否曾想知道谁登录了计算机?
这个工具就是为您准备的 :)
您是否希望获得一个简单的示例,说明如何执行命令、通过网络发送数据、使用 SMTP、查询注册表以获取信息等,为您的程序提供参考?
该工具的源代码将为您提供一个简单的视图,展示如何将这些技术结合起来以完成任务。这可能不是最好的,但我正在尽力 :)
如何使用
该工具将按以下流程工作:
- 创建一个新进程来运行系统提供的基于命令行的命令(例如 ipconfig.exe)。
- 重定向命令执行的标准输出,并从该输出信息生成一条消息。
- 将消息发送到指定的目标。
要使用此实用程序,只需运行可执行程序或带有一些参数运行程序(所有参数都是可选的)。
usage: IPAnnounceByEmail [-p:X] [-s:IP] [-c:S] [-t:S] [-f:S] [-?]
-p:X Remote port to send to
-s:IP Server's IP address or host name
-c:S Command to execute
-t:S Target email address for the message to be send to
-f:S Source email address that the message is send from
-? Get this usage information
如果未指定参数,工具将执行默认命令:“ipconfig /all”。如果工具无法从当前登录用户的配置文件中找到任何默认电子邮件帐户信息,输出将发送到默认地址:wesley_yang@sohu.com。这些信息可以在源代码中进行更改。
#define COMMAND_TO_EXEC "ipconfig /all" // Change
#define TARGET_ADDRESS "wesley_yang@sohu.com" // Change
#define SOURCE_ADDRESS "wesley_yang@sohu.com"
#define DEFAULT_SUBJECT " "
#define DEFAULT_SERVER_ADD "smtp.sohu.com"
更改这些定义后,您可以重新构建一个 EXE 文件并将其发送给您的朋友或客户。
此工具不需要电子邮件客户端应用程序。它通过嵌入式方法发送电子邮件。对于当前版本仅支持通过 SMTP 电子邮件发送消息公告的工具,在执行端环境中需要一个 SMTP 服务器。
关注点
1. 进程同步
在此实用程序的第一个版本中,我创建了一个新进程来执行“ipconfig.exe”命令。该实用程序运行正常,这意味着输出管道可以从该命令获取正确且完整的输出。在完成其他功能的测试后,我尝试执行一个新的命令,如“ping www.microsoft.com”,这次实用程序不会完全发送该执行的输出。我发现输出管道实际上没有获取我们在控制台中看到的完整输出。这是由于同步问题,主进程没有等待子进程完成。因此,我做了一些增强。
首先,返回子进程的句柄。
HANDLE createChildProcess(char* cmdName) { PROCESS_INFORMATION piProcInfo; STARTUPINFO siStartInfo; ZeroMemory(&piProcInfo,sizeof(PROCESS_INFORMATION)); ZeroMemory(&siStartInfo,sizeof(STARTUPINFO)); siStartInfo.cb = sizeof(STARTUPINFO); CreateProcess(NULL, cmdName, NULL, NULL, true, 0, NULL, NULL, &siStartInfo, &piProcInfo); return piProcInfo.hProcess; }
然后,在创建子进程执行命令时,添加了一个信号来等待命令执行完成。
//Execute the command and get handle of the child process for
//later use. hChildProcess=createChildProcess(cmdName); if (hChildProcess == NULL) ErrorExit ("Command execution error", logFile); if (!SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdOut)) ErrorExit ("Restor Stdout handle error!", logFile); //Here waiting for the command to be completed successfully. //It is useful if command is time correlative such as "ping ..." or
//"tracert ..." etc. if (WaitForSingleObject(hChildProcess, TIME_OUT_SETTING) ==
WAIT_TIMEOUT) { printf ("The command execution time out!\n"); TerminateProcess(hChildProcess, 0); printf ("The command is terminated!\n"); writeLog(logFile, "Command execution time out!"); }
2. 通过网络转换数据
在此实用程序中,数据转换非常重要。我使用 socket2 API 来发送和接收数据。由于网络通信功能内置于 Windows 系统中,程序可以在没有其他依赖的情况下执行。通过网络发送数据将有 3 个步骤:
- 第一步是准备要发送的数据。
- 在数据结构填充完毕并准备好发送后,下一步是初始化与 SMTP 服务器的套接字连接。
- 然后,将数据发送到服务器。
//Here send the announcement. //Steps of sending announcement: // 1. Fill the structure of SmtpMsg // 2. Initialize the network connection // 3. Send announcement setSmtpMessage (cmdResult); initConn (srv.address, srv.port, &ssmtp); printf("Now sending the message. Please wait...\n"); sendSmtpAnnounce();
基本的网络功能位于头文件:baseheader.h,因此需要将 winsock2.h 包含在文件中。
3. 发送电子邮件消息
电子邮件消息基于 SMTP 协议发送。该协议定义在 RFC821 中(参考:ftp://ftp.rfc-editor.org/in-notes/rfc821.txt)。建立与 SMTP 服务器(默认端口为 25)的 TCP/IP 连接后,就可以按照 SMTP 协议定义的格式将数据发送到服务器。
bool sendSmtpAnnounce (void) { char Data2Srv[BUFFER_DEFAULT_SIZE]; char FeedBack[BUFFER_DEFAULT_SIZE]; char* CRLF = "\r\n"; ZeroMemory(Data2Srv, BUFFER_DEFAULT_SIZE); //HELO command session //Used to identify the sender-SMTP to the receiver-SMTP //The sender-SMTP establishes a two-way transmission channel to a
//receiver-SMTP strcpy (Data2Srv, "HELO "); strcat (Data2Srv, srv.address); strcat (Data2Srv, CRLF); sendData (Data2Srv, ssmtp, FeedBack); writeLog(logFile, FeedBack); //MAIL command session //SMTP-sender sends a MAIL command indicating the sender of the
//mail. If the SMTP-receiver can accept mail it responds with an
//OK reply. strcpy (Data2Srv, "MAIL FROM <"); strcat (Data2Srv, msg.FromAdd); ... sendData (Data2Srv, ssmtp, FeedBack); writeLog(logFile, FeedBack); //RCPT command session //The SMTP-sender sends a RCPT command identifying a recipient of
//the mail. If the SMTP-receiver can accept mail for that recipient
//it responds with an OK reply strcpy (Data2Srv, "RCPT TO <"); strcat (Data2Srv, msg.ToAdd); ... sendData (Data2Srv, ssmtp, FeedBack); writeLog(logFile, FeedBack); //DATA command session //The SMTP-sender sends the mail data, terminating with a special
//sequence. If the SMTP-receiver successfully processes the mail
//data it responds with an OK reply. strcpy (Data2Srv, "DATA "); strcat (Data2Srv, CRLF); sendData (Data2Srv, ssmtp, FeedBack); writeLog(logFile, FeedBack); strcpy (Data2Srv, "SUBJECT: "); ... sendData (Data2Srv, ssmtp, FeedBack); writeLog(logFile, FeedBack); //End of SMTP message sending return true; }
4. 检索默认电子邮件帐户信息
在此工具的第一个版本中,电子邮件帐户是在源代码中定义的。虽然这为没有 SMTP 电子邮件发送帐户的用户提供了一个渠道,但也会造成一些不便。因此,在此版本中,已将从用户配置文件获取电子邮件发送信息的功能添加到代码中。提供电子邮件帐户信息的用户配置文件存储在以下注册表项中:
HKEY_CURRENT_USER\Software\Microsoft\Internet Account Manager
要获取这些信息,请使用以下代码:
//Here query the registry to check if the default email
//account setting is available or not. //If yes, use those value to set the global various. //If no, use the default value defined in these codes. if (getRegInfo(STR_VALUE, "Software\\Microsoft\\Internet Account Manager",
"Default Mail Account", defMailAccount, NULL)) { defSmtpInfo = true; strcpy (KEY,
"Software\\Microsoft\\Internet Account Manager\\Accounts\\"); strcat (KEY, defMailAccount); } if (defSmtpInfo && getRegInfo(STR_VALUE, KEY,
"SMTP Email Address", msg.FromAdd = new char [REG_BUFF], NULL)); else msg.FromAdd = SOURCE_ADDRESS;
函数 getRegInfo
定义在 baseheader.h 头文件中。
预定义的电子邮件信息仍然存在;同时,该工具也可以按以下顺序获取电子邮件发送信息:
- 参数。 使用命令行参数指定的信息。
- 用户配置文件。 如果参数中没有所需的电子邮件发送信息,工具将使用从注册表设置或所谓的用户配置文件中获取的电子邮件帐户信息。将使用默认帐户。
- 代码中的默认值。 如果此工具所需的执行信息既没有在参数中指定,也无法从用户配置文件中获取(例如,注册表中不存在默认帐户),则将使用预定义的默认信息。