Merge branch 'v4-0-trivial' into v4-0-test
[kai/samba.git] / source / libcli / nbt / nbtsocket.c
index 3f16bf6921ff94d3788ff1474cee1d98029f3846..95a1643efc42e0068c9d7fc30f208c587573f6d5 100644 (file)
@@ -7,7 +7,7 @@
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #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"
+#include "param/param.h"
 
-#define NBT_MAX_PACKET_SIZE 2048
 #define NBT_MAX_REPLIES 1000
 
 /*
   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);
        }
@@ -46,14 +45,15 @@ static int nbt_name_request_destructor(void *ptr)
                req->name_trn_id = 0;
        }
        if (req->te) {
+               talloc_free(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->incoming.handler == NULL) {
-               req->nbtsock->fde->flags &= ~EVENT_FD_READ;
+               EVENT_FD_NOT_READABLE(req->nbtsock->fde);
        }
        return 0;
 }
@@ -72,8 +72,8 @@ static void nbt_name_socket_send(struct nbt_name_socket *nbtsock)
                size_t len;
                
                len = req->encoded.length;
-               status = socket_sendto(nbtsock->sock, &req->encoded, &len, 0, 
-                                      req->dest_addr, req->dest_port);
+               status = socket_sendto(nbtsock->sock, &req->encoded, &len, 
+                                      req->dest);
                if (NT_STATUS_IS_ERR(status)) goto failed;              
 
                if (!NT_STATUS_IS_OK(status)) {
@@ -82,16 +82,16 @@ static void nbt_name_socket_send(struct nbt_name_socket *nbtsock)
                }
 
                DLIST_REMOVE(nbtsock->send_queue, req);
+               req->state = NBT_REQUEST_WAIT;
                if (req->is_reply) {
                        talloc_free(req);
                } else {
-                       req->state = NBT_REQUEST_WAIT;
-                       nbtsock->fde->flags |= EVENT_FD_READ;
+                       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;
 
@@ -103,39 +103,85 @@ failed:
        talloc_free(tmp_ctx);
        if (req->async.fn) {
                req->async.fn(req);
+       } else if (req->is_reply) {
+               talloc_free(req);
        }
        return;
 }
 
 
 /*
+  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);
+       } else if (req->is_reply) {
+               talloc_free(req);
+       }
+}
+
+
+
+/**
   handle recv events on a nbt name socket
 */
 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;
+       enum ndr_err_code ndr_err;
+       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) {
@@ -143,9 +189,11 @@ static void nbt_name_socket_recv(struct nbt_name_socket *nbtsock)
                return;
        }
 
-       status = ndr_pull_struct_blob(&blob, packet, packet, 
-                                     (ndr_pull_flags_fn_t)ndr_pull_nbt_name_packet);
-       if (!NT_STATUS_IS_OK(status)) {
+       /* parse the request */
+       ndr_err = ndr_pull_struct_blob(&blob, packet, nbtsock->iconv_convenience, packet,
+                                      (ndr_pull_flags_fn_t)ndr_pull_nbt_name_packet);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               status = ndr_map_error2ntstatus(ndr_err);
                DEBUG(2,("Failed to parse incoming NBT name packet - %s\n",
                         nt_errstr(status)));
                talloc_free(tmp_ctx);
@@ -154,55 +202,88 @@ 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_addr, src_port);
+                       nbtsock->incoming.handler(nbtsock, packet, src);
                }
                talloc_free(tmp_ctx);
                return;
        }
 
        /* find the matching request */
-       req = idr_find(nbtsock->idr, packet->name_trn_id);
+       req = (struct nbt_name_request *)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(global_loadparm, NULL, "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);
        }
 }
 
@@ -210,13 +291,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);
        }
 }
@@ -226,12 +308,12 @@ 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, 
-                                            struct event_context *event_ctx)
+_PUBLIC_ struct nbt_name_socket *nbt_name_socket_init(TALLOC_CTX *mem_ctx, 
+                                            struct event_context *event_ctx,
+                                            struct smb_iconv_convenience *iconv_convenience)
 {
        struct nbt_name_socket *nbtsock;
        NTSTATUS status;
-       struct fd_event fde;
 
        nbtsock = talloc(mem_ctx, struct nbt_name_socket);
        if (nbtsock == NULL) goto failed;
@@ -246,6 +328,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);
@@ -254,12 +338,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;
+       nbtsock->iconv_convenience = iconv_convenience;
 
-       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, nbtsock);
+       nbtsock->fde = event_add_fd(nbtsock->event_ctx, nbtsock, 
+                                   socket_get_fd(nbtsock->sock), 0,
+                                   nbt_name_socket_handler, nbtsock);
        
        return nbtsock;
 
@@ -268,97 +352,65 @@ 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,
-                                              BOOL allow_multiple_replies)
+                                              int timeout, int retries,
+                                              bool allow_multiple_replies)
 {
        struct nbt_name_request *req;
-       struct timed_event te;
        int id;
-       NTSTATUS status;
+       enum ndr_err_code ndr_err;
 
        req = talloc_zero(nbtsock, struct nbt_name_request);
        if (req == NULL) goto failed;
 
-       req->nbtsock = nbtsock;
-       req->dest_addr = talloc_strdup(req, dest_addr);
-       if (req->dest_addr == NULL) goto failed;
-       req->dest_port = dest_port;
+       req->nbtsock                = nbtsock;
        req->allow_multiple_replies = allow_multiple_replies;
-       req->state = NBT_REQUEST_SEND;
-       req->is_reply = False;
+       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 (request->name_trn_id == 0) {
-               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, 
-                              request->name_trn_id, UINT16_MAX);
-       if (id == -1) {
-               id = idr_get_new_above(req->nbtsock->idr, req, 
-                                      1+(generate_random()%(UINT16_MAX/2)),
+               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;
+
        request->name_trn_id = id;
        req->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, req);
+       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;
+       ndr_err = ndr_push_struct_blob(&req->encoded, req, 
+                                      req->nbtsock->iconv_convenience,
+                                      request,
+                                      (ndr_push_flags_fn_t)ndr_push_nbt_name_packet);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) goto failed;
 
        DLIST_ADD_END(nbtsock->send_queue, req, struct nbt_name_request *);
 
-       if (request->operation & NBT_FLAG_BROADCAST) {
-               socket_set_option(nbtsock->sock, "SO_BROADCAST", "1");
-       }
-
        if (DEBUGLVL(10)) {
                DEBUG(10,("Queueing nbt packet to %s:%d\n", 
-                         req->dest_addr, req->dest_port));
+                         req->dest->addr, req->dest->port));
                NDR_PRINT_DEBUG(nbt_name_packet, request);
        }
 
-       nbtsock->fde->flags |= EVENT_FD_WRITE;
+       EVENT_FD_WRITEABLE(nbtsock->fde);
 
        return req;
 
@@ -372,34 +424,39 @@ failed:
   send off a nbt name reply
 */
 NTSTATUS nbt_name_reply_send(struct nbt_name_socket *nbtsock, 
-                            const char *dest_addr, int dest_port,
+                            struct socket_address *dest,
                             struct nbt_name_packet *request)
 {
        struct nbt_name_request *req;
-       NTSTATUS status;
+       enum ndr_err_code ndr_err;
 
        req = talloc_zero(nbtsock, struct nbt_name_request);
        NT_STATUS_HAVE_NO_MEMORY(req);
 
        req->nbtsock   = nbtsock;
-       req->dest_addr = talloc_strdup(req, dest_addr);
-       if (req->dest_addr == NULL) goto failed;
-       req->dest_port = dest_port;
+       req->dest = dest;
+       if (talloc_reference(req, dest) == NULL) goto failed;
        req->state     = NBT_REQUEST_SEND;
-       req->is_reply = True;
+       req->is_reply = true;
 
        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)) {
+       if (DEBUGLVL(10)) {
+               NDR_PRINT_DEBUG(nbt_name_packet, request);              
+       }
+
+       ndr_err = ndr_push_struct_blob(&req->encoded, req, 
+                                      req->nbtsock->iconv_convenience,
+                                      request,
+                                      (ndr_push_flags_fn_t)ndr_push_nbt_name_packet);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
                talloc_free(req);
-               return status;
+               return ndr_map_error2ntstatus(ndr_err);
        }
 
        DLIST_ADD_END(nbtsock->send_queue, req, struct nbt_name_request *);
 
-       nbtsock->fde->flags |= EVENT_FD_WRITE;
+       EVENT_FD_WRITEABLE(nbtsock->fde);
 
        return NT_STATUS_OK;
 
@@ -419,9 +476,7 @@ NTSTATUS nbt_name_request_recv(struct nbt_name_request *req)
                if (event_loop_once(req->nbtsock->event_ctx) != 0) {
                        req->state = NBT_REQUEST_ERROR;
                        req->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
-                       if (req->async.fn) {
-                               req->async.fn(req);
-                       }
+                       break;
                }
        }
        return req->status;
@@ -433,12 +488,38 @@ NTSTATUS nbt_name_request_recv(struct nbt_name_request *req)
 */
 NTSTATUS nbt_set_incoming_handler(struct nbt_name_socket *nbtsock,
                                  void (*handler)(struct nbt_name_socket *, struct nbt_name_packet *, 
-                                                 const char *, int ),
+                                                 struct socket_address *),
                                  void *private)
 {
        nbtsock->incoming.handler = handler;
        nbtsock->incoming.private = private;
-       nbtsock->fde->flags |= EVENT_FD_READ;
+       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;
+}