adium: replace NSS crypto backend with OpenSSL
[siplcs.git] / src / adium / ESPurpleSIPEAccount.m
bloba2b9f01fe416f6811fc95d19dc46d2493afd4030
1 //
2 //  ESSIPEAccount.m
3 //  SIPEAdiumPlugin
4 //
5 //  Created by Matt Meissner on 10/30/09.
6 //  Modified by Michael Lamb on 2/27/13
7 //  Copyright 2013 Michael Lamb/Harris Kauffman. All rights reserved.
8 //
10 #import <AISharedAdium.h>
11 #import <AIAdium.h>
12 #import <Adium/AIStatus.h>
13 #import <Adium/AIStatusControllerProtocol.h>
14 #import <AIUtilities/AIHostReachabilityMonitor.h>
16 #import "ESPurpleSIPEAccount.h"
17 #import "ESSIPEService.h"
19 #include "sipe-core.h"
20 #include "sipe-backend.h"
21 #include "purple-private.h"
23 // C Declarations
24 extern void AILog(NSString *fmt, ...);
26 @class AICoreComponentLoader;
28 @implementation ESPurpleSIPEAccount
30 - (const char*)protocolPlugin
32         return "prpl-sipe";
35 - (NSString *)hostForPurple
37     NSString *server = [self preferenceForKey:KEY_SIPE_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
38     if (!server || [server length] == 0)
39     {
40         // set the KEY_CONNECT_HOST to "autodetect" just to make sure we don't lose it from the defaults
41         [self setPreference:@"autodetect" forKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
42         return @"autodetect";
43     } else {
44         return server;
45     }
48 -(void) didConnect
50     PurpleConnection *gc = purple_account_get_connection(account);
51     struct sipe_core_public *sipe_public = PURPLE_GC_TO_SIPE_CORE_PUBLIC;
53     // get the resolved hostname from sipe_private
54     NSString *host = [NSString stringWithUTF8String:sipe_core_transport_sip_server_name(sipe_public)];
55     AILog(@"(didConnect) server: %@",host);
57     // if we autodetected, the KEY_CONNECT_HOST will be "autodetect", we need to replace it with the real hostname
58     if([[self preferenceForKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS] compare:@"autodetect" options:NSCaseInsensitiveSearch] == NSOrderedSame )
59     {
60         [self setPreference:host forKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
61     }
64     // Adium Host reachability depends on having KEY_CONNECT_HOST filled in earlier, so we have to hack ourselves into the reachabilityMonitor
65     // TODO: Check with Adium team to see if there is a more elegant way to do this.
66     AIHostReachabilityMonitor   *monitor = [AIHostReachabilityMonitor defaultMonitor];
67     AICoreComponentLoader       *theComponentLoader = adium.componentLoader;
68     [monitor addObserver:[theComponentLoader pluginWithClassName:@"ESAccountNetworkConnectivityPlugin"] forHost:host];
70     [super didConnect];
73 #pragma mark Account Configuration
74 - (void)configurePurpleAccount
76         [super configurePurpleAccount];
78     // Account preferences
79     AILog(@"Configuring account: %s\n", self.purpleAccountName);
81     // !!! ------  HACK/Kludge alert!  ------
82     /*
83      * Adium's CBPurpleAccount class's implementation of configurePurpleAccount (called above)
84      * has the following line:
85      *
86      *         if (hostName && [hostName length]) {
87      *
88      * Which doesn't allow us to leave the KEY_CONNECT_HOST preference empty (Adium prompts for the user to fill it out)
89      * sipe-core is expecting the server account setting to be empty to engage the auto-detection piece.  The only way
90      * to fix this is to fake out Adium by storing the servername in a different key (KEY_SIPE_CONNECT_HOST) and setting a
91      * default KEY_CONNECT_HOST to something.  We then need to detect that we have an empty servername here, and
92      * "overwrite" the default placeholder with an empty string.
93      */
95     // if there is no host specified, we're looking to auto-detect
96     // TODO: change this to a checkbox, and enable/disable based on that.  Leaving a field blank is bad UI design.
97     NSString *server = [self preferenceForKey:KEY_SIPE_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
99     // if the server is empty, clear it in purple account (because the defaults hack for the superclass set it to "autodetect").  Otherwise, use the one provided
100     if([server isEqualToString:@""])
101     {
102         // force preference back to "autodetect" so that we can replace it when we get a hostname resolved
103         [self setPreference:@"autodetect" forKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
104         purple_account_set_string(account,"server", "");
105     } else {
106         // NOP.  Superclass already set this via the [self hostForPurple] response.
107     }
109     NSString *winLogin  = [self preferenceForKey:KEY_SIPE_WINDOWS_LOGIN group:GROUP_ACCOUNT_STATUS];
110     NSString *completeUserName = [NSString stringWithUTF8String:[self purpleAccountName]];
112     if (winLogin && [winLogin length]) {
113         // Configure the complete username ("user@domain.com,DOMAIN\user")
114         completeUserName = [NSString stringWithFormat:@"%@,%@",completeUserName, winLogin];
115         purple_account_set_username(account, [completeUserName UTF8String]);
116     } else {
117         purple_account_set_username(account, self.purpleAccountName);
118     }
120     NSString *thePassword = [self preferenceForKey:KEY_SIPE_PASSWORD group:GROUP_ACCOUNT_STATUS];
121     if (thePassword && [thePassword length])
122         purple_account_set_password(account, [thePassword UTF8String]);
124         BOOL sso = [[self preferenceForKey:KEY_SIPE_SINGLE_SIGN_ON group:GROUP_ACCOUNT_STATUS] boolValue];
125         purple_account_set_bool(account, "sso", sso);
127     if (sso) {
128             // Adium doesn't honor our "optional" password on account creation and will prompt if the password field is left blank, so we must force it to think there is one, but only if there isn't already a password saved
129             if (!thePassword)
130                 [self setPasswordTemporarily:@"placeholder"];
131     }
133         BOOL dontPublish = [[self preferenceForKey:KEY_SIPE_DONT_PUBLISH group:GROUP_ACCOUNT_STATUS] boolValue];
134         purple_account_set_bool(account, "dont-publish", dontPublish);
136     // Connection preferences
137     id connType = [self preferenceForKey:KEY_SIPE_CONNECTION_TYPE group:GROUP_ACCOUNT_STATUS];
138     if([connType isKindOfClass:[NSNumber class]])
139         {
140         // For backwards compatibility, we pick from an array if the preference is a NSNumber
141         NSMutableArray *myArray = [[NSMutableArray alloc] initWithObjects:@"auto", @"tls", @"tcp", nil];
142         connType = (NSString *)[myArray objectAtIndex:(NSUInteger)[self preferenceForKey:KEY_SIPE_CONNECTION_TYPE group:GROUP_ACCOUNT_STATUS]];
143         }
144     purple_account_set_string(account, "transport", [connType UTF8String]);
146     NSString *authScheme = [self preferenceForKey:KEY_SIPE_AUTH_SCHEME group:GROUP_ACCOUNT_STATUS];
147     purple_account_set_string(account, "authentication", [authScheme UTF8String]);
149         NSString *userAgent = [self preferenceForKey:KEY_SIPE_USER_AGENT group:GROUP_ACCOUNT_STATUS];
150     purple_account_set_string(account, "useragent", (userAgent.length != 0) ?  [userAgent UTF8String] : "" );
152     // Email preferences
153     NSString *emailURL = [self preferenceForKey:KEY_SIPE_EMAIL_URL group:GROUP_ACCOUNT_STATUS];
154     purple_account_set_string(account, "email_usr", (!emailURL.length) ?  [emailURL UTF8String] : "" );
156     // TODO: Use account name (user@domain) as default for this
157     NSString *email = [self preferenceForKey:KEY_SIPE_EMAIL group:GROUP_ACCOUNT_STATUS];
158     purple_account_set_string(account, "email", (!email.length) ?  [email UTF8String] : "" );
160     // TODO: Use Windows Login (DOMAIN\user) as default for this
161     NSString *emailLogin = [self preferenceForKey:KEY_SIPE_EMAIL_LOGIN group:GROUP_ACCOUNT_STATUS];
162     purple_account_set_string(account, "email_login", (!emailLogin.length) ?  [emailLogin UTF8String] : "" );
164     // TODO: Use default password as default for this
165     NSString *emailPassword = [self preferenceForKey:KEY_SIPE_EMAIL_PASSWORD group:GROUP_ACCOUNT_STATUS];
166     purple_account_set_string(account, "email_password", (!emailPassword.length) ?  [emailPassword UTF8String] : "" );
168     // Group chat preferences
169     NSString *groupchatUser = [self preferenceForKey:KEY_SIPE_GROUP_CHAT_PROXY group:GROUP_ACCOUNT_STATUS];
170     purple_account_set_string(account, "groupchat_user", (!groupchatUser.length) ? [groupchatUser UTF8String] : "" );
174 #pragma mark File transfer
176 - (BOOL)canSendFolders
178         return NO;
181 - (void)beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
183         [super _beginSendOfFileTransfer:fileTransfer];
186 - (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
188     [super acceptFileTransferRequest:fileTransfer];
191 - (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
193     [super rejectFileReceiveRequest:fileTransfer];
196 - (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
198         [super cancelFileTransfer:fileTransfer];
201 #pragma mark Status Messages
203  * @brief Status name to use for a Purple buddy
204  */
205 - (NSString *)statusNameForPurpleBuddy:(PurpleBuddy *)buddy
207     NSString *statusName = [super statusNameForPurpleBuddy:buddy];
208     PurplePresence  *presence = purple_buddy_get_presence(buddy);
209     PurpleStatus    *status = purple_presence_get_active_status(presence);
210     const char      *purpleStatusID = purple_status_get_id(status);
212     if (!purpleStatusID) return nil;
214     switch (sipe_purple_token_to_activity(purpleStatusID))
215     {
216         case SIPE_ACTIVITY_AVAILABLE:
217         case SIPE_ACTIVITY_ONLINE:
218             statusName = STATUS_NAME_AVAILABLE;
219             break;
220         case SIPE_ACTIVITY_AWAY:
221         case SIPE_ACTIVITY_INACTIVE:
222             statusName = STATUS_NAME_AWAY;
223             break;
224         case SIPE_ACTIVITY_BRB:
225             statusName = STATUS_NAME_BRB;
226             break;
227         case SIPE_ACTIVITY_BUSY:
228         case SIPE_ACTIVITY_BUSYIDLE:
229             statusName = STATUS_NAME_BUSY;
230             break;
231         case SIPE_ACTIVITY_DND:
232             statusName = STATUS_NAME_DND;
233             break;
234         case SIPE_ACTIVITY_LUNCH:
235             statusName = STATUS_NAME_LUNCH;
236             break;
237         case SIPE_ACTIVITY_INVISIBLE:
238             statusName = STATUS_NAME_INVISIBLE;
239             break;
240         case SIPE_ACTIVITY_OFFLINE:
241             statusName = STATUS_NAME_OFFLINE;
242             break;
243         case SIPE_ACTIVITY_ON_PHONE:
244             statusName = STATUS_NAME_PHONE;
245             break;
246         case SIPE_ACTIVITY_IN_CONF:
247         case SIPE_ACTIVITY_IN_MEETING:
248             statusName = STATUS_NAME_NOT_AT_DESK;
249             break;
250         case SIPE_ACTIVITY_OOF:
251             statusName = STATUS_NAME_NOT_IN_OFFICE;
252             break;
253         case SIPE_ACTIVITY_URGENT_ONLY:
254             statusName = STATUS_NAME_AWAY_FRIENDS_ONLY;
255             break;
256         default:
257             statusName = STATUS_NAME_OFFLINE;
258     }
260     return statusName;
265  * @brief Maps purple status IDs to Adium statuses
266  */
267  - (const char *)purpleStatusIDForStatus:(AIStatus *)statusState arguments:(NSMutableDictionary *)arguments
269      const gchar    *statusID;
270      NSString           *statusName = statusState.statusName;
271      NSString           *statusMessageString = [statusState statusMessageString];
273      if (!statusMessageString) statusMessageString = @"";
275      // TODO: figure out why sipe_status_activity_to_token calls return junk, instead of a gchar*
276      switch (statusState.statusType) {
277          case AIAvailableStatusType:
278              statusID = sipe_activity_map[SIPE_ACTIVITY_AVAILABLE].status_id;
279              //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_AVAILABLE);
280              break;
282          case AIAwayStatusType:
283              if (([statusName isEqualToString:STATUS_NAME_AWAY]) ||
284                  ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AWAY]] == NSOrderedSame))
285              {
286                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_AWAY);
287                  statusID = sipe_activity_map[SIPE_ACTIVITY_AWAY].status_id;
288              } else if (([statusName isEqualToString:STATUS_NAME_BRB]) ||
289                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BRB]] == NSOrderedSame))
290              {
291                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_BRB);
292                  statusID = sipe_activity_map[SIPE_ACTIVITY_BRB].status_id;
293              } else if (([statusName isEqualToString:STATUS_NAME_BUSY]) ||
294                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BUSY]] == NSOrderedSame))
295              {
296                  // TODO: Figure out how to determine if they should be "busy" or "busyidle"
297                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_BUSY);
298                  statusID = sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id;
299              } else if (([statusName isEqualToString:STATUS_NAME_DND]) ||
300                       ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_DND]] == NSOrderedSame))
301              {
302                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_DND);
303                  statusID = sipe_activity_map[SIPE_ACTIVITY_DND].status_id;
304              } else if (([statusName isEqualToString:STATUS_NAME_LUNCH]) ||
305                       ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_LUNCH]] == NSOrderedSame))
306              {
307                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_LUNCH);
308                  statusID = sipe_activity_map[SIPE_ACTIVITY_LUNCH].status_id;
309              } else if (([statusName isEqualToString:STATUS_NAME_PHONE]) ||
310                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_PHONE]] == NSOrderedSame))
311              {
312                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_ON_PHONE);
313                  statusID = sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].status_id;
314              } else if (([statusName isEqualToString:STATUS_NAME_NOT_AT_DESK]) ||
315                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AT_DESK]] == NSOrderedSame))
316              {
317                  // TODO: Figure out how to determine if they should be "In a meeting" or "In a conference"
318                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_IN_MEETING);
319                  statusID = sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].status_id;
320              } else if (([statusName isEqualToString:STATUS_NAME_NOT_IN_OFFICE]) ||
321                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_IN_OFFICE]] == NSOrderedSame))
322              {
323                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_OOF);
324                  statusID = sipe_activity_map[SIPE_ACTIVITY_OOF].status_id;
325              } else if (([statusName isEqualToString:STATUS_NAME_AWAY_FRIENDS_ONLY]) ||
326                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AWAY_FRIENDS_ONLY]] == NSOrderedSame))
327              {
328                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_URGENT_ONLY);
329                  statusID = sipe_activity_map[SIPE_ACTIVITY_URGENT_ONLY].status_id;
330              }
331              break;
332          case AIInvisibleStatusType:
333              //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_INVISIBLE);
334              statusID = sipe_activity_map[SIPE_ACTIVITY_INVISIBLE].status_id;
335              break;
337          case AIOfflineStatusType:
338              //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_OFFLINE);
339              statusID = sipe_activity_map[SIPE_ACTIVITY_OFFLINE].status_id;
340              break;
341      }
344      //If we didn't get a purple status type, request one from super
345      if (statusID == NULL) statusID = [super purpleStatusIDForStatus:statusState arguments:arguments];
347      return statusID;
351 @end