-
-
[原创]Netatalk CVE-2018-1160 复现及漏洞利用思路
-
发表于: 2023-4-6 18:00 18863
-
author:cxing
Date:2023年4月6日
introduction:Netatalk 是一个 Apple Filing Protocol (AFP) 的开源实现。 它为 Unix 风格系统提供了与 Macintosh 文件共享的功能。AFP的数据流量包格式为DSI(Data Stream Interface),DSI 在客户端和 AFP 服务器之间使用。
首先需要搭建netatalk的运行环境,这里使用docker搭建Ubuntu18.04,可以在docker Ubuntu18.04中进行环境复现。这里有两种方法,一是在docker中下载源码、安装依赖环境编译,二是在本机中下载源码、依赖编译。我这里选择了比较稳妥的的第一中。
运行Ubuntu 18.04的镜像,没有会自动从官方docker仓库中pull。
关于详细的安装步骤可以参考先知的这篇文章搭建环境,如果你想使用我的环境可以使用下面链接下载我的docker镜像。
链接:https://pan.baidu.com/s/1NJOfT9xS111RSmgcSbEW3Q
提取码:8r5i
你需要使用如下命令启动docker,以保证我docker中的设置正常运行。
netatalk处理请求类似于Apache,对于每一个用户请求都会为其fork一个子进程处理,而父进程则监控请求的处理情况。netatalk的关键运行模块主要有两个,主模块afpd和AFP协议流量包处理模块libnetatalk。其中afpd主要功能为初始化服务的环境、监听和接受处理请求并为之构建请求处理的环境,而libnetatalk是具体解析和处理dsi流量的。
注1:Netatalk的大部分功能性函数命名风格采用 模块命名空间_函数描述 的格式,如afp_exit、afp_over_dsi、dsi_opensession等。
注2: DSI流量包格式可以参考这篇wiki,Data_Stream_Interface。
对于理解Netatalk,需要用afp_start、afp_over_dsi为主线理解。
afp_start在main中被调用,通过阅读下面代码可以得知。第一个关键代码处调用了init_listening_sockets
其目的是watch atp, dsi sockets and ipc parent/child file descriptor
,也就是从这里开始监听APF请求了。继续往下看,我们发现了(child = dsi_start(&obj, (DSI *)(asev->data[i].private), server_children))
这行代码,返货了进程描述符,这意味着从这里开始已经真正开始接收和处理请求了。
我们再来看afp_start
函数。首先调用了dsi_getsession
,并且forked后进入afp_over_dsi
处理本次请求。我们先看dsi_getsession
,我们可以看到在第一个数据包中只允许我们利用DSI中的command字段访问两个Command命令或者说函数,分别是DSIGetStatus
和 DSIOpenSession
。我们查阅一下,DSIOpenSession
命令的分支即dsi_opensession
函数。我们看到switch语句在解析DSI session options时,DSIOPT_ATTNQUANT
分支中出现了一个memcpy(&dsi->attn_quantum, dsi->commands + i + 1, dsi->commands[i]);
语句,这里存在一个越界写漏洞。在进入到dsi_opensession
函数之前,会隐式的调用dsi_stream_receive
函数,将我们发送的DSI数据包中的Payload字段 copy to dsi->commands中。而Payload字段是可控的,用户发包时自由指定,只要服务可以解析即可。因此,我们发现payload在这里实际上解析的格式是payload[0]:code, payload[1]:size, payload[2:size -1]:data
,而memcpy拷贝至的dsi->attn_quantum变量却是一个uint32类型,也就说,只要我们合理设置size和data就可以触发越界写,覆盖&dsi->attn_quantum后面的字段。我们可以往后覆盖多少个字节呢?dsi->commands是一个uint8类型的指针,也就是解析格式中size最大值为255,我们可以往后覆盖255个字节。
DSI结构体如下,从attn_quantum字段往后溢出,我们最多可以溢出至data数组的部分空间(data数组非常大)。比较关键的是我们可以覆盖指针dsi->commands。在后面的漏洞分析小节,我们会纤细的讨论覆盖commands指针所导致的严重后果,这将使得我们可以RCE。afp_start->dsi_getsession->dsi_opensession这条路径我们分析至此。大意的作用从两个关键函数名也可以看出来,核心就是open session,配置一些东西并开启正式的连接会话,你可以理解为TCP建立连接前的三次握手,但是在配置过程中产生了越界写漏洞。
我们再继续从afp_start->afp_over_dsi开始看。afp_over_dsi处理正式连接的请求核心再这个while循环。首先第一行重要代码cmd = dsi_stream_receive(dsi);
,Blocking read on the network socket
,即阻塞地从socket连接中读取dsi steam,即会解析dsi流量填充dsi结构体,也就是反序列化dsi流量。我们进入快速阅读一下dsi_stream_receive
函数,注意我们关注的是该函数如何从socket中读取数据填充dsi结构体。我们可以明显的发现block
变量即是DSI Header,将block
copy to dsi.header
中。而其中关键的数据包的body也即payload或者说dsi data是同过一行if (dsi_stream_read(dsi, dsi->commands, dsi->cmdlen) != dsi->cmdlen)
从dsi结构体的buffer中读取到dsi->commands指针指向的内存中,最后其返回值返回block[1],也就是我们下图1中给出的DSI Header结构中的Command字段。至此我们了解该函数是如何把DSI流量数据填充进 dsi structure。
注3:这里我们就发现了任意地址写漏洞。我们最开始的越界写,第一次发包可以覆盖commands,而后续发包可以往commands中写入我们希望的数据,指针劫持并可控的写指针指向内存,并且我们发现我们可以写多少是由图1的total data length这个字段决定的,而这个字段是4字节大小且根据payload的长度计算值,因此我们可以写非常长的payload。
任意地址写漏洞是后续RCE的基。我们之所以可以如此是因为dsi->commands指针的生命周期与dsi结构体生命周期是一样的,存在于一次连接中。
我们继续回到afp_over_dsi的while循环中。再dsi_stream_receive
函数返回时,其返回值同时也是本次请求的Command字段,该返回值进入Switch语句执行对应的command命令,而我们着重关注最核心的case DSIFUNC_CMD
分支。这个分支的作用简而言之,就是根据我们DSI数据流中的Payload字段的数据,执行AFP回调函数。我们可以发现核心的afp_switch
变量,通过全局搜索afp_switch =
可以发现两处赋值(如图2所示)。通过变量名和两张回调函数表的内容,可以猜测,一个是未登入或未授权时走preauth_switch
,一个是登入成功或授权时走postauth_switch
。
注4:我们拥有了任意地址写,那么处理得当,我们可以为授权时通过任意地址写将afp_switch
的值改成postauth_switch
,我们将可以访问postauth_switch
中的回调函数,即触发未授权访问。更进一步,我们将preauth_switch
或postauth_switch
表中的一些项改成我们希望的代码段地址、one_gadget等,将触发RCE。因此目前我们已经从漏洞点中分析出未授权访问和RCE两种利用方式了。
bingo,我们已经将两条主线分析完毕,也从中点出了漏洞点以及可能的利用方式,那么我们这一小节将根据我们的代码分析、漏洞点分析,讨论可能的利用思路和方式。具体的,我们会讨论越界写漏洞导致潜在任意地址写如何得以实现,即将潜在的变为真正的;我们将讨论任意地址写得以实现后,将详细分析如何RCE,简单的提一下如何进行未授权访问postauth_switch
中的函数。
我们可以在第一次发送DSI数据包时,触发越界写,劫持commands指针,那么我们如何得以让commands指针写入我们希望的地址呢?在未开启ASLR时,这并不难,但现在我们开启了ASLR,我们没办法确定任意一个模块的base adress。这时我们不妨先,先看一看内存布局。尽管程序开启了ASLR,但是我们每次处理我们连接的是fork出来的子进程,而子进程的虚拟进程空间的内存布局与父进程是一致的,也就是说每次fork出来的子进程其地址在父进程生命周期内都是固定的。
注5:gdb附加容器进程进行调试请使用如下命令
sudo gdb -q -p `pgrep -n afp` --ex "set follow-fork-mode child"
如图3,通过观察,我们可以得知ASLR的Randomization主要是 0x00 00 7f ?? ?? ?? ?0 00,这样的随机化规律。那么,我们可以通过不断的写commands指针的地址试,逐个试探??,观察子进程是否crash。若commands指针地址不可写,那么后续读commands指针数据的操作将触发非法内存访问导致进程crash,无法响应我们的请求。也就是说,如果我们发的包修改的commands指针地址合法,我们会收到响应的数据包,如果没有那么就意味着我们写的commands指针地址非法。首先,commands指针原始的地址肯定是合法的、可写的。我们可以选择从0x00 00 7f ?? ?? ?? ?0 00
的高字节逐字节往低试探(即上诉格式的从左往右消除问号),每当我们收到响应包时,我们便确定了一个??
,转之继续往下一个??
试探,直至确定一个合法的可写地址。当我们确定一个合法的地址时,有什么用呢?ELF模块之间的相对位置通常是固定的,例如afpd永远是第一个加载的模块。由此当我们在内存中确定一个可写内存的位置时,其相对于其他模块、地址的偏移也是相对固定的,差也不会差太多。我们可以这样来爆破,我们从高地址开始逐渐向低地址爆破,然后每一个字节爆破的值从255->0开始,那么我们拿到的地址,几乎可以肯定的说落在地址最高的ELF模块中。同理,从低字节开始往高字节写,从0->255开始,把7f
也当作??
试探,几乎可以肯定你会得到一个落在afpd模块中的可读可写地址。由于后面我给出的Exploit是通过泄露libc,劫持__free_hook指针进行内存布局并RCE的。所以我的泄露地址思路是尽量泄露一个离libc近的地址,因为图5中libc地址足够高,因此我也选择泄露一个高地址。代码如下:该代码泄露出来的地址,落在最高的模块中。并且出于简单考虑,在这个形式中的泄露格式0x00 00 7f ?? ?? ?? ?0 00
,我把最后两个字节默认抹除为0了,也即只需要泄露三个字节,并且我们泄露出来的地址是0x1000对齐的。
那么我们既然泄露出了一个可读可写的地址,如果我想写libc中的一些数据怎么办?或者我想写afpd中的一些数据结构怎么办?那么自然需要泄露对应的基地址。以libc写为例子,我给出的代码泄露出来的地址要么是位于ld-2.27.so中,要么是位于其下方的mmap内存中,我们可以大概的估算一下我们泄露的地址与libc之间的距离,一大步的靠近,然后一路小跑抵达libc基地址。例如我这里算出来的一大步是0x18040000~0x1880000这个区间,我们从直接一大步跨过0x18040000,然后以0x1000一小步一小步的跑向libc。afpd同理,甚至更加简单。
好了,现在我们解决了任意地址写的问题,那么我们来考虑如何进行RCE。泄露了libc以及可以任意地址写,那么常规的思路就是劫持函数指针获得控制流。注意下面的讲解一开始你可能有点困惑,但请看到这以小节的最后你再读一遍就会明白了。由三个gadgets可以完成这个思路。具体的,先看这一段gadgets,setcontext + 53
(图4,红框)。我们可以看见只要我们能够控制rdi寄存器,那么我们就能控制几乎所有的寄存器,包括rsp和rip,也就是说我们就达成了劫持控制流、控制了几乎所有寄存器。这一段gadgets其实就是在进行SROP中 signal frame的构建,此时rdi相对于指向就是signal frame的顶部。因此,我们可以通过pwntools中的SigreturnFrame方便的控制这段代码对寄存器的赋值,只要我们可以控制rdi。
![image.png]
为了控制rdi,我们需要另外两个gadgets。一个是__libc_dlopen_mode + 56
,一个是fgetpos64+207
,分别如图5、图6所示。
那么我如何控制dl_open_hook呢?在libc2.27中,_dl_open_hook地址比free_hook大约高0x2b00左右(不同版本编译器编译出来的libc2.27可能略有差别,但总体大约再0x2b00左右)。距离这么远,我们可以覆盖到吗?答案是,可以。(在Netatalk的代码分析小节的注3部分,我们讨论了一次性可以最多写入多大的数据)简言之,我们将commands指针覆盖至free_hook的地址处,随后根据三条gadgets的调用链,依次往后布局内存,使得我们最终能够控制rdi,进而控制程序流以及几乎所有寄存器,完成RCE。
未授权访问的核心是泄露afpd的基地址,然后获取其中的三个关键数据结构preauth_switch
、postauth_switch
和afp_switch
,再通过任意地址写将afp_switch
变量的值写成postauth_switch
,即可进行未授权访问。!
https://gtrboy.github.io/posts/netatalk/#0x04-%E5%88%A9%E7%94%A8<br />https://xuanxuanblingbling.github.io/ctf/pwn/2021/11/06/netatalk/<br />https://medium.com/tenable-techblog/exploiting-an-18-year-old-bug-b47afe54172<br />https://en.wikipedia.org/wiki/Data_Stream_Interface#cite_note-2<br />https://xz.aliyun.com/t/3710
sudo docker run
-
it ubuntu:
18.04
/
bin
/
bash
sudo docker run
-
it ubuntu:
18.04
/
bin
/
bash
sudo docker run
-
p
548
:
548
-
it
-
-
privileged
=
true temp
-
image:latest
/
sbin
/
init
sudo docker run
-
p
548
:
548
-
it
-
-
privileged
=
true temp
-
image:latest
/
sbin
/
init
int
main(
int
ac, char
*
*
av) {
...
/
*
watch atp, dsi sockets
and
ipc parent
/
child
file
descriptor.
*
/
if
(!(init_listening_sockets(&obj))) {
LOG(log_error, logtype_afpd,
"main: couldn't initialize socket handler"
);
afp_exit(EXITERR_CONF);
}
...
while
(
1
) {
pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);
ret
=
poll(asev
-
>fdset, asev
-
>used,
-
1
);
pthread_sigmask(SIG_BLOCK, &sigs, NULL);
saveerrno
=
errno;
if
(gotsigchld) {
gotsigchld
=
0
;
child_handler();
continue
;
}
if
(reloadconfig) {
nologin
+
+
;
if
(!(reset_listening_sockets(&obj))) {
LOG(log_error, logtype_afpd,
"main: reset socket handlers"
);
afp_exit(EXITERR_CONF);
}
LOG(log_info, logtype_afpd,
"re-reading configuration file"
);
configfree(&obj, NULL);
afp_config_free(&obj);
if
(afp_config_parse(&obj,
"afpd"
) !
=
0
)
afp_exit(EXITERR_CONF);
if
(configinit(&obj) !
=
0
) {
LOG(log_error, logtype_afpd,
"config re-read: no servers configured"
);
afp_exit(EXITERR_CONF);
}
if
(!(init_listening_sockets(&obj))) {
LOG(log_error, logtype_afpd,
"main: couldn't initialize socket handler"
);
afp_exit(EXITERR_CONF);
}
nologin
=
0
;
reloadconfig
=
0
;
errno
=
saveerrno;
if
(server_children) {
server_child_kill(server_children, SIGHUP);
}
continue
;
}
if
(ret
=
=
0
)
continue
;
if
(ret <
0
) {
if
(errno
=
=
EINTR)
continue
;
LOG(log_error, logtype_afpd,
"main: can't wait for input: %s"
, strerror(errno));
break
;
}
for
(
int
i
=
0
; i < asev
-
>used; i
+
+
) {
if
(asev
-
>fdset[i].revents & (POLLIN | POLLERR | POLLHUP | POLLNVAL)) {
switch (asev
-
>data[i].fdtype) {
case LISTEN_FD:
/
/
here
if
((child
=
dsi_start(&obj, (DSI
*
)(asev
-
>data[i].private), server_children))) {
if
(!(asev_add_fd(asev, child
-
>afpch_ipc_fd, IPC_FD, child))) {
LOG(log_error, logtype_afpd,
"out of asev slots"
);
/
*
*
Close IPC fd here
and
mark it as unused
*
/
close(child
-
>afpch_ipc_fd);
child
-
>afpch_ipc_fd
=
-
1
;
/
*
*
Being unfriendly here, but we really
*
want to get rid of it. The
'child'
*
handle gets cleaned up
in
the SIGCLD
*
handler.
*
/
kill(child
-
>afpch_pid, SIGKILL);
}
}
break
;
case IPC_FD:
child
=
(afp_child_t
*
)(asev
-
>data[i].private);
LOG(log_debug, logtype_afpd,
"main: IPC request from child[%u]"
, child
-
>afpch_pid);
if
(ipc_server_read(server_children, child
-
>afpch_ipc_fd) !
=
0
) {
if
(!(asev_del_fd(asev, child
-
>afpch_ipc_fd))) {
LOG(log_error, logtype_afpd,
"child[%u]: no IPC fd"
);
}
close(child
-
>afpch_ipc_fd);
child
-
>afpch_ipc_fd
=
-
1
;
}
break
;
default:
LOG(log_debug, logtype_afpd,
"main: IPC request for unknown type"
);
break
;
}
/
*
switch
*
/
}
/
*
if
*
/
}
/
*
for
(i)
*
/
}
/
*
while
(
1
)
*
/
}
int
main(
int
ac, char
*
*
av) {
...
/
*
watch atp, dsi sockets
and
ipc parent
/
child
file
descriptor.
*
/
if
(!(init_listening_sockets(&obj))) {
LOG(log_error, logtype_afpd,
"main: couldn't initialize socket handler"
);
afp_exit(EXITERR_CONF);
}
...
while
(
1
) {
pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);
ret
=
poll(asev
-
>fdset, asev
-
>used,
-
1
);
pthread_sigmask(SIG_BLOCK, &sigs, NULL);
saveerrno
=
errno;
if
(gotsigchld) {
gotsigchld
=
0
;
child_handler();
continue
;
}
if
(reloadconfig) {
nologin
+
+
;
if
(!(reset_listening_sockets(&obj))) {
LOG(log_error, logtype_afpd,
"main: reset socket handlers"
);
afp_exit(EXITERR_CONF);
}
LOG(log_info, logtype_afpd,
"re-reading configuration file"
);
configfree(&obj, NULL);
afp_config_free(&obj);
if
(afp_config_parse(&obj,
"afpd"
) !
=
0
)
afp_exit(EXITERR_CONF);
if
(configinit(&obj) !
=
0
) {
LOG(log_error, logtype_afpd,
"config re-read: no servers configured"
);
afp_exit(EXITERR_CONF);
}
if
(!(init_listening_sockets(&obj))) {
LOG(log_error, logtype_afpd,
"main: couldn't initialize socket handler"
);
afp_exit(EXITERR_CONF);
}
nologin
=
0
;
reloadconfig
=
0
;
errno
=
saveerrno;
if
(server_children) {
server_child_kill(server_children, SIGHUP);
}
continue
;
}
if
(ret
=
=
0
)
continue
;
if
(ret <
0
) {
if
(errno
=
=
EINTR)
continue
;
LOG(log_error, logtype_afpd,
"main: can't wait for input: %s"
, strerror(errno));
break
;
}
for
(
int
i
=
0
; i < asev
-
>used; i
+
+
) {
if
(asev
-
>fdset[i].revents & (POLLIN | POLLERR | POLLHUP | POLLNVAL)) {
switch (asev
-
>data[i].fdtype) {
case LISTEN_FD:
/
/
here
if
((child
=
dsi_start(&obj, (DSI
*
)(asev
-
>data[i].private), server_children))) {
if
(!(asev_add_fd(asev, child
-
>afpch_ipc_fd, IPC_FD, child))) {
LOG(log_error, logtype_afpd,
"out of asev slots"
);
/
*
*
Close IPC fd here
and
mark it as unused
*
/
close(child
-
>afpch_ipc_fd);
child
-
>afpch_ipc_fd
=
-
1
;
/
*
*
Being unfriendly here, but we really
*
want to get rid of it. The
'child'
*
handle gets cleaned up
in
the SIGCLD
*
handler.
*
/
kill(child
-
>afpch_pid, SIGKILL);
}
}
break
;
case IPC_FD:
child
=
(afp_child_t
*
)(asev
-
>data[i].private);
LOG(log_debug, logtype_afpd,
"main: IPC request from child[%u]"
, child
-
>afpch_pid);
if
(ipc_server_read(server_children, child
-
>afpch_ipc_fd) !
=
0
) {
if
(!(asev_del_fd(asev, child
-
>afpch_ipc_fd))) {
LOG(log_error, logtype_afpd,
"child[%u]: no IPC fd"
);
}
close(child
-
>afpch_ipc_fd);
child
-
>afpch_ipc_fd
=
-
1
;
}
break
;
default:
LOG(log_debug, logtype_afpd,
"main: IPC request for unknown type"
);
break
;
}
/
*
switch
*
/
}
/
*
if
*
/
}
/
*
for
(i)
*
/
}
/
*
while
(
1
)
*
/
}
static afp_child_t
*
dsi_start(AFPObj
*
obj, DSI
*
dsi, server_child_t
*
server_children)
{
afp_child_t
*
child
=
NULL;
if
(dsi_getsession(dsi, server_children, obj
-
>options.tickleval, &child) !
=
0
) {
LOG(log_error, logtype_afpd,
"dsi_start: session error: %s"
, strerror(errno));
return
NULL;
}
/
/
we've forked.
if
(child
=
=
NULL) {
configfree(obj, dsi);
afp_over_dsi(obj);
/
*
start a session
*
/
exit (
0
);
}
return
child;
}
/
*
!
*
Start a DSI session, fork an afpd process
*
*
@param childp (w) after fork: parent
return
pointer to child, child returns NULL
*
@returns
0
on sucess,
any
other value denotes failure
*
/
/
*
DSI Commands
*
/
#define DSIFUNC_CLOSE 1 /* DSICloseSession */
#define DSIFUNC_CMD 2 /* DSICommand */
#define DSIFUNC_STAT 3 /* DSIGetStatus */
#define DSIFUNC_OPEN 4 /* DSIOpenSession */
#define DSIFUNC_TICKLE 5 /* DSITickle */
#define DSIFUNC_WRITE 6 /* DSIWrite */
#define DSIFUNC_ATTN 8 /* DSIAttention */
#define DSIFUNC_MAX 8 /* largest command */
int
dsi_getsession(DSI
*
dsi, server_child_t
*
serv_children,
int
tickleval, afp_child_t
*
*
childp)
{
switch (dsi
-
>header.dsi_command) {
case DSIFUNC_STAT:
/
*
send off status
and
return
*
/
{
/
*
OpenTransport
1.1
.
2
bug workaround:
*
*
OT code doesn't currently handle close sockets well. urk.
*
the workaround: wait
for
the client to close its
*
side. timeouts prevent indefinite resource use.
*
/
static struct timeval timeout
=
{
120
,
0
};
fd_set readfds;
dsi_getstatus(dsi);
FD_ZERO(&readfds);
FD_SET(dsi
-
>socket, &readfds);
free(dsi);
select(FD_SETSIZE, &readfds, NULL, NULL, &timeout);
exit(
0
);
}
break
;
case DSIFUNC_OPEN:
/
*
setup session
*
/
/
*
set
up the tickle timer
*
/
dsi
-
>timer.it_interval.tv_sec
=
dsi
-
>timer.it_value.tv_sec
=
tickleval;
dsi
-
>timer.it_interval.tv_usec
=
dsi
-
>timer.it_value.tv_usec
=
0
;
dsi_opensession(dsi);
*
childp
=
NULL;
return
0
;
default:
/
*
just close
*
/
LOG(log_info, logtype_dsi,
"DSIUnknown %d"
, dsi
-
>header.dsi_command);
dsi
-
>proto_close(dsi);
exit(EXITERR_CLNT);
}
}
/
*
DSI session options
*
/
#define DSIOPT_SERVQUANT 0x00 /* server request quantum */
#define DSIOPT_ATTNQUANT 0x01 /* attention quantum */
#define DSIOPT_REPLCSIZE 0x02 /* AFP replaycache size supported by the server (that's us) */
/
*
OpenSession.
set
up the connection
*
/
void dsi_opensession(DSI
*
dsi)
{
uint32_t i
=
0
;
/
*
this serves double duty. it must be
4
-
bytes
long
*
/
int
offs;
if
(setnonblock(dsi
-
>socket,
1
) <
0
) {
LOG(log_error, logtype_dsi,
"dsi_opensession: setnonblock: %s"
, strerror(errno));
AFP_PANIC(
"setnonblock error"
);
}
/
*
parse options
*
/
while
(i < dsi
-
>cmdlen) {
switch (dsi
-
>commands[i
+
+
]){
case DSIOPT_ATTNQUANT:
/
/
dsi_header.dsi_data[
0
]:code, dsi_header.dsi_data[
1
]:size, dsi_header.dsi_data[
2
:size
-
1
]:data
memcpy(&dsi
-
>attn_quantum, dsi
-
>commands
+
i
+
1
, dsi
-
>commands[i]);
/
/
越界写,上层函数会执行 memcpy(dsi
-
>commands, dsi_header
-
>dsi_data) dsi_header是我们发包的内容
dsi
-
>attn_quantum
=
ntohl(dsi
-
>attn_quantum);
case DSIOPT_SERVQUANT:
/
*
just ignore these
*
/
default:
i
+
=
dsi
-
>commands[i]
+
1
;
/
*
forward past length tag
+
length
*
/
break
;
}
}
/
/
...
dsi_send(dsi);
}
static afp_child_t
*
dsi_start(AFPObj
*
obj, DSI
*
dsi, server_child_t
*
server_children)
{
afp_child_t
*
child
=
NULL;
if
(dsi_getsession(dsi, server_children, obj
-
>options.tickleval, &child) !
=
0
) {
LOG(log_error, logtype_afpd,
"dsi_start: session error: %s"
, strerror(errno));
return
NULL;
}
/
/
we've forked.
if
(child
=
=
NULL) {
configfree(obj, dsi);
afp_over_dsi(obj);
/
*
start a session
*
/
exit (
0
);
}
return
child;
}
/
*
!
*
Start a DSI session, fork an afpd process
*
*
@param childp (w) after fork: parent
return
pointer to child, child returns NULL
*
@returns
0
on sucess,
any
other value denotes failure
*
/
/
*
DSI Commands
*
/
#define DSIFUNC_CLOSE 1 /* DSICloseSession */
#define DSIFUNC_CMD 2 /* DSICommand */
#define DSIFUNC_STAT 3 /* DSIGetStatus */
#define DSIFUNC_OPEN 4 /* DSIOpenSession */
#define DSIFUNC_TICKLE 5 /* DSITickle */
#define DSIFUNC_WRITE 6 /* DSIWrite */
#define DSIFUNC_ATTN 8 /* DSIAttention */
#define DSIFUNC_MAX 8 /* largest command */
int
dsi_getsession(DSI
*
dsi, server_child_t
*
serv_children,
int
tickleval, afp_child_t
*
*
childp)
{
switch (dsi
-
>header.dsi_command) {
case DSIFUNC_STAT:
/
*
send off status
and
return
*
/
{
/
*
OpenTransport
1.1
.
2
bug workaround:
*
*
OT code doesn't currently handle close sockets well. urk.
*
the workaround: wait
for
the client to close its
*
side. timeouts prevent indefinite resource use.
*
/
static struct timeval timeout
=
{
120
,
0
};
fd_set readfds;
dsi_getstatus(dsi);
FD_ZERO(&readfds);
FD_SET(dsi
-
>socket, &readfds);
free(dsi);
select(FD_SETSIZE, &readfds, NULL, NULL, &timeout);
exit(
0
);
}
break
;
case DSIFUNC_OPEN:
/
*
setup session
*
/
/
*
set
up the tickle timer
*
/
dsi
-
>timer.it_interval.tv_sec
=
dsi
-
>timer.it_value.tv_sec
=
tickleval;
dsi
-
>timer.it_interval.tv_usec
=
dsi
-
>timer.it_value.tv_usec
=
0
;
dsi_opensession(dsi);
*
childp
=
NULL;
return
0
;
default:
/
*
just close
*
/
LOG(log_info, logtype_dsi,
"DSIUnknown %d"
, dsi
-
>header.dsi_command);
dsi
-
>proto_close(dsi);
exit(EXITERR_CLNT);
}
}
/
*
DSI session options
*
/
#define DSIOPT_SERVQUANT 0x00 /* server request quantum */
#define DSIOPT_ATTNQUANT 0x01 /* attention quantum */
#define DSIOPT_REPLCSIZE 0x02 /* AFP replaycache size supported by the server (that's us) */
/
*
OpenSession.
set
up the connection
*
/
void dsi_opensession(DSI
*
dsi)
{
uint32_t i
=
0
;
/
*
this serves double duty. it must be
4
-
bytes
long
*
/
int
offs;
if
(setnonblock(dsi
-
>socket,
1
) <
0
) {
LOG(log_error, logtype_dsi,
"dsi_opensession: setnonblock: %s"
, strerror(errno));
AFP_PANIC(
"setnonblock error"
);
}
/
*
parse options
*
/
while
(i < dsi
-
>cmdlen) {
switch (dsi
-
>commands[i
+
+
]){
case DSIOPT_ATTNQUANT:
/
/
dsi_header.dsi_data[
0
]:code, dsi_header.dsi_data[
1
]:size, dsi_header.dsi_data[
2
:size
-
1
]:data
memcpy(&dsi
-
>attn_quantum, dsi
-
>commands
+
i
+
1
, dsi
-
>commands[i]);
/
/
越界写,上层函数会执行 memcpy(dsi
-
>commands, dsi_header
-
>dsi_data) dsi_header是我们发包的内容
dsi
-
>attn_quantum
=
ntohl(dsi
-
>attn_quantum);
case DSIOPT_SERVQUANT:
/
*
just ignore these
*
/
default:
i
+
=
dsi
-
>commands[i]
+
1
;
/
*
forward past length tag
+
length
*
/
break
;
}
}
/
/
...
dsi_send(dsi);
}
typedef struct DSI {
struct DSI
*
next
;
/
*
multiple listening addresses
*
/
AFPObj
*
AFPobj;
int
statuslen;
char status[
1400
];
char
*
signature;
struct dsi_block header;
struct sockaddr_storage server, client;
struct itimerval timer;
int
tickle;
/
*
tickle count
*
/
int
in_write;
/
*
in
the middle of writing multiple packets,
signal handlers can't write to the socket
*
/
int
msg_request;
/
*
pending message to the client
*
/
int
down_request;
/
*
pending SIGUSR1 down
in
5
mn
*
/
uint32_t attn_quantum, datasize, server_quantum;
uint16_t serverID, clientID;
uint8_t
*
commands;
/
*
DSI recieve
buffer
*
/
/
/
uint8_t data[DSI_DATASIZ];
/
*
DSI reply
buffer
*
/
size_t datalen, cmdlen;
off_t read_count, write_count;
uint32_t flags;
/
*
DSI flags like DSI_SLEEPING, DSI_DISCONNECTED
*
/
int
socket;
/
*
AFP session socket
*
/
int
serversock;
/
*
listening socket
*
/
/
*
DSI readahead
buffer
used
for
buffered reads
in
dsi_peek
*
/
size_t dsireadbuf;
/
*
size of the DSI readahead
buffer
used
in
dsi_peek()
*
/
char
*
buffer
;
/
*
buffer
start
*
/
char
*
start;
/
*
current
buffer
head
*
/
char
*
eof;
/
*
end of currently used
buffer
*
/
char
*
end;
#ifdef USE_ZEROCONF
char
*
bonjourname;
/
*
server name as UTF8 maxlen MAXINSTANCENAMELEN
*
/
int
zeroconf_registered;
#endif
/
*
protocol specific
open
/
close, send
/
receive
*
send
/
receive fill
in
the header
and
use dsi
-
>commands.
*
write
/
read just write
/
read data
*
/
pid_t (
*
proto_open)(struct DSI
*
);
void (
*
proto_close)(struct DSI
*
);
} DSI;
typedef struct DSI {
struct DSI
*
next
;
/
*
multiple listening addresses
*
/
AFPObj
*
AFPobj;
int
statuslen;
char status[
1400
];
char
*
signature;
struct dsi_block header;
struct sockaddr_storage server, client;
struct itimerval timer;
int
tickle;
/
*
tickle count
*
/
int
in_write;
/
*
in
the middle of writing multiple packets,
signal handlers can't write to the socket
*
/
int
msg_request;
/
*
pending message to the client
*
/
int
down_request;
/
*
pending SIGUSR1 down
in
5
mn
*
/
uint32_t attn_quantum, datasize, server_quantum;
uint16_t serverID, clientID;
uint8_t
*
commands;
/
*
DSI recieve
buffer
*
/
/
/
uint8_t data[DSI_DATASIZ];
/
*
DSI reply
buffer
*
/
size_t datalen, cmdlen;
off_t read_count, write_count;
uint32_t flags;
/
*
DSI flags like DSI_SLEEPING, DSI_DISCONNECTED
*
/
int
socket;
/
*
AFP session socket
*
/
int
serversock;
/
*
listening socket
*
/
/
*
DSI readahead
buffer
used
for
buffered reads
in
dsi_peek
*
/
size_t dsireadbuf;
/
*
size of the DSI readahead
buffer
used
in
dsi_peek()
*
/
char
*
buffer
;
/
*
buffer
start
*
/
char
*
start;
/
*
current
buffer
head
*
/
char
*
eof;
/
*
end of currently used
buffer
*
/
char
*
end;
#ifdef USE_ZEROCONF
char
*
bonjourname;
/
*
server name as UTF8 maxlen MAXINSTANCENAMELEN
*
/
int
zeroconf_registered;
#endif
/
*
protocol specific
open
/
close, send
/
receive
*
send
/
receive fill
in
the header
and
use dsi
-
>commands.
*
write
/
read just write
/
read data
*
/
pid_t (
*
proto_open)(struct DSI
*
);
void (
*
proto_close)(struct DSI
*
);
} DSI;
void afp_over_dsi(AFPObj
*
obj)
{
DSI
*
dsi
=
(DSI
*
) obj
-
>dsi;
/
/
...
while
(
1
) {
if
(sigsetjmp(recon_jmp,
1
) !
=
0
)
/
*
returning
from
SIGALARM handler
for
a primary reconnect
*
/
continue
;
/
*
Blocking read on the network socket
*
/
cmd
=
dsi_stream_receive(dsi);
if
(cmd
=
=
0
) {
/
*
cmd
=
=
0
is
the error condition
*
/
if
(dsi
-
>flags & DSI_RECONSOCKET) {
/
*
we just got a reconnect so we immediately
try
again to receive on the new fd
*
/
dsi
-
>flags &
=
~DSI_RECONSOCKET;
continue
;
}
/
*
the client sometimes logs out (afp_logout) but doesn't close the DSI session
*
/
if
(dsi
-
>flags & DSI_AFP_LOGGED_OUT) {
LOG(log_note, logtype_afpd,
"afp_over_dsi: client logged out, terminating DSI session"
);
afp_dsi_close(obj);
exit(
0
);
}
if
(dsi
-
>flags & DSI_RECONINPROG) {
LOG(log_note, logtype_afpd,
"afp_over_dsi: failed reconnect"
);
afp_dsi_close(obj);
exit(
0
);
}
/
*
Some error on the client connection, enter disconnected state
*
/
if
(dsi_disconnect(dsi) !
=
0
)
afp_dsi_die(EXITERR_CLNT);
ipc_child_state(obj, DSI_DISCONNECTED);
while
(dsi
-
>flags & DSI_DISCONNECTED)
pause();
/
*
gets interrupted by SIGALARM
or
SIGURG tickle
*
/
ipc_child_state(obj, DSI_RUNNING);
continue
;
/
*
continue
receiving until disconnect timer expires
*
or
a primary reconnect succeeds
*
/
}
if
(!(dsi
-
>flags & DSI_EXTSLEEP) && (dsi
-
>flags & DSI_SLEEPING)) {
LOG(log_debug, logtype_afpd,
"afp_over_dsi: got data, ending normal sleep"
);
dsi
-
>flags &
=
~DSI_SLEEPING;
dsi
-
>tickle
=
0
;
ipc_child_state(obj, DSI_RUNNING);
}
if
(reload_request) {
reload_request
=
0
;
load_volumes(AFPobj, LV_FORCE);
}
/
*
The first SIGINT enables debugging, the
next
restores the config
*
/
if
(debug_request) {
static
int
debugging
=
0
;
debug_request
=
0
;
dircache_dump();
uuidcache_dump();
if
(debugging) {
if
(obj
-
>options.logconfig)
setuplog(obj
-
>options.logconfig, obj
-
>options.logfile);
else
setuplog(
"default:note"
, NULL);
debugging
=
0
;
}
else
{
char logstr[
50
];
debugging
=
1
;
sprintf(logstr,
"/tmp/afpd.%u.XXXXXX"
, getpid());
setuplog(
"default:maxdebug"
, logstr);
}
}
dsi
-
>flags |
=
DSI_DATA;
dsi
-
>tickle
=
0
;
switch(cmd) {
case DSIFUNC_CLOSE:
LOG(log_debug, logtype_afpd,
"DSI: close session request"
);
afp_dsi_close(obj);
LOG(log_note, logtype_afpd,
"done"
);
exit(
0
);
case DSIFUNC_TICKLE:
dsi
-
>flags &
=
~DSI_DATA;
/
*
thats no data
in
the sense we use it
in
alarm_handler
*
/
LOG(log_debug, logtype_afpd,
"DSI: client tickle"
);
/
*
timer
is
not
every
30
seconds anymore, so we don't get killed on the client side.
*
/
if
((dsi
-
>flags & DSI_DIE))
dsi_tickle(dsi);
break
;
case DSIFUNC_CMD:
#ifdef AFS
if
( writtenfork ) {
if
( flushfork( writtenfork ) <
0
) {
LOG(log_error, logtype_afpd,
"main flushfork: %s"
, strerror(errno) );
}
writtenfork
=
NULL;
}
#endif /* AFS */
function
=
(u_char) dsi
-
>commands[
0
];
/
*
AFP replay cache
*
/
rc_idx
=
dsi
-
>clientID
%
REPLAYCACHE_SIZE;
LOG(log_debug, logtype_dsi,
"DSI request ID: %u"
, dsi
-
>clientID);
if
(replaycache[rc_idx].DSIreqID
=
=
dsi
-
>clientID
&& replaycache[rc_idx].AFPcommand
=
=
function) {
LOG(log_note, logtype_afpd,
"AFP Replay Cache match: id: %u / cmd: %s"
,
dsi
-
>clientID, AfpNum2name(function));
err
=
replaycache[rc_idx].result;
/
*
AFP replay cache end
*
/
}
else
{
/
*
send off an afp command.
in
a couple cases, we take advantage
*
of the fact that we're a stream
-
based protocol.
*
/
if
(afp_switch[function]) {
dsi
-
>datalen
=
DSI_DATASIZ;
dsi
-
>flags |
=
DSI_RUNNING;
LOG(log_debug, logtype_afpd,
"<== Start AFP command: %s"
, AfpNum2name(function));
AFP_AFPFUNC_START(function, (char
*
)AfpNum2name(function));
err
=
(
*
afp_switch[function])(obj,
(char
*
)dsi
-
>commands, dsi
-
>cmdlen,
(char
*
)&dsi
-
>data, &dsi
-
>datalen);
AFP_AFPFUNC_DONE(function, (char
*
)AfpNum2name(function));
LOG(log_debug, logtype_afpd,
"==> Finished AFP command: %s -> %s"
,
AfpNum2name(function), AfpErr2name(err));
dir_free_invalid_q();
dsi
-
>flags &
=
~DSI_RUNNING;
/
*
Add result to the AFP replay cache
*
/
replaycache[rc_idx].DSIreqID
=
dsi
-
>clientID;
replaycache[rc_idx].AFPcommand
=
function;
replaycache[rc_idx].result
=
err;
}
else
{
LOG(log_maxdebug, logtype_afpd,
"bad function %X"
, function);
dsi
-
>datalen
=
0
;
err
=
AFPERR_NOOP;
}
}
/
*
single shot toggle that gets
set
by dsi_readinit.
*
/
if
(dsi
-
>flags & DSI_NOREPLY) {
dsi
-
>flags &
=
~DSI_NOREPLY;
break
;
}
else
if
(!dsi_cmdreply(dsi, err)) {
LOG(log_error, logtype_afpd,
"dsi_cmdreply(%d): %s"
, dsi
-
>socket, strerror(errno) );
if
(dsi_disconnect(dsi) !
=
0
)
afp_dsi_die(EXITERR_CLNT);
}
break
;
case DSIFUNC_WRITE:
/
*
FPWrite
and
FPAddIcon
*
/
function
=
(u_char) dsi
-
>commands[
0
];
if
( afp_switch[ function ] !
=
NULL ) {
dsi
-
>datalen
=
DSI_DATASIZ;
dsi
-
>flags |
=
DSI_RUNNING;
LOG(log_debug, logtype_afpd,
"<== Start AFP command: %s"
, AfpNum2name(function));
AFP_AFPFUNC_START(function, (char
*
)AfpNum2name(function));
err
=
(
*
afp_switch[function])(obj,
(char
*
)dsi
-
>commands, dsi
-
>cmdlen,
(char
*
)&dsi
-
>data, &dsi
-
>datalen);
AFP_AFPFUNC_DONE(function, (char
*
)AfpNum2name(function));
LOG(log_debug, logtype_afpd,
"==> Finished AFP command: %s -> %s"
,
AfpNum2name(function), AfpErr2name(err));
dsi
-
>flags &
=
~DSI_RUNNING;
}
else
{
LOG(log_error, logtype_afpd,
"(write) bad function %x"
, function);
dsi
-
>datalen
=
0
;
err
=
AFPERR_NOOP;
}
if
(!dsi_wrtreply(dsi, err)) {
LOG(log_error, logtype_afpd,
"dsi_wrtreply: %s"
, strerror(errno) );
if
(dsi_disconnect(dsi) !
=
0
)
afp_dsi_die(EXITERR_CLNT);
}
break
;
case DSIFUNC_ATTN:
/
*
attention replies
*
/
break
;
/
*
error. this usually implies a mismatch of some kind
*
between server
and
client.
if
things are correct,
*
we need to flush the rest of the packet
if
necessary.
*
/
default:
LOG(log_info, logtype_afpd,
"afp_dsi: spurious command %d"
, cmd);
dsi_writeinit(dsi, dsi
-
>data, DSI_DATASIZ);
dsi_writeflush(dsi);
break
;
}
pending_request(dsi);
fce_pending_events(obj);
}
}
/
*
!
*
Read DSI command
and
data
*
*
@param dsi (rw) DSI handle
*
*
@
return
DSI function on success,
0
on failure
*
/
int
dsi_stream_receive(DSI
*
dsi)
{
char block[DSI_BLOCKSIZ];
LOG(log_maxdebug, logtype_dsi,
"dsi_stream_receive: START"
);
if
(dsi
-
>flags & DSI_DISCONNECTED)
return
0
;
/
*
read
in
the header
*
/
if
(dsi_buffered_stream_read(dsi, (uint8_t
*
)block, sizeof(block)) !
=
sizeof(block))
return
0
;
dsi
-
>header.dsi_flags
=
block[
0
];
dsi
-
>header.dsi_command
=
block[
1
];
if
(dsi
-
>header.dsi_command
=
=
0
)
return
0
;
memcpy(&dsi
-
>header.dsi_requestID, block
+
2
, sizeof(dsi
-
>header.dsi_requestID));
memcpy(&dsi
-
>header.dsi_data.dsi_doff, block
+
4
, sizeof(dsi
-
>header.dsi_data.dsi_doff));
dsi
-
>header.dsi_data.dsi_doff
=
htonl(dsi
-
>header.dsi_data.dsi_doff);
memcpy(&dsi
-
>header.dsi_len, block
+
8
, sizeof(dsi
-
>header.dsi_len));
memcpy(&dsi
-
>header.dsi_reserved, block
+
12
, sizeof(dsi
-
>header.dsi_reserved));
dsi
-
>clientID
=
ntohs(dsi
-
>header.dsi_requestID);
/
*
make sure we don't over
-
write our buffers.
*
/
dsi
-
>cmdlen
=
MIN
(ntohl(dsi
-
>header.dsi_len), dsi
-
>server_quantum);
/
*
Receiving DSIWrite data
is
done
in
AFP function,
not
here
*
/
if
(dsi
-
>header.dsi_data.dsi_doff) {
LOG(log_maxdebug, logtype_dsi,
"dsi_stream_receive: write request"
);
dsi
-
>cmdlen
=
dsi
-
>header.dsi_data.dsi_doff;
}
/
/
TCP fork dsi
if
(dsi_stream_read(dsi, dsi
-
>commands, dsi
-
>cmdlen) !
=
dsi
-
>cmdlen)
return
0
;
LOG(log_debug, logtype_dsi,
"dsi_stream_receive: DSI cmdlen: %zd"
, dsi
-
>cmdlen);
return
block[
1
];
}
void afp_over_dsi(AFPObj
*
obj)
{
DSI
*
dsi
=
(DSI
*
) obj
-
>dsi;
/
/
...
while
(
1
) {
if
(sigsetjmp(recon_jmp,
1
) !
=
0
)
/
*
returning
from
SIGALARM handler
for
a primary reconnect
*
/
continue
;
/
*
Blocking read on the network socket
*
/
cmd
=
dsi_stream_receive(dsi);
if
(cmd
=
=
0
) {
/
*
cmd
=
=
0
is
the error condition
*
/
if
(dsi
-
>flags & DSI_RECONSOCKET) {
/
*
we just got a reconnect so we immediately
try
again to receive on the new fd
*
/
dsi
-
>flags &
=
~DSI_RECONSOCKET;
continue
;
}
/
*
the client sometimes logs out (afp_logout) but doesn't close the DSI session
*
/
if
(dsi
-
>flags & DSI_AFP_LOGGED_OUT) {
LOG(log_note, logtype_afpd,
"afp_over_dsi: client logged out, terminating DSI session"
);
afp_dsi_close(obj);
exit(
0
);
}
if
(dsi
-
>flags & DSI_RECONINPROG) {
LOG(log_note, logtype_afpd,
"afp_over_dsi: failed reconnect"
);
afp_dsi_close(obj);
exit(
0
);
}
/
*
Some error on the client connection, enter disconnected state
*
/
if
(dsi_disconnect(dsi) !
=
0
)
afp_dsi_die(EXITERR_CLNT);
ipc_child_state(obj, DSI_DISCONNECTED);
while
(dsi
-
>flags & DSI_DISCONNECTED)
pause();
/
*
gets interrupted by SIGALARM
or
SIGURG tickle
*
/
ipc_child_state(obj, DSI_RUNNING);
continue
;
/
*
continue
receiving until disconnect timer expires
*
or
a primary reconnect succeeds
*
/
}
if
(!(dsi
-
>flags & DSI_EXTSLEEP) && (dsi
-
>flags & DSI_SLEEPING)) {
LOG(log_debug, logtype_afpd,
"afp_over_dsi: got data, ending normal sleep"
);
dsi
-
>flags &
=
~DSI_SLEEPING;
dsi
-
>tickle
=
0
;
ipc_child_state(obj, DSI_RUNNING);
}
if
(reload_request) {
reload_request
=
0
;
load_volumes(AFPobj, LV_FORCE);
}
/
*
The first SIGINT enables debugging, the
next
restores the config
*
/
if
(debug_request) {
static
int
debugging
=
0
;
debug_request
=
0
;
dircache_dump();
uuidcache_dump();
if
(debugging) {
if
(obj
-
>options.logconfig)
setuplog(obj
-
>options.logconfig, obj
-
>options.logfile);
else
setuplog(
"default:note"
, NULL);
debugging
=
0
;
}
else
{
char logstr[
50
];
debugging
=
1
;
sprintf(logstr,
"/tmp/afpd.%u.XXXXXX"
, getpid());
setuplog(
"default:maxdebug"
, logstr);
}
}
dsi
-
>flags |
=
DSI_DATA;
dsi
-
>tickle
=
0
;
switch(cmd) {
case DSIFUNC_CLOSE:
LOG(log_debug, logtype_afpd,
"DSI: close session request"
);
afp_dsi_close(obj);
LOG(log_note, logtype_afpd,
"done"
);
exit(
0
);
case DSIFUNC_TICKLE:
dsi
-
>flags &
=
~DSI_DATA;
/
*
thats no data
in
the sense we use it
in
alarm_handler
*
/
LOG(log_debug, logtype_afpd,
"DSI: client tickle"
);
/
*
timer
is
not
every
30
seconds anymore, so we don't get killed on the client side.
*
/
if
((dsi
-
>flags & DSI_DIE))
dsi_tickle(dsi);
break
;
case DSIFUNC_CMD:
#ifdef AFS
if
( writtenfork ) {
if
( flushfork( writtenfork ) <
0
) {
LOG(log_error, logtype_afpd,
"main flushfork: %s"
, strerror(errno) );
}
writtenfork
=
NULL;
}
#endif /* AFS */
function
=
(u_char) dsi
-
>commands[
0
];
/
*
AFP replay cache
*
/
rc_idx
=
dsi
-
>clientID
%
REPLAYCACHE_SIZE;
LOG(log_debug, logtype_dsi,
"DSI request ID: %u"
, dsi
-
>clientID);
if
(replaycache[rc_idx].DSIreqID
=
=
dsi
-
>clientID
&& replaycache[rc_idx].AFPcommand
=
=
function) {
LOG(log_note, logtype_afpd,
"AFP Replay Cache match: id: %u / cmd: %s"
,
dsi
-
>clientID, AfpNum2name(function));
err
=
replaycache[rc_idx].result;
/
*
AFP replay cache end
*
/
}
else
{
/
*
send off an afp command.
in
a couple cases, we take advantage
*
of the fact that we're a stream
-
based protocol.
*
/
if
(afp_switch[function]) {
dsi
-
>datalen
=
DSI_DATASIZ;
dsi
-
>flags |
=
DSI_RUNNING;
LOG(log_debug, logtype_afpd,
"<== Start AFP command: %s"
, AfpNum2name(function));
AFP_AFPFUNC_START(function, (char
*
)AfpNum2name(function));
err
=
(
*
afp_switch[function])(obj,
(char
*
)dsi
-
>commands, dsi
-
>cmdlen,
(char
*
)&dsi
-
>data, &dsi
-
>datalen);
AFP_AFPFUNC_DONE(function, (char
*
)AfpNum2name(function));
LOG(log_debug, logtype_afpd,
"==> Finished AFP command: %s -> %s"
,
AfpNum2name(function), AfpErr2name(err));
dir_free_invalid_q();
dsi
-
>flags &
=
~DSI_RUNNING;
/
*
Add result to the AFP replay cache
*
/
replaycache[rc_idx].DSIreqID
=
dsi
-
>clientID;
replaycache[rc_idx].AFPcommand
=
function;
replaycache[rc_idx].result
=
err;
}
else
{
LOG(log_maxdebug, logtype_afpd,
"bad function %X"
, function);
dsi
-
>datalen
=
0
;
err
=
AFPERR_NOOP;
}
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)