标题: net user查询命令的自行实现、兼谈学习方法 日期: 2003-12-11 13:09 "net user scz"命令可以查看本机用户信息,比如密码是否过期,但无法查看指定服 务器上指定用户信息。如果做为域用户登录,情况有变化。现在的需求是不想做为域 用户登录,又想查看域控制器上的用户信息。 Samba所带的rpcclient满足需求: -------------------------------------------------------------------------- $ ./rpcclient -U scz Password: rpcclient $> lookupnames scz scz S-1-5-21-999999999-999999999-999999999-1173 rpcclient $> queryuser 1173 User Name : scz Logon Time : Thu, 11 Dec 2003 09:23:33 GMT Password last set Time : Wed, 24 Sep 2003 13:27:03 GMT Password can change Time : Wed, 24 Sep 2003 13:27:03 GMT Password must change Time : Tue, 23 Dec 2003 13:27:03 GMT user_rid : 0x495 group_rid : 0x201 rpcclient $> -------------------------------------------------------------------------- 先用lookupnames获取目标用户的RID,然后用queryuser查询之。上述信息处理过, 只保留了我们感兴趣的部分。 MSDN里找到NetUserGetInfo()函数,附了一个例子,查询USER_INFO_10信息。我们需 要查询USER_INFO_3信息。 如果已经存在到目标服务器的SMB会话,直接利用之。也可指定用户、密码重新建立 SMB会话。MSDN的例子太简单,基本不实用。 USER_INFO_3结构中没有密码过期的直接信息,只有usri3_password_age成员用于记 录最后一次设置密码到"当前时刻"之间的秒数。显然只用NetUserGetInfo()不能满足 原始需求。 在lusrmgr.msc中没有密码何时过期的设置。但在gpedit.msc中有: 本地计算机策略 计算机配置 Windows设置 安全设置 帐户策略 密码策略 密码最短存留期 密码最长存留期 闹了半天这是全局设置,不是用户名相关的设置。如果我对Windows使用熟悉的话, 早该想到不能只依赖USER_INFO_3结构。看样子net user scz、rpcclient这些命令报 告密码何时过期是结合了组策略信息、服务器当前时间的。 取服务器当前时间,减去usri3_password_age,就是最后一次设置密码的时刻。然后 分别加上密码最短存留期、密码最长存留期,就是何时可以修改密码、何时必须修改 密码的时刻。 取服务器当前时间,需要区分目标是远程还是本地。远程时用NetRemoteTOD()获取, 本地时用C运行时库函数time()获取。 queryuser.c没有使用ctime(),因其使用了静态缓冲区,多线程编程时不太妙。尽管 queryuser.c本身是单线程程序,但是在时间允许的前提下,不应该降低编程要求, 为此自行实现了PrintTimeString(),这个过程也比较困难,缺乏sample。MSDN中的 例子总是跟不上需求。 USER_INFO_3结构中的时间是RtlTime。C运行时库计算时间以1970年以基准,Win32 API计算时间以1601年为基准。这就涉及一个转换问题: ( RtlTime + 11644473600 ) * 10000000 -> FILETIME MSDN垃圾到连这点都不提,我是测试最初的queryuser.c发现问题后用Google搜出来 的。 剩下就是如何获取组策略信息。tk提到过net.exe最终调用了net1.exe。用dumpbin察 看之: > dumpbin /imports %systemroot%\system32\net1.exe | find "Net" 71C219C5 DF NetShareEnum 71C54782 B2 NetRemoteTOD 71C42EC3 EF NetUserGetInfo 71C42B4F ED NetUserEnum 71C43878 F1 NetUserModalsGet 71C43BB3 F2 NetUserModalsSet 71C47413 D4 NetServiceEnum 有些已经很熟悉了,有些还很陌生,注意到NetUserModalsGet()。在MSDN中确认要找 的就是它,查询USER_MODALS_INFO_0信息即可。 以上就是解决原始需求的整个过程,记录备忘,学习方法本身也需要不断总结。经验 积累得多了,就越容易发现问题、解决问题。 操作系统的不同永远不应该成为老虎吃天、无处下爪的理由。做为程序员,依据已有 知识、经验,配合正确的学习方法、学习手段以及不可或缺的勤奋快速向陌生领域的 纵深挺进,这是最基本的"素质"要求,否则不适合做程序员。数学史上的希尔伯特、 军事史上的古德里安都是这种快速向陌生领域纵深挺进的典范。虽然无法望及大师们 的项背,但学点精神总是可以的。 有几点学习经验: 1) 勇于、勤于Google,看到一切陌生但可能有意思的关键字,勇敢地将它们丢到互 联网上去搜索。不要放过一丝可能相关的信息。我曾经将Google的页面点击到45 页以后。 在一个txt文件中记录URL,而不是在浏览器提供的收藏夹中保存URL。不要只记录 跟当前工作相关的URL,而应该记录那些自己曾经感兴趣、正在感兴趣、即将感兴 趣的URL。 2) 有空的时候,不要看跟当前工作紧密相关的书籍,而应该看看不相关的方向。精 一个方向是正确的,但不能局限在这个视界里。 要避免贪多导致的最终一事无成。 不要陷入辩论中。辩论是口头上的巨人,对自己没什么好处。带着思辩的哲学观 点静观其"辩",可能对自己更有帮助些。各种操作系统的优劣,其辩论者十之七 八是跟风者。各种语言的比较,其辩论者十之七八是跟风者。 因时因地做出选择,没有亘古不变的选择。 3) 不要狂妄自大,但也不可妄自菲薄。每个人有自己擅长的,也有自己一窍不通的。 保持良好的学习心态,对自己写出来的疑问、猜测、回答、建议负责。提问的时 候,不应显得卑下,但保持必要的对陌生人的小学生守则上就有的礼节。回答的 时候,尽可能提供有帮助的信息,而不是"炫"。 一定要多交流,顾步自封显然会有麻烦。要不耻下问、上问。 4) 理论->实践->再理论,这条方法论至今不过时。学习理论,不迷信理论,大胆做 出科学的猜测,即使将来推翻了自己的猜测,也应记录下这个过程。如果实践肯 定了自己的猜测,更要立即整理、抽象、升华。 要勤于记录学习过程,结果固然重要,解决问题的过程同样重要。如果不是为了 树立一贯正确的高大形象,不想造个神出来,不妨将自己无数次失败的瞬间记录 下来,以后就可以少走很多弯路。 上述每一条背后都有真实的故事,以后有机会了,可以写写这些故事。经验是与人相 关的,仅仅是个人的经验,不代表真理,不代表指导思想。写出来,是为了交流学习 方法本身,没有别的意思。我也只是很普通的程序员,还好,不是太丢CS的人。