Prevent NT_STATUS 0xF1000000 errors from appearing when
[jra/samba/.git] / source3 / libsmb / async_smb.c
1 /*
2    Unix SMB/CIFS implementation.
3    Infrastructure for async SMB client requests
4    Copyright (C) Volker Lendecke 2008
5
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.
10
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.
15
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/>.
18 */
19
20 #include "includes.h"
21
22 /*
23  * Fetch an error out of a NBT packet
24  */
25
26 NTSTATUS cli_pull_error(char *buf)
27 {
28         uint32_t flags2 = SVAL(buf, smb_flg2);
29
30         if (flags2 & FLAGS2_32_BIT_ERROR_CODES) {
31                 return NT_STATUS(IVAL(buf, smb_rcls));
32         }
33
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)
38                 return NT_STATUS_OK;
39
40         return NT_STATUS_DOS(CVAL(buf, smb_rcls), SVAL(buf,smb_err));
41 }
42
43 /*
44  * Compatibility helper for the sync APIs: Fake NTSTATUS in cli->inbuf
45  */
46
47 void cli_set_error(struct cli_state *cli, NTSTATUS status)
48 {
49         uint32_t flags2 = SVAL(cli->inbuf, smb_flg2);
50
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));
56                 return;
57         }
58
59         SSVAL(cli->inbuf, smb_flg2, flags2 | FLAGS2_32_BIT_ERROR_CODES);
60         SIVAL(cli->inbuf, smb_rcls, NT_STATUS_V(status));
61         return;
62 }
63
64 /*
65  * Allocate a new mid
66  */
67
68 static uint16_t cli_new_mid(struct cli_state *cli)
69 {
70         uint16_t result;
71         struct cli_request *req;
72
73         while (true) {
74                 result = cli->mid++;
75                 if (result == 0) {
76                         continue;
77                 }
78
79                 for (req = cli->outstanding_requests; req; req = req->next) {
80                         if (result == req->mid) {
81                                 break;
82                         }
83                 }
84
85                 if (req == NULL) {
86                         return result;
87                 }
88         }
89 }
90
91 static char *cli_request_print(TALLOC_CTX *mem_ctx, struct async_req *req)
92 {
93         char *result = async_req_print(mem_ctx, req);
94         struct cli_request *cli_req = cli_request_get(req);
95
96         if (result == NULL) {
97                 return NULL;
98         }
99
100         return talloc_asprintf_append_buffer(
101                 result, "mid=%d\n", cli_req->mid);
102 }
103
104 static int cli_request_destructor(struct cli_request *req)
105 {
106         if (req->enc_state != NULL) {
107                 common_free_enc_buffer(req->enc_state, req->outbuf);
108         }
109         DLIST_REMOVE(req->cli->outstanding_requests, req);
110         return 0;
111 }
112
113 /*
114  * Create a fresh async smb request
115  */
116
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)
122 {
123         struct async_req *result;
124         struct cli_request *cli_req;
125         size_t bufsize = smb_size + num_words * 2 + num_bytes;
126
127         result = async_req_new(mem_ctx, ev);
128         if (result == NULL) {
129                 return NULL;
130         }
131
132         cli_req = (struct cli_request *)talloc_size(
133                 result, sizeof(*cli_req) + bufsize);
134         if (cli_req == NULL) {
135                 TALLOC_FREE(result);
136                 return NULL;
137         }
138         talloc_set_name_const(cli_req, "struct cli_request");
139         result->private_data = cli_req;
140         result->print = cli_request_print;
141
142         cli_req->async = result;
143         cli_req->cli = cli;
144         cli_req->outbuf = ((char *)cli_req + sizeof(*cli_req));
145         cli_req->sent = 0;
146         cli_req->mid = cli_new_mid(cli);
147         cli_req->inbuf = NULL;
148         cli_req->enc_state = NULL;
149
150         SCVAL(cli_req->outbuf, smb_wct, num_words);
151         SSVAL(cli_req->outbuf, smb_vwv + num_words * 2, num_bytes);
152
153         DLIST_ADD_END(cli->outstanding_requests, cli_req,
154                       struct cli_request *);
155         talloc_set_destructor(cli_req, cli_request_destructor);
156
157         DEBUG(10, ("cli_request_new: mid=%d\n", cli_req->mid));
158
159         *preq = cli_req;
160         return result;
161 }
162
163 /*
164  * Convenience function to get the SMB part out of an async_req
165  */
166
167 struct cli_request *cli_request_get(struct async_req *req)
168 {
169         if (req == NULL) {
170                 return NULL;
171         }
172         return talloc_get_type_abort(req->private_data, struct cli_request);
173 }
174
175 /*
176  * A PDU has arrived on cli->evt_inbuf
177  */
178
179 static void handle_incoming_pdu(struct cli_state *cli)
180 {
181         struct cli_request *req;
182         uint16_t mid;
183         size_t raw_pdu_len, buf_len, pdu_len, rest_len;
184         char *pdu;
185         NTSTATUS status;
186
187         /*
188          * The encrypted PDU len might differ from the unencrypted one
189          */
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;
193
194         if (buf_len == raw_pdu_len) {
195                 /*
196                  * Optimal case: Exactly one PDU was in the socket buffer
197                  */
198                 pdu = cli->evt_inbuf;
199                 cli->evt_inbuf = NULL;
200         }
201         else {
202                 DEBUG(11, ("buf_len = %d, raw_pdu_len = %d, splitting "
203                            "buffer\n", (int)buf_len, (int)raw_pdu_len));
204
205                 if (raw_pdu_len < rest_len) {
206                         /*
207                          * The PDU is shorter, talloc_memdup that one.
208                          */
209                         pdu = (char *)talloc_memdup(
210                                 cli, cli->evt_inbuf, raw_pdu_len);
211
212                         memmove(cli->evt_inbuf, cli->evt_inbuf + raw_pdu_len,
213                                 buf_len - raw_pdu_len);
214
215                         cli->evt_inbuf = TALLOC_REALLOC_ARRAY(
216                                 NULL, cli->evt_inbuf, char, rest_len);
217
218                         if (pdu == NULL) {
219                                 status = NT_STATUS_NO_MEMORY;
220                                 goto invalidate_requests;
221                         }
222                 }
223                 else {
224                         /*
225                          * The PDU is larger than the rest, talloc_memdup the
226                          * rest
227                          */
228                         pdu = cli->evt_inbuf;
229
230                         cli->evt_inbuf = (char *)talloc_memdup(
231                                 cli, pdu + raw_pdu_len, rest_len);
232
233                         if (cli->evt_inbuf == NULL) {
234                                 status = NT_STATUS_NO_MEMORY;
235                                 goto invalidate_requests;
236                         }
237                 }
238
239         }
240
241         /*
242          * TODO: Handle oplock break requests
243          */
244
245         if (cli_encryption_on(cli) && CVAL(pdu, 0) == 0) {
246                 uint16_t enc_ctx_num;
247
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",
251                                    nt_errstr(status)));
252                         goto invalidate_requests;
253                 }
254
255                 if (enc_ctx_num != cli->trans_enc_state->enc_ctx_num) {
256                         DEBUG(10, ("wrong enc_ctx %d, expected %d\n",
257                                    enc_ctx_num,
258                                    cli->trans_enc_state->enc_ctx_num));
259                         status = NT_STATUS_INVALID_HANDLE;
260                         goto invalidate_requests;
261                 }
262
263                 status = common_decrypt_buffer(cli->trans_enc_state,
264                                                pdu);
265                 if (!NT_STATUS_IS_OK(status)) {
266                         DEBUG(10, ("common_decrypt_buffer returned %s\n",
267                                    nt_errstr(status)));
268                         goto invalidate_requests;
269                 }
270         }
271
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;
276         }
277
278         mid = SVAL(pdu, smb_mid);
279
280         DEBUG(10, ("handle_incoming_pdu: got mid %d\n", mid));
281
282         for (req = cli->outstanding_requests; req; req = req->next) {
283                 if (req->mid == mid) {
284                         break;
285                 }
286         }
287
288         pdu_len = smb_len(pdu) + 4;
289
290         if (req == NULL) {
291                 DEBUG(3, ("Request for mid %d not found, dumping PDU\n", mid));
292
293                 TALLOC_FREE(pdu);
294                 return;
295         }
296
297         req->inbuf = talloc_move(req, &pdu);
298
299         async_req_done(req->async);
300         return;
301
302  invalidate_requests:
303
304         DEBUG(10, ("handle_incoming_pdu: Aborting with %s\n",
305                    nt_errstr(status)));
306
307         for (req = cli->outstanding_requests; req; req = req->next) {
308                 async_req_error(req->async, status);
309         }
310         return;
311 }
312
313 /*
314  * fd event callback. This is the basic connection to the socket
315  */
316
317 static void cli_state_handler(struct event_context *event_ctx,
318                               struct fd_event *event, uint16 flags, void *p)
319 {
320         struct cli_state *cli = (struct cli_state *)p;
321         struct cli_request *req;
322
323         DEBUG(11, ("cli_state_handler called with flags %d\n", flags));
324
325         if (flags & EVENT_FD_READ) {
326                 int res, available;
327                 size_t old_size, new_size;
328                 char *tmp;
329
330                 res = ioctl(cli->fd, FIONREAD, &available);
331                 if (res == -1) {
332                         DEBUG(10, ("ioctl(FIONREAD) failed: %s\n",
333                                    strerror(errno)));
334                         goto sock_error;
335                 }
336
337                 if (available == 0) {
338                         /* EOF */
339                         goto sock_error;
340                 }
341
342                 old_size = talloc_get_size(cli->evt_inbuf);
343                 new_size = old_size + available;
344
345                 if (new_size < old_size) {
346                         /* wrap */
347                         goto sock_error;
348                 }
349
350                 tmp = TALLOC_REALLOC_ARRAY(cli, cli->evt_inbuf, char,
351                                            new_size);
352                 if (tmp == NULL) {
353                         /* nomem */
354                         goto sock_error;
355                 }
356                 cli->evt_inbuf = tmp;
357
358                 res = recv(cli->fd, cli->evt_inbuf + old_size, available, 0);
359                 if (res == -1) {
360                         DEBUG(10, ("recv failed: %s\n", strerror(errno)));
361                         goto sock_error;
362                 }
363
364                 DEBUG(11, ("cli_state_handler: received %d bytes, "
365                            "smb_len(evt_inbuf) = %d\n", (int)res,
366                            smb_len(cli->evt_inbuf)));
367
368                 /* recv *might* have returned less than announced */
369                 new_size = old_size + res;
370
371                 /* shrink, so I don't expect errors here */
372                 cli->evt_inbuf = TALLOC_REALLOC_ARRAY(cli, cli->evt_inbuf,
373                                                       char, new_size);
374
375                 while ((cli->evt_inbuf != NULL)
376                        && ((smb_len(cli->evt_inbuf) + 4) <= new_size)) {
377                         /*
378                          * we've got a complete NBT level PDU in evt_inbuf
379                          */
380                         handle_incoming_pdu(cli);
381                         new_size = talloc_get_size(cli->evt_inbuf);
382                 }
383         }
384
385         if (flags & EVENT_FD_WRITE) {
386                 size_t to_send;
387                 ssize_t sent;
388
389                 for (req = cli->outstanding_requests; req; req = req->next) {
390                         to_send = smb_len(req->outbuf)+4;
391                         if (to_send > req->sent) {
392                                 break;
393                         }
394                 }
395
396                 if (req == NULL) {
397                         event_fd_set_not_writeable(event);
398                         return;
399                 }
400
401                 sent = send(cli->fd, req->outbuf + req->sent,
402                             to_send - req->sent, 0);
403
404                 if (sent < 0) {
405                         goto sock_error;
406                 }
407
408                 req->sent += sent;
409
410                 if (req->sent == to_send) {
411                         return;
412                 }
413         }
414         return;
415
416  sock_error:
417         for (req = cli->outstanding_requests; req; req = req->next) {
418                 req->async->state = ASYNC_REQ_ERROR;
419                 req->async->status = map_nt_error_from_unix(errno);
420         }
421         TALLOC_FREE(cli->fd_event);
422         close(cli->fd);
423         cli->fd = -1;
424 }
425
426 /*
427  * Holder for a talloc_destructor, we need to zero out the pointers in cli
428  * when deleting
429  */
430 struct cli_tmp_event {
431         struct cli_state *cli;
432 };
433
434 static int cli_tmp_event_destructor(struct cli_tmp_event *e)
435 {
436         TALLOC_FREE(e->cli->fd_event);
437         TALLOC_FREE(e->cli->event_ctx);
438         return 0;
439 }
440
441 /*
442  * Create a temporary event context for use in the sync helper functions
443  */
444
445 struct cli_tmp_event *cli_tmp_event_ctx(TALLOC_CTX *mem_ctx,
446                                         struct cli_state *cli)
447 {
448         struct cli_tmp_event *state;
449
450         if (cli->event_ctx != NULL) {
451                 return NULL;
452         }
453
454         state = talloc(mem_ctx, struct cli_tmp_event);
455         if (state == NULL) {
456                 return NULL;
457         }
458         state->cli = cli;
459         talloc_set_destructor(state, cli_tmp_event_destructor);
460
461         cli->event_ctx = event_context_init(state);
462         if (cli->event_ctx == NULL) {
463                 TALLOC_FREE(state);
464                 return NULL;
465         }
466
467         cli->fd_event = event_add_fd(cli->event_ctx, state, cli->fd,
468                                      EVENT_FD_READ, cli_state_handler, cli);
469         if (cli->fd_event == NULL) {
470                 TALLOC_FREE(state);
471                 return NULL;
472         }
473         return state;
474 }
475
476 /*
477  * Attach an event context permanently to a cli_struct
478  */
479
480 NTSTATUS cli_add_event_ctx(struct cli_state *cli,
481                            struct event_context *event_ctx)
482 {
483         cli->event_ctx = event_ctx;
484         cli->fd_event = event_add_fd(event_ctx, cli, cli->fd, EVENT_FD_READ,
485                                      cli_state_handler, cli);
486         if (cli->fd_event == NULL) {
487                 return NT_STATUS_NO_MEMORY;
488         }
489         return NT_STATUS_OK;
490 }