smbclient-add-rap_netserverenum3-support   [plain text]


Index: samba/source/libsmb/clirap.c
===================================================================
--- samba/source/libsmb/clirap.c.orig
+++ samba/source/libsmb/clirap.c
@@ -3,7 +3,9 @@
    client RAP calls
    Copyright (C) Andrew Tridgell         1994-1998
    Copyright (C) Gerald (Jerry) Carter   2004
-   
+
+   Copyright (C) 2007 Apple Inc. All rights reserved.
+
    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
@@ -216,51 +218,116 @@ BOOL cli_NetServerEnum(struct cli_state 
 	char *p;
 	pstring param;
 	int uLevel = 1;
-	int count = -1;
+	uint32 func = RAP_NetServerEnum2;
+	char *last_entry = NULL;
+	int total_cnt = 0;
+	int return_cnt = 0;
+	int res;
 
 	errno = 0; /* reset */
 
-	/* send a SMBtrans command with api NetServerEnum */
-	p = param;
-	SSVAL(p,0,0x68); /* api number */
-	p += 2;
-	pstrcpy_base(p,"WrLehDz", param);
-	p = skip_string(param,sizeof(param),p);
-  
-	pstrcpy_base(p,"B16BBDz", param);
+	/*
+	 * This may take more than one transaction, so we should loop until
+	 * we no longer get a more data to process or we have all of the
+	 * items.
+	 */
+	do {
+		/* send a SMBtrans command with api NetServerEnum */
+		p = param;
+		SIVAL(p,0,func); /* api number */
+		p += 2;
+		/* Next time through we need to use the continue api */
+		func = RAP_NetServerEnum3;
 
-	p = skip_string(param,sizeof(param),p);
-	SSVAL(p,0,uLevel);
-	SSVAL(p,2,CLI_BUFFER_SIZE);
-	p += 4;
-	SIVAL(p,0,stype);
-	p += 4;
+		if (last_entry) {
+			pstrcpy_base(p,"WrLehDOz", param);
+		} else {
+			pstrcpy_base(p,"WrLehDz", param);
+		}
+
+		p = skip_string(param, sizeof(param), p);
+		pstrcpy_base(p,"B16BBDz", param);
+
+		p = skip_string(param, sizeof(param), p);
+		SSVAL(p,0,uLevel);
+		SSVAL(p,2,CLI_BUFFER_SIZE);
+		p += 4;
+		SIVAL(p,0,stype);
+		p += 4;
+
+		/* We have more data, tell the server were to continue from */
+		if (last_entry) {
+			p += push_ascii(p, last_entry,
+				sizeof(pstring) - PTR_DIFF(p,param) - 1,
+				STR_TERMINATE|STR_UPPER);
+		} else
+			p += push_ascii(p, workgroup,
+				sizeof(pstring) - PTR_DIFF(p,param) - 1,
+				STR_TERMINATE|STR_UPPER);
+
+		if (!cli_api(cli,
+			    param, PTR_DIFF(p,param), 8, /* params, length, max */
+			    NULL, 0, CLI_BUFFER_SIZE,    /* data, length, max */
+			    &rparam, &rprcnt,      /* return params, return size */
+			    &rdata, &rdrcnt)) {           /* return data, return size */
+			res = -1;	/* error happen break out of the loop */
+			break;
+		}
+		res = rparam ? SVAL(rparam,0) : -1;
 
-	p += push_ascii(p, workgroup, sizeof(pstring)-PTR_DIFF(p,param)-1, STR_TERMINATE|STR_UPPER);
-	
-	if (cli_api(cli, 
-                    param, PTR_DIFF(p,param), 8,        /* params, length, max */
-                    NULL, 0, CLI_BUFFER_SIZE,               /* data, length, max */
-                    &rparam, &rprcnt,                   /* return params, return size */
-                    &rdata, &rdrcnt                     /* return data, return size */
-                   )) {
-		int res = rparam? SVAL(rparam,0) : -1;
-			
 		if (res == 0 || res == ERRmoredata ||
                     (res != -1 && cli_errno(cli) == 0)) {
-			int i;
+			char *sname = NULL;
+			int i, count;
 			int converter=SVAL(rparam,2);
 
-			count=SVAL(rparam,4);
+ 			/* Get the number of items returned in this buffer */
+			count = SVAL(rparam, 4);
+
+			/* The next field contains the number of items left,
+			 * including those returned in this buffer. So the
+			 * first time through this should contain all of the
+			 * entries.
+			 */
+			if (total_cnt == 0) {
+				total_cnt = SVAL(rparam, 6);
+			}
+
+			/* Keep track of how many we have read */
+			return_cnt += count;
 			p = rdata;
+			/*
+			 * The last name in the previous NetServerEnum reply is
+			 * sent back to server in the NetServerEnum3 request
+			 * (last_entry). The next reply should repeat this entry
+			 * as the first element. We have no proof that this is
+			 * always true, but from traces that seems to be the
+			 * behavior from Window Servers. So first lets do a lot
+			 * of checking, just being paraniod. If the string
+			 * matches then we already saw this entry so skip it.
+			 *
+			 * NOTE: sv1_name field must be null terminated and has
+			 * a max size of 16 (NetBIOS Name).
+			 */
+			if (last_entry && count && p && (strncmp(last_entry, p, 16) == 0)) {
+			    count -= 1;	/* Skip this entry */
+			    return_cnt = -1; /* Not part of total, so don't count. */
+			    p = rdata+26; /* Skip the whole record */
+			}
 					
 			for (i = 0;i < count;i++, p += 26) {
-				char *sname = p;
-				int comment_offset = (IVAL(p,22) & 0xFFFF)-converter;
-				const char *cmnt = comment_offset?(rdata+comment_offset):"";
+				int comment_offset;
+				const char *cmnt;
 				pstring s1, s2;
 
-				if (comment_offset < 0 || comment_offset > (int)rdrcnt) continue;
+				sname = p;
+				comment_offset = (IVAL(p,22) & 0xFFFF) - converter;
+				cmnt = comment_offset ? (rdata + comment_offset) : "";
+
+				if (comment_offset < 0 ||
+				    comment_offset > (int)rdrcnt) {
+				    continue;
+				}
 
 				stype = IVAL(p,18) & ~SV_TYPE_LOCAL_LIST_ONLY;
 
@@ -268,24 +335,46 @@ BOOL cli_NetServerEnum(struct cli_state 
 				pull_ascii_pstring(s2, cmnt);
 				fn(s1, stype, s2, state);
 			}
+
+			/* We are done with the old last entry, so now we can free it */
+			if (last_entry) {
+				SAFE_FREE(last_entry); /* This will set it to null */
+			}
+			/* We always make a copy of  the last entry if we have one */
+			if (sname) {
+				last_entry = strdup(sname);
+			}
+			/* If we have more data, but no last entry then error out */
+			if (!last_entry && (res == ERRmoredata)) {
+				errno = EINVAL;
+				res = 0;
+			}
 		}
-	}
-  
-	SAFE_FREE(rparam);
-	SAFE_FREE(rdata);
 
-	if (count < 0) {
-	    errno = cli_errno(cli);
+		SAFE_FREE(rparam);
+		SAFE_FREE(rdata);
+	} while ((res == ERRmoredata) && (total_cnt > return_cnt));
+
+	/* Do any needed cleanup */
+	if (rparam)
+		SAFE_FREE(rparam);
+	if (rdata)
+		SAFE_FREE(rdata);
+	if (last_entry)
+		SAFE_FREE(last_entry);
+
+	if (res == -1) {
+		errno = cli_errno(cli);
 	} else {
-	    if (!count) {
+	    if (!return_cnt) {
 		/* this is a very special case, when the domain master for the 
 		   work group isn't part of the work group itself, there is something
 		   wild going on */
-		errno = ENOENT;
+			errno = ENOENT;
 	    }
 	}
 			
-	return(count > 0);
+	return(return_cnt > 0);
 }
 
 /****************************************************************************