1/*
2 * Copyright (c) 2010 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <sys/param.h>
25#include <sys/errno.h>
26#include <sys/stat.h>
27#include <err.h>
28#include <stdio.h>
29#include <unistd.h>
30#include <strings.h>
31#include <stdlib.h>
32#include <sysexits.h>
33
34#include <stdint.h>
35#include <netsmb/smb.h>
36
37#include <smbclient/smbclient.h>
38#include <smbclient/smbclient_internal.h>
39#include <smbclient/smbclient_netfs.h>
40#include <smbclient/ntstatus.h>
41
42#include "common.h"
43
44#include "lmshare.h"
45#include "netshareenum.h"
46#include "rap.h"
47
48#include <NetFS/NetFS.h>
49
50/*
51 * Get a list of all mount volumes. The calling routine will need to free the memory.
52 */
53struct statfs *smb_getfsstat(int *fs_cnt)
54{
55	struct statfs *fs;
56	int bufsize = 0;
57
58	/* See what we need to allocate */
59	*fs_cnt = getfsstat(NULL, bufsize, MNT_NOWAIT);
60	if (*fs_cnt <=  0)
61		return NULL;
62	bufsize = *fs_cnt * (int)sizeof(*fs);
63	fs = (struct statfs *)malloc(bufsize);
64	if (fs == NULL)
65		return NULL;
66
67	*fs_cnt = getfsstat(fs, bufsize, MNT_NOWAIT);
68	if (*fs_cnt < 0) {
69		*fs_cnt = 0;
70		free (fs);
71		fs = NULL;
72	}
73	return fs;
74}
75
76/*
77 * Does the same thing as strlen, except only looks up
78 * to max chars inside the buffer.
79 * Taken from the darwin osfmk/device/subrs.c file.
80 * inputs:
81 *      s       string whose length is to be measured
82 *      max     maximum length of string to search for null
83 * outputs:
84 *      length of s or max; whichever is smaller
85 */
86static size_t smb_strnlen(const char *s, size_t max)
87{
88	const char *es = s + max, *p = s;
89	while(*p && p != es)
90		p++;
91
92	return p - s;
93}
94
95/*
96 * Convert the input value into a CFString Ref.
97 */
98static CFStringRef convertToStringRef(const void *inStr, size_t maxLen, int unicode)
99{
100	char *cStr = NULL;
101	CFStringRef strRef = NULL;
102
103	if (inStr == NULL) {
104		return NULL;
105	}
106	if (unicode) {
107		cStr = SMBConvertFromUTF16ToUTF8((const uint16_t *)inStr, maxLen, 0);
108	} else {
109		cStr = SMBConvertFromCodePageToUTF8((const char *)inStr);
110	}
111	if (cStr) {
112		strRef = CFStringCreateWithCString(NULL, cStr, kCFStringEncodingUTF8);
113		free(cStr);
114	}
115	return strRef;
116}
117
118static void addShareToDictionary(SMBHANDLE inConnection,
119								 CFMutableDictionaryRef shareDict,
120								 CFStringRef shareName,  CFStringRef comments,
121								 u_int16_t shareType, struct statfs *fs, int fs_cnt)
122{
123	CFMutableDictionaryRef currDict = NULL;
124	CFRange foundSlash;
125	CFRange	foundPercentSign;
126    CFStringRef tempShareName1 = NULL;
127    CFStringRef tempShareName2 = NULL;
128
129	if (shareName == NULL) {
130		return;
131	}
132
133	currDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
134										 &kCFTypeDictionaryKeyCallBacks,
135										 &kCFTypeDictionaryValueCallBacks);
136	if (currDict == NULL) {
137		/* Log error here, but keep going */
138		SMBLogInfo("addShareToDictionary: Couldn't create the dictionary!", ASL_LEVEL_DEBUG);
139		return;
140	}
141
142	if (CFStringHasSuffix(shareName, CFSTR("$"))) {
143		CFDictionarySetValue (currDict, kNetFSIsHiddenKey, kCFBooleanTrue);
144	}
145
146	if (comments) {
147		CFDictionarySetValue (currDict, kNetCommentStrKey, comments);
148	}
149
150	switch (shareType) {
151		case SMB_ST_DISK:
152			CFDictionarySetValue (currDict, kNetShareTypeStrKey, CFSTR("Disk"));
153			/* Now check to see if this share is already mounted */
154			if (fs) {
155				/* We only care if its already mounted ignore any other errors for now */
156				if (SMBCheckForAlreadyMountedShare(inConnection, shareName, currDict, fs, fs_cnt) == EEXIST) {
157					CFDictionarySetValue (currDict, kNetFSAlreadyMountedKey, kCFBooleanTrue);
158				} else {
159					CFDictionarySetValue (currDict, kNetFSAlreadyMountedKey, kCFBooleanFalse);
160				}
161
162			}
163			break;
164		case SMB_ST_PRINTER:
165			CFDictionarySetValue (currDict, kNetShareTypeStrKey, CFSTR("Printer"));
166			CFDictionarySetValue (currDict, kNetFSPrinterShareKey, kCFBooleanTrue);
167			CFDictionarySetValue (currDict, kNetFSAlreadyMountedKey, kCFBooleanFalse);
168			break;
169		case SMB_ST_PIPE:
170			CFDictionarySetValue (currDict, kNetShareTypeStrKey, CFSTR("Pipe"));
171			CFDictionarySetValue (currDict, kNetFSAlreadyMountedKey, kCFBooleanFalse);
172			break;
173		case SMB_ST_COMM:
174			CFDictionarySetValue (currDict, kNetShareTypeStrKey, CFSTR("Comm"));
175			CFDictionarySetValue (currDict, kNetFSAlreadyMountedKey, kCFBooleanFalse);
176			break;
177		default:
178			CFDictionarySetValue (currDict, kNetShareTypeStrKey, CFSTR("Unknown"));
179			CFDictionarySetValue (currDict, kNetFSAlreadyMountedKey, kCFBooleanFalse);
180			break;
181	}
182	CFDictionarySetValue (currDict, kNetFSHasPasswordKey, kCFBooleanFalse);
183
184    /* Check for a '/' or '%' in the share name */
185    foundSlash = CFStringFind (shareName, CFSTR("/"), 0);
186    foundPercentSign = CFStringFind (shareName, CFSTR("%"), 0);
187    if ( (foundSlash.location != kCFNotFound) || (foundPercentSign.location != kCFNotFound) ) {
188        /* found a '/' or '%' in the name, so set a disply name to be used */
189        CFDictionarySetValue (currDict, kNetFSDisplayNameKey, shareName);
190
191        /* escape the vol name to get '/' converted to %2f and '%' to %25 */
192        tempShareName1 = CFURLCreateStringByAddingPercentEscapes(NULL, shareName, NULL, CFSTR("/%"), kCFStringEncodingUTF8);
193
194        /* re-escape it leaving the '/' as %2f and '%' as %25 */
195        tempShareName2 = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, tempShareName1, CFSTR("/%"), kCFStringEncodingUTF8);
196
197        CFDictionarySetValue (shareDict, tempShareName2, currDict);
198
199        CFRelease (tempShareName1);
200        CFRelease (tempShareName2);
201    }
202    else {
203        CFDictionarySetValue (shareDict, shareName, currDict);
204    }
205
206	CFRelease (currDict);
207}
208
209int smb_netshareenum(SMBHANDLE inConnection, CFDictionaryRef *outDict,
210					 int DiskAndPrintSharesOnly)
211{
212	int error = 0;
213	NTSTATUS status;
214	SMBServerPropertiesV1 properties;
215	CFMutableDictionaryRef shareDict = NULL;
216	uint32_t ii;
217	CFStringRef shareName, comments;
218	u_int16_t shareType;
219	struct statfs *fs = NULL;
220	int fs_cnt = 0;
221
222	fs = smb_getfsstat(&fs_cnt);
223
224	shareDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
225										 &kCFTypeDictionaryKeyCallBacks,
226										 &kCFTypeDictionaryValueCallBacks);
227	if (shareDict == NULL) {
228		error = ENOMEM;
229		goto done;
230	}
231
232	status = SMBGetServerProperties(inConnection, &properties, kPropertiesVersion, sizeof(properties));
233	if (!NT_SUCCESS(status)) {
234		/* Should never happen */
235		error = errno;
236		goto done;
237	}
238	/* Only use RPC if the server supports DCE/RPC and UNICODE */
239	if (properties.capabilities & SMB_CAP_RPC_REMOTE_APIS) {
240		PSHARE_ENUM_STRUCT InfoStruct = NULL;
241		NET_API_STATUS api_status;
242
243		/* Try getting a list of shares with the SRVSVC RPC service. */
244		api_status = NetShareEnum(properties.serverName, 1, &InfoStruct);
245		if (api_status == 0) {
246			for (ii = 0; ii < InfoStruct->ShareInfo.Level1->EntriesRead; ii++) {
247				shareType = OSSwapLittleToHostInt16(InfoStruct->ShareInfo.Level1->Buffer[ii].shi1_type);
248				/* They only want the disk and printer shares */
249				if (DiskAndPrintSharesOnly && (shareType != SMB_ST_DISK) && (shareType != SMB_ST_PRINTER))
250					continue;
251				shareName = convertToStringRef(InfoStruct->ShareInfo.Level1->Buffer[ii].shi1_netname, 1024, TRUE);
252				if (shareName == NULL) {
253					continue;
254				}
255				if (InfoStruct->ShareInfo.Level1->Buffer[ii].shi1_remark) {
256					comments = convertToStringRef(InfoStruct->ShareInfo.Level1->Buffer[ii].shi1_remark, 1024, TRUE);
257				} else {
258					comments = NULL;
259				}
260				addShareToDictionary(inConnection, shareDict, shareName, comments, shareType, fs, fs_cnt);
261				CFRelease(shareName);
262				if (comments) {
263					CFRelease(comments);
264				}
265			}
266			NetApiBufferFree(InfoStruct);
267			goto done;
268		}
269		SMBLogInfo("Looking up shares with RPC failed api_status = %d", ASL_LEVEL_DEBUG, api_status);
270	}
271	/*
272	 * OK, that didn't work - either they don't support RPC or we
273	 * got an error in either case try RAP if enabled (lanman_on pref is set).
274	 */
275	if (properties.internalFlags & kLanmanOn) {
276		void *rBuffer = NULL;
277		unsigned char *endBuffer;
278		uint32_t rBufferSize = 0;
279		struct smb_share_info_1 *shareInfo1;
280		uint32_t entriesRead = 0;
281
282		SMBLogInfo("Looking up shares RAP", ASL_LEVEL_DEBUG);
283
284		/* Try getting a list of shares with the RAP protocol. */
285		error = RapNetShareEnum(inConnection, 1, &rBuffer, &rBufferSize, &entriesRead, NULL);
286		if (error) {
287			SMBLogInfo("Looking up shares with RAP failed, error=%d", ASL_LEVEL_DEBUG, error);
288			goto done;
289		}
290		endBuffer = (unsigned char *)rBuffer + rBufferSize;
291
292		for (shareInfo1 = (struct smb_share_info_1 *)rBuffer, ii = 0;
293			 (ii < entriesRead) && (((unsigned  char *)shareInfo1 + sizeof(smb_share_info_1)) <= endBuffer);
294			 ii++, shareInfo1++) {
295
296			shareInfo1->shi1_pad = 0; /* Just to be safe */
297			/* Note we need to swap this item */
298			shareType = OSSwapLittleToHostInt16(shareInfo1->shi1_type);
299			shareName = convertToStringRef(shareInfo1->shi1_netname,  sizeof(shareInfo1->shi1_netname), FALSE);
300			if (shareName == NULL) {
301				continue;
302			}
303			/* Assume we have no comments for this entry */
304			comments = NULL;
305			/*
306			 * The shi1_remark gets swapped in the rap processing, someday we just
307			 * take another look at this an make it work the same for all values.
308			 */
309			if ((shareInfo1->shi1_remark != 0) && (shareInfo1->shi1_remark < rBufferSize)) {
310				unsigned char *remarks = (unsigned char *)rBuffer + shareInfo1->shi1_remark;
311
312				/*
313				 * Make sure the comments don't start pass the end of the buffer
314				 * and we have a comment.
315				 */
316				if ((remarks < endBuffer) && *remarks) {
317					size_t maxlen = endBuffer - remarks;
318					/* Now make sure the comment is a null terminate string */
319					maxlen = smb_strnlen((const char *)remarks, maxlen);
320					remarks[maxlen] = 0;
321					comments = convertToStringRef(remarks, maxlen, FALSE);
322				}
323
324			}
325			addShareToDictionary(inConnection, shareDict, shareName, comments, shareType, fs, fs_cnt);
326			CFRelease(shareName);
327			if (comments) {
328				CFRelease(comments);
329			}
330		}
331		RapNetApiBufferFree(rBuffer);
332	}
333done:
334	if (fs) {
335		free(fs);
336	}
337	if (error) {
338		*outDict = NULL;
339		if (shareDict) {
340			CFRelease(shareDict);
341		}
342	} else {
343		*outDict = shareDict;
344	}
345	return error;
346}
347
348