1/*
2 * Copyright (c) 2010 - 2013 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 <CoreFoundation/CoreFoundation.h>
25#include <SystemConfiguration/SystemConfiguration.h>
26#include <netsmb/smb_lib.h>
27#include <smbclient/smbclient.h>
28#include <smbclient/smbclient_internal.h>
29#include "rcfile.h"
30#include "preference.h"
31#include "smb_preferences.h"
32
33#define defaultCodePage  437
34
35
36/*
37 * level values:
38 * 0 - default/global
39 * 1 - server
40 * 2 - server:share
41 */
42static void readPreferenceSection(struct rcfile *rcfile, struct smb_prefs *prefs,
43								   const char *sname, int level)
44{
45	char	*p;
46	int32_t	altflags;
47
48	/* global only preferences */
49	if (level == 0) {
50		/*
51		 * Neither of these are defined in the man pages. We should add
52		 * kernel log level, then in future remove debug level.
53		 */
54		rc_getint(rcfile, sname, "debug_level", &prefs->KernelLogLevel);
55		rc_getint(rcfile, sname, "kloglevel", &prefs->KernelLogLevel);
56
57        /*
58         * Check for SMB 1, SMB 2/3 Negotiation. Default is to start with SMB 1
59         * and try to negotiate to SMB 2/3
60         * 0 = try both SMB 1/2/3
61         * 1 = SMB 1 only
62         * 2 = SMB 2 only
63         * 3 = SMB 3 only
64         */
65        rc_getstringptr(rcfile, sname, "smb_neg", &p);
66        if (p) {
67            if (strcmp(p, "normal") == 0) {
68                /* start with SMB 1 and try for SMB 2/3 */
69                prefs->smb_negotiate = 0;
70
71                /* if SMB 1 or SMB 2/3, then also turn off netbios */
72                if (prefs->tryBothPorts) {
73                    prefs->tryBothPorts = FALSE;
74                    prefs->tcp_port = SMB_TCP_PORT_445;
75                }
76            }
77            else {
78                if (strcmp(p, "smb1_only") == 0) {
79                    prefs->smb_negotiate = 1;
80                }
81                else if ((strcmp(p, "smb2_only") == 0) ||
82                         (strcmp(p, "smb3_only") == 0)) {
83                    if (strcmp(p, "smb2_only") == 0) {
84                        prefs->smb_negotiate = 2;
85                    }
86                    else {
87                        prefs->smb_negotiate = 3;
88                    }
89
90                    /* if SMB 2/3 only, then also turn off netbios */
91                    if (prefs->tryBothPorts) {
92                        prefs->tryBothPorts = FALSE;
93                        prefs->tcp_port = SMB_TCP_PORT_445;
94                    }
95                }
96            }
97        }
98
99		/* Check for Require Signing */
100		/* Only get the value if it exist, ignore any error we don't care */
101		(void)rc_getbool(rcfile, sname, "signing_required", (int *) &prefs->signing_required);
102
103		/* Only get the value if it exists */
104        if (rc_getbool(rcfile, sname, "validate_neg_off", &altflags) == 0) {
105            if (altflags)
106                prefs->altflags |= SMBFS_MNT_VALIDATE_NEG_OFF;
107            else
108                prefs->altflags &= ~SMBFS_MNT_VALIDATE_NEG_OFF;
109        }
110	}
111
112	/* server only preferences */
113	if (level == 1) {
114		rc_getstringptr(rcfile, sname, "addr", &p);
115		if (p) {
116			if (prefs->NetBIOSDNSName) {
117				CFRelease(prefs->NetBIOSDNSName);
118			}
119			prefs->NetBIOSDNSName = CFStringCreateWithCString(kCFAllocatorSystemDefault, p, kCFStringEncodingUTF8);
120		}
121	}
122
123	/* global or server preferences */
124	if ((level == 0) || (level == 1)) {
125		rc_getint(rcfile, sname, "nbtimeout", &prefs->NetBIOSResolverTimeout);
126		/* Make sure they set it to something */
127		if (prefs->NetBIOSResolverTimeout == 0) {
128			prefs->NetBIOSResolverTimeout = DefaultNetBIOSResolverTimeout;
129		}
130
131		/*
132		 * We default to trying both ports, if this is not set then the URL
133		 * is overriding the preference, ignore the preference file setting.
134		 */
135		if (prefs->tryBothPorts) {
136			rc_getstringptr(rcfile, sname, "port445", &p);
137			/* See if the configuration file wants us to use a specific port.  */
138			if (p) {
139				if (strcmp(p, "netbios_only") == 0) {
140					prefs->tryBothPorts = FALSE;
141					prefs->tcp_port = NBSS_TCP_PORT_139;
142				}
143				else if (strcmp(p, "no_netbios") == 0) {
144					prefs->tryBothPorts = FALSE;
145					prefs->tcp_port = SMB_TCP_PORT_445;
146				}
147			}
148		}
149
150		/* Really should be getting this from System Configuration */
151		rc_getstringptr(rcfile, sname, "minauth", &p);
152		if (p) {
153			/*
154			 * "minauth" was set in this section; override
155			 * the current minimum authentication setting.
156			 */
157			if (strcmp(p, "kerberos") == 0) {
158				/*
159				 * Don't fall back to NTLMv2, NTLMv1, or
160				 * a clear text password.
161				 */
162				prefs->minAuthAllowed = SMB_MINAUTH_KERBEROS;
163			} else if (strcmp(p, "ntlmv2") == 0) {
164				/*
165				 * Don't fall back to NTLMv1 or a clear
166				 * text password.
167				 */
168				prefs->minAuthAllowed = SMB_MINAUTH_NTLMV2;
169			} else if (strcmp(p, "ntlm") == 0) {
170				/*
171				 * Don't send the LM response over the wire.
172				 */
173				prefs->minAuthAllowed = SMB_MINAUTH_NTLM;
174			} else if (strcmp(p, "lm") == 0) {
175				/*
176				 * Fail if the server doesn't do encrypted
177				 * passwords.
178				 */
179				prefs->minAuthAllowed = SMB_MINAUTH_LM;
180			} else if (strcmp(p, "none") == 0) {
181				/*
182				 * Anything goes.
183				 * (The following statement should be
184				 * optimized away.)
185				 */
186				prefs->minAuthAllowed = SMB_MINAUTH;
187			}
188		}
189
190        rc_getint(rcfile, sname, "max_resp_timeout", &prefs->max_resp_timeout);
191		/* Make sure they set it to something reasonable */
192		if (prefs->max_resp_timeout > 600) {
193			prefs->max_resp_timeout = 600; /* 10 mins is a long, long time */
194		}
195	}
196
197	/* global, server, user, or share preferences */
198
199	/* Only get the value if it exist */
200	if (rc_getbool(rcfile, sname, "compound_on", &altflags) == 0) {
201		if (altflags)
202			prefs->altflags |= SMBFS_MNT_COMPOUND_ON;
203		else
204			prefs->altflags &= ~SMBFS_MNT_COMPOUND_ON;
205	}
206
207	/* Only get the value if it exist */
208	if (rc_getbool(rcfile, sname, "notify_off", &altflags) == 0) {
209		if (altflags)
210			prefs->altflags |= SMBFS_MNT_NOTIFY_OFF;
211		else
212			prefs->altflags &= ~SMBFS_MNT_NOTIFY_OFF;
213	}
214
215	/* Only get the value if it exist */
216	if (rc_getbool(rcfile, sname, "streams", &altflags) == 0) {
217		if (altflags)
218			prefs->altflags |= SMBFS_MNT_STREAMS_ON;
219		else
220			prefs->altflags &= ~SMBFS_MNT_STREAMS_ON;
221	}
222
223	/* Only get the value if it exist */
224	if ( rc_getbool(rcfile, sname, "soft", &altflags) == 0) {
225		if (altflags)
226			prefs->altflags |= SMBFS_MNT_SOFT;
227		else
228			prefs->altflags &= ~SMBFS_MNT_SOFT;
229	}
230
231    /*
232     * Start of the HIDDEN options of nsmb.
233     */
234
235    /*
236	 * We are not adding this in the man pages, because we do not want to keep
237	 * this as a configuration option. This is for debug purposes only and
238	 * should be removed once <rdar://problem/7236779> is complete. Force
239	 * ACLs on if we have a network sid.
240	 */
241	if (rc_getbool(rcfile, sname, "debug_acl_on", &altflags) == 0) {
242		if (altflags)
243			prefs->altflags |= SMBFS_MNT_DEBUG_ACL_ON;
244		else
245			prefs->altflags &= ~SMBFS_MNT_DEBUG_ACL_ON;
246	}
247
248    /*
249	 * Another hidden config option. Force readdirattr off
250	 */
251	if (rc_getbool(rcfile, sname, "readdirattr_off", &altflags) == 0) {
252		if (altflags)
253			prefs->altflags |= SMBFS_MNT_READDIRATTR_OFF;
254		else
255			prefs->altflags &= ~SMBFS_MNT_READDIRATTR_OFF;
256	}
257
258    /*
259	 * Another hidden config option, to force LANMAN on
260	 */
261    if (rc_getbool(rcfile, sname, "lanman_on", &altflags) == 0) {
262		if (altflags) {
263			prefs->lanman_on = 1;
264			smb_log_info("%s: LANMAN support enabled", ASL_LEVEL_DEBUG, __FUNCTION__);
265		} else {
266			prefs->lanman_on = 0;
267		}
268	}
269
270    /*
271	 * Another hidden config option, to force Kerberos off
272     * When <12991970> is fixed, remove this code
273	 */
274    if (rc_getbool(rcfile, sname, "kerberos_off", &altflags) == 0) {
275		if (altflags) {
276			prefs->altflags |= SMBFS_MNT_KERBEROS_OFF;
277		} else {
278			prefs->altflags &= ~SMBFS_MNT_KERBEROS_OFF;
279		}
280	}
281
282    /*
283	 * Another hidden config option, to force File IDs off
284	 */
285    if (rc_getbool(rcfile, sname, "file_ids_off", &altflags) == 0) {
286		if (altflags) {
287			prefs->altflags |= SMBFS_MNT_FILE_IDS_OFF;
288		} else {
289			prefs->altflags &= ~SMBFS_MNT_FILE_IDS_OFF;
290		}
291	}
292
293    /*
294	 * Another hidden config option, to force AAPL off
295	 */
296    if (rc_getbool(rcfile, sname, "aapl_off", &altflags) == 0) {
297		if (altflags) {
298			prefs->altflags |= SMBFS_MNT_AAPL_OFF;
299		} else {
300			prefs->altflags &= ~SMBFS_MNT_AAPL_OFF;
301		}
302	}
303}
304
305static CFStringRef getLocalNetBIOSNameUsingHostName()
306{
307	CFMutableStringRef NetBIOSName = NULL;
308	char buf[_POSIX_HOST_NAME_MAX+1], *cp;
309
310	if (gethostname(buf, sizeof(buf)) != 0) {
311		smb_log_info("%s: Couldn't obtain the Local NetBIOS Name using gethostname",
312					 ASL_LEVEL_DEBUG, __FUNCTION__);
313		return NULL;
314	}
315	cp = strchr(buf, '.');
316	if (cp)
317		*cp = 0;
318	buf[MIN(SMB_MAXNetBIOSNAMELEN, _POSIX_HOST_NAME_MAX)] = 0;
319	NetBIOSName = CFStringCreateMutable(kCFAllocatorSystemDefault, 0);
320	if (NetBIOSName) {
321		CFStringAppendCString(NetBIOSName, buf, kCFStringEncodingUTF8);
322		CFStringUppercase(NetBIOSName, CFLocaleGetSystem());
323	}
324	return NetBIOSName;
325}
326
327
328/*
329 * Retrieve any SMB System Configuration Preference. This routine always succeeds,
330 * on any failure we fill in the default values.
331 */
332static void getSCPreferences(struct smb_prefs *prefs)
333{
334	SCPreferencesRef scPrefs;
335	CFMutableStringRef NetBIOSName = NULL;
336	CFStringRef DOSCodePage;
337
338	scPrefs = SCPreferencesCreate(kCFAllocatorDefault, CFSTR("SMB Client"), CFSTR(kSMBPreferencesAppID));
339	if (!scPrefs) {
340		smb_log_info("%s: Couldn't obtain system config preferences: %s",
341					 ASL_LEVEL_DEBUG, __FUNCTION__, strerror(errno));
342		prefs->WINSAddresses = NULL;
343		prefs->WinCodePage = CFStringConvertWindowsCodepageToEncoding(defaultCodePage);
344		goto done;
345	}
346	NetBIOSName = (CFMutableStringRef)SCPreferencesGetValue(scPrefs, CFSTR(kSMBPrefNetBIOSName));
347	if (NetBIOSName) {
348		NetBIOSName = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, NetBIOSName);
349	}
350	if (NetBIOSName) {
351		CFStringUppercase(NetBIOSName, CFLocaleGetSystem());
352	}
353	prefs->LocalNetBIOSName = NetBIOSName;
354
355	prefs->WINSAddresses = SCPreferencesGetValue(scPrefs, CFSTR(kSMBPrefWINSServerAddressList));
356	if (prefs->WINSAddresses) {
357		CFRetain(prefs->WINSAddresses);
358#ifdef SMB_DEBUG
359		char wins[SMB_MAX_DNS_SRVNAMELEN+1];
360		CFIndex ii, count = CFArrayGetCount(prefs->WINSAddresses);
361
362		for (ii=0; ii < count; ii++) {
363			CFStringRef winString = CFArrayGetValueAtIndex(prefs->WINSAddresses, ii);
364			wins[0] = 0;
365			CFStringGetCString(winString, wins, sizeof(wins), kCFStringEncodingUTF8);
366			smb_log_info("WINS[%d] \"%s\" ", ASL_LEVEL_ERR, (int)ii, wins);
367		}
368#endif // SMB_DEBUG
369	}
370	DOSCodePage = SCPreferencesGetValue(scPrefs, CFSTR(kSMBPrefDOSCodePage));
371	if (DOSCodePage) {
372		if ((CFStringHasPrefix(DOSCodePage, CFSTR("CP")) == FALSE) && (CFStringHasPrefix(DOSCodePage, CFSTR("cp")) == FALSE)) {
373			CFMutableStringRef WinCodePageStr = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, CFSTR("cp"));
374			if (WinCodePageStr) {
375				CFStringAppend(WinCodePageStr, DOSCodePage);
376				prefs->WinCodePage = CFStringConvertIANACharSetNameToEncoding(WinCodePageStr);
377				CFRelease(WinCodePageStr);
378				goto done;
379			}
380		} else {
381			prefs->WinCodePage = CFStringConvertIANACharSetNameToEncoding(DOSCodePage);
382			goto done;
383		}
384	}
385	/* We have no other choice use the default code page */
386	prefs->WinCodePage = CFStringConvertWindowsCodepageToEncoding(defaultCodePage);
387done:
388	if (scPrefs) {
389		CFRelease(scPrefs);
390	}
391	if (prefs->LocalNetBIOSName == NULL) {
392		/* If all else fail try using the host name */
393		prefs->LocalNetBIOSName = getLocalNetBIOSNameUsingHostName();
394	}
395	/* Test to make sure we have a code page we can use. */
396	if (CFStringIsEncodingAvailable(prefs->WinCodePage) == FALSE) {
397		prefs->WinCodePage = CFStringConvertWindowsCodepageToEncoding(defaultCodePage);
398	}
399
400}
401
402void getDefaultPreferences(struct smb_prefs *prefs)
403{
404	 /* <11860141> Disable LANMAN (RAP) for getting share lists */
405	memset(prefs, 0, sizeof(*prefs));
406	prefs->tryBothPorts = TRUE;
407	prefs->tcp_port = SMB_TCP_PORT_445;
408	prefs->altflags = SMBFS_MNT_STREAMS_ON | SMBFS_MNT_COMPOUND_ON;
409	prefs->minAuthAllowed = SMB_MINAUTH_NTLMV2;
410	prefs->NetBIOSResolverTimeout = DefaultNetBIOSResolverTimeout;
411
412	/* Now get any values stored in the System Configuration */
413	getSCPreferences(prefs);
414}
415
416void releasePreferenceInfo(struct smb_prefs *prefs)
417{
418	if (prefs->LocalNetBIOSName) {
419		CFRelease(prefs->LocalNetBIOSName);
420		prefs->LocalNetBIOSName = NULL;
421	}
422	if (prefs->WINSAddresses) {
423		CFRelease(prefs->WINSAddresses);
424		prefs->WINSAddresses = NULL;
425	}
426	if (prefs->NetBIOSDNSName) {
427		CFRelease(prefs->NetBIOSDNSName);
428		prefs->NetBIOSDNSName = NULL;
429	}
430}
431
432void setWINSAddress(struct smb_prefs *prefs, const char *winsAddress, int count)
433{
434	int ii;
435	CFMutableArrayRef winsArray = CFArrayCreateMutable( kCFAllocatorSystemDefault, 0,
436													   &kCFTypeArrayCallBacks );
437	if (winsArray == NULL) {
438		return;
439	}
440	for (ii=0; ii < count; ii++) {
441		CFStringRef winsName = CFStringCreateWithCString(kCFAllocatorSystemDefault, winsAddress, kCFStringEncodingUTF8);
442		if (winsName) {
443			CFArrayAppendValue(winsArray, winsName);
444			CFRelease(winsName);
445		}
446		winsAddress += strlen(winsAddress) + 1;
447	}
448	if (CFArrayGetCount(winsArray) == 0) {
449		CFRelease(winsArray);
450	}
451	if (prefs->WINSAddresses) {
452		CFRelease(prefs->WINSAddresses);
453	}
454	prefs->WINSAddresses = winsArray;
455}
456
457void readPreferences(struct smb_prefs *prefs, char *serverName, char *shareName,
458					 int noUserPrefs, int resetPrefs)
459{
460	struct rcfile *rcfile;
461	char sname[SMB_MAX_DNS_SRVNAMELEN + SMB_MAXSHARENAMELEN + 4];
462
463	/* Set the default values */
464	if (resetPrefs) {
465		getDefaultPreferences(prefs);
466	}
467
468	/* Now read the nsmb.conf file preference */
469	rcfile = smb_open_rcfile(noUserPrefs);
470	if (rcfile == NULL) {
471		return;
472	}
473	/* Read the global preference section */
474	readPreferenceSection(rcfile, prefs, "default", 0);
475
476	/* Need a server name, before we can read any of the [server] sections. */
477	if (serverName) {
478		readPreferenceSection(rcfile, prefs, serverName, 1);
479		/* Need a server and share name, before we can read any of the [server:share] sections. */
480		if (shareName) {
481			snprintf(sname, sizeof(sname), "%s:%s", serverName, shareName);
482			readPreferenceSection(rcfile, prefs, sname, 2);
483		}
484	}
485	/* Done with it close the preference file */
486	rc_close(rcfile);
487}
488
489
490CFStringEncoding getPrefsCodePage( void )
491{
492	struct smb_prefs prefs;
493
494	getDefaultPreferences(&prefs);
495	releasePreferenceInfo(&prefs);
496	return prefs.WinCodePage;
497}
498