2 Unix SMB/CIFS implementation.
3 Infrastructure for async SMB client requests
4 Copyright (C) Volker Lendecke 2008
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 * Fetch an error out of a NBT packet
26 NTSTATUS
cli_pull_error(char *buf
)
28 uint32_t flags2
= SVAL(buf
, smb_flg2
);
30 if (flags2
& FLAGS2_32_BIT_ERROR_CODES
) {
31 return NT_STATUS(IVAL(buf
, smb_rcls
));
34 /* if the client uses dos errors, but there is no error,
35 we should return no error here, otherwise it looks
36 like an unknown bad NT_STATUS. jmcd */
37 if (CVAL(buf
, smb_rcls
) == 0)
40 return NT_STATUS_DOS(CVAL(buf
, smb_rcls
), SVAL(buf
,smb_err
));
44 * Compatibility helper for the sync APIs: Fake NTSTATUS in cli->inbuf
47 void cli_set_error(struct cli_state
*cli
, NTSTATUS status
)
49 uint32_t flags2
= SVAL(cli
->inbuf
, smb_flg2
);
51 if (NT_STATUS_IS_DOS(status
)) {
52 SSVAL(cli
->inbuf
, smb_flg2
,
53 flags2
& ~FLAGS2_32_BIT_ERROR_CODES
);
54 SCVAL(cli
->inbuf
, smb_rcls
, NT_STATUS_DOS_CLASS(status
));
55 SSVAL(cli
->inbuf
, smb_err
, NT_STATUS_DOS_CODE(status
));
59 SSVAL(cli
->inbuf
, smb_flg2
, flags2
| FLAGS2_32_BIT_ERROR_CODES
);
60 SIVAL(cli
->inbuf
, smb_rcls
, NT_STATUS_V(status
));
68 static uint16_t cli_new_mid(struct cli_state
*cli
)
71 struct cli_request
*req
;
79 for (req
= cli
->outstanding_requests
; req
; req
= req
->next
) {
80 if (result
== req
->mid
) {
91 static char *cli_request_print(TALLOC_CTX
*mem_ctx
, struct async_req
*req
)
93 char *result
= async_req_print(mem_ctx
, req
);
94 struct cli_request
*cli_req
= cli_request_get(req
);
100 return talloc_asprintf_append_buffer(
101 result
, "mid=%d\n", cli_req
->mid
);
104 static int cli_request_destructor(struct cli_request
*req
)
106 if (req
->enc_state
!= NULL
) {
107 common_free_enc_buffer(req
->enc_state
, req
->outbuf
);
109 DLIST_REMOVE(req
->cli
->outstanding_requests
, req
);
114 * Create a fresh async smb request
117 struct async_req
*cli_request_new(TALLOC_CTX
*mem_ctx
,
118 struct event_context
*ev
,
119 struct cli_state
*cli
,
120 uint8_t num_words
, size_t num_bytes
,
121 struct cli_request
**preq
)
123 struct async_req
*result
;
124 struct cli_request
*cli_req
;
125 size_t bufsize
= smb_size
+ num_words
* 2 + num_bytes
;
127 result
= async_req_new(mem_ctx
, ev
);
128 if (result
== NULL
) {
132 cli_req
= (struct cli_request
*)talloc_size(
133 result
, sizeof(*cli_req
) + bufsize
);
134 if (cli_req
== NULL
) {
138 talloc_set_name_const(cli_req
, "struct cli_request");
139 result
->private_data
= cli_req
;
140 result
->print
= cli_request_print
;
142 cli_req
->async
= result
;
144 cli_req
->outbuf
= ((char *)cli_req
+ sizeof(*cli_req
));
146 cli_req
->mid
= cli_new_mid(cli
);
147 cli_req
->inbuf
= NULL
;
148 cli_req
->enc_state
= NULL
;
150 SCVAL(cli_req
->outbuf
, smb_wct
, num_words
);
151 SSVAL(cli_req
->outbuf
, smb_vwv
+ num_words
* 2, num_bytes
);
153 DLIST_ADD_END(cli
->outstanding_requests
, cli_req
,
154 struct cli_request
*);
155 talloc_set_destructor(cli_req
, cli_request_destructor
);
157 DEBUG(10, ("cli_request_new: mid=%d\n", cli_req
->mid
));
164 * Convenience function to get the SMB part out of an async_req
167 struct cli_request
*cli_request_get(struct async_req
*req
)
172 return talloc_get_type_abort(req
->private_data
, struct cli_request
);
176 * A PDU has arrived on cli->evt_inbuf
179 static void handle_incoming_pdu(struct cli_state
*cli
)
181 struct cli_request
*req
;
183 size_t raw_pdu_len
, buf_len
, pdu_len
, rest_len
;
188 * The encrypted PDU len might differ from the unencrypted one
190 raw_pdu_len
= smb_len(cli
->evt_inbuf
) + 4;
191 buf_len
= talloc_get_size(cli
->evt_inbuf
);
192 rest_len
= buf_len
- raw_pdu_len
;
194 if (buf_len
== raw_pdu_len
) {
196 * Optimal case: Exactly one PDU was in the socket buffer
198 pdu
= cli
->evt_inbuf
;
199 cli
->evt_inbuf
= NULL
;
202 DEBUG(11, ("buf_len = %d, raw_pdu_len = %d, splitting "
203 "buffer\n", (int)buf_len
, (int)raw_pdu_len
));
205 if (raw_pdu_len
< rest_len
) {
207 * The PDU is shorter, talloc_memdup that one.
209 pdu
= (char *)talloc_memdup(
210 cli
, cli
->evt_inbuf
, raw_pdu_len
);
212 memmove(cli
->evt_inbuf
, cli
->evt_inbuf
+ raw_pdu_len
,
213 buf_len
- raw_pdu_len
);
215 cli
->evt_inbuf
= TALLOC_REALLOC_ARRAY(
216 NULL
, cli
->evt_inbuf
, char, rest_len
);
219 status
= NT_STATUS_NO_MEMORY
;
220 goto invalidate_requests
;
225 * The PDU is larger than the rest, talloc_memdup the
228 pdu
= cli
->evt_inbuf
;
230 cli
->evt_inbuf
= (char *)talloc_memdup(
231 cli
, pdu
+ raw_pdu_len
, rest_len
);
233 if (cli
->evt_inbuf
== NULL
) {
234 status
= NT_STATUS_NO_MEMORY
;
235 goto invalidate_requests
;
242 * TODO: Handle oplock break requests
245 if (cli_encryption_on(cli
) && CVAL(pdu
, 0) == 0) {
246 uint16_t enc_ctx_num
;
248 status
= get_enc_ctx_num((uint8_t *)pdu
, &enc_ctx_num
);
249 if (!NT_STATUS_IS_OK(status
)) {
250 DEBUG(10, ("get_enc_ctx_num returned %s\n",
252 goto invalidate_requests
;
255 if (enc_ctx_num
!= cli
->trans_enc_state
->enc_ctx_num
) {
256 DEBUG(10, ("wrong enc_ctx %d, expected %d\n",
258 cli
->trans_enc_state
->enc_ctx_num
));
259 status
= NT_STATUS_INVALID_HANDLE
;
260 goto invalidate_requests
;
263 status
= common_decrypt_buffer(cli
->trans_enc_state
,
265 if (!NT_STATUS_IS_OK(status
)) {
266 DEBUG(10, ("common_decrypt_buffer returned %s\n",
268 goto invalidate_requests
;
272 if (!cli_check_sign_mac(cli
, pdu
)) {
273 DEBUG(10, ("cli_check_sign_mac failed\n"));
274 status
= NT_STATUS_ACCESS_DENIED
;
275 goto invalidate_requests
;
278 mid
= SVAL(pdu
, smb_mid
);
280 DEBUG(10, ("handle_incoming_pdu: got mid %d\n", mid
));
282 for (req
= cli
->outstanding_requests
; req
; req
= req
->next
) {
283 if (req
->mid
== mid
) {
288 pdu_len
= smb_len(pdu
) + 4;
291 DEBUG(3, ("Request for mid %d not found, dumping PDU\n", mid
));
297 req
->inbuf
= talloc_move(req
, &pdu
);
299 async_req_done(req
->async
);
304 DEBUG(10, ("handle_incoming_pdu: Aborting with %s\n",
307 for (req
= cli
->outstanding_requests
; req
; req
= req
->next
) {
308 async_req_error(req
->async
, status
);
314 * fd event callback. This is the basic connection to the socket
317 static void cli_state_handler(struct event_context
*event_ctx
,
318 struct fd_event
*event
, uint16 flags
, void *p
)
320 struct cli_state
*cli
= (struct cli_state
*)p
;
321 struct cli_request
*req
, *next
;
324 DEBUG(11, ("cli_state_handler called with flags %d\n", flags
));
326 if (flags
& EVENT_FD_READ
) {
328 size_t old_size
, new_size
;
331 res
= ioctl(cli
->fd
, FIONREAD
, &available
);
333 DEBUG(10, ("ioctl(FIONREAD) failed: %s\n",
335 status
= map_nt_error_from_unix(errno
);
339 if (available
== 0) {
341 status
= NT_STATUS_END_OF_FILE
;
345 old_size
= talloc_get_size(cli
->evt_inbuf
);
346 new_size
= old_size
+ available
;
348 if (new_size
< old_size
) {
350 status
= NT_STATUS_UNEXPECTED_IO_ERROR
;
354 tmp
= TALLOC_REALLOC_ARRAY(cli
, cli
->evt_inbuf
, char,
358 status
= NT_STATUS_NO_MEMORY
;
361 cli
->evt_inbuf
= tmp
;
363 res
= sys_recv(cli
->fd
, cli
->evt_inbuf
+ old_size
, available
, 0);
365 DEBUG(10, ("recv failed: %s\n", strerror(errno
)));
366 status
= map_nt_error_from_unix(errno
);
370 DEBUG(11, ("cli_state_handler: received %d bytes, "
371 "smb_len(evt_inbuf) = %d\n", (int)res
,
372 smb_len(cli
->evt_inbuf
)));
374 /* recv *might* have returned less than announced */
375 new_size
= old_size
+ res
;
377 /* shrink, so I don't expect errors here */
378 cli
->evt_inbuf
= TALLOC_REALLOC_ARRAY(cli
, cli
->evt_inbuf
,
381 while ((cli
->evt_inbuf
!= NULL
)
382 && ((smb_len(cli
->evt_inbuf
) + 4) <= new_size
)) {
384 * we've got a complete NBT level PDU in evt_inbuf
386 handle_incoming_pdu(cli
);
387 new_size
= talloc_get_size(cli
->evt_inbuf
);
391 if (flags
& EVENT_FD_WRITE
) {
395 for (req
= cli
->outstanding_requests
; req
; req
= req
->next
) {
396 to_send
= smb_len(req
->outbuf
)+4;
397 if (to_send
> req
->sent
) {
403 event_fd_set_not_writeable(event
);
407 sent
= sys_send(cli
->fd
, req
->outbuf
+ req
->sent
,
408 to_send
- req
->sent
, 0);
411 status
= map_nt_error_from_unix(errno
);
417 if (req
->sent
== to_send
) {
424 for (req
= cli
->outstanding_requests
; req
; req
= next
) {
426 async_req_error(req
->async
, status
);
428 TALLOC_FREE(cli
->fd_event
);
434 * Holder for a talloc_destructor, we need to zero out the pointers in cli
437 struct cli_tmp_event
{
438 struct cli_state
*cli
;
441 static int cli_tmp_event_destructor(struct cli_tmp_event
*e
)
443 TALLOC_FREE(e
->cli
->fd_event
);
444 TALLOC_FREE(e
->cli
->event_ctx
);
449 * Create a temporary event context for use in the sync helper functions
452 struct cli_tmp_event
*cli_tmp_event_ctx(TALLOC_CTX
*mem_ctx
,
453 struct cli_state
*cli
)
455 struct cli_tmp_event
*state
;
457 if (cli
->event_ctx
!= NULL
) {
461 state
= talloc(mem_ctx
, struct cli_tmp_event
);
466 talloc_set_destructor(state
, cli_tmp_event_destructor
);
468 cli
->event_ctx
= event_context_init(state
);
469 if (cli
->event_ctx
== NULL
) {
474 cli
->fd_event
= event_add_fd(cli
->event_ctx
, state
, cli
->fd
,
475 EVENT_FD_READ
, cli_state_handler
, cli
);
476 if (cli
->fd_event
== NULL
) {
484 * Attach an event context permanently to a cli_struct
487 NTSTATUS
cli_add_event_ctx(struct cli_state
*cli
,
488 struct event_context
*event_ctx
)
490 cli
->event_ctx
= event_ctx
;
491 cli
->fd_event
= event_add_fd(event_ctx
, cli
, cli
->fd
, EVENT_FD_READ
,
492 cli_state_handler
, cli
);
493 if (cli
->fd_event
== NULL
) {
494 return NT_STATUS_NO_MEMORY
;