作者归档:Neil

linux 清理磁盘,查找大文件

查看磁盘的占用:

df  -h
neil@dedia:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2       139G   37G   96G  28% /
none            4.0K     0  4.0K   0% /sys/fs/cgroup
udev            983M  4.0K  983M   1% /dev
tmpfs           199M  500K  198M   1% /run
none            5.0M     0  5.0M   0% /run/lock
none            992M     0  992M   0% /run/shm
none            100M     0  100M   0% /run/user
/dev/sda1       180M   34M  134M  20% /boot

如果需要清理磁盘的话, 用 find 命令找出大文件,然后酌情删除:

find / -size +100M -exec ls -lh {} \;

这就是要查找磁盘上大于100m 的文件.

参考:

http://lovesoo.org/linux-system-to-find-clean-the-disk-file.html

安装 jekyll 记录

* 安装 ruby

apt-get install ruby1.9.1

* 一定要再安装 ruby dev

apt-get install ruby1.9.1-dev

* 安装 jekyll

gem install jekyll

查看是否安装成功:

jekyll --version
  • 如果出现错误,找不到javascript 运行时, 类似:
execJs: 'Could not find a JavaScript runtime' but execjs AND therubyracer are in Gemfile

请安装 nodejs:

wget wget http://nodejs.org/dist/v0.10.31/node-v0.10.31.tar.gz

tar xf node-v0.10.31.tar.gz

cd node-v0.10.31

./configure

make && make install

* 如果一切顺利的话, 顺便再把markdown也装上吧:

gem install rdiscount

7z文件格式及其源码的分析(六)-完结篇

上一篇在这里 7z文件格式及其源码的分析(五)

这一篇主要介绍7z的流式压缩和解压原理.

对于流式处理来讲, 主要问题是要处理好文件头的问题. 因为是流式的, 所以,不可能走回头路. 也就不可能说压缩完了之后回到开头来修改头的信息.

7z文件的文件头其实是分为两部分的, 这两部分文件头在前面的几篇分析中都有介绍:

  1. 头Header, 就是写在文件前面的 Header. 这个头比较小, 主要记录了文件的版本信息, 以及尾header的位置偏移和校验和等等. 其主要作用是文件类型识别和查找到尾Header

  2. 尾Header, 就是写在文件末尾的 Header. 这里记录了 7z 文件的全部压缩信息, 解压主要就靠这里面的信息了.

7z 常见的压缩过程是: 先把 头Header 的一些位置保留, 然后压缩数据. 压缩完之后, 尾Header 写完之后, 再把 尾Header 的大小,偏移,校验和写回到 头Header 中去.

在流式环境下, 最后一步回头写 头Header 的过程显然不可能完成. 因为已经流过了.

那 7z 是怎么处理这个问题的呢? 7z 支持流式压缩吗?

7z 的文档里,并没有明确的回答这个问题. 我们还是让它的代码回答吧.

我们找到 7z 的解压代码, 7zIn.cpp 文件. 找到 HRESULT CInArchive::ReadDatabase2() 函数. 大概在 1136 行.

这个函数就是在读取 头Header 信息, 并查找 尾Header 位置.

我们截取它开头的一部分代码片段:

HRESULT CInArchive::ReadDatabase2(
    DECL_EXTERNAL_CODECS_LOC_VARS
    CArchiveDatabaseEx &db
    #ifndef _NO_CRYPTO
    , ICryptoGetTextPassword *getTextPassword, bool &passwordIsDefined
    #endif
    )
{
  db.Clear();
  db.ArchiveInfo.StartPosition = _arhiveBeginStreamPosition;

  db.ArchiveInfo.Version.Major = _header[6];
  db.ArchiveInfo.Version.Minor = _header[7];

  if (db.ArchiveInfo.Version.Major != kMajorVersion)
    ThrowUnsupportedVersion();

  UInt32 crcFromArchive = Get32(_header + 8);
  UInt64 nextHeaderOffset = Get64(_header + 0xC);
  UInt64 nextHeaderSize = Get64(_header + 0x14);
  UInt32 nextHeaderCRC = Get32(_header + 0x1C);
  UInt32 crc = CrcCalc(_header + 0xC, 20);

  #ifdef FORMAT_7Z_RECOVERY
  if (crcFromArchive == 0 && nextHeaderOffset == 0 && nextHeaderSize == 0 && nextHeaderCRC == 0)
  {
    UInt64 cur, cur2;
    RINOK(_stream->Seek(0, STREAM_SEEK_CUR, &cur));
    const int kCheckSize = 500;
    Byte buf[kCheckSize];
    RINOK(_stream->Seek(0, STREAM_SEEK_END, &cur2));
    int checkSize = kCheckSize;
    if (cur2 - cur < kCheckSize)
      checkSize = (int)(cur2 - cur);
    RINOK(_stream->Seek(-checkSize, STREAM_SEEK_END, &cur2));

    RINOK(ReadStream_FALSE(_stream, buf, (size_t)checkSize));

    int i;
    for (i = (int)checkSize - 2; i >= 0; i--)
      if (buf[i] == 0x17 && buf[i + 1] == 0x6 || buf[i] == 0x01 && buf[i + 1] == 0x04)
        break;
    if (i < 0)
      return S_FALSE;
    nextHeaderSize = checkSize - i;
    nextHeaderOffset = cur2 - cur + i;
    nextHeaderCRC = CrcCalc(buf + i, (size_t)nextHeaderSize);
    RINOK(_stream->Seek(cur, STREAM_SEEK_SET, NULL));
  }
  else
  #endif
  {
    if (crc != crcFromArchive)
      ThrowIncorrect();
  }

  db.ArchiveInfo.StartPositionAfterHeader = _arhiveBeginStreamPosition + kHeaderSize;


(...)

}

可以看到, 下面这几句是在从 头Header 上读取 尾Header 信息.

  UInt32 crcFromArchive = Get32(_header + 8);
  UInt64 nextHeaderOffset = Get64(_header + 0xC);
  UInt64 nextHeaderSize = Get64(_header + 0x14);
  UInt32 nextHeaderCRC = Get32(_header + 0x1C);
  UInt32 crc = CrcCalc(_header + 0xC, 20);

紧接着, 后面又有一句判断:

if (crcFromArchive == 0 && nextHeaderOffset == 0 && nextHeaderSize == 0 && nextHeaderCRC == 0)

如果这几个都读不到怎么办.

看代码, 接下来, 它会从文件末尾开始, 倒着向前查找特征字符:

if (buf[i] == 0x17 && buf[i + 1] == 0x6 || buf[i] == 0x01 && buf[i + 1] == 0x04)

这就是在找 0x17 0x06 或者 0x01 0x04 这两个特征串.

代码已经很明确了:

如果找到这其中之一, 就算是找到 尾Header 了.

为什么能这么做呢, 这两个串代表什么呢?

通过查看7z的文档(事实上, 前面讲的 尾Header 的结构的时候也能发现)知道.

  1. 0x17 是代表压缩Header的标记 kEncodedHeader. 后面的 0x06 是 packstream 的标记 kPackInfo.
  2. 0x01 是代表普通Header的标记 kHeader. 而 0x04 是 mainstream 的标记 kMainStreamsInfo.

这正是 7z 的两种 尾Header 的格式.

到这里, 可以总结一下了:

7z 流式压缩
压缩时可以把 头Header 中的 偏移量和 crc 和等 留空.
在解压时, 如果检测到这些留空了, 则会从文件末尾开始查找 尾Header 特征串.

这种方法可以一定程度实现 流式压缩. 虽然 7z 的文件结构本身,限制它不适合流式压缩的.

欢迎大家讨论交流: Neil 的博客

Ubuntu 启用磁盘配额 quota

quota 可以为用户或用户组设置磁盘配额. 限制用户或用户组能使用的磁盘大小.

1. 安装 quota :

sudo apt-get install quota

2. 编辑 /etc/fstab 文件,启用 quota

vi  /etc/fstab

添加 usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0 到需要启用 quota 的分区. 比如 /:

# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
# / was on /dev/sda2 during installation
UUID=7cf8dc2b-2541-4684-8931-844b6bd01e9c /               ext4    errors=remount-ro,usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0 0       1
# /boot was on /dev/sda1 during installation
UUID=4541b27d-84d6-4f70-9077-7cb9308634b6 /boot           ext4    defaults        0       2
# swap was on /dev/sda3 during installation
UUID=ccae84a6-7423-4e60-9448-0cf05a053fbc none            swap    sw              0       0

3. 重新挂载分区:

touch /aquota.user /aquota.group
chmod 600 /aquota.*
mount -o remount /

4. 启用 quota :

quotacheck -avugmc
quotaon -avug

参考:

http://www.howtoforge.com/how-to-set-up-journaled-quota-on-debian-lenny
http://manpages.ubuntu.com/manpages/hardy/zh_CN/man1/quota.1.html

sed 命令的简单用法

我们在编写shell的时候, 可能需要修改一些配置文件, 比如需要修改mysql的配置文件, 在里面添加一些设置,修改一些或者删除一些.

怎么在shell中来编辑 文本文件呢.

在交互式环境下, 使用vi就可以了. 手动去修改. 但是在无人值守的shell中呢?

这里,就要用到 sed 命令了.

sed 的意思是: stream editor, 就是流式编辑器.

跟很多命令行工具(比如awk)一样, 他的功能及其强大, 当然用法和参数也及其复杂. 这里以极简的方式记录几种用法, 常用的编辑功能基本都能满足了.

sed 处理的基本方式

它的基本命令格式大概是这个样子的:

sed -options /patterns  file

它的意思是要处理 文件 file, 并且把处理结果直接回显到屏幕上. 所以通常需要配合重定向来保存处理结果.

它通常的工作样式是这样的:

sed -options  /patterns  file > saved.txt

当然, 可以不用重定向, 而直接指定保存文件名. 这里用到 -i 参数.
比如:
sed -i saved.txt -options /patterns file
这样在-i后面跟上一个文件名,就可以了.
如果 -i 后面没有跟文件名, 那么, 编辑结果将会保存到原文件.
sed -i -options /patters file
这样, 就是直接修改原文件了.
因为我通常都是想直接修改原文件的, 所以下面我都加上-i 参数了.

sed 的基本编辑操作.

替换 是我们最常用的操作.
他的命令样式为:

sed -i  "s/aaaa/bbbb"  file

这个命令就是把file中的所有字符串aaaa替换成bbbb, 并保存到原文件中(因为有-i 参数).

前插入 就是在某个特征字符串的前面插入内容.

命令为:

sed -i  "/aaa/i bbbb"  file  

这个命令是在aaa的前面插入一行bbbb, 注意中间的那个字母 i, 表示insert.

后插入 就是在某个特征字符串的后面插入内容.

命令为:

sed -i  "/aaa/a bbbb" file 

这个命令是在aaa的后面插入一样bbbb, 注意中间那个字母a, 表示append.


欢迎访问我的个人博客: http://byNeil.com

mysql免密码登录, 及使用-e 参数执行sql脚本

我们在编写shell脚本的时候,经常需要和 mysql 交互.

如果是交互环境,可能使用这样的命令登录到mysql

#mysql –uroot –p

然后按提示输入密码, 登录. 如果在脚本中, 我们就不得不把密码写在 –p 参数后面. 这样 很容易暴露密码.

幸好mysql提供的有解决方案. 在 “~/.my.cnf” 文件中保存密码就行了.

MySQL官方文档

文件内容大概如下:

[client]

password="MySQL密码"

user=MySQL用户名

其中user 行可以省略, 默认使用当前的用户名填充mysql的登录用户名

再次使用 mysql 命令的时候,就无需输入用户名和密码了,可以自动登录.

还可以给 mysql 命令使用 --defaults-file 参数来指定特定的配置文件路径:

mysql --defaults-file=/folder1/folder2/filename -u 用户名

实现了免密码登录之后, 在脚本中就可以直接使用 -e 参数来执行sql脚本了, 而不用像交互式一样登录到mysql之后执行了.

mysql -e "CREATE DATABASE test" 

到这里, 基本上就可以实现完全的无值守 mysql脚本操作了.

参考: http://yzs.me/2142.html

Nodejs 的 c++ module 如何正确链接到 OpenSSL

事情的起因是这样的, 因为某些原因, 最近在写 Nodejs 的 c++ module, 然后在js这边调用。  网络通信自然离不开ssl, 于是需要链接到Openssl的库。

我们本来的期望是,需要用户安装有Openssl的运行库, 然后我们的c++ module 动态链接到Openssl的so库上来运行。 

起初一切看起来还不错,直到我们发现这个openssl的函数不能工作:

 PKCS7_sign()

我们发现:

  1. 如果我们的 c++ 模块与Openssl库动态链接的话, 编译都没问题. 但是运行会出现: PKCS7_sign 符号无法找到的错误.
  2. 如果我们的 c++ 模块与Openssl库静态链接的话, 编译也没问题, 但是运行时,调用这个函数的地方没有效果, 这个函数返回值是 0. 按照文档表示出现错误, 但是用 Openssl的函数 ERR_get_error 获取错误码也是0. 表示没有错误码.

在linux上是这样, 那在Mac上呢? 用Mac试了一下, 发现Mac没有问题. 于是,想到这可能是Nodejs的一个bug. 然后就去 Nodejs 给它报了一个bug: [https://github.com/joyent/node/issues/8026][1]

同时, google上搜索了 nodejs linking to openssl 类似的关键字.

找到这样几篇文章:
https://github.com/TooTallNate/node-gyp/wiki/Linking-to-OpenSSL

https://github.com/joyent/node/issues/3915

http://serverfault.com/questions/338092/how-can-i-build-node-js-using-static-libssl-and-crypto-libraries

https://github.com/robhawkes/node-extension/issues/1

通过搜索, 我们发现, 原来Nodejs自己也使用了Openssl 库, 推测nodejs自己的crypto模块也是使用Openssl lib实现的. 这点从Nodejs的源码中就能发现, 它包含了最新的Openssl的全部源码.

其中写上面第一篇文章: https://github.com/TooTallNate/node-gyp/wiki/Linking-to-OpenSSL 的那个帅哥是Nodejs的开发人员.

基本结论:

  1. Nodejs 自己使用了Openssl
  2. 在Nodejs 0.6之前, Nodejs是动态链接到 Openssl 库的. 而之后的版本都是静态链接的.

这时发现 Node 那边已经回复我的bug了: https://github.com/joyent/node/issues/8026

Node 解释的原因:

Node 自己编译之后, 把自己没用到的符号清除, 所以我们在运行时就找不到符号了. 于是他们把这bug 修掉了. 保留了全部符号. 这导致 Node 的体积大了 400k.

感谢Node的快速回复, 不得不佩服Node的活跃程度. 赞.

解决:Windows8.1 局域网共享文件拷贝速度慢的问题

 

最近新旧电脑更替, 需要拷贝文件到新电脑。几百G的文件,局域网拷贝只有3M左右的速度。 找了很多方法都没用。

最后试试给笔记本插上网线吧,  速度果然解决。 12M的拷贝速度,还可以。

 

看来是无线的速率太低。 虽然我的路由器是300M的速度, 但是实际局域网可能是因为链接有低速的手机设备,所以实际工作在54M的频段上。

ubuntu 设置时区

 

tzselect
按照提示进行选择时区
sudo cp /usr/share/zoneinfo/Asia/ShangHai /etc/localtime
执行
sudo ntpdate cn.pool.ntp.org
cn.pool.ntp.org是位于中国的公共NTP服务器,用来同步你的时间

时间
sudo date -s 20080701

http://www.cnblogs.com/lipeil/archive/2012/07/06/2578916.html

tzselect按照提示进行选择时区sudo cp /usr/share/zoneinfo/Asia/ShangHai /etc/localtime执行sudo ntpdate cn.pool.ntp.orgcn.pool.ntp.org是位于中国的公共NTP服务器,用来同步你的时间
时间sudo date -s 20080701

NTLM认证协议及SSPI的NTLM实现

没错,NTLM就是微软应用最广泛的认证协议之一。 NTLM是NT LAN Manager的缩写,这也说明了协议的来源。NTLM 是 Windows NT 早期版本的标准安全协议。Windows 2000内置三种基本安全协议之一。 NTLM适用范围非常广,既可用于域内的认证服务, 也可用于没有AD的环境,让两台独立电脑相互认证。

你可能知道NTLM可以认证用户身份,但是你可能不知道NTLM可以提供会话安全服务(NTLM Session security)。 

本文也会分成几篇来写, 现在计划可能包含下面几个大方面的内容:

1.  NTLM协议的认证

包括 NTLMv1  和 NTLMv2 两个版本。  涉及到NTLM的认证全过程,以及NTLM 的 EPA(Extended Protection for Authentication)实现。

2. NTLM Session Security

即NTLM的会话安全,或者说是NTLM的SSPI实现。 这一部分内容可能不是很常用,能找到的资料也比较少。 这里先简单的说两句。 我们都知道NTLM可以用于认证,实际上,认证过后,NTLM还能提供安全服务。 比如提供对后续通信的安全签名,防篡改,或者对后续通信进行安全可信赖加密,防止被窃听。这一部分实际上是微软SSPI服务内容。 SSPI是Security Support Provider Interface(Microsoft安全支持提供器接口)。  SSPI是一个安全框架,很多协议都对它有实现。 比如我们熟知的Kerberos认证协议,它也有SSPI实现的部分。

3. NTLM消息的实例

这一部分,我们会找一个具体的实例来解释认证的全过程。


值得注意的是 NTLM 现在已经不光用于windows平台了,Linux 或者 java 平台也可以进行 NTLM 认证,使用 NTLM 的安全服务(但是Java平台对NTLM的会话安全支持貌似有问题)。

作为一个开始,我们先来介绍几个基础概念, 概念清楚了,然后再看技术细节。

1. 什么是认证。

认证就是承认和证明的意思。 就是你能证明你的身份。 比如你要访问一个受保护的资源,服务器需要认证你的身份。 你可以声称你是系统管理员, 但是怎么证明你就是管理员呢。 方法很多,这里有个简单直接的方法就是证明你知道管理员的密码。

认证的问题转化为: 怎么证明你知道你所声称的用户的密码?.

一个简单暴力的证明方法是,让你直接提供密码给服务器,然后服务器去数据库里面比对,看你提供的密码对不对。如果对,认证通过。否则失败。 常见的所谓Windows Forms认证,或者叫做windows basic 认证就是这种方式。 简单直接。 但是密码需要在网络上传输,安全问题就不说了,你懂的。

怎样在不直接提供密码的情况下,间接证明你知道密码呢?  NTLM就是干这个的了。

  • 什么是NTLM 认证。

NTLM是一种在不直接提供密码的情况下,间接证明客户端知道用户密码的方法。

NTLM认证最常见的应用场景恐怕就是用在浏览器(http协议)上的认证了。 但是实际上,NTLM 只规定了认证的流程,和认证消息格式。 并不跟具体的协议相关。 所以跟http就更没有必然联系了。 浏览器只是在http协议头上携带了NTLM的消息而已,通过了认证。 我们知道http通常是明文的,所以如果直接传输密码非常不安全,NTLM就有效的防止了这个问题。

怎么理解这个问题呢,举个夸张点的例子,  如果不嫌烦,客户端和服务端甚至可以通过传递小纸条的方式传递NTLM消息,来认证身份。 而纸条的内容是明文的, 中间传递者都可以随意查看,但是却无法知道密码,也无法伪造。

现在可以开始技术细节了, 我们还是采取由大及小的方式。 先从整体介绍,再逐步深入。

  • NTLM 的 认证消息,及认证流程。

前面说过了,NTLM消息并不和任何传输协议绑定, 它的认证消息理论上可以通过任何方式传递,所以我们的讨论都集中在协议本身,而不去关心下层的传输方式。

我们先看一个图:

image

NTLM认证共需要三个消息完成:

(1).  Type1 消息。 Negotiate 协商消息。

客户端在发起认证时,首先向服务器发送协商消息。 协商需要认证的主体,用户,机器以及需要使用的安全服务等等信息。 并通知服务器自己支持的协议内容,加密等级等等。

 

(2). Type2 消息。 Challenge 挑战消息。

服务器在收到客户端的协商消息之后, 会读取其中的内容,并从中选择出自己所能接受的服务内容,加密等级,安全服务等等。 并生成一个随机数challenge, 然后生成challenge消息返回给客户端。

 

(3). Type3 消息。 Authenticate认证消息。

客户端在收到服务端发回的Challenge消息之后, 读取熬服务端所支持的内容,和随机数challenge。  决定服务端所支持的内容是否满足自己的要求。 如果满足,则使用自己的密码以及服务器的随机数challenge通过复杂的运算,期间可能需要自己生成一个客户端随机数client challenge也加入运算, 并最终生成一个认证消息。并发回给服务器。

 

(4). 服务器在收到 Type3的消息之后, 回经过几乎同样的运算,并比较自己计算出的认证消息和服务端发回来的认证消息是否匹配。如果匹配,则证明客户端掌握了正确的密码,认证成功。 允许客户端使用后续服务。  如果不匹配,则认证失败。

 

  1. NTLM 认证消息的结构.

NTLM的消息很简单, 只有三种, Type1,  Type2 和 Type3.   它们都有相似的结构。  认证消息都是二进制的,但是通常我们见到的都是它们的Base64的编码格式。 类似这种:

TlRMTVNTUAADAAAAGAAYAHAAAACSAJIAiAAAAAAAAAAAAAAAGgAaAEgAAAAOAA4AYgAAAAAAAAAAAAAABYKIogAAAAAAAAAPYQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBOAEUASQBMAC0AUABDALZLpLeO2n6Sx1s9JjrAfQOqf2QsmfTeP9cjC86k7BsjZEsKzjOoYBcBAQAAAAAAAEDGE3IuR88Bqn9kLJn03j8AAAAAAgAEAEsAQQABAAoARgBTAFcARQBCAAQADABrAGEALgBjAG8AbQADABgAZgBzAHcAZQBiAC4AawBhAC4AYwBvAG0ABQAMAGsAYQAuAGMAbwBtAAcACAC0gtdyLkfPAQAAAAAAAAAA

所以,如果你看到这种形式不要吃惊,把他们用Base64解码即可。

协议中的数字都是采用小端的方式存储。

(1).  消息头

这三种消息都具有相似的消息头:

image

 

 

(2) Flags 标记。

这三种消息一般都会携带一个 4字节的int值, 作为消息的Flags。 Flags在三种消息中的位置不一样,所以没有当做消息头来介绍。 不过,这个flags非常重要。这里先单独来介绍:

(下面是我的代码片段,重点标志我做了注释,后面用到的时候会进一步介绍)


flagsExps[0x1] = "Unicode"; flagsExps[0x2] = "OEM"; flagsExps[0x4] = "Request Target"; flagsExps[0x8] = "r10(must be zero)"; flagsExps[0x10] = "Negotiate Sign"; // 需要协商签名服务 flagsExps[0x20] = "Negotiate Seal"; // 需要协商加密服务 flagsExps[0x40] = "Negotiate Datagram Style"; //UDP 非连接模式 flagsExps[0x80] = "Negotiate Lan Manager Key"; // 在某些特定的NTLMv1下使用 Lan manager key 后面会详细介绍 //================================ flagsExps[0x100] = "Negotiate Netware(r9 must be zero)"; flagsExps[0x200] = "Negotiate NTLM"; flagsExps[0x400] = "r8(should be zero)"; flagsExps[0x800] = "Negotiate Anonymous"; //使用匿名登录 flagsExps[0x1000] = "Negotiate Domain Supplied"; flagsExps[0x2000] = "Negotiate Workstation Supplied"; flagsExps[0x4000] = "Negotiate Local Call(r7)"; flagsExps[0x8000] = "Negotiate Always Sign"; //=============================== flagsExps[0x10000] = "Target Type is a Domain."; flagsExps[0x20000] = "Target Type is a Server."; flagsExps[0x40000] = "Target Type Share(r6)"; flagsExps[0x80000] = "Negotiate NTLM2 Key(EXTENDED_SESSIONSECURITY)"; //使用扩展会话安全 flagsExps[0x100000] = "Request Init Response(NTLMSSP_NEGOTIATE_IDENTIFY)"; flagsExps[0x200000] = "Request Accept Response(r5, must be zero)"; flagsExps[0x400000] = "Request Non-NT Session Key"; flagsExps[0x800000] = "Negotiate Target Info"; //协商 携带 TargetInfo //=============================== flagsExps[0x1000000] = "r4(must be zero)"; flagsExps[0x2000000] = "NTLMSSP_NEGOTIATE_VERSION(协商携带操作系统版本号, 一般会忽略此项,仅供调试用途 )"; flagsExps[0x4000000] = "r3(must be zero)"; flagsExps[0x8000000] = "r2(must be zero)"; flagsExps[0x10000000] = "r1(must be zero)"; flagsExps[0x20000000] = "Negotiate 128"; // 协商128位加密 flagsExps[0x40000000] = "Negotiate Key Exchange"; //协商交换key, 在会话安全中,使用交换key来加密内容,而不是直接使用会话key flagsExps[0x80000000] = "Negotiate 56"; //协商56位加密

(3). Type 1 消息

我这里使用下面参考文档2中的一个图来介绍:

image

 

a . 前面首先是 8个字节+4个字节的 协议头。前面已经介绍过了。注意,消息类型为1

b . 然后是四个字节的Flags。 前面也介绍过了。  这个Flags中表达了客户端想要使用的NTLM的认证服务, 以及客户端自己所支持的服务。

 

c. 然后,是若干个可选的security buffer。

注意上图中的 security buffer, 安全缓冲区。 它是一个8字节固定大小的结构体。它看起来像是这个样子:

image

它其实是一个缓冲区指针。 它指向一个区域, 这个区域相对于Type1消息起始的偏移量为 offset, 这个区域的大小长度为 MaxLength, 其中的有效使用大小为:Length.

 

如果 Flags 中包含 0x1000 标记, 则需要提供域。把域名写入到这个缓冲区中。 字符集由Flags中的 OEM或者Unicode决定。

 

如果Flags中包含0x2000标记,则需要提供workstation的名字。  方法同上。

 

d. 然后是8字节的操作系统版本号。

如果Flags中包含0x2000000, 则需要提供 操作系统的版本号。

操作系统版本号的写法,请参考 参考文档1 的 2.2.2.10 节。

image

 

e. 然后,就是携带的前面security buffer中所指向的数据了。

 

在Type1的消息中,只有前面的 a 和 b项,是必须的, 后面的都是可选的。

 

 

 

(4) Type2 消息

服务端在收到 Type1消息之后, 会生成Type2消息返回给客户端。

这个也比较简单,我们还是用 参考文档2 中的一个图来介绍:

 

image

 

a . 前面首先还是 8个字节+4个字节的 协议头。前面已经介绍过了。 注意此时的消息类型已经是 2了。

b . 然后是四个字节的Flags。 前面也介绍过了。  这个Flags中表达了服务端所能接受的服务。

 

c. 上图中的TargetName 是要访问的机器名。 存储方法是security buffer. 前面介绍过了,不多说了。

 

d. Flags 不多说了。

 

e. Challenge, 这是一个服务端生成的8字节的随机数, 客户端会用来计算key. 后面会详细介绍用法。

 

f. Context, 这是一个8字节的值,其实是两个连续的32位值。 表示一个内部handle。 当服务端发现客户端就是自身的时候(在用一台电脑上),就会生成一个内部的安全handle,填充到这个字段。并且在Flags中设置  0x4000标记。 表示这是一个本地认证。

 

g. TargetInformation 也是一个security buffer。 其存储方式前面介绍过。 但是它的buffer中的内容需要介绍一下。基本上,TargetInformation是由一系列连续的 AV_PAIR 结构体连接起来的。

而AV_PAIR的结构如下:

(请参考下面 参考文档1 的 2.2.2.1节)

image

 

i) 先是两个字节的int16值, 表示 id, 表示这个AV结构的类型。

目前有如下类型:

image

image

 

 

ii) 然后是两个字节的int16值,表示值的长度。

 

iii) 然后就是值。 它的长度在上面指定的。

 

这个TargetInformation 结构非常重要。 后面Type3中还会用到。

 

h. 然后是8字节的操作系统版本号。 Type1中已经介绍过了。 不多说了。

 

然后后面就是要携带的数据了。

 

到这里先告一段落吧, 这里介绍了 NTLM 的概念以及认证流程。 并详细介绍了Type1和Type2消息, 以及Flags每个标志位的意义。 这大概覆盖了NTLM内容的30%左右的内容。  Type3消息是整个认证中最为复杂和关键的部分,我们将在下一篇中独立介绍。

希望大家多多支持。

欢迎访问我的个人独立博客: http://byNeil.com , 希望和大家多多交流共同进步。

 

 

参考文档:

  1. MS-NLMP-NT-LAN-Manager-NTLM-Authentication-Protocol-Specification

  2. The NTLM Authentication Protocol and Security Support Provider

  3. freebsd-freebsd  源码实现

  4. Mozilla Firefox source code Developer Guide - MDN-firefox 源码

  5. Heimdal NTLM 开源工程

  6. samba.org-pub-unpacked-samba_3_current-librpc-idl-ntlmssp.idl