1/* SOGoSystemDefaults.m - this file is part of SOGo
2 *
3 * Copyright (C) 2009-2021 Inverse inc.
4 * Copyright (C) 2012 Jeroen Dekkers <jeroen@dekkers.ch>
5 *
6 * This file is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
9 * any later version.
10 *
11 * This file is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; see the file COPYING.  If not, write to
18 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
20 */
21
22#import <dlfcn.h>
23
24#import <Foundation/NSBundle.h>
25#import <Foundation/NSFileManager.h>
26#import <Foundation/NSFileManager.h>
27#import <Foundation/NSUserDefaults.h>
28
29#import <NGExtensions/NSObject+Logs.h>
30
31#import "NSArray+Utilities.h"
32#import "NSDictionary+Utilities.h"
33#import "SOGoStartupLogger.h"
34
35#import "SOGoSystemDefaults.h"
36#import "SOGoConstants.h"
37
38@implementation SOGoSystemDefaults
39
40#if defined(LDAP_CONFIG)
41#endif
42
43typedef void (*NSUserDefaultsInitFunction) ();
44
45#define DIR_SEP "/"
46
47#ifndef NSUIntegerMax
48#define NSUIntegerMax UINTPTR_MAX
49#endif
50
51static void
52BootstrapNSUserDefaults ()
53{
54  char *filename;
55  NSUserDefaultsInitFunction SOGoNSUserDefaultsBootstrap;
56  void *handle;
57
58  filename = SOGO_LIBDIR DIR_SEP "libSOGoNSUserDefaults.so.1";
59  handle = dlopen (filename, RTLD_NOW | RTLD_GLOBAL);
60  if (handle)
61    {
62      SOGoNSUserDefaultsBootstrap = dlsym (handle,
63                                             "SOGoNSUserDefaultsBootstrap");
64      if (SOGoNSUserDefaultsBootstrap)
65        SOGoNSUserDefaultsBootstrap ();
66    }
67}
68
69static void
70_injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
71                              NSString *filename, NSObject *logger)
72{
73  NSDictionary *newConfig, *fileAttrs;
74  NSFileManager *fm;
75
76  fm = [NSFileManager defaultManager];
77  if ([fm fileExistsAtPath: filename])
78    {
79      fileAttrs = [fm fileAttributesAtPath: filename
80                              traverseLink: YES];
81      if (![fileAttrs objectForKey: @"NSFileSize"])
82        {
83          [logger errorWithFormat:
84                  @"Can't get file attributes from '%@'",
85                  filename];
86          exit(1);
87        }
88      if ([[fileAttrs objectForKey: @"NSFileSize"] intValue] == 0 )
89        {
90          [logger warnWithFormat:
91                  @"Empty file: '%@'. Skipping",
92                  filename];
93        }
94      else
95        {
96          newConfig = [NSDictionary dictionaryWithContentsOfFile: filename];
97          if (newConfig)
98              [defaultsDict addEntriesFromDictionary: newConfig];
99          else
100            {
101              [logger errorWithFormat:
102                      @"Cannot read configuration from '%@'. Aborting",
103                      filename];
104              exit(1);
105            }
106        }
107    }
108}
109
110+ (void) prepareUserDefaults
111{
112  /* Load settings from configuration files and
113   * enforce the following order of precedence.
114   * First match wins
115   *   1. Command line arguments
116   *   2. .GNUstepDefaults
117   *   3. /usr/local/etc/sogo/{debconf,sogo}.conf
118   *   4. SOGoDefaults.plist
119   *
120   * The default standardUserDefaults search list is as follows:
121   *   GSPrimaryDomain
122   *   NSArgumentDomain (command line arguments)
123   *   applicationDomain (sogod)
124   *   NSGlobalDomain
125   *   GSConfigDomain
126   *   (languages)
127   *   NSRegistrationDomain
128   *
129   * We'll end up with this search list:
130   *   NSArgumentDomain (command line arguments)
131   *   sogodRuntimeDomain (config from all config files)
132   *   GSPrimaryDomain
133   *   NSGlobalDomain
134   *   GSConfigDomain
135   *   (languages)
136   *   NSRegistrationDomain (SOPE loads its defaults in this one)
137   */
138
139  NSDictionary *sogodDomain;
140  NSMutableDictionary *configFromFiles;
141  NSUserDefaults *ud;
142  SOGoStartupLogger *logger;
143  NSBundle *bundle;
144  NSString *confFiles[] = {@"/usr/local/etc/sogo/debconf.conf",
145                           @"/usr/local/etc/sogo/sogo.conf"};
146  NSString *filename, *redirectURL;
147  NSUInteger count;
148
149  logger = [SOGoStartupLogger sharedLogger];
150
151  /* Load the configuration from the standard user default files */
152  ud = [NSUserDefaults standardUserDefaults];
153
154  /* Populate configFromFiles with default values from SOGoDefaults.plist */
155  configFromFiles = [NSMutableDictionary dictionaryWithCapacity:0];
156  bundle = [NSBundle bundleForClass: self];
157  filename = [bundle pathForResource: @"SOGoDefaults" ofType: @"plist"];
158  if (filename)
159    _injectConfigurationFromFile (configFromFiles, filename, logger);
160
161  /* Fill/Override configFromFiles values with configuration stored
162   *  in "/etc" */
163  for (count = 0; count < sizeof(confFiles)/sizeof(confFiles[0]); count++)
164    _injectConfigurationFromFile (configFromFiles, confFiles[count], logger);
165
166  /* This dance is required to let other appplications (sogo-tool) use
167   * options from the sogod domain while preserving the order of precedence
168   *  - remove the 'sogod' domain from the user defaults search list
169   *  - Load the content of the sogod domain into configFromFiles
170   *    Thereby overriding values from the config files loaded above
171   */
172  [ud removeSuiteNamed: @"sogod"];
173  sogodDomain = [ud persistentDomainForName: @"sogod"];
174  if ([sogodDomain count])
175    [configFromFiles addEntriesFromDictionary: sogodDomain];
176
177  /* Add a volatile domain containing the config to the search list.
178   * The domain is added at the very front of the search list
179   */
180  [ud setVolatileDomain: configFromFiles
181                forName: @"sogodRuntimeDomain"];
182  [ud addSuiteNamed: @"sogodRuntimeDomain"];
183
184  /* NSArgumentsDomain goes back in front of the search list */
185  [ud addSuiteNamed: @"NSArgumentDomain"];
186
187  /* issue a warning if WOApplicationRedirectURL is used */
188  redirectURL = [ud stringForKey: @"WOApplicationRedirectURL"];
189  if (redirectURL)
190    {
191      [logger warnWithFormat:
192                @"Using obsolete 'WOApplicationRedirectURL' user default."];
193      [logger warnWithFormat:
194                @"  Please configure the use of the x-webobjects-XXX headers"
195              @" with your webserver (see sample files)."];
196      if ([redirectURL hasSuffix: @"/"])
197        [ud setObject: [redirectURL substringToIndex: [redirectURL length] - 1]
198               forKey: @"WOApplicationRedirectURL"];
199    }
200}
201
202+ (void) initialize
203{
204  BootstrapNSUserDefaults ();
205  [self prepareUserDefaults];
206}
207
208+ (SOGoSystemDefaults *) sharedSystemDefaults
209{
210  static SOGoSystemDefaults *sharedSystemDefaults = nil;
211  NSUserDefaults *ud;
212
213  if (!sharedSystemDefaults)
214    {
215      ud = [NSUserDefaults standardUserDefaults];
216      sharedSystemDefaults = [self defaultsSourceWithSource: ud
217                                            andParentSource: nil];
218      [sharedSystemDefaults retain];
219    }
220
221  return sharedSystemDefaults;
222}
223
224- (id) init
225{
226  if ((self = [super init]))
227    {
228      loginDomains = nil;
229    }
230
231  return self;
232}
233
234- (void) dealloc
235{
236  [loginDomains release];
237  [super dealloc];
238}
239
240- (BOOL) migrate
241{
242  static NSDictionary *migratedKeys = nil;
243
244  if (!migratedKeys)
245    {
246      migratedKeys = [NSDictionary dictionaryWithObjectsAndKeys:
247                                     @"SOGoProfileURL", @"AgenorProfileURL",
248                                   @"SOGoTimeZone", @"SOGoServerTimeZone",
249                                   nil];
250      [migratedKeys retain];
251    }
252
253  return ([self migrateOldDefaultsWithDictionary: migratedKeys]
254          | [super migrate]);
255}
256
257- (NSArray *) domainIds
258{
259  return [[self dictionaryForKey: @"domains"] allKeys];
260}
261
262- (BOOL) enableDomainBasedUID
263{
264  return [self boolForKey: @"SOGoEnableDomainBasedUID"];
265}
266
267- (NSArray *) loginDomains
268{
269  NSMutableArray *filteredLoginDomains;
270  NSArray *domains;
271  id currentObject;
272  int count;
273
274  if (self->loginDomains == nil)
275    {
276      filteredLoginDomains = [NSMutableArray arrayWithArray: [self stringArrayForKey: @"SOGoLoginDomains"]];
277      domains = [self domainIds];
278      count = [filteredLoginDomains count];
279      while (count > 0)
280        {
281          count--;
282          currentObject = [filteredLoginDomains objectAtIndex: count];
283          if (![domains containsObject: currentObject])
284            {
285              [filteredLoginDomains removeObject: currentObject];
286              [self warnWithFormat: @"SOGoLoginDomains contains an invalid domain : %@", currentObject];
287            }
288        }
289
290      ASSIGN (self->loginDomains, filteredLoginDomains);
291    }
292
293  return self->loginDomains;
294}
295
296- (NSArray *) visibleDomainsForDomain: (NSString *) domain
297{
298  NSMutableArray *domains;
299  NSArray *definedDomains, *visibleDomains, *currentGroup;
300  NSEnumerator *groups;
301  NSString *currentDomain;
302
303  definedDomains = [self domainIds];
304  visibleDomains = [self arrayForKey: @"SOGoDomainsVisibility"];
305  domains = [NSMutableArray array];
306  groups = [visibleDomains objectEnumerator];
307  while ((currentGroup = (NSArray *)[groups nextObject]))
308    {
309      if ([currentGroup containsObject: domain])
310        [domains addObjectsFromArray: currentGroup];
311    }
312
313  // Remove lookup domain and invalid domains
314  groups = [domains objectEnumerator];
315  while ((currentDomain = [groups nextObject]))
316    {
317      if ([currentDomain isEqualToString: domain] || ![definedDomains containsObject: currentDomain])
318        [domains removeObject: currentDomain];
319    }
320
321  return [domains uniqueObjects];
322}
323
324/* System-level only */
325
326- (BOOL) crashOnSessionCreate
327{
328  return [self boolForKey: @"SOGoCrashOnSessionCreate"];
329}
330
331- (BOOL) debugRequests
332{
333  return [self boolForKey: @"SOGoDebugRequests"];
334}
335
336- (BOOL) debugLeaks;
337{
338  return [self boolForKey: @"SOGoDebugLeaks"];
339}
340
341- (int) vmemLimit
342{
343  return [self integerForKey: @"SxVMemLimit"];
344}
345
346- (BOOL) trustProxyAuthentication;
347{
348  return [self boolForKey: @"SOGoTrustProxyAuthentication"];
349}
350
351- (NSString *) encryptionKey;
352{
353  return [self stringForKey: @"SOGoEncryptionKey"];
354}
355
356- (BOOL) useRelativeURLs
357{
358  return [self boolForKey: @"WOUseRelativeURLs"];
359}
360
361- (NSString *) sieveFolderEncoding
362{
363  return [self stringForKey: @"SOGoSieveFolderEncoding"];
364}
365
366
367- (BOOL) isWebAccessEnabled
368{
369  return [self boolForKey: @"SOGoWebAccessEnabled"];
370}
371
372- (BOOL) isCalendarDAVAccessEnabled
373{
374  return [self boolForKey: @"SOGoCalendarDAVAccessEnabled"];
375}
376
377- (BOOL) isAddressBookDAVAccessEnabled
378{
379  return [self boolForKey: @"SOGoAddressBookDAVAccessEnabled"];
380}
381
382- (BOOL) enableEMailAlarms
383{
384  return [self boolForKey: @"SOGoEnableEMailAlarms"];
385}
386
387- (NSString *) faviconRelativeURL
388{
389  return [self stringForKey: @"SOGoFaviconRelativeURL"];
390}
391
392- (NSString *) zipPath
393{
394  return [self stringForKey: @"SOGoZipPath"];
395}
396
397- (int) port
398{
399  return [self integerForKey: @"WOPort"];
400}
401
402- (int) workers
403{
404  return [self integerForKey: @"WOWorkersCount"];
405}
406
407- (NSString *) logFile
408{
409  return [self stringForKey: @"WOLogFile"];
410}
411
412- (NSString *) pidFile
413{
414  return [self stringForKey: @"WOPidFile"];
415}
416
417- (NSTimeInterval) cacheCleanupInterval
418{
419  return [self floatForKey: @"SOGoCacheCleanupInterval"];
420}
421
422- (NSString *) memcachedHost
423{
424  return [self stringForKey: @"SOGoMemcachedHost"];
425}
426
427- (BOOL) uixDebugEnabled
428{
429  return [self boolForKey: @"SOGoUIxDebugEnabled"];
430}
431
432- (BOOL) easDebugEnabled
433{
434  return [self boolForKey: @"SOGoEASDebugEnabled"];
435}
436
437- (BOOL) tnefDecoderDebugEnabled
438{
439  return [self boolForKey: @"SOGoTnefDecoderDebugEnabled"];
440}
441
442- (BOOL) xsrfValidationEnabled
443{
444  id o;
445
446  if (!(o = [self objectForKey: @"SOGoXSRFValidationEnabled"]))
447    {
448      return YES;
449    }
450
451  return [o boolValue];
452}
453
454- (NSString *) pageTitle
455{
456  return [self stringForKey: @"SOGoPageTitle"];
457}
458
459- (NSString *) helpURL
460{
461  return [self stringForKey: @"SOGoHelpURL"];
462}
463
464- (NSArray *) supportedLanguages
465{
466  static NSArray *supportedLanguages = nil;
467
468  if (!supportedLanguages)
469    {
470      supportedLanguages = [self stringArrayForKey: @"SOGoSupportedLanguages"];
471      [supportedLanguages retain];
472    }
473
474  return supportedLanguages;
475}
476
477- (BOOL) userCanChangePassword
478{
479  return [self boolForKey: SOGoPasswordChangeEnabled];
480}
481
482- (BOOL) uixAdditionalPreferences
483{
484  return [self boolForKey: @"SOGoUIxAdditionalPreferences"];
485}
486
487- (NSString *) loginSuffix
488{
489  return [self stringForKey: @"SOGoLoginSuffix"];
490}
491
492- (NSString *) authenticationType
493{
494  return [[self stringForKey: @"SOGoAuthenticationType"] lowercaseString];
495}
496
497- (NSString *) davAuthenticationType
498{
499  return [[self stringForKey: @"SOGoDAVAuthenticationType"] lowercaseString];
500}
501
502- (NSString *) CASServiceURL
503{
504  return [self stringForKey: @"SOGoCASServiceURL"];
505}
506
507- (BOOL) CASLogoutEnabled
508{
509  return [self boolForKey: @"SOGoCASLogoutEnabled"];
510}
511
512/* SAML2 support */
513- (NSString *) SAML2PrivateKeyLocation
514{
515  return [self stringForKey: @"SOGoSAML2PrivateKeyLocation"];
516}
517
518- (NSString *) SAML2CertificateLocation;
519{
520  return [self stringForKey: @"SOGoSAML2CertificateLocation"];
521}
522
523- (NSString *) SAML2IdpMetadataLocation
524{
525  return [self stringForKey: @"SOGoSAML2IdpMetadataLocation"];
526}
527
528- (NSString *) SAML2IdpPublicKeyLocation
529{
530  return [self stringForKey: @"SOGoSAML2IdpPublicKeyLocation"];
531}
532
533- (NSString *) SAML2IdpCertificateLocation
534{
535  return [self stringForKey: @"SOGoSAML2IdpCertificateLocation"];
536}
537
538- (BOOL) SAML2LogoutEnabled
539{
540  return [self boolForKey: @"SOGoSAML2LogoutEnabled"];
541}
542
543- (NSString *) SAML2LogoutURL
544{
545  return [self stringForKey: @"SOGoSAML2LogoutURL"];
546}
547
548- (NSString *) SAML2LoginAttribute
549{
550  return [self stringForKey: @"SOGoSAML2LoginAttribute"];
551}
552
553- (BOOL) enablePublicAccess
554{
555  return [self boolForKey: @"SOGoEnablePublicAccess"];
556}
557
558//
559//
560//
561- (int) maximumFailedLoginCount
562{
563  return [self integerForKey: @"SOGoMaximumFailedLoginCount"];
564}
565
566- (int) maximumFailedLoginInterval
567{
568  int v;
569
570  v = [self integerForKey: @"SOGoMaximumFailedLoginInterval"];
571
572  if (!v)
573    v = 10;
574
575  return v;
576}
577
578- (int) failedLoginBlockInterval
579{
580  int v;
581
582  v = [self integerForKey: @"SOGoFailedLoginBlockInterval"];
583
584  if (!v)
585    v = 300;
586
587  return v;
588}
589
590//
591//
592//
593- (int) maximumMessageSizeLimit
594{
595  return [self integerForKey: @"SOGoMaximumMessageSizeLimit"];
596}
597
598//
599//
600//
601- (NSUInteger) maximumMessageSubmissionCount
602{
603  NSUInteger v;
604
605  v = [self integerForKey: @"SOGoMaximumMessageSubmissionCount"];
606
607  if (!v)
608    return NSUIntegerMax;
609
610  return v;
611}
612
613- (NSUInteger) maximumRecipientCount
614{
615  NSUInteger v;
616
617  v = [self integerForKey: @"SOGoMaximumRecipientCount"];
618
619  if (!v)
620    return NSUIntegerMax;
621
622  return v;
623}
624
625- (int) maximumSubmissionInterval
626{
627  int v;
628
629  v = [self integerForKey: @"SOGoMaximumSubmissionInterval"];
630
631  if (!v)
632    v = 30;
633
634  return v;
635}
636
637- (int) messageSubmissionBlockInterval
638{
639  int v;
640
641  v = [self integerForKey: @"SOGoMessageSubmissionBlockInterval"];
642
643  if (!v)
644    v = 300;
645
646  return v;
647}
648
649//
650// SOGo rate-limiting
651//
652- (int) maximumRequestCount
653{
654  return [self integerForKey: @"SOGoMaximumRequestCount"];
655}
656
657- (int) maximumRequestInterval
658{
659  int v;
660
661  v = [self integerForKey: @"SOGoMaximumRequestInterval"];
662
663  if (!v)
664    v = 30;
665
666  return v;
667}
668
669- (int) requestBlockInterval
670{
671  int v;
672
673  v = [self integerForKey: @"SOGoRequestBlockInterval"];
674
675  if (!v)
676    v = 300;
677
678  return v;
679}
680
681
682//
683// SOGo EAS settings
684//
685- (int) maximumPingInterval
686{
687  int v;
688
689  v = [self integerForKey: @"SOGoMaximumPingInterval"];
690
691  if (!v)
692    v = 10;
693
694  return v;
695}
696
697- (int) maximumSyncInterval
698{
699  int v;
700
701  v = [self integerForKey: @"SOGoMaximumSyncInterval"];
702
703  if (!v)
704    v = 30;
705
706  return v;
707}
708
709- (int) internalSyncInterval
710{
711  int v;
712
713  v = [self integerForKey: @"SOGoInternalSyncInterval"];
714
715  if (!v)
716    v = 10;
717
718  return v;
719}
720
721- (int) maximumSyncWindowSize
722{
723  return [self integerForKey: @"SOGoMaximumSyncWindowSize"];
724}
725
726- (int) maximumSyncResponseSize
727{
728  int v;
729
730  v = [self integerForKey: @"SOGoMaximumSyncResponseSize"];
731
732  if (v > 0)
733    v = v * 1024;
734
735  return v;
736}
737
738- (BOOL) easSearchInBody
739{
740  return [self boolForKey: @"SOGoEASSearchInBody"];
741}
742
743//
744// See https://msdn.microsoft.com/en-us/library/gg672032(v=exchg.80).aspx
745//
746- (int) maximumPictureSize
747{
748  int v;
749
750  v = [self integerForKey: @"SOGoMaximumPictureSize"];
751
752  if (!v)
753    v = 102400;
754
755  return v;
756}
757
758@end
759