1/* UIxMailAccountActions.m - this file is part of SOGo 2 * 3 * Copyright (C) 2007-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#if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) 22#include <openssl/bio.h> 23#include <openssl/err.h> 24#include <openssl/pem.h> 25#include <openssl/x509.h> 26#endif 27 28#import <Foundation/NSData.h> 29#import <Foundation/NSDictionary.h> 30 31#import <NGHttp/NGHttpRequest.h> 32 33#import <NGObjWeb/NSException+HTTP.h> 34#import <NGObjWeb/WOContext+SoObjects.h> 35#define COMPILING_NGOBJWEB 1 /* httpRequest is needed in importCertificateAction */ 36#import <NGObjWeb/WORequest.h> 37#undef COMPILING_NGOBJWEB 38#import <NGObjWeb/WOResponse.h> 39 40#import <NGMime/NGMimeBodyPart.h> 41#import <NGMime/NGMimeHeaderFields.h> 42#import <NGMime/NGMimeMultipartBody.h> 43#import <NGMime/NGMimeType.h> 44 45#import <NGImap4/NSString+Imap4.h> 46#import <NGExtensions/NSString+misc.h> 47 48#import <Mailer/NSData+SMIME.h> 49#import <Mailer/SOGoMailAccount.h> 50#import <Mailer/SOGoDraftObject.h> 51#import <Mailer/SOGoDraftsFolder.h> 52#import <SOGo/NSArray+Utilities.h> 53#import <SOGo/NSDictionary+Utilities.h> 54#import <SOGo/NSObject+Utilities.h> 55#import <SOGo/NSString+Utilities.h> 56#import <SOGo/SOGoDomainDefaults.h> 57#import <SOGo/SOGoUser.h> 58 59#import "UIxMailAccountActions.h" 60 61@implementation UIxMailAccountActions 62 63- (WOResponse *) listMailboxesAction 64{ 65 SOGoMailAccount *co; 66 NSArray *folders; 67 NSDictionary *data; 68 69 co = [self clientObject]; 70 71 folders = [co allFoldersMetadata: SOGoMailStandardListing]; 72 73 data = [NSDictionary dictionaryWithObjectsAndKeys: 74 folders, @"mailboxes", 75 [co getInboxQuota], @"quotas", 76 nil]; 77 78 return [self responseWithStatus: 200 79 andJSONRepresentation: data]; 80} 81 82- (WOResponse *) listAllMailboxesAction 83{ 84 SOGoMailAccount *co; 85 NSArray *folders; 86 NSDictionary *data; 87 88 co = [self clientObject]; 89 90 folders = [co allFoldersMetadata: SOGoMailSubscriptionsManagementListing]; 91 92 data = [NSDictionary dictionaryWithObjectsAndKeys: 93 folders, @"mailboxes", 94 nil]; 95 96 return [self responseWithStatus: 200 97 andJSONRepresentation: data]; 98} 99 100/* compose */ 101 102- (NSString *) _emailFromIdentity: (NSDictionary *) identity 103{ 104 NSString *fullName, *format; 105 106 fullName = [identity objectForKey: @"fullName"]; 107 if ([fullName length]) 108 format = @"%{fullName} <%{email}>"; 109 else 110 format = @"%{email}"; 111 112 return [identity keysWithFormat: format]; 113} 114 115- (WOResponse *) composeAction 116{ 117 BOOL save, isHTML; 118 NSDictionary *data, *identity; 119 NSMutableDictionary *headers; 120 NSString *accountName, *mailboxName, *messageName; 121 NSString *value, *signature, *nl, *space; 122 SOGoDraftObject *newDraftMessage; 123 SOGoDraftsFolder *drafts; 124 SOGoMailAccount *co; 125 SOGoUserDefaults *ud; 126 id mailTo; 127 128 co = [self clientObject]; 129 drafts = [co draftsFolderInContext: context]; 130 newDraftMessage = [drafts newDraft]; 131 headers = [NSMutableDictionary dictionary]; 132 133 save = NO; 134 135 value = [[self request] formValueForKey: @"mailto"]; 136 if ([value length] > 0) 137 { 138 mailTo = [[value stringByUnescapingURL] objectFromJSONString]; 139 if (mailTo && [mailTo isKindOfClass: [NSArray class]]) 140 { 141 [headers setObject: (NSArray *) mailTo forKey: @"to"]; 142 save = YES; 143 } 144 } 145 146 value = [[self request] formValueForKey: @"subject"]; 147 if ([value length] > 0) 148 { 149 [headers setObject: [value stringByUnescapingURL] forKey: @"subject"]; 150 save = YES; 151 } 152 153 identity = [co defaultIdentity]; 154 if (identity) 155 { 156 [headers setObject: [self _emailFromIdentity: identity] forKey: @"from"]; 157 signature = [identity objectForKey: @"signature"]; 158 if ([signature length]) 159 { 160 ud = [[context activeUser] userDefaults]; 161 [newDraftMessage setIsHTML: [[ud mailComposeMessageType] isEqualToString: @"html"]]; 162 isHTML = [newDraftMessage isHTML]; 163 nl = (isHTML? @"<br />" : @"\n"); 164 space = (isHTML ? @" " : @" "); 165 [newDraftMessage setText: [NSString stringWithFormat: @"%@%@--%@%@%@", nl, nl, space, nl, signature]]; 166 } 167 save = YES; 168 } 169 170 if (save) 171 { 172 [newDraftMessage setHeaders: headers]; 173 [newDraftMessage storeInfo]; 174 } 175 176 accountName = [co nameInContainer]; 177 mailboxName = [drafts absoluteImap4Name]; // Ex: /INBOX/Drafts/ 178 mailboxName = [mailboxName substringWithRange: NSMakeRange(1, [mailboxName length] -2)]; 179 messageName = [newDraftMessage nameInContainer]; 180 data = [NSDictionary dictionaryWithObjectsAndKeys: 181 accountName, @"accountId", 182 mailboxName, @"mailboxPath", 183 messageName, @"draftId", nil]; 184 185 return [self responseWithStatus: 201 186 andString: [data jsonRepresentation]]; 187} 188 189- (WOResponse *) _performDelegationAction: (SEL) action 190{ 191 SOGoMailAccount *co; 192 WOResponse *response; 193 NSString *uid; 194 195 co = [self clientObject]; 196 if ([[co nameInContainer] isEqualToString: @"0"]) 197 { 198 uid = [[context request] formValueForKey: @"uid"]; 199 if ([uid length] > 0) 200 { 201 [co performSelector: action 202 withObject: [NSArray arrayWithObject: uid]]; 203 response = [self responseWith204]; 204 } 205 else 206 response = [self responseWithStatus: 500 207 andString: @"Missing 'uid' parameter."]; 208 } 209 else 210 response = [self responseWithStatus: 403 211 andString: @"This action cannot be performed on secondary accounts."]; 212 213 return response; 214} 215 216- (WOResponse *) addDelegateAction 217{ 218 return [self _performDelegationAction: @selector (addDelegates:)]; 219} 220 221- (WOResponse *) removeDelegateAction 222{ 223 return [self _performDelegationAction: @selector (removeDelegates:)]; 224} 225 226- (WOResponse *) certificateAction 227{ 228 NSData *pem; 229 NSDictionary *data; 230 WOResponse *response; 231 232 pem = [[self clientObject] certificate]; 233 234 if (pem) 235 { 236 data = [pem certificateDescription]; 237 if (data) 238 { 239 response = [self responseWithStatus: 200 andJSONRepresentation: data]; 240 } 241 else 242 { 243 data = [NSDictionary 244 dictionaryWithObject: [self labelForKey: @"Error reading the certificate. Please install a new certificate."] 245 forKey: @"message"]; 246 response = [self responseWithStatus: 500 andJSONRepresentation: data]; 247 } 248 } 249 else 250 { 251 data = [NSDictionary 252 dictionaryWithObject: [self labelForKey: @"No certificate associated to account."] 253 forKey: @"message"]; 254 response = [self responseWithStatus: 404 andJSONRepresentation: data]; 255 } 256 257 return response; 258} 259 260- (WOResponse *) importCertificateAction 261{ 262 NSArray *parts; 263 NGMimeBodyPart *part; 264 NGMimeContentDispositionHeaderField *header; 265 NSData *pkcs12; 266 NSString *mimeType, *name, *password; 267 WOResponse *response; 268 id data; 269 270 unsigned int count, max; 271 272 password = nil; 273 pkcs12 = nil; 274 response = [self responseWithStatus: 507]; 275 276 data = [[[context request] httpRequest] body]; 277 278 if (![data isKindOfClass: [NSException class]]) 279 { 280 parts = [data parts]; 281 max = [parts count]; 282 for (count = 0; count < max; count++) 283 { 284 part = [parts objectAtIndex: count]; 285 header = (NGMimeContentDispositionHeaderField *)[part headerForKey: @"content-disposition"]; 286 name = [header name]; 287 if ([name isEqualToString: @"password"]) 288 { 289 password = [part body]; 290 } 291 else if ([name isEqualToString: @"file"]) 292 { 293 mimeType = [(NGMimeType *)[part headerForKey: @"content-type"] stringValue]; 294 if ([mimeType hasSuffix: @"pkcs12"]) 295 { 296 pkcs12 = [part body]; 297 } 298 else 299 { 300 response = [self responseWithStatus: 507]; 301 } 302 } 303 } 304 } 305 306 if (password && pkcs12) 307 { 308 NSData *certificate; 309 NSDictionary *description; 310 311 certificate = [pkcs12 convertPKCS12ToPEMUsingPassword: password]; 312 313 if (!certificate) 314 return [self responseWithStatus: 507]; 315 316 [[self clientObject] setCertificate: certificate]; 317 318 description = [certificate certificateDescription]; 319 if (description) 320 { 321 response = [self responseWithStatus: 200 andJSONRepresentation: description]; 322 } 323 else 324 { 325 description = [NSDictionary 326 dictionaryWithObject: [self labelForKey: @"Error reading the certificate. Please install a new certificate."] 327 forKey: @"message"]; 328 response = [self responseWithStatus: 500 andJSONRepresentation: description]; 329 } 330 } 331 332 return response; 333} 334 335- (WOResponse *) removeCertificateAction 336{ 337 [[self clientObject] setCertificate: nil]; 338 339 return [self responseWith204]; 340} 341 342@end 343