r21316: if we got an unexpected nbt packet that most times mean
[bbaumbach/samba-autobuild/.git] / source4 / libcli / nbt / nbtsocket.c
index 664e6fdce0b8d3538a80e8b644026a018f2b5b47..c1e30fc2457c38f4424ae7a9d20b13c918b215bf 100644 (file)
 */
 
 #include "includes.h"
-#include "events.h"
-#include "dlinklist.h"
+#include "lib/events/events.h"
+#include "lib/util/dlinklist.h"
 #include "libcli/nbt/libnbt.h"
+#include "lib/socket/socket.h"
+#include "librpc/gen_ndr/ndr_nbt.h"
 
-#define NBT_MAX_PACKET_SIZE 2048
 #define NBT_MAX_REPLIES 1000
 
-/*
-  destroy a nbt socket
-*/
-static int nbtsock_destructor(void *ptr)
-{
-       struct nbt_name_socket *nbtsock = talloc_get_type(ptr, struct nbt_name_socket);
-       event_remove_fd(nbtsock->event_ctx, nbtsock->fde);
-       return 0;
-}
-
 /*
   destroy a pending request
 */
-static int nbt_name_request_destructor(void *ptr)
-{
-       struct nbt_name_request *req = talloc_get_type(ptr, struct nbt_name_request);
-       
+static int nbt_name_request_destructor(struct nbt_name_request *req)
+{      
        if (req->state == NBT_REQUEST_SEND) {
                DLIST_REMOVE(req->nbtsock->send_queue, req);
        }
        if (req->state == NBT_REQUEST_WAIT) {
                req->nbtsock->num_pending--;
        }
-       if (req->request->name_trn_id != 0) {
-               idr_remove(req->nbtsock->idr, req->request->name_trn_id);
-               req->request->name_trn_id = 0;
+       if (req->name_trn_id != 0 && !req->is_reply) {
+               idr_remove(req->nbtsock->idr, req->name_trn_id);
+               req->name_trn_id = 0;
        }
        if (req->te) {
-               event_remove_timed(req->nbtsock->event_ctx, req->te);
                req->te = NULL;
        }
        if (req->nbtsock->send_queue == NULL) {
-               req->nbtsock->fde->flags &= ~EVENT_FD_WRITE;
+               EVENT_FD_NOT_WRITEABLE(req->nbtsock->fde);
        }
-       if (req->nbtsock->num_pending == 0) {
-               req->nbtsock->fde->flags &= ~EVENT_FD_READ;
+       if (req->nbtsock->num_pending == 0 && 
+           req->nbtsock->incoming.handler == NULL) {
+               EVENT_FD_NOT_READABLE(req->nbtsock->fde);
        }
        return 0;
 }
@@ -75,31 +64,15 @@ static int nbt_name_request_destructor(void *ptr)
 static void nbt_name_socket_send(struct nbt_name_socket *nbtsock)
 {
        struct nbt_name_request *req = nbtsock->send_queue;
-       TALLOC_CTX *tmp_ctx = talloc_new(req);
+       TALLOC_CTX *tmp_ctx = talloc_new(nbtsock);
        NTSTATUS status;
 
        while ((req = nbtsock->send_queue)) {
-               DATA_BLOB blob;
                size_t len;
                
-               if (DEBUGLVL(10)) {
-                       DEBUG(10,("Sending nbt packet to %s:%d\n", 
-                                 req->dest_addr, req->dest_port));
-                       NDR_PRINT_DEBUG(nbt_name_packet, req->request);
-               }
-
-               status = ndr_push_struct_blob(&blob, tmp_ctx, req->request, 
-                                             (ndr_push_flags_fn_t)
-                                             ndr_push_nbt_name_packet);
-               if (!NT_STATUS_IS_OK(status)) goto failed;
-
-               if (req->request->operation & NBT_FLAG_BROADCAST) {
-                       socket_set_option(nbtsock->sock, "SO_BROADCAST", "1");
-               }
-
-               len = blob.length;
-               status = socket_sendto(nbtsock->sock, &blob, &len, 0, 
-                                      req->dest_addr, req->dest_port);
+               len = req->encoded.length;
+               status = socket_sendto(nbtsock->sock, &req->encoded, &len, 
+                                      req->dest);
                if (NT_STATUS_IS_ERR(status)) goto failed;              
 
                if (!NT_STATUS_IS_OK(status)) {
@@ -109,11 +82,15 @@ static void nbt_name_socket_send(struct nbt_name_socket *nbtsock)
 
                DLIST_REMOVE(nbtsock->send_queue, req);
                req->state = NBT_REQUEST_WAIT;
-               nbtsock->fde->flags |= EVENT_FD_READ;
-               nbtsock->num_pending++;
+               if (req->is_reply) {
+                       talloc_free(req);
+               } else {
+                       EVENT_FD_READABLE(nbtsock->fde);
+                       nbtsock->num_pending++;
+               }
        }
 
-       nbtsock->fde->flags &= ~EVENT_FD_WRITE;
+       EVENT_FD_NOT_WRITEABLE(nbtsock->fde);
        talloc_free(tmp_ctx);
        return;
 
@@ -130,6 +107,44 @@ failed:
 }
 
 
+/*
+  handle a request timeout
+*/
+static void nbt_name_socket_timeout(struct event_context *ev, struct timed_event *te,
+                                   struct timeval t, void *private)
+{
+       struct nbt_name_request *req = talloc_get_type(private, 
+                                                      struct nbt_name_request);
+
+       if (req->num_retries != 0) {
+               req->num_retries--;
+               req->te = event_add_timed(req->nbtsock->event_ctx, req, 
+                                         timeval_add(&t, req->timeout, 0),
+                                         nbt_name_socket_timeout, req);
+               if (req->state != NBT_REQUEST_SEND) {
+                       req->state = NBT_REQUEST_SEND;
+                       DLIST_ADD_END(req->nbtsock->send_queue, req, 
+                                     struct nbt_name_request *);
+               }
+               EVENT_FD_WRITEABLE(req->nbtsock->fde);
+               return;
+       }
+
+       nbt_name_request_destructor(req);
+       if (req->num_replies == 0) {
+               req->state = NBT_REQUEST_TIMEOUT;
+               req->status = NT_STATUS_IO_TIMEOUT;
+       } else {
+               req->state = NBT_REQUEST_DONE;
+               req->status = NT_STATUS_OK;
+       }
+       if (req->async.fn) {
+               req->async.fn(req);
+       }
+}
+
+
+
 /*
   handle recv events on a nbt name socket
 */
@@ -137,27 +152,30 @@ static void nbt_name_socket_recv(struct nbt_name_socket *nbtsock)
 {
        TALLOC_CTX *tmp_ctx = talloc_new(nbtsock);
        NTSTATUS status;
-       const char *src_addr;
-       int src_port;
+       struct socket_address *src;
        DATA_BLOB blob;
-       size_t nread;
+       size_t nread, dsize;
        struct nbt_name_packet *packet;
        struct nbt_name_request *req;
 
-       blob = data_blob_talloc(tmp_ctx, NULL, NBT_MAX_PACKET_SIZE);
+       status = socket_pending(nbtsock->sock, &dsize);
+       if (!NT_STATUS_IS_OK(status)) {
+               talloc_free(tmp_ctx);
+               return;
+       }
+
+       blob = data_blob_talloc(tmp_ctx, NULL, dsize);
        if (blob.data == NULL) {
                talloc_free(tmp_ctx);
                return;
        }
 
-       status = socket_recvfrom(nbtsock->sock, blob.data, blob.length, &nread, 0,
-                                &src_addr, &src_port);
+       status = socket_recvfrom(nbtsock->sock, blob.data, blob.length, &nread,
+                                tmp_ctx, &src);
        if (!NT_STATUS_IS_OK(status)) {
                talloc_free(tmp_ctx);
                return;
        }
-       talloc_steal(tmp_ctx, src_addr);
-       blob.length = nread;
 
        packet = talloc(tmp_ctx, struct nbt_name_packet);
        if (packet == NULL) {
@@ -165,6 +183,7 @@ static void nbt_name_socket_recv(struct nbt_name_socket *nbtsock)
                return;
        }
 
+       /* parse the request */
        status = ndr_pull_struct_blob(&blob, packet, packet, 
                                      (ndr_pull_flags_fn_t)ndr_pull_nbt_name_packet);
        if (!NT_STATUS_IS_OK(status)) {
@@ -176,11 +195,16 @@ static void nbt_name_socket_recv(struct nbt_name_socket *nbtsock)
 
        if (DEBUGLVL(10)) {
                DEBUG(10,("Received nbt packet of length %d from %s:%d\n", 
-                         blob.length, src_addr, src_port));
+                         (int)blob.length, src->addr, src->port));
                NDR_PRINT_DEBUG(nbt_name_packet, packet);
        }
 
+       /* if its not a reply then pass it off to the incoming request
+          handler, if any */
        if (!(packet->operation & NBT_FLAG_REPLY)) {
+               if (nbtsock->incoming.handler) {
+                       nbtsock->incoming.handler(nbtsock, packet, src);
+               }
                talloc_free(tmp_ctx);
                return;
        }
@@ -188,40 +212,70 @@ static void nbt_name_socket_recv(struct nbt_name_socket *nbtsock)
        /* find the matching request */
        req = idr_find(nbtsock->idr, packet->name_trn_id);
        if (req == NULL) {
-               DEBUG(2,("Failed to match request for incoming name packet id 0x%04x\n",
-                        packet->name_trn_id));
+               if (nbtsock->unexpected.handler) {
+                       nbtsock->unexpected.handler(nbtsock, packet, src);
+               } else {
+                       DEBUG(10,("Failed to match request for incoming name packet id 0x%04x on %p\n",
+                                packet->name_trn_id, nbtsock));
+               }
                talloc_free(tmp_ctx);
                return;
        }
 
+       /* if this is a WACK response, this we need to go back to waiting,
+          but perhaps increase the timeout */
+       if ((packet->operation & NBT_OPCODE) == NBT_OPCODE_WACK) {
+               if (req->received_wack || packet->ancount < 1) {
+                       nbt_name_request_destructor(req);
+                       req->status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+                       req->state  = NBT_REQUEST_ERROR;
+                       goto done;
+               }
+               talloc_free(req->te);
+               /* we know we won't need any more retries - the server
+                  has received our request */
+               req->num_retries   = 0;
+               req->received_wack = True;
+               /* although there can be a timeout in the packet, w2k3 screws it up,
+                  so better to set it ourselves */                
+               req->timeout = lp_parm_int(-1, "nbt", "wack_timeout", 30);
+               req->te = event_add_timed(req->nbtsock->event_ctx, req, 
+                                         timeval_current_ofs(req->timeout, 0),
+                                         nbt_name_socket_timeout, req);
+               talloc_free(tmp_ctx);
+               return;
+       }
+       
+
        req->replies = talloc_realloc(req, req->replies, struct nbt_name_reply, req->num_replies+1);
        if (req->replies == NULL) {
                nbt_name_request_destructor(req);
-               req->state = NBT_REQUEST_DONE;
+               req->state  = NBT_REQUEST_ERROR;
                req->status = NT_STATUS_NO_MEMORY;
+               goto done;
+       }
+
+       talloc_steal(req, src);
+       req->replies[req->num_replies].dest   = src;
+       talloc_steal(req, packet);
+       req->replies[req->num_replies].packet = packet;
+       req->num_replies++;
+
+       /* if we don't want multiple replies then we are done */
+       if (req->allow_multiple_replies &&
+           req->num_replies < NBT_MAX_REPLIES) {
                talloc_free(tmp_ctx);
-               if (req->async.fn) {
-                       req->async.fn(req);
-               }
                return;
        }
 
-       req->replies[req->num_replies].reply_addr = talloc_steal(req, src_addr);
-       req->replies[req->num_replies].reply_port = src_port;
-       req->replies[req->num_replies].packet = talloc_steal(req, packet);
-       req->num_replies++;
+       nbt_name_request_destructor(req);
+       req->state  = NBT_REQUEST_DONE;
+       req->status = NT_STATUS_OK;
 
+done:
        talloc_free(tmp_ctx);
-
-       /* if we don't want multiple replies then we are done */
-       if (!req->allow_multiple_replies ||
-           req->num_replies == NBT_MAX_REPLIES) {
-               nbt_name_request_destructor(req);
-               req->state = NBT_REQUEST_DONE;
-               req->status = NT_STATUS_OK;
-               if (req->async.fn) {
-                       req->async.fn(req);
-               }
+       if (req->async.fn) {
+               req->async.fn(req);
        }
 }
 
@@ -229,13 +283,14 @@ static void nbt_name_socket_recv(struct nbt_name_socket *nbtsock)
   handle fd events on a nbt_name_socket
 */
 static void nbt_name_socket_handler(struct event_context *ev, struct fd_event *fde,
-                                   struct timeval t, uint16_t flags)
+                                   uint16_t flags, void *private)
 {
-       struct nbt_name_socket *nbtsock = talloc_get_type(fde->private, 
+       struct nbt_name_socket *nbtsock = talloc_get_type(private, 
                                                          struct nbt_name_socket);
        if (flags & EVENT_FD_WRITE) {
                nbt_name_socket_send(nbtsock);
-       } else if (flags & EVENT_FD_READ) {
+       } 
+       if (flags & EVENT_FD_READ) {
                nbt_name_socket_recv(nbtsock);
        }
 }
@@ -245,12 +300,11 @@ static void nbt_name_socket_handler(struct event_context *ev, struct fd_event *f
   initialise a nbt_name_socket. The event_ctx is optional, if provided
   then operations will use that event context
 */
-struct nbt_name_socket *nbt_name_socket_init(TALLOC_CTX *mem_ctx, 
+_PUBLIC_ struct nbt_name_socket *nbt_name_socket_init(TALLOC_CTX *mem_ctx, 
                                             struct event_context *event_ctx)
 {
        struct nbt_name_socket *nbtsock;
        NTSTATUS status;
-       struct fd_event fde;
 
        nbtsock = talloc(mem_ctx, struct nbt_name_socket);
        if (nbtsock == NULL) goto failed;
@@ -265,6 +319,8 @@ struct nbt_name_socket *nbt_name_socket_init(TALLOC_CTX *mem_ctx,
        status = socket_create("ip", SOCKET_TYPE_DGRAM, &nbtsock->sock, 0);
        if (!NT_STATUS_IS_OK(status)) goto failed;
 
+       socket_set_option(nbtsock->sock, "SO_BROADCAST", "1");
+
        talloc_steal(nbtsock, nbtsock->sock);
 
        nbtsock->idr = idr_init(nbtsock);
@@ -272,14 +328,12 @@ struct nbt_name_socket *nbt_name_socket_init(TALLOC_CTX *mem_ctx,
 
        nbtsock->send_queue = NULL;
        nbtsock->num_pending = 0;
+       nbtsock->incoming.handler = NULL;
+       nbtsock->unexpected.handler = NULL;
 
-       fde.fd = socket_get_fd(nbtsock->sock);
-       fde.flags = 0;
-       fde.handler = nbt_name_socket_handler;
-       fde.private = nbtsock;
-       nbtsock->fde = event_add_fd(nbtsock->event_ctx, &fde);
-
-       talloc_set_destructor(nbtsock, nbtsock_destructor);
+       nbtsock->fde = event_add_fd(nbtsock->event_ctx, nbtsock, 
+                                   socket_get_fd(nbtsock->sock), 0,
+                                   nbt_name_socket_handler, nbtsock);
        
        return nbtsock;
 
@@ -288,80 +342,63 @@ failed:
        return NULL;
 }
 
-/*
-  handle a request timeout
-*/
-static void nbt_name_socket_timeout(struct event_context *ev, struct timed_event *te,
-                                   struct timeval t)
-{
-       struct nbt_name_request *req = talloc_get_type(te->private, 
-                                                      struct nbt_name_request);
-       nbt_name_request_destructor(req);
-       if (req->num_replies == 0) {
-               req->state = NBT_REQUEST_TIMEOUT;
-               req->status = NT_STATUS_IO_TIMEOUT;
-       } else {
-               req->state = NBT_REQUEST_DONE;
-               req->status = NT_STATUS_OK;
-       }
-       if (req->async.fn) {
-               req->async.fn(req);
-       }
-}
-
 /*
   send off a nbt name request
 */
 struct nbt_name_request *nbt_name_request_send(struct nbt_name_socket *nbtsock, 
-                                              const char *dest_addr, int dest_port,
+                                              struct socket_address *dest,
                                               struct nbt_name_packet *request,
-                                              struct timeval timeout,
+                                              int timeout, int retries,
                                               BOOL allow_multiple_replies)
 {
        struct nbt_name_request *req;
-       struct timed_event te;
        int id;
+       NTSTATUS status;
 
        req = talloc_zero(nbtsock, struct nbt_name_request);
        if (req == NULL) goto failed;
 
-       req->nbtsock = nbtsock;
-       req->dest_addr = dest_addr;
-       req->dest_port = dest_port;
-       req->request = talloc_reference(req, request);
+       req->nbtsock                = nbtsock;
        req->allow_multiple_replies = allow_multiple_replies;
-       req->state = NBT_REQUEST_SEND;
+       req->state                  = NBT_REQUEST_SEND;
+       req->is_reply               = False;
+       req->timeout                = timeout;
+       req->num_retries            = retries;
+       req->dest                   = dest;
+       if (talloc_reference(req, dest) == NULL) goto failed;
 
        /* we select a random transaction id unless the user supplied one */
-       if (req->request->name_trn_id == 0) {
-               req->request->name_trn_id = generate_random() % UINT16_MAX;
-       }
-
-       /* choose the next available transaction id >= the one asked for.
-          The strange 2nd call is to try to make the ids less guessable
-          and less likely to collide. It's not possible to make NBT secure 
-          to ID guessing, but this at least makes accidential collisions
-          less likely */
-       id = idr_get_new_above(req->nbtsock->idr, req, 
-                              req->request->name_trn_id, UINT16_MAX);
-       if (id == -1) {
-               id = idr_get_new_above(req->nbtsock->idr, req, 
-                                      1+(generate_random()%(UINT16_MAX/2)),
+       if (request->name_trn_id == 0) {
+               id = idr_get_new_random(req->nbtsock->idr, req, UINT16_MAX);
+       } else {
+               if (idr_find(req->nbtsock->idr, request->name_trn_id)) goto failed;
+               id = idr_get_new_above(req->nbtsock->idr, req, request->name_trn_id, 
                                       UINT16_MAX);
        }
        if (id == -1) goto failed;
-       req->request->name_trn_id = id;
 
-       te.next_event = timeout;
-       te.handler = nbt_name_socket_timeout;
-       te.private = req;
-       req->te = event_add_timed(nbtsock->event_ctx, &te);
+       request->name_trn_id = id;
+       req->name_trn_id     = id;
+
+       req->te = event_add_timed(nbtsock->event_ctx, req, 
+                                 timeval_current_ofs(req->timeout, 0),
+                                 nbt_name_socket_timeout, req);
        
        talloc_set_destructor(req, nbt_name_request_destructor);        
 
+       status = ndr_push_struct_blob(&req->encoded, req, request, 
+                                     (ndr_push_flags_fn_t)ndr_push_nbt_name_packet);
+       if (!NT_STATUS_IS_OK(status)) goto failed;
+
        DLIST_ADD_END(nbtsock->send_queue, req, struct nbt_name_request *);
 
-       nbtsock->fde->flags |= EVENT_FD_WRITE;
+       if (DEBUGLVL(10)) {
+               DEBUG(10,("Queueing nbt packet to %s:%d\n", 
+                         req->dest->addr, req->dest->port));
+               NDR_PRINT_DEBUG(nbt_name_packet, request);
+       }
+
+       EVENT_FD_WRITEABLE(nbtsock->fde);
 
        return req;
 
@@ -370,6 +407,50 @@ failed:
        return NULL;
 }
 
+
+/*
+  send off a nbt name reply
+*/
+NTSTATUS nbt_name_reply_send(struct nbt_name_socket *nbtsock, 
+                            struct socket_address *dest,
+                            struct nbt_name_packet *request)
+{
+       struct nbt_name_request *req;
+       NTSTATUS status;
+
+       req = talloc_zero(nbtsock, struct nbt_name_request);
+       NT_STATUS_HAVE_NO_MEMORY(req);
+
+       req->nbtsock   = nbtsock;
+       req->dest = dest;
+       if (talloc_reference(req, dest) == NULL) goto failed;
+       req->state     = NBT_REQUEST_SEND;
+       req->is_reply = True;
+
+       talloc_set_destructor(req, nbt_name_request_destructor);        
+
+       if (DEBUGLVL(10)) {
+               NDR_PRINT_DEBUG(nbt_name_packet, request);              
+       }
+
+       status = ndr_push_struct_blob(&req->encoded, req, request, 
+                                     (ndr_push_flags_fn_t)ndr_push_nbt_name_packet);
+       if (!NT_STATUS_IS_OK(status)) {
+               talloc_free(req);
+               return status;
+       }
+
+       DLIST_ADD_END(nbtsock->send_queue, req, struct nbt_name_request *);
+
+       EVENT_FD_WRITEABLE(nbtsock->fde);
+
+       return NT_STATUS_OK;
+
+failed:
+       talloc_free(req);
+       return NT_STATUS_NO_MEMORY;
+}
+
 /*
   wait for a nbt request to complete
 */
@@ -388,3 +469,45 @@ NTSTATUS nbt_name_request_recv(struct nbt_name_request *req)
        }
        return req->status;
 }
+
+
+/*
+  setup a handler for incoming requests
+*/
+NTSTATUS nbt_set_incoming_handler(struct nbt_name_socket *nbtsock,
+                                 void (*handler)(struct nbt_name_socket *, struct nbt_name_packet *, 
+                                                 struct socket_address *),
+                                 void *private)
+{
+       nbtsock->incoming.handler = handler;
+       nbtsock->incoming.private = private;
+       EVENT_FD_READABLE(nbtsock->fde);
+       return NT_STATUS_OK;
+}
+
+
+/*
+  turn a NBT rcode into a NTSTATUS
+*/
+NTSTATUS nbt_rcode_to_ntstatus(uint8_t rcode)
+{
+       int i;
+       struct {
+               enum nbt_rcode rcode;
+               NTSTATUS status;
+       } map[] = {
+               { NBT_RCODE_FMT, NT_STATUS_INVALID_PARAMETER },
+               { NBT_RCODE_SVR, NT_STATUS_SERVER_DISABLED },
+               { NBT_RCODE_NAM, NT_STATUS_OBJECT_NAME_NOT_FOUND },
+               { NBT_RCODE_IMP, NT_STATUS_NOT_SUPPORTED },
+               { NBT_RCODE_RFS, NT_STATUS_ACCESS_DENIED },
+               { NBT_RCODE_ACT, NT_STATUS_ADDRESS_ALREADY_EXISTS },
+               { NBT_RCODE_CFT, NT_STATUS_CONFLICTING_ADDRESSES }
+       };
+       for (i=0;i<ARRAY_SIZE(map);i++) {
+               if (map[i].rcode == rcode) {
+                       return map[i].status;
+               }
+       }
+       return NT_STATUS_UNSUCCESSFUL;
+}