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