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 ? @"&nbsp;" : @" ");
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