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