r4891: - added a generic resolve_name() async interface in libcli/resolve/,
[jelmer/samba4-debian.git] / source / libcli / nbt / nbtsocket.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    low level socket handling for nbt requests
5
6    Copyright (C) Andrew Tridgell 2005
7    
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12    
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17    
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22
23 #include "includes.h"
24 #include "events.h"
25 #include "dlinklist.h"
26 #include "libcli/nbt/libnbt.h"
27
28 #define NBT_MAX_PACKET_SIZE 2048
29 #define NBT_MAX_REPLIES 1000
30
31 /*
32   destroy a nbt socket
33 */
34 static int nbtsock_destructor(void *ptr)
35 {
36         struct nbt_name_socket *nbtsock = talloc_get_type(ptr, struct nbt_name_socket);
37         event_remove_fd(nbtsock->event_ctx, nbtsock->fde);
38         return 0;
39 }
40
41 /*
42   destroy a pending request
43 */
44 static int nbt_name_request_destructor(void *ptr)
45 {
46         struct nbt_name_request *req = talloc_get_type(ptr, struct nbt_name_request);
47         
48         if (req->state == NBT_REQUEST_SEND) {
49                 DLIST_REMOVE(req->nbtsock->send_queue, req);
50         }
51         if (req->state == NBT_REQUEST_WAIT) {
52                 req->nbtsock->num_pending--;
53         }
54         if (req->request->name_trn_id != 0) {
55                 idr_remove(req->nbtsock->idr, req->request->name_trn_id);
56                 req->request->name_trn_id = 0;
57         }
58         if (req->te) {
59                 event_remove_timed(req->nbtsock->event_ctx, req->te);
60                 req->te = NULL;
61         }
62         if (req->nbtsock->send_queue == NULL) {
63                 req->nbtsock->fde->flags &= ~EVENT_FD_WRITE;
64         }
65         if (req->nbtsock->num_pending == 0) {
66                 req->nbtsock->fde->flags &= ~EVENT_FD_READ;
67         }
68         return 0;
69 }
70
71
72 /*
73   handle send events on a nbt name socket
74 */
75 static void nbt_name_socket_send(struct nbt_name_socket *nbtsock)
76 {
77         struct nbt_name_request *req = nbtsock->send_queue;
78         TALLOC_CTX *tmp_ctx = talloc_new(req);
79         NTSTATUS status;
80
81         while ((req = nbtsock->send_queue)) {
82                 DATA_BLOB blob;
83                 size_t len;
84                 
85                 if (DEBUGLVL(10)) {
86                         DEBUG(10,("Sending nbt packet:\n"));
87                         NDR_PRINT_DEBUG(nbt_name_packet, req->request);
88                 }
89
90                 status = ndr_push_struct_blob(&blob, tmp_ctx, req->request, 
91                                               (ndr_push_flags_fn_t)
92                                               ndr_push_nbt_name_packet);
93                 if (!NT_STATUS_IS_OK(status)) goto failed;
94
95                 if (req->request->operation & NBT_FLAG_BROADCAST) {
96                         socket_set_option(nbtsock->sock, "SO_BROADCAST", "1");
97                 }
98
99                 len = blob.length;
100                 status = socket_sendto(nbtsock->sock, &blob, &len, 0, 
101                                        req->dest_addr, req->dest_port);
102                 if (NT_STATUS_IS_ERR(status)) goto failed;              
103
104                 if (!NT_STATUS_IS_OK(status)) {
105                         talloc_free(tmp_ctx);
106                         return;
107                 }
108
109                 DLIST_REMOVE(nbtsock->send_queue, req);
110                 req->state = NBT_REQUEST_WAIT;
111                 nbtsock->fde->flags |= EVENT_FD_READ;
112                 nbtsock->num_pending++;
113         }
114
115         nbtsock->fde->flags &= ~EVENT_FD_WRITE;
116         talloc_free(tmp_ctx);
117         return;
118
119 failed:
120         DLIST_REMOVE(nbtsock->send_queue, req);
121         nbt_name_request_destructor(req);
122         req->status = status;
123         req->state = NBT_REQUEST_ERROR;
124         if (req->async.fn) {
125                 req->async.fn(req);
126         }
127         talloc_free(tmp_ctx);
128         return;
129 }
130
131
132 /*
133   handle recv events on a nbt name socket
134 */
135 static void nbt_name_socket_recv(struct nbt_name_socket *nbtsock)
136 {
137         TALLOC_CTX *tmp_ctx = talloc_new(nbtsock);
138         NTSTATUS status;
139         const char *src_addr;
140         int src_port;
141         DATA_BLOB blob;
142         size_t nread;
143         struct nbt_name_packet *packet;
144         struct nbt_name_request *req;
145
146         blob = data_blob_talloc(tmp_ctx, NULL, NBT_MAX_PACKET_SIZE);
147         if (blob.data == NULL) {
148                 talloc_free(tmp_ctx);
149                 return;
150         }
151
152         status = socket_recvfrom(nbtsock->sock, blob.data, blob.length, &nread, 0,
153                                  &src_addr, &src_port);
154         if (!NT_STATUS_IS_OK(status)) {
155                 talloc_free(tmp_ctx);
156                 return;
157         }
158         talloc_steal(tmp_ctx, src_addr);
159
160         packet = talloc(tmp_ctx, struct nbt_name_packet);
161         if (packet == NULL) {
162                 talloc_free(tmp_ctx);
163                 return;
164         }
165
166         status = ndr_pull_struct_blob(&blob, packet, packet, 
167                                       (ndr_pull_flags_fn_t)ndr_pull_nbt_name_packet);
168         if (!NT_STATUS_IS_OK(status)) {
169                 DEBUG(2,("Failed to parse incoming NBT name packet - %s\n",
170                          nt_errstr(status)));
171                 talloc_free(tmp_ctx);
172                 return;
173         }
174
175         if (DEBUGLVL(10)) {
176                 DEBUG(10,("Received nbt packet:\n"));
177                 NDR_PRINT_DEBUG(nbt_name_packet, packet);
178         }
179
180         if (!(packet->operation & NBT_FLAG_REPLY)) {
181                 talloc_free(tmp_ctx);
182                 return;
183         }
184
185         /* find the matching request */
186         req = idr_find(nbtsock->idr, packet->name_trn_id);
187         if (req == NULL) {
188                 DEBUG(2,("Failed to match request for incoming name packet id 0x%04x\n",
189                          packet->name_trn_id));
190                 talloc_free(tmp_ctx);
191                 return;
192         }
193
194         req->replies = talloc_realloc(req, req->replies, struct nbt_name_reply, req->num_replies+1);
195         if (req->replies == NULL) {
196                 nbt_name_request_destructor(req);
197                 req->state = NBT_REQUEST_DONE;
198                 req->status = NT_STATUS_NO_MEMORY;
199                 talloc_free(tmp_ctx);
200                 if (req->async.fn) {
201                         req->async.fn(req);
202                 }
203                 return;
204         }
205
206         req->replies[req->num_replies].reply_addr = talloc_steal(req, src_addr);
207         req->replies[req->num_replies].reply_port = src_port;
208         req->replies[req->num_replies].packet = talloc_steal(req, packet);
209         req->num_replies++;
210
211         talloc_free(tmp_ctx);
212
213         /* if we don't want multiple replies then we are done */
214         if (!req->allow_multiple_replies ||
215             req->num_replies == NBT_MAX_REPLIES) {
216                 nbt_name_request_destructor(req);
217                 req->state = NBT_REQUEST_DONE;
218                 req->status = NT_STATUS_OK;
219                 if (req->async.fn) {
220                         req->async.fn(req);
221                 }
222         }
223 }
224
225 /*
226   handle fd events on a nbt_name_socket
227 */
228 static void nbt_name_socket_handler(struct event_context *ev, struct fd_event *fde,
229                                     struct timeval t, uint16_t flags)
230 {
231         struct nbt_name_socket *nbtsock = talloc_get_type(fde->private, 
232                                                           struct nbt_name_socket);
233         if (flags & EVENT_FD_WRITE) {
234                 nbt_name_socket_send(nbtsock);
235         } else if (flags & EVENT_FD_READ) {
236                 nbt_name_socket_recv(nbtsock);
237         }
238 }
239
240
241 /*
242   initialise a nbt_name_socket. The event_ctx is optional, if provided
243   then operations will use that event context
244 */
245 struct nbt_name_socket *nbt_name_socket_init(TALLOC_CTX *mem_ctx, 
246                                              struct event_context *event_ctx)
247 {
248         struct nbt_name_socket *nbtsock;
249         NTSTATUS status;
250         struct fd_event fde;
251
252         nbtsock = talloc(mem_ctx, struct nbt_name_socket);
253         if (nbtsock == NULL) goto failed;
254
255         if (event_ctx == NULL) {
256                 nbtsock->event_ctx = event_context_init(nbtsock);
257         } else {
258                 nbtsock->event_ctx = talloc_reference(nbtsock, event_ctx);
259         }
260         if (nbtsock->event_ctx == NULL) goto failed;
261
262         status = socket_create("ip", SOCKET_TYPE_DGRAM, &nbtsock->sock, 0);
263         if (!NT_STATUS_IS_OK(status)) goto failed;
264
265         talloc_steal(nbtsock, nbtsock->sock);
266
267         nbtsock->idr = idr_init(nbtsock);
268         if (nbtsock->idr == NULL) goto failed;
269
270         nbtsock->send_queue = NULL;
271         nbtsock->num_pending = 0;
272
273         fde.fd = socket_get_fd(nbtsock->sock);
274         fde.flags = 0;
275         fde.handler = nbt_name_socket_handler;
276         fde.private = nbtsock;
277         nbtsock->fde = event_add_fd(nbtsock->event_ctx, &fde);
278
279         talloc_set_destructor(nbtsock, nbtsock_destructor);
280         
281         return nbtsock;
282
283 failed:
284         talloc_free(nbtsock);
285         return NULL;
286 }
287
288 /*
289   handle a request timeout
290 */
291 static void nbt_name_socket_timeout(struct event_context *ev, struct timed_event *te,
292                                     struct timeval t)
293 {
294         struct nbt_name_request *req = talloc_get_type(te->private, 
295                                                        struct nbt_name_request);
296         nbt_name_request_destructor(req);
297         if (req->num_replies == 0) {
298                 req->state = NBT_REQUEST_TIMEOUT;
299                 req->status = NT_STATUS_IO_TIMEOUT;
300         } else {
301                 req->state = NBT_REQUEST_DONE;
302                 req->status = NT_STATUS_OK;
303         }
304         if (req->async.fn) {
305                 req->async.fn(req);
306         }
307 }
308
309 /*
310   send off a nbt name request
311 */
312 struct nbt_name_request *nbt_name_request_send(struct nbt_name_socket *nbtsock, 
313                                                const char *dest_addr, int dest_port,
314                                                struct nbt_name_packet *request,
315                                                struct timeval timeout,
316                                                BOOL allow_multiple_replies)
317 {
318         struct nbt_name_request *req;
319         struct timed_event te;
320         int id;
321
322         req = talloc_zero(nbtsock, struct nbt_name_request);
323         if (req == NULL) goto failed;
324
325         req->nbtsock = nbtsock;
326         req->dest_addr = dest_addr;
327         req->dest_port = dest_port;
328         req->request = talloc_reference(req, request);
329         req->allow_multiple_replies = allow_multiple_replies;
330         req->state = NBT_REQUEST_SEND;
331
332         /* we select a random transaction id unless the user supplied one */
333         if (req->request->name_trn_id == 0) {
334                 req->request->name_trn_id = random() % UINT16_MAX;
335         }
336
337         /* choose the next available transaction id >= the one asked for.
338            The strange 2nd call is to try to make the ids less guessable
339            and less likely to collide. It's not possible to make NBT secure 
340            to ID guessing, but this at least makes accidential collisions
341            less likely */
342         id = idr_get_new_above(req->nbtsock->idr, req, 
343                                req->request->name_trn_id, UINT16_MAX);
344         if (id == -1) {
345                 id = idr_get_new_above(req->nbtsock->idr, req, 1+(random()%(UINT16_MAX/2)),
346                                        UINT16_MAX);
347         }
348         if (id == -1) goto failed;
349
350         te.next_event = timeout;
351         te.handler = nbt_name_socket_timeout;
352         te.private = req;
353         req->te = event_add_timed(nbtsock->event_ctx, &te);
354         
355         talloc_set_destructor(req, nbt_name_request_destructor);        
356
357         DLIST_ADD_END(nbtsock->send_queue, req, struct nbt_name_request *);
358
359         nbtsock->fde->flags |= EVENT_FD_WRITE;
360
361         return req;
362
363 failed:
364         talloc_free(req);
365         return NULL;
366 }
367
368 /*
369   wait for a nbt request to complete
370 */
371 NTSTATUS nbt_name_request_recv(struct nbt_name_request *req)
372 {
373         if (!req) return NT_STATUS_NO_MEMORY;
374
375         while (req->state < NBT_REQUEST_DONE) {
376                 if (event_loop_once(req->nbtsock->event_ctx) != 0) {
377                         req->state = NBT_REQUEST_ERROR;
378                         req->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
379                         if (req->async.fn) {
380                                 req->async.fn(req);
381                         }
382                 }
383         }
384         return req->status;
385 }