smbclient提供了类似FTP式的共享文件操作功能, 本篇从源码角度讲解smbclient的实现,smbclient命令的具体使用可通过help命令和互联网查到大量资料。

以下从源码角度分析一个smbclient命令是如何发到远端机器上和处理返回结果的。这里以一个简单的命令“close <fnum>”为例,分析程序的整个过程如下:

step 1. cmd_close(void)位于source3/client/client.c中。每个smbclient命令都有一个类似cmd_***命名的函数,这些函数作用是为step 2中的cli_***函数准备参数。

- 从全局内存stackframe获取临时内存“停靠点”。该步主要使用talloc库并配合samba自身的需要完成内存的管理。具体可参见上一篇博文的talloc加深理解。

- 分析传入的<fnum>, 简单调用atoi转换为字符串类型。

- 调用cli_close()。

Step 2. cli_close(cli, fnum)位于source3/client/client.c中。 它接收cmd_***传递的函数,做实质性的工作。这里第一个参数cli是cli_state全局类型数据,该数据结构中几乎包含了当前连接的connection的绝大多数信息,例如:

cli_state的前驱和后继、当前connection 信息、客户domain名、用户名、server domain、os、posix 能力、打开的管道list、机会锁信息、使用smb1 or smb2?及其相关联的session、tree connection、打开的句柄。第二

个参数是上面准备的打开的句柄。不同的cli_***command可能需要另外的参数,这些参数主要是为下层协议具体command准备的。可参见CIFS or SMB2协议来确定每个命令具体需要的参数有哪些。

- 根据cli->connection->protocol类型判断当前connection所使用的协议类型,若为SMB2,则调用SMB2处理函数cli_smb2_close_fnum(位于source3/libsmb/cli_smb2_fnum.c中)。这里重点分析SMB1处理。

- 若当前connection使用smb1,则根据当前connection->的event等待队列来判断是否有outgoing或pending的message,如果有则出错返回。(因为这里我们使用的都是同步的smb1 message)

- 调用samba_tevent_context_init分配tevent

- 调用cli_close_send()发送close immediate CIFS message。后续具体分析这个函数。

- 调用tevent_req_poll()等待event接收事件

- 调用cli_close_recv()接收server返回的close 数据报并返回

上面蓝色标记部分为IO处理的tevent库使用,会另文介绍。本文继续深入分析红色部分的协议处理部分,两个红色标记函数完成了协议组包、协议发送和协议接收处理。

cli_close_send():

Step 1: 调用cli_close_create(),根据协议组包并设置event类型和callback函数

- 调用tevent_req_create()创建一个immediate类型event,并设置状态为TEVENT_REQ_IN_PROGRESS状态;

- 调用cli_smb_req_create(TALLOC_CTX, tevent_context, cli_state, smb_command, additinal_flags, wct, *vwv, iov_count, iovec*),然后又向下调用smb1cli_req_create()(位于libcli/smb/smbXcli_base.c中)完成

按协议的具体组包工作。组包时需要注意,SMB1在组包时并没有定义每个CIFS协议的完整数据包格式,只是定义了header(加上wct)字段,发送是通过控制iov和iov_count来进行的,具体看下面code:

	smb1cli_req_flags(conn->protocol,
conn->smb1.capabilities,
smb_command,
additional_flags,
clear_flags,
&flags,
additional_flags2,
clear_flags2,
&flags2); //获取当前命令所需的flags
//设置CIFS header的各个域,完成大小端转换
SIVAL(state->smb1.hdr, 0, SMB_MAGIC);
SCVAL(state->smb1.hdr, HDR_COM, smb_command);
SIVAL(state->smb1.hdr, HDR_RCLS, NT_STATUS_V(NT_STATUS_OK));
SCVAL(state->smb1.hdr, HDR_FLG, flags);
SSVAL(state->smb1.hdr, HDR_FLG2, flags2);
SSVAL(state->smb1.hdr, HDR_PIDHIGH, pid >> 16);
SSVAL(state->smb1.hdr, HDR_TID, tid);
SSVAL(state->smb1.hdr, HDR_PID, pid);
SSVAL(state->smb1.hdr, HDR_UID, uid);
SSVAL(state->smb1.hdr, HDR_MID, 0); /* this comes later */
SCVAL(state->smb1.hdr, HDR_WCT, wct); state->smb1.vwv = vwv;// ??? //计算bcc字段
SSVAL(state->smb1.bytecount_buf, 0, smbXcli_iov_len(bytes_iov, iov_count)); //以下为每个SMB1 command都包含的域
state->smb1.iov[0].iov_base = (void *)state->length_hdr; //“SMB”
state->smb1.iov[0].iov_len = sizeof(state->length_hdr);
state->smb1.iov[1].iov_base = (void *)state->smb1.hdr; //header域
state->smb1.iov[1].iov_len = sizeof(state->smb1.hdr);
state->smb1.iov[2].iov_base = (void *)state->smb1.vwv; //wct字段具体包含的数据
state->smb1.iov[2].iov_len = wct * sizeof(uint16_t);
state->smb1.iov[3].iov_base = (void *)state->smb1.bytecount_buf;//bcc域
state->smb1.iov[3].iov_len = sizeof(uint16_t); //特殊smb1 command若还有其他字段则通过iov[4]进行发送
if (iov_count != 0) {
memcpy(&state->smb1.iov[4], bytes_iov,
iov_count * sizeof(*bytes_iov));
}
state->smb1.iov_count = iov_count + 4;

- 组包结束后调用tevent_req_set_callbak()设置该event的callback函数为cli_close_done(),在该函数中调用cli_smb_recv()函数完成smb数据包的接收。注意,此时组包和event相关注册活动均已完成,ready for 发送。

Step 2: 调用smb1cli_req_chain_submit(),该函数完成具体的message发送工作。

cli_close_recv():

- 由于注册的是immediate时间,程序将block在上一步的tevent_req_poll()直到对应event接到通知,并从注册的callback中返回。此时我们已经接收收到了close的返回数据报(callback函数接收处理的),针对close command只需要简单处理返回值即可。其他复杂command可能需要对返回值作进一步的分析,如保存打开的文件句柄、保存treeid等信息。

总结:

其实client端的实现还是相对比较简单,关键点是tevent库的使用和协议的组包分析。tevent库的使用可以通过资料快速掌握,但协议的组包和处理包括大量细节,没必要全部掌握,分析一个简单命令即可。

Samba 源码解析之SMBclient命令流的更多相关文章

  1. Samba 源码解析之内存管理

    由于工作需要想研究下Samba的源码,下载后发现目录结构还是很清晰的.一般大家可能会对source3和source4文件夹比较疑惑.这两个文件夹针对的是Samba主版本号,所以你可以暂时先看一个.这里 ...

  2. Redis系列(九):数据结构Hash源码解析和HSET、HGET命令

    2.源码解析 1.相关命令如下: {"hset",hsetCommand,,"wmF",,NULL,,,,,}, {"hsetnx",hse ...

  3. Sentinel源码解析四(流控策略和流控效果)

    引言 在分析Sentinel的上一篇文章中,我们知道了它是基于滑动窗口做的流量统计,那么在当我们能够根据流量统计算法拿到流量的实时数据后,下一步要做的事情自然就是基于这些数据做流控.在介绍Sentin ...

  4. Redis系列(十):数据结构Set源码解析和SADD、SINTER、SDIFF、SUNION、SPOP命令

    1.介绍 Hash是以K->V形式存储,而Set则是K存储,空间节省了很多 Redis中Set是String类型的无序集合:集合成员是唯一的. 这就意味着集合中不能出现重复的数据.可根据应用场景 ...

  5. 45.限流Throttling及源码解析

    什么是限流? 限流类似于权限机制,它也决定是否接受当前请求,用于控制客户端在某段时间内允许向API发出请求的次数,也就是频率 假设有客户端(比如爬虫程序)短时间发起大量请求,超过了服务器能够处理的能力 ...

  6. CBV流程之View源码解析

    CBV是基于反射实现根据请求方式不同,执行不同的方法. 请求流程:view源码解析 1.urls.py :请求一定来执行视图下的as_view方法.也可以直接点击as_view()来找源码. 2.vi ...

  7. IPerf——网络测试工具介绍与源码解析(1)

    IPerf是一个开源的测试网络宽带并能统计并报告延迟抖动.数据包丢失率信息的控制台命令程序,通过参数选项可以方便地看出,通过设置不同的选项值对网络带宽的影响,对于学习网络编程还是有一定的借鉴意义,至少 ...

  8. 【转载】okhttp源码解析

    转自:http://www.open-open.com/lib/view/open1472216742720.html https://blog.piasy.com/2016/07/11/Unders ...

  9. Android源码解析系列

    转载请标明出处:一片枫叶的专栏 知乎上看了一篇非常不错的博文:有没有必要阅读Android源码 看完之后痛定思过,平时所学往往是知其然然不知其所以然,所以为了更好的深入Android体系,决定学习an ...

随机推荐

  1. [hdu5379]Mahjong tree

    一棵子树的每一个儿子相当于划分一个区间,同时这些区间一定要存在一个点连续(直接的儿子),因此每一棵树最多只有两个儿子存在子树,并且这两个儿子所分到的区间一定是该区间最左和最右两段,所以ans*=(so ...

  2. [loj2477]劈配

    考虑依次选择每一位考生,设当前选到第$i+1$位,前i个分别为$p1,p2,--pi$(注意:这里只确定了导师的志愿编号),然后枚举第$p_{i+1}$,通过网络流建图+判定,复杂度为$o(nm*f( ...

  3. 【机器学习基础】卷积神经网络(CNN)基础

    最近几天陆续补充了一些"线性回归"部分内容,这节继续机器学习基础部分,这节主要对CNN的基础进行整理,仅限于基础原理的了解,更复杂的内容和实践放在以后再进行总结. 卷积神经网络的基 ...

  4. ES6学习 第五章 正则的扩展

    前言 本章介绍正则的扩展.有些不常用的知识了解即可. 本章原文链接:正则的扩展 RegExp 构造函数 从 ES6 开始,如果RegExp构造函数第一个参数是一个正则对象,并且第二个标志存在且为标志参 ...

  5. Revit二次开发之获取本机已安装的Revit版本与路径

    在revit安装目录下找到 RevitAddInUtility.dll 在项目中引用 使用以下方法 using Autodesk.RevitAddIns; /// <summary> // ...

  6. Hbuilder/Uniapp 格式化的时候,很多属性会排列在一行,如何结局?

    因为自己遇到这个问题,然后百度了很久都得不到解决办法,所以解决问题以后在博客园写下此文,希望能帮助到更多人! //在jsbeautifyrc.js里面的html中加入这个,就OK了 "wra ...

  7. Codeforces 1383D - Rearrange(构造)

    Codeforces 题面传送门 & 洛谷题面传送门 一道不算困难的构造,花了一节英语课把它搞出来了,题解简单写写吧( 考虑从大往小加数,显然第三个条件可以被翻译为,每次加入一个元素,如果它所 ...

  8. 【数据库】本地KEGG数据库如何拆分子库?

    目录 KEGG本地库文件 按物种拆分KEGG数据库 1.获得物种分类信息 2.获得物种分类的序列信息并建库 3.获得物种分类的K-ko对应文件 根据相似性原理,序列相似,功能相似,所有功能注释无非是用 ...

  9. Linux— file命令 用于辨识文件类型

    Linux file命令用于辨识文件类型. 通过file指令,我们得以辨识该文件的类型. 语法 file [-bcLvz][-f <名称文件>][-m <魔法数字文件>...] ...

  10. Go语言缺陷

    我为什么放弃Go语言 目录(?)[+] 我为什么放弃Go语言 有好几次,当我想起来的时候,总是会问自己:我为什么要放弃Go语言?这个决定是正确的吗?是明智和理性的吗?其实我一直在认真思考这个问题. 开 ...