1/* SOGoToolCleanup.m - this file is part of SOGo 2 * 3 * Copyright (C) 2016-2020 Inverse inc. 4 * 5 * This file is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2, or (at your option) 8 * any later version. 9 * 10 * This file is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; see the file COPYING. If not, write to 17 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18 * Boston, MA 02111-1307, USA. 19 */ 20 21#import <Foundation/NSAutoreleasePool.h> 22#import <Foundation/NSDictionary.h> 23#import <Foundation/NSEnumerator.h> 24#import <Foundation/NSString.h> 25 26#import <GDLAccess/EOAdaptorChannel.h> 27 28#import <GDLContentStore/GCSChannelManager.h> 29#import <GDLContentStore/GCSFolderManager.h> 30#import <GDLContentStore/GCSFolder.h> 31#import <GDLContentStore/NSURL+GCS.h> 32 33#import <SOGo/SOGoUserManager.h> 34#import <SOGo/NSArray+Utilities.h> 35#import <SOGo/SOGoUser.h> 36#import <SOGo/SOGoSystemDefaults.h> 37 38#import "SOGoTool.h" 39 40@interface SOGoToolCleanup : SOGoTool 41{ 42 NSArray *usersToCleanup; 43 unsigned int days; 44} 45 46@end 47 48@implementation SOGoToolCleanup 49 50+ (NSString *) command 51{ 52 return @"cleanup"; 53} 54 55+ (NSString *) description 56{ 57 return @"cleanup deleted elements of user(s)"; 58} 59 60- (id) init 61{ 62 if ((self = [super init])) 63 { 64 usersToCleanup = nil; 65 days = 0; 66 } 67 68 return self; 69} 70 71- (void) dealloc 72{ 73 [usersToCleanup release]; 74 [super dealloc]; 75} 76 77- (void) usage 78{ 79 fprintf (stderr, "cleanup [days] [user]...\n\n" 80 " days the age of deleted records to purge in days\n" 81 " user the user to purge the records or ALL for everybody\n\n" 82 "Example: sogo-tool cleanup jdoe\n"); 83} 84 85- (BOOL) fetchUserIDs: (NSArray *) users 86{ 87 NSAutoreleasePool *pool; 88 SOGoUserManager *lm; 89 NSDictionary *infos; 90 NSString *user; 91 id allUsers; 92 int count, max; 93 94 lm = [SOGoUserManager sharedUserManager]; 95 96 max = [users count]; 97 user = [users objectAtIndex: 0]; 98 if (max == 1 && [user isEqualToString: @"ALL"]) 99 { 100 GCSFolderManager *fm; 101 GCSChannelManager *cm; 102 NSURL *folderLocation; 103 EOAdaptorChannel *fc; 104 NSArray *attrs; 105 NSMutableArray *allSqlUsers; 106 NSString *sql; 107 108 fm = [GCSFolderManager defaultFolderManager]; 109 cm = [fm channelManager]; 110 folderLocation = [fm folderInfoLocation]; 111 fc = [cm acquireOpenChannelForURL: folderLocation]; 112 if (fc) 113 { 114 allSqlUsers = [NSMutableArray new]; 115 sql = [NSString stringWithFormat: @"SELECT DISTINCT c_path2 FROM %@", 116 [folderLocation gcsTableName]]; 117 [fc evaluateExpressionX: sql]; 118 attrs = [fc describeResults: NO]; 119 while ((infos = [fc fetchAttributes: attrs withZone: NULL])) 120 { 121 user = [infos objectForKey: @"c_path2"]; 122 if (user) 123 [allSqlUsers addObject: user]; 124 } 125 [cm releaseChannel: fc immediately: YES]; 126 127 users = allSqlUsers; 128 max = [users count]; 129 [allSqlUsers autorelease]; 130 } 131 } 132 133 pool = [[NSAutoreleasePool alloc] init]; 134 allUsers = [NSMutableArray new]; 135 for (count = 0; count < max; count++) 136 { 137 if (count > 0 && count%100 == 0) 138 { 139 DESTROY(pool); 140 pool = [[NSAutoreleasePool alloc] init]; 141 } 142 143 user = [users objectAtIndex: count]; 144 infos = [lm contactInfosForUserWithUIDorEmail: user]; 145 if (infos) 146 [allUsers addObject: infos]; 147 else 148 { 149 // We haven't found the user based on the GCS table name 150 // Let's try to strip the domain part and search again. 151 // This can happen when using SOGoEnableDomainBasedUID (YES) 152 // but login in SOGo using a UID without domain (DomainLessLogin gets set) 153 NSRange r; 154 155 r = [user rangeOfString: @"@"]; 156 157 if (r.location != NSNotFound) 158 { 159 user = [user substringToIndex: r.location]; 160 infos = [lm contactInfosForUserWithUIDorEmail: user]; 161 if (infos) 162 [allUsers addObject: infos]; 163 else 164 NSLog (@"user '%@' unknown", user); 165 } 166 else 167 NSLog (@"user '%@' unknown", user); 168 } 169 } 170 [allUsers autorelease]; 171 172 ASSIGN (usersToCleanup, allUsers); 173 DESTROY(pool); 174 175 return ([usersToCleanup count] > 0); 176} 177 178- (BOOL) parseArguments 179{ 180 BOOL rc; 181 NSRange usersRange; 182 int max; 183 184 max = [arguments count]; 185 if (max > 1) 186 { 187 days = [[arguments objectAtIndex: 0] intValue]; 188 usersRange.location = 1; 189 usersRange.length = max - 1; 190 rc = [self fetchUserIDs: [arguments subarrayWithRange: usersRange]]; 191 } 192 else 193 { 194 [self usage]; 195 rc = NO; 196 } 197 198 return rc; 199} 200 201- (BOOL) cleanupFolder: (NSString *) folder 202 withFM: (GCSFolderManager *) fm 203{ 204 GCSFolder *gcsFolder; 205 NSException *error; 206 BOOL rc; 207 unsigned int count; 208 209 gcsFolder = [fm folderAtPath: folder]; 210 211 count = [gcsFolder recordsCountDeletedBefore: days]; 212 error = nil; 213 if (count > 0) 214 error = [gcsFolder purgeDeletedRecordsBefore: days]; 215 if (error) 216 { 217 NSLog(@"Unable to purge records of folder %@", folder); 218 rc = NO; 219 } 220 else 221 { 222 NSLog(@"Purged %u records from folder %@", count, folder); 223 rc = YES; 224 } 225 226 return rc; 227} 228 229- (BOOL) cleanupUserFolders: (NSString *) uid 230{ 231 GCSFolderManager *fm; 232 NSArray *folders; 233 int count, max; 234 NSString *basePath, *folder; 235 236 fm = [GCSFolderManager defaultFolderManager]; 237 basePath = [NSString stringWithFormat: @"/Users/%@", uid]; 238 folders = [fm listSubFoldersAtPath: basePath recursive: YES]; 239 max = [folders count]; 240 for (count = 0; count < max; count++) 241 { 242 folder = [NSString stringWithFormat: @"%@/%@", basePath, [folders objectAtIndex: count]]; 243 //NSLog (@"folder %d: %@", count, folder); 244 [self cleanupFolder: folder withFM: fm]; 245 } 246 247 return YES; 248} 249 250- (BOOL) cleanupUser: (NSDictionary *) theUser 251{ 252 NSString *gcsUID, *domain; 253 SOGoSystemDefaults *sd; 254 255 sd = [SOGoSystemDefaults sharedSystemDefaults]; 256 257 domain = [theUser objectForKey: @"c_domain"]; 258 gcsUID = [theUser objectForKey: @"c_uid"]; 259 260 if ([sd enableDomainBasedUID] && [gcsUID rangeOfString: @"@"].location == NSNotFound) 261 gcsUID = [NSString stringWithFormat: @"%@@%@", gcsUID, domain]; 262 263 return [self cleanupUserFolders: gcsUID]; 264} 265 266- (BOOL) proceed 267{ 268 NSAutoreleasePool *pool; 269 int count, max; 270 BOOL rc; 271 272 rc = YES; 273 274 pool = [NSAutoreleasePool new]; 275 276 max = [usersToCleanup count]; 277 for (count = 0; rc && count < max; count++) 278 { 279 rc = [self cleanupUser: [usersToCleanup objectAtIndex: count]]; 280 if ((count % 10) == 0) 281 [pool emptyPool]; 282 } 283 284 [pool release]; 285 286 return rc; 287} 288 289- (BOOL) run 290{ 291 return ([self parseArguments] && [self proceed]); 292} 293 294@end 295