/* SOGoToolUpdateAutoReply.m - this file is part of SOGo * * Copyright (C) 2011-2022 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #import #import #import #import #import #import #import #import #import #import #import #import #import "SOGo/SOGoCredentialsFile.h" #import #import #import #import #import #import "SOGoTool.h" #define statusquoAutoReply 0x00 #define enableAutoReply 0x01 #define disableAutoReply 0x02 #define temporarilyAutoReply 0x10 #define temporarilyEnableAutoReply 0x11 #define temporarilyDisableAutoReply 0x12 @interface SOGoToolUpdateAutoReply : SOGoTool @end @implementation SOGoToolUpdateAutoReply + (void) initialize { } + (NSString *) command { return @"update-autoreply"; } + (NSString *) description { return @"enable or disable auto reply for reached start/end dates"; } - (void) usage { fprintf (stderr, "update-autoreply -p credentialFile\n\n" " -p credentialFile Specify the file containing the sieve admin credentials\n" " The file should contain a single line:\n" " username:password\n" "\n" "The update-autoreply action should be configured as a daily cronjob.\n"); } - (BOOL) checkConstraintsForRow: (NSDictionary *) infos { NSArray *weekdays, *startTime, *endTime; NSCalendarDate *now, *startDate, *endDate; NSDictionary *defaults, *vacationOptions; NSString *c_defaults; NSTimeZone *timeZone; BOOL hasConstraints; unsigned int beginOfDaysSecs, endDateTime, startDateTime, result; result = statusquoAutoReply; c_defaults = [infos objectForKey: @"c_defaults"]; startDate = endDate = nil; hasConstraints = NO; if ([c_defaults isNotNull]) { defaults = [c_defaults objectFromJSONString]; vacationOptions = (NSDictionary *) [defaults objectForKey: @"Vacation"]; if ([[vacationOptions objectForKey: @"enabled"] boolValue]) { timeZone = [NSTimeZone timeZoneWithName: (NSString *)[defaults objectForKey: @"SOGoTimeZone"]]; now = [NSCalendarDate calendarDate]; [now setTimeZone: timeZone]; beginOfDaysSecs = [[now beginOfDay] timeIntervalSince1970]; // We handle the start date if ([[vacationOptions objectForKey: @"startDateEnabled"] boolValue]) { hasConstraints = YES; startDateTime = [[vacationOptions objectForKey: @"startDate"] intValue]; if (beginOfDaysSecs >= startDateTime) result = enableAutoReply; else result = temporarilyDisableAutoReply; } // We handle the end date if ([[vacationOptions objectForKey: @"endDateEnabled"] boolValue]) { endDateTime = [[vacationOptions objectForKey: @"endDate"] intValue]; if (endDateTime < beginOfDaysSecs) result = disableAutoReply; } if (!(result & disableAutoReply)) { // We handle the start time if ([[vacationOptions objectForKey: @"startTimeEnabled"] boolValue]) { hasConstraints = YES; startTime = [[vacationOptions objectForKey: @"startTime"] componentsSeparatedByString: @":"]; startDate = [NSCalendarDate dateWithYear: [now yearOfCommonEra] month: [now monthOfYear] day: [now dayOfMonth] hour: [[startTime objectAtIndex: 0] intValue] minute: [[startTime objectAtIndex: 1] intValue] second: [now secondOfMinute] timeZone: [now timeZone]]; if ([startDate compare: now] == NSOrderedSame || [startDate compare: now] == NSOrderedAscending) result = temporarilyEnableAutoReply; else if (![[vacationOptions objectForKey: @"endTimeEnabled"] boolValue]) result = temporarilyDisableAutoReply; } // We handle the end time // NOTE: if end time is enabled, start time must be defined if ([[vacationOptions objectForKey: @"endTimeEnabled"] boolValue]) { endTime = [[vacationOptions objectForKey: @"endTime"] componentsSeparatedByString: @":"]; endDate = [NSCalendarDate dateWithYear: [now yearOfCommonEra] month: [now monthOfYear] day: [now dayOfMonth] hour: [[endTime objectAtIndex: 0] intValue] minute: [[endTime objectAtIndex: 1] intValue] second: [now secondOfMinute] timeZone: [now timeZone]]; if ([endDate compare: now] == NSOrderedSame || [endDate compare: now] == NSOrderedAscending) { if ([startDate compare: endDate] == NSOrderedAscending || (result != temporarilyEnableAutoReply)) result = temporarilyDisableAutoReply; } } } if (result != disableAutoReply) { // We handle the weekdays if ([[vacationOptions objectForKey: @"weekdaysEnabled"] boolValue]) { hasConstraints = YES; weekdays = [vacationOptions objectForKey: @"days"]; if ([weekdays containsObject: [NSString stringWithFormat: @"%i", [now dayOfWeek]]]) result = temporarilyEnableAutoReply; else result = temporarilyDisableAutoReply; } } if (result == statusquoAutoReply && !hasConstraints) // No action and no constraint on start date; enable the auto-reply result = enableAutoReply; } } return result; } - (BOOL) updateAutoReplyForLogin: (NSString *) theLogin withSieveUsername: (NSString *) theUsername andPassword: (NSString *) thePassword disabling: (BOOL) disabling temporarily: (BOOL) temporarily { NSMutableDictionary *vacationOptions; SOGoUserDefaults *userDefaults; SOGoUser *user; BOOL result; NSException *error; result = YES; user = [SOGoUser userWithLogin: theLogin]; userDefaults = [user userDefaults]; vacationOptions = [[userDefaults vacationOptions] mutableCopy]; [vacationOptions autorelease]; if (!temporarily) { if (disabling) { [vacationOptions setObject: [NSNumber numberWithBool: NO] forKey: @"enabled"]; } else { // We do NOT enable the vacation message automatically if the domain // preference is disabled. if (![[user domainDefaults] vacationPeriodEnabled]) { NSLog(@"SOGoVacationPeriodEnabled set to NO for the domain - ignoring."); return NO; } [vacationOptions setObject: [NSNumber numberWithBool: NO] forKey: @"startDateEnabled"]; } [userDefaults setVacationOptions: vacationOptions]; result = [userDefaults synchronize]; } if (result) { SOGoUserFolder *home; SOGoMailAccounts *folder; SOGoMailAccount *account; WOContext *localContext; Class SOGoMailAccounts_class; [[SOGoProductLoader productLoader] loadProducts: [NSArray arrayWithObject: @"Mailer.SOGo"]]; SOGoMailAccounts_class = NSClassFromString(@"SOGoMailAccounts"); localContext = [WOContext context]; [localContext setActiveUser: user]; home = [user homeFolderInContext: localContext]; folder = [SOGoMailAccounts_class objectWithName: @"Mail" inContainer: home]; account = [folder lookupName: @"0" inContext: localContext acquire: NO]; [account setContext: localContext]; error = [account updateFiltersWithUsername: theUsername andPassword: thePassword forceActivation: NO]; if (error && !temporarily) { // Can't update Sieve script -- Reactivate auto-reply if (disabling) [vacationOptions setObject: [NSNumber numberWithBool: YES] forKey: @"enabled"]; else [vacationOptions setObject: [NSNumber numberWithBool: YES] forKey: @"startDateEnabled"]; [userDefaults setVacationOptions: vacationOptions]; [userDefaults synchronize]; result = NO; } } return result; } - (void) updateAutoReplyWithUsername: (NSString *) theUsername andPassword: (NSString *) thePassword { GCSChannelManager *cm; EOAdaptorChannel *channel; NSArray *attrs; NSDictionary *infos; NSString *sql, *profileURL, *user; NSURL *tableURL; SOGoSystemDefaults *sd; unsigned int result; sd = [SOGoSystemDefaults sharedSystemDefaults]; profileURL = [sd profileURL]; if (!profileURL) { NSLog(@"Couldn't obtain the profileURL. (Hint: SOGoProfileURL)"); } else { tableURL = [[NSURL alloc] initWithString: profileURL]; cm = [GCSChannelManager defaultChannelManager]; channel = [cm acquireOpenChannelForURL: tableURL]; if (!channel) { NSLog(@"Couldn't acquire channel for profileURL"); } else { sql = [NSString stringWithFormat: @"SELECT c_uid, c_defaults FROM %@", [tableURL gcsTableName]]; [channel evaluateExpressionX: sql]; attrs = [channel describeResults: NO]; while ((infos = [channel fetchAttributes: attrs withZone: NULL])) { user = [infos objectForKey: @"c_uid"]; if (verbose) NSLog(@"Checking user %@\n", user); result = [self checkConstraintsForRow: infos]; if (result & enableAutoReply) { if ([self updateAutoReplyForLogin: user withSieveUsername: theUsername andPassword: thePassword disabling: NO temporarily: !!(result & temporarilyAutoReply)]) NSLog(@"Enabled auto-reply of user %@", user); else NSLog(@"An error occured while enabling auto-reply of user %@", user); } else if (result & disableAutoReply) { if ([self updateAutoReplyForLogin: user withSieveUsername: theUsername andPassword: thePassword disabling: YES temporarily: !!(result & temporarilyAutoReply)]) NSLog(@"Removed auto-reply of user %@", user); else NSLog(@"An error occured while removing auto-reply of user %@", user); } } } } } - (BOOL) run { NSRange r; NSString *creds, *credsFilename, *authname, *authpwd; SOGoCredentialsFile *cf; BOOL rc; int max; max = [sanitizedArguments count]; creds = nil; authname = nil; authpwd = nil; rc = NO; credsFilename = [[NSUserDefaults standardUserDefaults] stringForKey: @"p"]; if (credsFilename) { cf = [SOGoCredentialsFile credentialsFromFile: credsFilename]; authname = [cf username]; authpwd = [cf password]; } /* DEPRECATED: this is only kept around to avoid breaking existing setups */ if (max > 0) { /* assume we got the creds directly on the cli */ creds = [sanitizedArguments objectAtIndex: 0]; if (creds) { r = [creds rangeOfString: @":"]; if (r.location == NSNotFound) { NSLog(@"Invalid credential string format (user:pass)"); } else { authname = [creds substringToIndex: r.location]; authpwd = [creds substringFromIndex: r.location+1]; } } } if (authname && authpwd) { [self updateAutoReplyWithUsername: authname andPassword: authpwd]; rc = YES; } if (!rc) [self usage]; return rc; } @end