Compare commits
267
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8aa338e64 | ||
|
|
2651b5aa9d | ||
|
|
ee4ec09897 | ||
|
|
7e21c6c6a7 | ||
|
|
1432600fae | ||
|
|
97add602df | ||
|
|
35ebb7aaeb | ||
|
|
200c353645 | ||
|
|
d39208efa0 | ||
|
|
348a40837a | ||
|
|
0094d65bb9 | ||
|
|
0e95de31cf | ||
|
|
04673e4673 | ||
|
|
6ec002f023 | ||
|
|
40d8e72c74 | ||
|
|
8692e647bd | ||
|
|
e9dff9cdbf | ||
|
|
cddfac82dd | ||
|
|
cb08abfa67 | ||
|
|
1146038c76 | ||
|
|
3ef94da9d6 | ||
|
|
27840093f5 | ||
|
|
9534816a21 | ||
|
|
b951c72451 | ||
|
|
0e6e802243 | ||
|
|
4292a45e62 | ||
|
|
0ff0d43e1e | ||
|
|
056004bca1 | ||
|
|
dbf5179e69 | ||
|
|
433da56b23 | ||
|
|
cf91b2dcbb | ||
|
|
ca5df1a153 | ||
|
|
ad9947e627 | ||
|
|
b94175cc0c | ||
|
|
4d42e3f176 | ||
|
|
9f55cdc725 | ||
|
|
fc863bf63f | ||
|
|
2e0fc3ca09 | ||
|
|
8f7df73a29 | ||
|
|
8b1b938899 | ||
|
|
42587f7422 | ||
|
|
d3a4136516 | ||
|
|
6af55414fb | ||
|
|
105ca88aef | ||
|
|
008b8e51cc | ||
|
|
cd37e989db | ||
|
|
c1acce0725 | ||
|
|
a0320400d2 | ||
|
|
05dc51ec30 | ||
|
|
5b6096e32c | ||
|
|
e37ae5fec5 | ||
|
|
789113bb44 | ||
|
|
513c17bc2f | ||
|
|
30c0d8a425 | ||
|
|
789b55c274 | ||
|
|
798ad1502c | ||
|
|
07c06db69d | ||
|
|
9f861bd629 | ||
|
|
c3a4f4aeb4 | ||
|
|
8c612fc0a2 | ||
|
|
562f81f21f | ||
|
|
3040c275d8 | ||
|
|
96986280ee | ||
|
|
b53d1254d9 | ||
|
|
589cfaa2f4 | ||
|
|
7ef507bdb4 | ||
|
|
fb117f48c8 | ||
|
|
a102a94ac5 | ||
|
|
f31a07fd74 | ||
|
|
964e6f0cb1 | ||
|
|
d7979871bc | ||
|
|
e9cc088153 | ||
|
|
a0e5699e60 | ||
|
|
272fa8f898 | ||
|
|
312817b7ae | ||
|
|
e8f0471bcf | ||
|
|
f78300a12e | ||
|
|
33d3154d15 | ||
|
|
7b5c787718 | ||
|
|
5da7903ce1 | ||
|
|
1fa4b8871d | ||
|
|
e7da4c19b8 | ||
|
|
cbf3d89f3e | ||
|
|
7929fd394f | ||
|
|
8f7b2bfbed | ||
|
|
8a4e79963f | ||
|
|
d29c00827c | ||
|
|
f1b52a7486 | ||
|
|
c7d3546be8 | ||
|
|
4742f2fd31 | ||
|
|
9feefba545 | ||
|
|
c12b1395e7 | ||
|
|
605c026425 | ||
|
|
807cefaa39 | ||
|
|
2c050d847e | ||
|
|
158c5e45c4 | ||
|
|
174b44ed50 | ||
|
|
260aa1971b | ||
|
|
d99bbbb37e | ||
|
|
09c76b3649 | ||
|
|
04a6217512 | ||
|
|
1801d0d60f | ||
|
|
fd063fd5b3 | ||
|
|
a4c51a90bb | ||
|
|
2fdb924aff | ||
|
|
824b38332c | ||
|
|
64461764c8 | ||
|
|
0683dafc22 | ||
|
|
058df21ada | ||
|
|
84f3fd5e1c | ||
|
|
7b4e4f7345 | ||
|
|
543abb39d5 | ||
|
|
f75af12db8 | ||
|
|
91d193fa0f | ||
|
|
0e5e9ddb74 | ||
|
|
a41fb9e1a9 | ||
|
|
c61fe4a188 | ||
|
|
0b43f11d61 | ||
|
|
52bb3baa8b | ||
|
|
979e1342c7 | ||
|
|
4019cb7556 | ||
|
|
6af0058657 | ||
|
|
a78f06e909 | ||
|
|
62823815a7 | ||
|
|
78e50b7a2a | ||
|
|
841d6896fc | ||
|
|
18ffd1a744 | ||
|
|
45eeb5fa24 | ||
|
|
b8f31069ed | ||
|
|
e8e41f15e3 | ||
|
|
e3559d5ca3 | ||
|
|
de5da7bd0d | ||
|
|
bc09aeb830 | ||
|
|
d1dbceb407 | ||
|
|
2601ad4563 | ||
|
|
da51482ce1 | ||
|
|
62e25f6c13 | ||
|
|
78292495bb | ||
|
|
72efaa7432 | ||
|
|
daaad938cb | ||
|
|
5e1f487e49 | ||
|
|
989021d561 | ||
|
|
656410eb6b | ||
|
|
4e2d5098c7 | ||
|
|
3433a2e765 | ||
|
|
eabb40a0bf | ||
|
|
860d635c9c | ||
|
|
501edfc319 | ||
|
|
2b5e7a68d7 | ||
|
|
95b3e9d4fa | ||
|
|
dda13e602b | ||
|
|
c49137e620 | ||
|
|
24fc9a950b | ||
|
|
ad3a9c87f8 | ||
|
|
3e6cd3c53c | ||
|
|
bc963d53c6 | ||
|
|
57bbb255cc | ||
|
|
127a3c487d | ||
|
|
d92cd9c3f3 | ||
|
|
c69382cb5d | ||
|
|
88a675596c | ||
|
|
b71df14b14 | ||
|
|
73dc86a6ed | ||
|
|
9221811fdc | ||
|
|
560c1dcd82 | ||
|
|
0e5514e425 | ||
|
|
94af9bb780 | ||
|
|
43e5662950 | ||
|
|
96f2c64320 | ||
|
|
001d76fd05 | ||
|
|
62f64314c0 | ||
|
|
3f94516e31 | ||
|
|
d8fab6bb21 | ||
|
|
047a98b870 | ||
|
|
f0980a9cbd | ||
|
|
4216f9e726 | ||
|
|
ba1757fd05 | ||
|
|
91ca8b8bec | ||
|
|
5b089bbe14 | ||
|
|
9437ba8b9a | ||
|
|
94b171675f | ||
|
|
aaaa16ed40 | ||
|
|
d6d33f9f0b | ||
|
|
4ed2c727a2 | ||
|
|
ee1ffef5b5 | ||
|
|
6b17d5298c | ||
|
|
e36d5dfb7c | ||
|
|
56e1583ccc | ||
|
|
c1ccbae316 | ||
|
|
91cefbdff1 | ||
|
|
e65e0f191f | ||
|
|
bc172c5895 | ||
|
|
ae42fd869a | ||
|
|
cd8f957774 | ||
|
|
7180b5988d | ||
|
|
9aeecead6c | ||
|
|
15d7c69d94 | ||
|
|
a25e5acba4 | ||
|
|
0ceccdd612 | ||
|
|
86643b4339 | ||
|
|
edc01e9532 | ||
|
|
9d1d12af54 | ||
|
|
a303011237 | ||
|
|
5ccc12639b | ||
|
|
2048fb19cf | ||
|
|
46ade7640a | ||
|
|
2e46e89d58 | ||
|
|
eb13aa14b2 | ||
|
|
47075b40a2 | ||
|
|
e71afc982e | ||
|
|
948c89f5bb | ||
|
|
7aaadd724d | ||
|
|
5923639154 | ||
|
|
fbdabc9615 | ||
|
|
3bb40e4024 | ||
|
|
278d4ab4cb | ||
|
|
f872dc52c6 | ||
|
|
2e98929900 | ||
|
|
856b0654ab | ||
|
|
456a66b66b | ||
|
|
f140bba03a | ||
|
|
123ccef770 | ||
|
|
4628292fb3 | ||
|
|
06814c2dee | ||
|
|
fa2fbd3177 | ||
|
|
729802222f | ||
|
|
e458a78a10 | ||
|
|
14b60cd756 | ||
|
|
9c299cf0de | ||
|
|
5b3d84ee24 | ||
|
|
350677bbce | ||
|
|
c6f532dea8 | ||
|
|
2bc24eca82 | ||
|
|
ac91a303c9 | ||
|
|
70984def1f | ||
|
|
699849caec | ||
|
|
113f076981 | ||
|
|
3a8336484c | ||
|
|
4475ac651d | ||
|
|
c2e7f6a866 | ||
|
|
32968092e2 | ||
|
|
1fd4d54d59 | ||
|
|
d9f059a9e3 | ||
|
|
83d52a16f4 | ||
|
|
55aaeaf9a6 | ||
|
|
69cbe5d619 | ||
|
|
82735fa8a7 | ||
|
|
33dce130ce | ||
|
|
372bd676aa | ||
|
|
fc34befe47 | ||
|
|
eb4ad5e086 | ||
|
|
a2a6bfab59 | ||
|
|
22bd650b72 | ||
|
|
d4f0ccd3f7 | ||
|
|
8b45f2c731 | ||
|
|
060dea3818 | ||
|
|
487bbf74c0 | ||
|
|
d1e2418d65 | ||
|
|
13ad6ff875 | ||
|
|
f6ca946058 | ||
|
|
42bb3b288b | ||
|
|
61945734c7 | ||
|
|
e866fd5fda | ||
|
|
7ff9921709 | ||
|
|
34fcfe024b | ||
|
|
2d5d1b4297 | ||
|
|
7d38a48f68 |
No files matched your search
@@ -0,0 +1,114 @@
|
||||
# Contributing to SOGo
|
||||
|
||||
## Reporting Bugs and Suggesting Enhancements
|
||||
|
||||
If you encounter a possible bug with SOGo, you can access our
|
||||
[bug tracking system](https://sogo.nu/bugs/).
|
||||
|
||||
Please make sure to respect the following guidelines when reporting a bug:
|
||||
|
||||
* verify that the bug you found is not already known or even fixed in the `master` version
|
||||
* make the actual facts very clear; be precise, we need to be able to reproduce the problem
|
||||
* explain your speculations, if any
|
||||
* add a screenshot to the ticket if appropriate
|
||||
|
||||
## Submitting a Pull Request
|
||||
|
||||
Begin by reading [SOGo Developer's Guide](../Documentation/SOGoDevelopersGuide.asciidoc).
|
||||
|
||||
### Git Commit Guidelines
|
||||
|
||||
We have very precise rules over how our git commit messages can be formatted. This leads to **more
|
||||
readable messages** that are easy to follow when looking through the **project history**.
|
||||
|
||||
It is important to note that we use the git commit messages to **generate** the
|
||||
[CHANGELOG](../CHANGELOG.md) document. Improperly formatted commit messages may result in your
|
||||
change not appearing in the CHANGELOG of the next release.
|
||||
|
||||
### Commit Message Format
|
||||
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
|
||||
format that includes a **type**, a **scope** and a **subject**:
|
||||
|
||||
```html
|
||||
<type>(<scope>): <subject>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
> Any line of the commit message cannot be longer than 100 characters.
|
||||
> This allows the message to be easier to read on GitHub as well as in various Git tools.
|
||||
|
||||
#### Type
|
||||
Must be one of the following:
|
||||
|
||||
* **feat**: A new feature
|
||||
* **fix**: A bug fix
|
||||
* **docs**: Documentation only changes
|
||||
* **i18n**: Change in localizable strings
|
||||
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
|
||||
semi-colons, etc)
|
||||
* **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||
* **perf**: A code change that improves performance
|
||||
* **test**: Adding missing tests
|
||||
* **chore**: Changes to the build and packaging process or auxiliary tools (sogo-tool,
|
||||
sogo-ealarms-notify) and libraries such as documentation generation
|
||||
|
||||
#### Scope
|
||||
The scope could be anything that helps specifying the scope (or feature) that is changing.
|
||||
|
||||
Examples
|
||||
|
||||
* mail
|
||||
* mail(js)
|
||||
* calendar(css)
|
||||
* addressbook
|
||||
* preferences(js)
|
||||
* core
|
||||
* eas
|
||||
|
||||
#### Subject
|
||||
The subject contains a succinct description of the change:
|
||||
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize first letter
|
||||
* no dot (.) at the end
|
||||
|
||||
#### Body
|
||||
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
The body should include the motivation for the change and contrast this with previous behavior.
|
||||
|
||||
#### Footer
|
||||
The footer should contain any information about **Breaking Changes** and is also the
|
||||
place to reference [Mantis](https://sogo.nu/bus/) issues that this commit **Fixes** or **Resolves**.
|
||||
|
||||
> Breaking Changes are intended to be highlighted in the CHANGELOG as changes that will require
|
||||
> community users to modify their code after updating to a version that contains this commit.
|
||||
|
||||
#### Sample Commit messages
|
||||
```text
|
||||
fix(calendar): don't raise exception when renaming with same name
|
||||
|
||||
this would break Apple Calendar.app when creating a new calendar
|
||||
|
||||
Fixes #4813
|
||||
```
|
||||
```text
|
||||
feat(calendar(js)): optionally expand LDAP groups in attendees editor
|
||||
|
||||
* add `/members` action for LDIF groups
|
||||
* add button to expand invited LDAP groups
|
||||
|
||||
Fixes #2506
|
||||
```
|
||||
```text
|
||||
fix(core): set default Sieve port to 4190
|
||||
|
||||
BREAKING CHANGE: the default port for the SOGoSieveServer configuration default is now 4190 (was
|
||||
2000).
|
||||
|
||||
You need to explicitly set the port if you use a different port.
|
||||
|
||||
Closes #4826
|
||||
```
|
||||
@@ -99,7 +99,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if ((o = [n flattenedValueAtIndex: 1 forKey: @""]))
|
||||
[s appendFormat: @"<FirstName xmlns=\"Contacts:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
|
||||
if ((o = [n flattenedValueAtIndex: 2 forKey: @""]))
|
||||
[s appendFormat: @"<MiddleName xmlns=\"Contacts:\">%@</MiddleName>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [n flattenedValueAtIndex: 3 forKey: @""]))
|
||||
[s appendFormat: @"<Title xmlns=\"Contacts:\">%@</Title>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [n flattenedValueAtIndex: 4 forKey: @""]))
|
||||
[s appendFormat: @"<Suffix xmlns=\"Contacts:\">%@</Suffix>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [self workCompany]))
|
||||
[s appendFormat: @"<CompanyName xmlns=\"Contacts:\">%@</CompanyName>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
@@ -302,8 +311,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
CardElement *element;
|
||||
NSMutableArray *addressLines, *other_addresses;
|
||||
id o;
|
||||
|
||||
id o, l, m, f, p, s;
|
||||
|
||||
l = m = f = p = s = nil;
|
||||
|
||||
// Contact's note
|
||||
if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"]))
|
||||
[self setNote: o];
|
||||
@@ -529,10 +540,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
if ((o = [theValues objectForKey: @"FileAs"]) || ![self _isGhosted: @"FileAs" inContext: context])
|
||||
[self setFn: [theValues objectForKey: @"FileAs"]];
|
||||
|
||||
[self setNWithFamily: [theValues objectForKey: @"LastName"]
|
||||
given: [theValues objectForKey: @"FirstName"]
|
||||
additional: nil prefixes: nil suffixes: nil];
|
||||
|
||||
if ((o = [theValues objectForKey: @"LastName"]) || ![self _isGhosted: @"LastName" inContext: context])
|
||||
l = o ? o : @"";
|
||||
|
||||
if ((o = [theValues objectForKey: @"FirstName"]) || ![self _isGhosted: @"FirstName" inContext: context])
|
||||
f = o ? o : @"";
|
||||
|
||||
if ((o = [theValues objectForKey: @"MiddleName"]) || ![self _isGhosted: @"MiddleName" inContext: context])
|
||||
m = o ? o : @"";
|
||||
|
||||
if ((o = [theValues objectForKey: @"Title"]) || ![self _isGhosted: @"Title" inContext: context])
|
||||
p = o ? o : @"";
|
||||
|
||||
if ((o = [theValues objectForKey: @"Suffix"]) || ![self _isGhosted: @"Suffix" inContext: context])
|
||||
s = o ? o : @"";
|
||||
|
||||
[self setNWithFamily: l given: f additional: m prefixes: p suffixes: s];
|
||||
|
||||
// IM information
|
||||
if ((o = [theValues objectForKey: @"IMAddress"]) || ![self _isGhosted: @"IMAddress" inContext: context])
|
||||
[[self uniqueChildWithTag: @"x-aim"]
|
||||
|
||||
@@ -2012,9 +2012,8 @@ FIXME
|
||||
{
|
||||
bodyPreferenceType = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"];
|
||||
|
||||
// By default, send MIME mails. See #3146 for details.
|
||||
if (!bodyPreferenceType)
|
||||
bodyPreferenceType = @"4";
|
||||
bodyPreferenceType = @"1";
|
||||
|
||||
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
|
||||
mimeTruncation = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"];
|
||||
@@ -2593,7 +2592,6 @@ FIXME
|
||||
//[output appendFormat: @"<Status>%d</Status>", 14];
|
||||
}
|
||||
|
||||
[output appendString: @"<Collections>"];
|
||||
s = nil;
|
||||
|
||||
// We enter our loop detection change
|
||||
@@ -2696,6 +2694,8 @@ FIXME
|
||||
//
|
||||
if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"])
|
||||
{
|
||||
[output appendString: @"<Collections>"];
|
||||
|
||||
// We always return the last generated response.
|
||||
// If we only return <Sync><Collections/></Sync>,
|
||||
// iOS powered devices will simply crash.
|
||||
|
||||
@@ -44,6 +44,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalEvent.h>
|
||||
#import <NGCards/iCalAlarm.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
|
||||
#import <NGExtensions/NGBase64Coding.h>
|
||||
@@ -90,6 +91,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <Appointments/SOGoAppointmentFolder.h>
|
||||
#import <Appointments/SOGoAppointmentFolders.h>
|
||||
#import <Appointments/SOGoAppointmentObject.h>
|
||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
|
||||
#import <Contacts/SOGoContactGCSFolder.h>
|
||||
#import <Contacts/SOGoContactFolders.h>
|
||||
@@ -707,11 +709,13 @@ void handle_eas_terminate(int signum)
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
|
||||
// update the cache anyway regardless of any error; if the rename fails next folderSync will to the cleanup
|
||||
[folderToUpdate renameTo: [NSString stringWithFormat: @"%@", [displayName stringByEncodingImap4FolderName]]];
|
||||
|
||||
if (folderType == ActiveSyncEventFolder)
|
||||
nameInCache = [NSString stringWithFormat: @"vevent/%@", serverId];
|
||||
{
|
||||
nameInCache = [NSString stringWithFormat: @"vevent/%@", serverId];
|
||||
// We do the rename only for EventFolder. TaskFolder and EventFolder have the same target on server.
|
||||
// In case of an error we expect the foldersync to do the cleanup.
|
||||
[folderToUpdate renameTo: [NSString stringWithFormat: @"%@", [displayName stringByEncodingImap4FolderName]]];
|
||||
}
|
||||
else
|
||||
nameInCache = [NSString stringWithFormat: @"vtodo/%@",serverId];
|
||||
|
||||
@@ -1626,11 +1630,11 @@ void handle_eas_terminate(int signum)
|
||||
// ServerId might have been set if LongId was defined in the initial request. If not, it is
|
||||
// a normal ItemOperations (Fetch) to get a complete email
|
||||
if (!serverId)
|
||||
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
|
||||
serverId = [[(id)[aFetch getElementsByTagName: @"ServerId"] lastObject] textValue];
|
||||
|
||||
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
|
||||
bodyPreferenceType = [[(id)[[(id)[aFetch getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
|
||||
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
|
||||
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
|
||||
mimeSupport = [[(id)[aFetch getElementsByTagName: @"MIMESupport"] lastObject] textValue];
|
||||
[context setObject: mimeSupport forKey: @"MIMESupport"];
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/gg675490%28v=exchg.80%29.aspx
|
||||
@@ -1644,10 +1648,10 @@ void handle_eas_terminate(int signum)
|
||||
[s appendString: @"<Fetch>"];
|
||||
[s appendString: @"<Status>1</Status>"];
|
||||
|
||||
if ([[[(id)[theDocumentElement getElementsByTagName: @"LongId"] lastObject] textValue] length])
|
||||
if ([[[(id)[aFetch getElementsByTagName: @"LongId"] lastObject] textValue] length])
|
||||
{
|
||||
[s appendString: @"<Class xmlns=\"AirSync:\">Email</Class>"];
|
||||
[s appendFormat: @"<LongId xmlns=\"Search:\">%@</LongId>", [[(id)[theDocumentElement getElementsByTagName: @"LongId"] lastObject] textValue]];
|
||||
[s appendFormat: @"<LongId xmlns=\"Search:\">%@</LongId>", [[(id)[aFetch getElementsByTagName: @"LongId"] lastObject] textValue]];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1671,8 +1675,75 @@ void handle_eas_terminate(int signum)
|
||||
}
|
||||
else
|
||||
{
|
||||
[theResponse setStatus: 500];
|
||||
return;
|
||||
NSMutableDictionary *uidCache, *folderMetadata;
|
||||
NSString *easId;
|
||||
id sogoObject, currentCollection, componentObject;
|
||||
|
||||
// ServerId might have been set if LongId was defined in the initial request. If not, it is
|
||||
// a normal ItemOperations (Fetch).
|
||||
if (!serverId)
|
||||
serverId = [[(id)[aFetch getElementsByTagName: @"ServerId"] lastObject] textValue];
|
||||
|
||||
currentCollection = [self collectionFromId: realCollectionId type: folderType];
|
||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: realCollectionId withType: folderType]];
|
||||
|
||||
uidCache = [folderMetadata objectForKey: @"UidCache"];
|
||||
if (uidCache)
|
||||
{
|
||||
easId = [[uidCache allKeysForObject: serverId] objectAtIndex: 0];
|
||||
|
||||
if (easId)
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Found easId: %@ for serverId: %@", easId, serverId];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Use original serverId: %@", serverId];
|
||||
|
||||
easId = serverId;
|
||||
}
|
||||
}
|
||||
else
|
||||
easId = serverId;
|
||||
|
||||
|
||||
sogoObject = [currentCollection lookupName: [easId sanitizedServerIdWithType: folderType]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
|
||||
if (folderType == ActiveSyncContactFolder)
|
||||
componentObject = [sogoObject vCard];
|
||||
else
|
||||
componentObject = [sogoObject component: NO secure: YES];
|
||||
|
||||
[s appendString: @"<Fetch>"];
|
||||
[s appendString: @"<Status>1</Status>"];
|
||||
|
||||
if ([[[(id)[aFetch getElementsByTagName: @"LongId"] lastObject] textValue] length])
|
||||
{
|
||||
if (folderType == ActiveSyncContactFolder)
|
||||
[s appendString: @"<Class xmlns=\"AirSync:\">Contacts</Class>"];
|
||||
else if (folderType == ActiveSyncEventFolder)
|
||||
[s appendString: @"<Class xmlns=\"AirSync:\">Calendar</Class>"];
|
||||
else if (folderType == ActiveSyncTaskFolder)
|
||||
[s appendString: @"<Class xmlns=\"AirSync:\">Task</Class>"];
|
||||
|
||||
[s appendFormat: @"<LongId xmlns=\"Search:\">%@</LongId>", [[(id)[aFetch getElementsByTagName: @"LongId"] lastObject] textValue]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[s appendFormat: @"<CollectionId xmlns=\"AirSyncBase:\">%@</CollectionId>", collectionId];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSyncBase:\">%@</ServerId>", serverId];
|
||||
}
|
||||
|
||||
[s appendString: @"<Properties>"];
|
||||
|
||||
[s appendString: [componentObject activeSyncRepresentationInContext: context]];
|
||||
|
||||
[s appendString: @"</Properties>"];
|
||||
[s appendString: @"</Fetch>"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1793,6 +1864,7 @@ void handle_eas_terminate(int signum)
|
||||
{
|
||||
NSString *realCollectionId, *requestId, *easRequestId, *participationStatus, *calendarId;
|
||||
SOGoAppointmentObject *appointmentObject;
|
||||
iCalAlarm *alarm = nil;
|
||||
SOGoMailObject *mailObject;
|
||||
NSMutableDictionary *uidCache, *folderMetadata;
|
||||
NSMutableString *s, *nameInCache;
|
||||
@@ -1890,6 +1962,9 @@ void handle_eas_terminate(int signum)
|
||||
event = [[calendar events] lastObject];
|
||||
calendarId = [event uid];
|
||||
|
||||
// We take the organizers's alarm to start with
|
||||
alarm = [event firstSupportedAlarm];
|
||||
|
||||
// Fetch the SOGoAppointmentObject
|
||||
collection = [[context activeUser] personalCalendarFolderInContext: context];
|
||||
nameInCache = [NSString stringWithFormat: @"vevent/%@", [collection nameInContainer]];
|
||||
@@ -1910,7 +1985,7 @@ void handle_eas_terminate(int signum)
|
||||
{
|
||||
appointmentObject = [[SOGoAppointmentObject alloc] initWithName: [NSString stringWithFormat: @"%@.ics", [event uid]]
|
||||
inContainer: collection];
|
||||
[appointmentObject saveComponent: event force: YES];
|
||||
[appointmentObject saveCalendar: [event parent]];
|
||||
}
|
||||
|
||||
if (uidCache && [calendarId length] > 64)
|
||||
@@ -1947,10 +2022,10 @@ void handle_eas_terminate(int signum)
|
||||
participationStatus = @"TENTATIVE";
|
||||
else
|
||||
participationStatus = @"DECLINED";
|
||||
|
||||
|
||||
[appointmentObject changeParticipationStatus: participationStatus
|
||||
withDelegate: nil
|
||||
alarm: nil];
|
||||
alarm: alarm];
|
||||
|
||||
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
@@ -2592,7 +2667,7 @@ void handle_eas_terminate(int signum)
|
||||
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
[s appendString: @"<Provision xmlns=\"Provision:\">"];
|
||||
[s appendString: @"<AllowHTMLEmail>1</AllowHTMLEmail>"];
|
||||
[s appendString: @"<Status>1</Status><Policies><Policy><PolicyType>MS-EAS-Provisioning-WBXML</PolicyType><Status>2</Status></Policy></Policies>"];
|
||||
[s appendString: @"</Provision>"];
|
||||
|
||||
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
|
||||
@@ -258,6 +258,38 @@ struct GlobalObjectId {
|
||||
return rc;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (NSString *) _personalNameFrom: (NSArray *) enveloppeAddresses
|
||||
{
|
||||
NGImap4EnvelopeAddress *address;
|
||||
NSString *email, *rc, *name;
|
||||
NSMutableArray *addresses;
|
||||
int i, max;
|
||||
|
||||
rc = nil;
|
||||
max = [enveloppeAddresses count];
|
||||
|
||||
if (max > 0)
|
||||
{
|
||||
addresses = [NSMutableArray array];
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
address = [enveloppeAddresses objectAtIndex: i];
|
||||
name = [address personalName];
|
||||
email = [NSString stringWithFormat: @"%@", (name ? name : [address baseEMail])];
|
||||
|
||||
if (email)
|
||||
[addresses addObject: email];
|
||||
}
|
||||
rc = [addresses componentsJoinedByString: @";"];
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -740,7 +772,8 @@ struct GlobalObjectId {
|
||||
}
|
||||
}
|
||||
|
||||
return [theContent substringToIndex: i];
|
||||
if (i >= 0)
|
||||
return [theContent substringToIndex: i];
|
||||
}
|
||||
|
||||
*wasTruncated = 0;
|
||||
@@ -859,7 +892,13 @@ struct GlobalObjectId {
|
||||
// If there are multiple e-mail addresses, they are separated by commas."
|
||||
value = [self _emailAddressesFrom: [[self envelope] to]];
|
||||
if (value)
|
||||
[s appendFormat: @"<To xmlns=\"Email:\">%@</To>", [value activeSyncRepresentationInContext: context]];
|
||||
{
|
||||
[s appendFormat: @"<To xmlns=\"Email:\">%@</To>", [value activeSyncRepresentationInContext: context]];
|
||||
// DisplayTo - If there are multiple display names, they are separated by semi-colons.
|
||||
value = [self _personalNameFrom: [[self envelope] to]];
|
||||
if (value)
|
||||
[s appendFormat: @"<DisplayTo xmlns=\"Email:\">%@</DisplayTo>", [value activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
|
||||
// From
|
||||
value = [self _emailAddressesFrom: [[self envelope] from]];
|
||||
@@ -881,9 +920,6 @@ struct GlobalObjectId {
|
||||
else
|
||||
[s appendFormat: @"<DateReceived xmlns=\"Email:\">%@</DateReceived>", [[NSDate date] activeSyncRepresentationInContext: context]];
|
||||
|
||||
// DisplayTo
|
||||
[s appendFormat: @"<DisplayTo xmlns=\"Email:\">%@</DisplayTo>", [[context activeUser] login]];
|
||||
|
||||
// Cc - same syntax as the To field
|
||||
value = [self _emailAddressesFrom: [[self envelope] cc]];
|
||||
if (value)
|
||||
|
||||
@@ -187,7 +187,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
attendee = [attendees objectAtIndex: i];
|
||||
[s appendFormat: @"<Attendee_Email xmlns=\"Calendar:\">%@</Attendee_Email>", [[attendee rfc822Email] activeSyncRepresentationInContext: context]];
|
||||
[s appendFormat: @"<Attendee_Name xmlns=\"Calendar:\">%@</Attendee_Name>", [[attendee cn] activeSyncRepresentationInContext: context]];
|
||||
if ([[attendee cn] length])
|
||||
[s appendFormat: @"<Attendee_Name xmlns=\"Calendar:\">%@</Attendee_Name>", [[attendee cn] activeSyncRepresentationInContext: context]];
|
||||
else
|
||||
[s appendFormat: @"<Attendee_Name xmlns=\"Calendar:\">%@</Attendee_Name>", [[attendee rfc822Email] activeSyncRepresentationInContext: context]];
|
||||
|
||||
attendee_status = [self _attendeeStatus: attendee];
|
||||
|
||||
@@ -905,8 +908,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
component: (id) component
|
||||
{
|
||||
NSString *status;
|
||||
iCalAlarm *alarm;
|
||||
iCalPerson *attendee;
|
||||
id o;
|
||||
|
||||
|
||||
attendee = [self userAsAttendee: [context activeUser]];
|
||||
status = [attendee partStat];
|
||||
alarm = nil;
|
||||
|
||||
// See: https://msdn.microsoft.com/en-us/library/ee202290(v=exchg.80).aspx
|
||||
//
|
||||
// 0 == Free
|
||||
@@ -925,11 +935,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
else
|
||||
status = @"TENTATIVE";
|
||||
|
||||
// There's no delegate in EAS
|
||||
[(SOGoAppointmentObject *) component changeParticipationStatus: status
|
||||
withDelegate: nil
|
||||
alarm: nil];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"Reminder"]) && [o length])
|
||||
{
|
||||
if ([self hasAlarms])
|
||||
alarm = [[self firstSupportedAlarm] mutableCopy];
|
||||
else
|
||||
alarm = [[iCalAlarm alloc] init];
|
||||
|
||||
[alarm takeActiveSyncValues: theValues inContext: context];
|
||||
|
||||
|
||||
}
|
||||
|
||||
// There's no delegate in EAS
|
||||
[(SOGoAppointmentObject *) component changeParticipationStatus: status
|
||||
withDelegate: nil
|
||||
alarm: alarm];
|
||||
RELEASE(alarm);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -42,6 +42,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
#import <NGCards/iCalTrigger.h>
|
||||
|
||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
ProxyPass /SOGo http://127.0.0.1:20000/SOGo interpolate
|
||||
ProxyPass / http://127.0.0.1:20000/SOGo/dav/ interpolate
|
||||
<Location />
|
||||
<IfVersion < 2.4>
|
||||
<IfVersion < 2.4>
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</IfVersion>
|
||||
|
||||
+1
-2
@@ -6,7 +6,7 @@ Alias /SOGo/WebServerResources/ \
|
||||
<Directory /usr/lib/GNUstep/SOGo/>
|
||||
AllowOverride None
|
||||
|
||||
<IfVersion < 2.4>
|
||||
<IfVersion < 2.4>
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</IfVersion>
|
||||
@@ -99,4 +99,3 @@ ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
|
||||
RewriteRule ^/.well-known/caldav/?$ /SOGo/dav [R=301]
|
||||
RewriteRule ^/.well-known/carddav/?$ /SOGo/dav [R=301]
|
||||
</IfModule>
|
||||
|
||||
+3085
File diff suppressed because it is too large.
Load diff
@@ -1,3 +1,219 @@
|
||||
commit 69cbe5d619561b69426a305d4b5d4f3e95e6370b
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Thu Oct 31 13:25:48 2019 -0400
|
||||
|
||||
Preparation for release 4.1.1
|
||||
|
||||
M Documentation/docinfo.xml
|
||||
M Documentation/includes/global-attributes.asciidoc
|
||||
M NEWS
|
||||
M Version
|
||||
|
||||
commit 82735fa8a7d5d0cf742fc8c5792024df3a53e9a5
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Thu Oct 31 13:09:54 2019 -0400
|
||||
|
||||
chore(js/css): update generated files
|
||||
|
||||
M UI/WebServerResources/js/Common.js
|
||||
M UI/WebServerResources/js/Common.js.map
|
||||
M UI/WebServerResources/js/Scheduler.services.js
|
||||
M UI/WebServerResources/js/Scheduler.services.js.map
|
||||
M UI/WebServerResources/js/vendor/lodash.js
|
||||
M UI/WebServerResources/js/vendor/lodash.min.js
|
||||
|
||||
commit 33dce130ceb76ed851bb197e67aad7f28f43447c
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Thu Oct 31 13:08:28 2019 -0400
|
||||
|
||||
chore(js): fix path of lodash library
|
||||
|
||||
M UI/WebServerResources/Gruntfile.js
|
||||
|
||||
commit 372bd676aaf6f274aa11bd58217016d80c1080f1
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Thu Oct 31 11:59:55 2019 -0400
|
||||
|
||||
chore: update commit message for generated files
|
||||
|
||||
M UI/WebServerResources/GNUmakefile
|
||||
|
||||
commit fc34befe47bd1661e30a6fcf7fea6775c410f551
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Thu Oct 31 11:59:38 2019 -0400
|
||||
|
||||
docs: update CONTRIBUTING file
|
||||
|
||||
M .github/CONTRIBUTING.md
|
||||
|
||||
commit eb4ad5e086f3261f0ddb39175aad3d258d5a88af
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Thu Oct 31 11:52:43 2019 -0400
|
||||
|
||||
i18n(de): update German translation
|
||||
|
||||
M UI/MailerUI/German.lproj/Localizable.strings
|
||||
|
||||
commit a2a6bfab59f9be75ca671a49463fe8040a493d58
|
||||
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
Date: Thu Oct 31 09:04:10 2019 -0400
|
||||
|
||||
fix(core): avoid warning by including missing file
|
||||
|
||||
M ActiveSync/iCalToDo+ActiveSync.m
|
||||
|
||||
commit 22bd650b72249ff314484577e86cef4b0c8c73d2
|
||||
Author: InverseBot <sogo@inverse.ca>
|
||||
Date: Thu Oct 31 02:07:08 2019 -0400
|
||||
|
||||
(js/css) Update generated files
|
||||
|
||||
M UI/WebServerResources/js/Common.js
|
||||
M UI/WebServerResources/js/Common.js.map
|
||||
M UI/WebServerResources/js/Scheduler.services.js
|
||||
M UI/WebServerResources/js/Scheduler.services.js.map
|
||||
|
||||
commit d4f0ccd3f726b78673b02ad47c34eef0dc35f207
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Wed Oct 30 13:54:06 2019 -0400
|
||||
|
||||
docs: update CONTRIBUTING file
|
||||
|
||||
M .github/CONTRIBUTING.md
|
||||
|
||||
commit 8b45f2c731d1a015c1d9c9639b3e76488c696770
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Wed Oct 30 13:46:07 2019 -0400
|
||||
|
||||
fix(calendar(js)): swap start-end dates when delta is negative
|
||||
|
||||
When creating an event by drag'n'dropping from bottom to top, the
|
||||
original coordinate become the start date.
|
||||
|
||||
M UI/WebServerResources/js/Scheduler/CalendarListController.js
|
||||
M UI/WebServerResources/js/Scheduler/Component.service.js
|
||||
|
||||
commit 060dea38182a52c19c4dd149b5b07f834870b550
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Wed Oct 30 13:42:45 2019 -0400
|
||||
|
||||
style(mail(js)): add missing semi-colon
|
||||
|
||||
M UI/WebServerResources/js/Mailer/MessageEditorController.js
|
||||
|
||||
commit 487bbf74c042ed29e3da40a68cfaf769f8bcc381
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Wed Oct 30 11:42:53 2019 -0400
|
||||
|
||||
fix(calendar(js)): don't modify time when computing dates interval
|
||||
|
||||
Fixes #4861
|
||||
|
||||
M UI/WebServerResources/js/Common/utils.js
|
||||
|
||||
commit d1e2418d65ee33496363cceac27d372c656aba04
|
||||
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
Date: Wed Oct 30 13:44:13 2019 -0400
|
||||
|
||||
(fix) fixed EAS provisioning support for Outlook/iOS (fixes #4853)
|
||||
|
||||
M ActiveSync/SOGoActiveSyncDispatcher+Sync.m
|
||||
M ActiveSync/SOGoActiveSyncDispatcher.m
|
||||
M NEWS
|
||||
|
||||
commit 13ad6ff8755e59670071212cfddd178bb8846ee9
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Wed Oct 30 11:13:40 2019 -0400
|
||||
|
||||
Initial CONTRIBUTING file
|
||||
|
||||
A .github/CONTRIBUTING.md
|
||||
|
||||
commit f6ca946058f16ae32bbde966ff1c850e4979db0f
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Wed Oct 30 11:04:59 2019 -0400
|
||||
|
||||
fix(calendar): don't allow RDATE unless already defined
|
||||
|
||||
Since RDATE are not properly supported in EAS, hide the possibility to
|
||||
specify recurring dates unless the component being edited already
|
||||
contains RDATE(s).
|
||||
|
||||
M UI/Scheduler/UIxCalMainView.m
|
||||
M UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox
|
||||
M UI/Templates/SchedulerUI/UIxCalMainView.wox
|
||||
M UI/Templates/SchedulerUI/UIxTaskEditorTemplate.wox
|
||||
M UI/WebServerResources/js/Scheduler/ComponentController.js
|
||||
|
||||
commit 42bb3b288b65842dc869be94f1f8b461dd326233
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Tue Oct 29 11:02:47 2019 -0400
|
||||
|
||||
(i18n) Update Polish translation
|
||||
|
||||
Fixes #4851
|
||||
|
||||
M UI/PreferencesUI/Polish.lproj/Localizable.strings
|
||||
|
||||
commit 61945734c73117f595e19f960defa7238ebb41f9
|
||||
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
Date: Mon Oct 28 15:14:35 2019 -0400
|
||||
|
||||
(doc) quote default values (fixes #4827)
|
||||
|
||||
M Scripts/sogo.conf
|
||||
|
||||
commit e866fd5fdaa677879ef611ff72e57140ea664f43
|
||||
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
Date: Mon Oct 28 14:39:39 2019 -0400
|
||||
|
||||
(fix) fixed Apple Calendar creation (fixes #4813)
|
||||
|
||||
M NEWS
|
||||
M SoObjects/Appointments/SOGoAppointmentFolderObject.m
|
||||
M SoObjects/Appointments/SOGoAppointmentFolders.m
|
||||
M SoObjects/SOGo/SOGoGCSFolder.m
|
||||
M UI/Contacts/UIxContactFolderProperties.m
|
||||
M UI/Scheduler/UIxCalendarProperties.m
|
||||
|
||||
commit 7ff992170900e6e51b5655bc9f5df93711d7f20d
|
||||
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
Date: Fri Oct 25 13:34:41 2019 -0400
|
||||
|
||||
(fix) removed unused method
|
||||
|
||||
M SoObjects/Mailer/SOGoMailAccount.h
|
||||
M SoObjects/Mailer/SOGoMailAccount.m
|
||||
|
||||
commit 34fcfe024b8b8e02d2f7c3e26105d4075a3a07e8
|
||||
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
Date: Fri Oct 25 13:31:13 2019 -0400
|
||||
|
||||
(fix) use the supplied Sieve creds to fetch the IMAP4 separator (fixes #4846)
|
||||
|
||||
M NEWS
|
||||
M SoObjects/SOGo/SOGoSieveManager.h
|
||||
M SoObjects/SOGo/SOGoSieveManager.m
|
||||
|
||||
commit 2d5d1b4297480648cf0404f218ceaf5e5ae487bc
|
||||
Author: InverseBot <sogo@inverse.ca>
|
||||
Date: Fri Oct 25 01:27:37 2019 -0400
|
||||
|
||||
(js/css) Update generated files
|
||||
|
||||
M UI/WebServerResources/js/Common.js
|
||||
M UI/WebServerResources/js/Common.js.map
|
||||
M UI/WebServerResources/js/Scheduler.services.js
|
||||
M UI/WebServerResources/js/Scheduler.services.js.map
|
||||
|
||||
commit df15fed8fddb66765765e343e3da6e46ac03de72
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Thu Oct 24 10:43:20 2019 -0400
|
||||
|
||||
Update ChangeLog
|
||||
|
||||
M ChangeLog
|
||||
|
||||
commit 1c179f805da50d087ca07bdadb66c9563a41d45e
|
||||
Author: Francis Lachapelle <flachapelle@inverse.ca>
|
||||
Date: Thu Oct 24 10:42:30 2019 -0400
|
||||
|
||||
@@ -1028,6 +1028,11 @@ _bindDN_ and _bindPassword_ will still be required to find the proper DN
|
||||
|bindFields (optional)
|
||||
|An array of fields to use when doing indirect binds.
|
||||
|
||||
|lookupFields (optional)
|
||||
|Lookup fields for LDAP queries. Default is `(*)`. This can be utilized
|
||||
to lookup operational fields (which are per default not part of the result)
|
||||
such as `memberOf`: `lookupFields = ("*", "memberOf");`
|
||||
|
||||
|hostname
|
||||
|A space-delimited list of LDAP URLs or LDAP hostnames.
|
||||
|
||||
@@ -1069,7 +1074,7 @@ Possible values are: `none`, `plain`, `crypt`, `md5`, `md5-crypt`,
|
||||
(plus setting of the encoding with `.b64` or `.hex`).
|
||||
|
||||
For a more detailed description see
|
||||
http://wiki.dovecot.org/Authentication/PasswordSchemes.
|
||||
https://doc.dovecot.org/configuration_manual/authentication/password_schemes/.
|
||||
|
||||
Note that `cram-md5` is not actually using cram-md5 (due to the lack of
|
||||
challenge-response mechanism), its just saving the intermediate MD5
|
||||
@@ -1106,7 +1111,7 @@ not work for entries in this source and thus, freebusy lookups.
|
||||
repository
|
||||
|
||||
|listRequiresDot (optional)
|
||||
|If set to `YES`, listing of this LDAP source is only possible when performing a search (respecting the SOGoSearchMinimumWordLength parameter) or when explicitely typing a single dot.
|
||||
|If set to `YES`, listing of this LDAP source is only possible when performing a search (respecting the SOGoSearchMinimumWordLength parameter) or when explicitly typing a single dot.
|
||||
Defaults to `YES` when unset.
|
||||
|
||||
|ModulesConstraints (optional)
|
||||
@@ -1659,12 +1664,14 @@ they have the same name as popular LDAP attributes (such as `givenName`,
|
||||
|The default algorithm used for password encryption when changing
|
||||
passwords. Possible values are: `none`, `plain`, `crypt`, `md5`,
|
||||
`md5-crypt`, `smd5`, `cram-md5`, `ldap-md5`, and `sha`, `sha256`,
|
||||
`sha512` and its ssha (e.g. `ssha` or `ssha256`) variants. Passwords can
|
||||
have the scheme prepended in the form `{scheme}encryptedPass`.
|
||||
`sha256-crypt`, `sha512`, `sha512-crypt`, its ssha (e.g. `ssha` or
|
||||
`ssha256`) variants, `blf-crypt`, `PBKDF2`, and `sym-aes-128-cbc`.
|
||||
Passwords can have the scheme prepended in the form
|
||||
`{scheme}encryptedPass`.
|
||||
|
||||
If no scheme is given, _userPasswordAlgorithm_ is used instead. The
|
||||
schemes listed above follow the algorithms described in
|
||||
http://wiki.dovecot.org/Authentication/PasswordSchemes.
|
||||
https://doc.dovecot.org/configuration_manual/authentication/password_schemes/.
|
||||
|
||||
Note that `cram-md5` is not actually using cram-md5 (due to the lack of
|
||||
challenge-response mechanism), its just saving the intermediate MD5
|
||||
@@ -1673,7 +1680,13 @@ context as Dovecot stores in its database.
|
||||
|prependPasswordScheme
|
||||
|The default behaviour is to store newly set passwords without the
|
||||
scheme (default: `NO`). This can be overridden by setting to `YES` and
|
||||
will result in passwords stored as `{scheme}encryptedPass`.
|
||||
will result in passwords stored as `{scheme}encryptedPass`. For
|
||||
`sym-aes-128-cbc`, always set this to `NO`.
|
||||
|
||||
|keyPath
|
||||
|For `sym-aes-128-cbc`, a global key file is required. This value
|
||||
must be set to the full path where the key file is. The key file
|
||||
must also be readable by the `sogo` user.
|
||||
|
||||
|canAuthenticate
|
||||
|If set to `YES`, this SQL source is used for authentication.
|
||||
@@ -1737,7 +1750,7 @@ See the _Multi-domains Configuration_ section in this document for more
|
||||
information.
|
||||
|
||||
|listRequiresDot (optional)
|
||||
|If set to `YES`, listing of this SQL source is only possible when performing a search (respecting the SOGoSearchMinimumWordLength parameter) or when explicitely typing a single dot.
|
||||
|If set to `YES`, listing of this SQL source is only possible when performing a search (respecting the SOGoSearchMinimumWordLength parameter) or when explicitly typing a single dot.
|
||||
Defaults to `YES` when unset.
|
||||
|
||||
|ModulesConstraints (optional)
|
||||
@@ -1806,10 +1819,16 @@ are:
|
||||
|D |SOGoSMTPServer
|
||||
|The DNS name or IP address of the SMTP server used when
|
||||
_SOGoMailingMechanism_ is set to `smtp`.
|
||||
Supported formats are: `smtp://domain:port`, `smtps://domain`,
|
||||
`domain:port`, `smtp://domain:port/?tls=YES`. Using the option
|
||||
`tls=YES` will enforce using STARTTLS smtp connections. Thus,
|
||||
`smtp://localhost:587/?tls=YES` would use the default MUA port
|
||||
on localhost with STARTTLS enforced.
|
||||
|
||||
|D |SOGoSMTPAuthenticationType
|
||||
|Activate SMTP authentication and specifies which type is in use.
|
||||
Current, only `PLAIN` is supported and other values will be ignored.
|
||||
Current, only `PLAIN` is supported and other values will cause
|
||||
the authentication to fail.
|
||||
|
||||
|S |WOSendMail
|
||||
|The path of the sendmail binary.
|
||||
@@ -1844,16 +1863,18 @@ messages.
|
||||
|
||||
Defaults to `Drafts` when unset.
|
||||
|
||||
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
|
||||
For example: `INBOX/Drafts`.
|
||||
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
|
||||
For example: `INBOX/Drafts`. You must use a `/` even if your real
|
||||
IMAP separator is a `.`.
|
||||
|
||||
|U |SOGoSentFolderName
|
||||
|Parameter used to set the IMAP folder name used to store sent messages.
|
||||
|
||||
Defaults to `Sent` when unset.
|
||||
|
||||
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
|
||||
For example: `INBOX/Sent`.
|
||||
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
|
||||
For example: `INBOX/Sent`. You must use a `/` even if your real
|
||||
IMAP separator is a `.`.
|
||||
|
||||
|U |SOGoTrashFolderName
|
||||
|Parameter used to set the IMAP folder name used to store deleted
|
||||
@@ -1861,8 +1882,9 @@ messages.
|
||||
|
||||
Defaults to `Trash` when unset.
|
||||
|
||||
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
|
||||
For example: `INBOX/Trash`.
|
||||
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
|
||||
For example: `INBOX/Trash`. You must use a `/` even if your real
|
||||
IMAP separator is a `.`.
|
||||
|
||||
|U |SOGoJunkFolderName
|
||||
|Parameter used to set the IMAP folder name used to store junk
|
||||
@@ -1870,8 +1892,9 @@ messages.
|
||||
|
||||
Defaults to `Junk` when unset.
|
||||
|
||||
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
|
||||
For example: `INBOX/Junk`. Also see the SOGoMailJunkSettings for
|
||||
Use a `/` as a hierarchy separator if referring to an IMAP subfolder.
|
||||
For example: `INBOX/Junk`. You must use a `/` even if your real
|
||||
IMAP separator is a `.`. Also see the SOGoMailJunkSettings for
|
||||
more options regarding junk/not-junk actions.
|
||||
|
||||
|D |SOGoIMAPCASServiceName
|
||||
@@ -2009,7 +2032,7 @@ values are:
|
||||
* `Mail`
|
||||
* `Contacts`
|
||||
|
||||
Defaults to `Calendar` when unset.
|
||||
Defaults to `Mail` when unset.
|
||||
|
||||
|S |SOGoFaviconRelativeURL
|
||||
|Parameter used to specify the relative URL of the site favion.
|
||||
@@ -2071,13 +2094,17 @@ Requires Sieve script support on the IMAP host.
|
||||
|
||||
Defaults to `NO` when unset.
|
||||
|
||||
When enabling this parameter, one may have to also enable the associated
|
||||
|D |SOGoVacationPeriodEnabled
|
||||
|When enabling this parameter, one may have to also enable the associated
|
||||
cronjob in `/etc/cron.d/sogo` in order to activate automatic vacation
|
||||
message activation and expiration.
|
||||
message activation and expiration if your Sieve server does not support
|
||||
the date extension.
|
||||
|
||||
See the _Cronjob — Vacation messages activation and expiration_ section
|
||||
below for details.
|
||||
|
||||
Defaults to `YES` when unset.
|
||||
|
||||
|D |SOGoVacationDefaultSubject
|
||||
|Parameter used to define a default vacation subject if user don't specify a
|
||||
custom subject.
|
||||
@@ -2134,6 +2161,16 @@ host.
|
||||
|
||||
Defaults to `NO` when unset.
|
||||
|
||||
|D |SOGoSieveScriptHeaderTemplateFile
|
||||
|Parameter used to set the full path of the Sieve script that will be
|
||||
automatically prepended to any Sieve scripts a user might define. The file
|
||||
must be encoded in UTF-8 and it must also respect the RFC5228 syntax.
|
||||
|
||||
|D |SOGoSieveScriptFooterTemplateFile
|
||||
|Parameter used to set the full path of the Sieve script that will be
|
||||
automatically appended to any Sieve scripts a user might define. The file
|
||||
must be encoded in UTF-8 and it must also respect the RFC5228 syntax.
|
||||
|
||||
|U |SOGoSieveFilters
|
||||
|Parameter used to define initial Sieve scripts for users. The user
|
||||
can still modify the scripts and the initial values will be written
|
||||
@@ -2352,7 +2389,7 @@ Defaults to `below`.
|
||||
* `text`
|
||||
* `html`
|
||||
|
||||
Defaults to `text`.
|
||||
Defaults to `html`.
|
||||
|
||||
|S |SOGoEnableEMailAlarms
|
||||
|Parameter used to enable email-based alarms on events and tasks.
|
||||
@@ -2443,7 +2480,7 @@ like this:
|
||||
SOGoTrashFolderName = Trash;
|
||||
SOGoJunkFolderName = Junk;
|
||||
SOGoMailingMechanism = smtp;
|
||||
SOGoSMTPServer = 127.0.0.1;
|
||||
SOGoSMTPServer = "smtp://127.0.0.1";
|
||||
SOGoUserSources = (
|
||||
{
|
||||
type = ldap;
|
||||
@@ -2999,10 +3036,10 @@ To access your personal calendar:
|
||||
`http://127.0.0.1/SOGo/dav/jdoe/Calendar/personal/`
|
||||
* Click on Continue.
|
||||
|
||||
Apple Calendar and iOS
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
Apple Calendar (macOS, iOS, iPadOS)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Apple Calendar and Mac OS X and the calendar application on iOS can also be used
|
||||
Apple Calendar on macOS, iOS, and iPadOS can also be used
|
||||
as a client application for SOGo.
|
||||
|
||||
To configure the application so it works with SOGo, create a new account and specify,
|
||||
@@ -3055,8 +3092,8 @@ The virtual host should be defined like:
|
||||
This configuration is also required if you want to configure a CardDAV
|
||||
account on an Apple iOS device (version 4.0 and later).
|
||||
|
||||
Microsoft ActiveSync / Mobile Devices
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Microsoft ActiveSync
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can synchronize contacts, emails, events and tasks from SOGo with
|
||||
any mobile devices that support Microsoft ActiveSync. Microsoft Outlook
|
||||
|
||||
@@ -26,7 +26,7 @@ devices (iPhone, iPod touch, and iPad).
|
||||
The instructions are based on version {release_version} of SOGo.
|
||||
|
||||
The latest version of this guide is available at
|
||||
http://www.sogo.nu/downloads/documentation.html.
|
||||
https://sogo.nu/downloads/documentation.html.
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
@@ -263,7 +263,7 @@ The syntax is:
|
||||
----
|
||||
"<extension ID>"
|
||||
=> array( "application" => "thunderbird",
|
||||
"version" => "<exact version found in the extension's install.rdf file>",
|
||||
"version" => "<exact version found in the extension's manifest.json file>",
|
||||
"filename" => "<exact filename on the filesystem>" )
|
||||
----
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Guide_ for more information on installing and configuring SOGo.
|
||||
The instructions are based on version {release_version} of SOGo.
|
||||
|
||||
The latest version of this guide is available
|
||||
at http://www.sogo.nu/downloads/documentation.html.
|
||||
at https://sogo.nu/downloads/documentation.html.
|
||||
|
||||
Introduction
|
||||
------------
|
||||
@@ -42,9 +42,8 @@ Project Links
|
||||
|
||||
The project source and binaries for installation are hosted on GitHub and SourceForge or can be downloaded from https://caldavsynchronizer.org[caldavsynchronizer.org]
|
||||
|
||||
https://github.com/aluxnimm/outlookcaldavsynchronizer
|
||||
|
||||
https://sourceforge.net/projects/outlookcaldavsynchronizer/
|
||||
* https://github.com/aluxnimm/outlookcaldavsynchronizer
|
||||
* https://sourceforge.net/projects/outlookcaldavsynchronizer/
|
||||
|
||||
Social Media
|
||||
~~~~~~~~~~~~
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- TODO have the build system take care of this -->
|
||||
<releaseinfo>Version 4.1.0 - October 2019</releaseinfo>
|
||||
<subtitle>for version 4.1.0</subtitle>
|
||||
<date>2019-10-24</date>
|
||||
<releaseinfo>Version 4.3.2 - May 2020</releaseinfo>
|
||||
<subtitle>for version 4.3.2</subtitle>
|
||||
<date>2020-05-06</date>
|
||||
|
||||
<legalnotice>
|
||||
<para>Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".</para>
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
Authors:
|
||||
- Inverse inc. <info@inverse.ca>
|
||||
|
||||
Copyright (C) 2008-2019 Inverse inc.
|
||||
Copyright (C) 2008-2020 Inverse inc.
|
||||
License: GFDL 1.2 or later. http://www.gnu.org/licenses/fdl.html
|
||||
|
||||
////
|
||||
|
||||
// TODO have the build system take care of this
|
||||
|
||||
:release_version: 4.1.0
|
||||
:release_version: 4.3.2
|
||||
|
||||
// vim: set syntax=asciidoc tabstop=2 shiftwidth=2 expandtab:
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
@interface EOQualifier(GCS)
|
||||
|
||||
- (void)_gcsAppendToString:(NSMutableString *)_ms;
|
||||
- (void) _gcsAppendToString: (NSMutableString *) _ms;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
|
||||
@implementation EOQualifier(GCS)
|
||||
|
||||
- (void)_appendAndQualifier:(EOAndQualifier *)_q
|
||||
toString:(NSMutableString *)_ms
|
||||
- (void) _appendAndQualifier: (EOAndQualifier *) _q
|
||||
toString: (NSMutableString *) _ms
|
||||
{
|
||||
// TODO: move to EOQualifier category
|
||||
NSArray *qs;
|
||||
@@ -46,12 +46,12 @@
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i != 0) [_ms appendString:@" AND "];
|
||||
if (count > 1) [_ms appendString:@"("];
|
||||
[[qs objectAtIndex:i] _gcsAppendToString:_ms];
|
||||
[[qs objectAtIndex:i] _gcsAppendToString: _ms];
|
||||
if (count > 1) [_ms appendString:@")"];
|
||||
}
|
||||
}
|
||||
- (void)_appendOrQualifier:(EOAndQualifier *)_q
|
||||
toString:(NSMutableString *)_ms
|
||||
- (void)_appendOrQualifier: (EOAndQualifier *) _q
|
||||
toString: (NSMutableString *) _ms
|
||||
{
|
||||
// TODO: move to EOQualifier category
|
||||
NSArray *qs;
|
||||
@@ -64,21 +64,21 @@
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i != 0) [_ms appendString:@" OR "];
|
||||
if (count > 1) [_ms appendString:@"("];
|
||||
[[qs objectAtIndex:i] _gcsAppendToString:_ms];
|
||||
[[qs objectAtIndex:i] _gcsAppendToString: _ms];
|
||||
if (count > 1) [_ms appendString:@")"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_appendNotQualifier:(EONotQualifier *)_q
|
||||
toString:(NSMutableString *)_ms
|
||||
- (void)_appendNotQualifier: (EONotQualifier *)_q
|
||||
toString:(NSMutableString *) _ms
|
||||
{
|
||||
[_ms appendString:@" NOT ("];
|
||||
[[_q qualifier] _gcsAppendToString:_ms];
|
||||
[[_q qualifier] _gcsAppendToString: _ms];
|
||||
[_ms appendString:@")"];
|
||||
}
|
||||
|
||||
- (void)_appendKeyValueQualifier:(EOKeyValueQualifier *)_q
|
||||
toString:(NSMutableString *)_ms
|
||||
- (void) _appendKeyValueQualifier: (EOKeyValueQualifier *) _q
|
||||
toString: (NSMutableString *) _ms
|
||||
{
|
||||
id val;
|
||||
NSString *qKey, *qOperator, *qValue, *qFormat;
|
||||
@@ -115,10 +115,13 @@
|
||||
qOperator = @"=";
|
||||
}
|
||||
|
||||
if ([val isKindOfClass:[NSNumber class]])
|
||||
if ([val isKindOfClass: [NSNumber class]])
|
||||
qValue = [val stringValue];
|
||||
else if ([val isKindOfClass:[NSString class]]) {
|
||||
qValue = [NSString stringWithFormat: @"'%@'", val];
|
||||
else if ([val isKindOfClass: [NSString class]]) {
|
||||
if ([(EOKeyValueQualifier *)self formatted])
|
||||
qValue = val;
|
||||
else
|
||||
qValue = [NSString stringWithFormat: @"'%@'", val];
|
||||
}
|
||||
else {
|
||||
qValue = @"NULL";
|
||||
@@ -151,23 +154,31 @@
|
||||
[_ms appendFormat: qFormat, qKey, qOperator, qValue];
|
||||
}
|
||||
|
||||
- (void)_appendQualifier:(EOQualifier *)_q toString:(NSMutableString *)_ms {
|
||||
- (void) _appendQualifier: (EOQualifier *) _q
|
||||
toString: (NSMutableString *) _ms
|
||||
{
|
||||
if (_q == nil) return;
|
||||
|
||||
if ([_q isKindOfClass:[EOAndQualifier class]])
|
||||
[self _appendAndQualifier:(id)_q toString:_ms];
|
||||
else if ([_q isKindOfClass:[EOOrQualifier class]])
|
||||
[self _appendOrQualifier:(id)_q toString:_ms];
|
||||
else if ([_q isKindOfClass:[EOKeyValueQualifier class]])
|
||||
[self _appendKeyValueQualifier:(id)_q toString:_ms];
|
||||
else if ([_q isKindOfClass:[EONotQualifier class]])
|
||||
[self _appendNotQualifier:(id)_q toString:_ms];
|
||||
if ([_q isKindOfClass: [EOAndQualifier class]])
|
||||
[self _appendAndQualifier: (id)_q
|
||||
toString: _ms];
|
||||
else if ([_q isKindOfClass: [EOOrQualifier class]])
|
||||
[self _appendOrQualifier: (id)_q
|
||||
toString:_ms];
|
||||
else if ([_q isKindOfClass: [EOKeyValueQualifier class]])
|
||||
[self _appendKeyValueQualifier: (id)_q
|
||||
toString:_ms];
|
||||
else if ([_q isKindOfClass: [EONotQualifier class]])
|
||||
[self _appendNotQualifier: (id)_q
|
||||
toString:_ms];
|
||||
else
|
||||
[self errorWithFormat:@"unknown qualifier: %@", _q];
|
||||
}
|
||||
|
||||
- (void)_gcsAppendToString:(NSMutableString *)_ms {
|
||||
[self _appendQualifier:self toString:_ms];
|
||||
- (void) _gcsAppendToString: (NSMutableString *) _ms
|
||||
{
|
||||
[self _appendQualifier: self
|
||||
toString: _ms];
|
||||
}
|
||||
|
||||
@end /* EOQualifier(GCS) */
|
||||
@@ -38,20 +38,24 @@
|
||||
- "c_version" (update revision of the file in the folder)
|
||||
*/
|
||||
|
||||
@class NSArray;
|
||||
@class NSDictionary;
|
||||
@class NSException;
|
||||
@class NSMutableArray;
|
||||
@class NSMutableString;
|
||||
@class NSNumber;
|
||||
@class NSString;
|
||||
@class NSURL;
|
||||
@class NSNumber;
|
||||
@class NSArray;
|
||||
@class NSMutableArray;
|
||||
@class NSException;
|
||||
@class NSMutableString;
|
||||
@class NSDictionary;
|
||||
@class EOQualifier;
|
||||
@class EOFetchSpecification;
|
||||
|
||||
@class EOAdaptor;
|
||||
@class EOAdaptorChannel;
|
||||
@class EOAttribute;
|
||||
@class EOFetchSpecification;
|
||||
@class EOKeyValueQualifier;
|
||||
@class EOQualifier;
|
||||
@class GCSChannelManager;
|
||||
@class GCSFolderManager;
|
||||
@class GCSFolderType;
|
||||
@class GCSChannelManager;
|
||||
|
||||
@interface GCSFolder : NSObject
|
||||
{
|
||||
@@ -149,6 +153,13 @@
|
||||
|
||||
- (NSCalendarDate *) lastModificationDate;
|
||||
|
||||
/* helpers */
|
||||
|
||||
- (EOAttribute *) _attributeForColumn: (NSString *) _field;
|
||||
- (void) _findQualifiers: (id) qualifier
|
||||
withAdaptor: (EOAdaptor *) adaptor;
|
||||
- (void) _formatQualifierValue: (EOKeyValueQualifier *) qualifier
|
||||
withAdaptor: (EOAdaptor *) adaptor;
|
||||
@end
|
||||
|
||||
#endif /* __GDLContentStore_GCSFolder_H__ */
|
||||
@@ -380,10 +380,31 @@ static GCSStringFormatter *stringFormatter = nil;
|
||||
|
||||
- (NSString *) _sqlForQualifier: (EOQualifier *) qualifier
|
||||
{
|
||||
static EOAdaptor *adaptor = nil;
|
||||
NSMutableString *ms;
|
||||
|
||||
if (qualifier)
|
||||
{
|
||||
if (!adaptor)
|
||||
{
|
||||
EOAdaptorContext *adaptorCtx;
|
||||
EOAdaptorChannel *channel;
|
||||
channel = [self acquireStoreChannel];
|
||||
adaptorCtx = [channel adaptorContext];
|
||||
adaptor = [adaptorCtx adaptor];
|
||||
}
|
||||
|
||||
if ([qualifier isKindOfClass: [EOAndQualifier class]])
|
||||
[self _findQualifiers: (id)qualifier withAdaptor: adaptor];
|
||||
else if ([qualifier isKindOfClass: [EOOrQualifier class]])
|
||||
[self _findQualifiers: (id)qualifier withAdaptor: adaptor];
|
||||
else if ([qualifier isKindOfClass: [EOKeyValueQualifier class]])
|
||||
[self _formatQualifierValue: (EOKeyValueQualifier *)qualifier withAdaptor: adaptor];
|
||||
else if ([qualifier isKindOfClass: [EONotQualifier class]])
|
||||
[self _formatQualifierValue: (EOKeyValueQualifier *)[(id)qualifier qualifier] withAdaptor: adaptor];
|
||||
else
|
||||
[self errorWithFormat:@"unknown qualifier: %@", qualifier];
|
||||
|
||||
ms = [NSMutableString stringWithCapacity:32];
|
||||
[qualifier _gcsAppendToString: ms];
|
||||
}
|
||||
@@ -393,6 +414,51 @@ static GCSStringFormatter *stringFormatter = nil;
|
||||
return ms;
|
||||
}
|
||||
|
||||
- (void) _findQualifiers: (id) qualifier
|
||||
withAdaptor: (EOAdaptor *) adaptor
|
||||
{
|
||||
NSArray *qs;
|
||||
unsigned i, count;
|
||||
|
||||
if (qualifier == nil) return;
|
||||
|
||||
qs = [qualifier qualifiers];
|
||||
if ((count = [qs count]) == 0)
|
||||
return;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
id q = [qs objectAtIndex: i];
|
||||
if ([q isKindOfClass: [EOAndQualifier class]])
|
||||
[self _findQualifiers: q withAdaptor: adaptor];
|
||||
else if ([q isKindOfClass:[EOOrQualifier class]])
|
||||
[self _findQualifiers: q withAdaptor: adaptor];
|
||||
else if ([q isKindOfClass:[EOKeyValueQualifier class]])
|
||||
[self _formatQualifierValue: (EOKeyValueQualifier *)q withAdaptor: adaptor];
|
||||
else if ([q isKindOfClass:[EONotQualifier class]])
|
||||
[self _formatQualifierValue: (EOKeyValueQualifier *)[q qualifier] withAdaptor: adaptor];
|
||||
else
|
||||
[self errorWithFormat:@"unknown qualifier: %@", q];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) _formatQualifierValue: (EOKeyValueQualifier *) qualifier
|
||||
withAdaptor: (EOAdaptor *) adaptor
|
||||
{
|
||||
NSString *field;
|
||||
EOAttribute *attribute;
|
||||
NSString *formattedValue;
|
||||
|
||||
field = [qualifier key];
|
||||
attribute = [self _attributeForColumn: field];
|
||||
if (attribute)
|
||||
{
|
||||
formattedValue = [adaptor formatValue: [qualifier value]
|
||||
forAttribute: attribute];
|
||||
[qualifier setValue: formattedValue];
|
||||
[qualifier setFormatted: YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)_sqlForSortOrderings:(NSArray *)_so {
|
||||
NSMutableString *sql;
|
||||
unsigned i, count;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* CardVersitRenderer.m - this file is part of SOPE
|
||||
*
|
||||
* Copyright (C) 2006-2012 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-2020 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
|
||||
@@ -49,7 +47,7 @@
|
||||
|
||||
- (NSString *) renderElement: (CardElement *) anElement
|
||||
{
|
||||
NSMutableString *rendering;
|
||||
NSMutableString *rendering, *buffer;
|
||||
NSDictionary *attributes;
|
||||
NSMutableDictionary *values;
|
||||
NSString *finalRendering, *tag;
|
||||
@@ -73,10 +71,12 @@
|
||||
attributes = [anElement attributes];
|
||||
if ([attributes count])
|
||||
{
|
||||
[rendering appendString: @";"];
|
||||
[attributes versitRenderInString: rendering
|
||||
buffer = [NSMutableString string];
|
||||
[attributes versitRenderInString: buffer
|
||||
withKeyOrdering: [anElement orderOfAttributeKeys]
|
||||
asAttributes: YES];
|
||||
if ([buffer length])
|
||||
[rendering appendFormat: @";%@", buffer];
|
||||
}
|
||||
|
||||
/* values */
|
||||
|
||||
@@ -192,7 +192,7 @@ static inline id GetProperty(NSDictionary *_set, NSString *_name) {
|
||||
|
||||
if ([val respondsToSelector:@selector(descriptionWithCalendarFormat:)]) {
|
||||
static NSTimeZone *gmt = nil;
|
||||
if (gmt == nil) gmt = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
|
||||
if (gmt == nil) gmt = [NSTimeZone timeZoneForSecondsFromGMT: 0];
|
||||
[val setTimeZone:gmt];
|
||||
val = [val descriptionWithCalendarFormat:calfmt];
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
static NSTimeZone *gmt = nil;
|
||||
static inline void _setupGMT(void) {
|
||||
if (gmt == nil)
|
||||
gmt = [[NSTimeZone timeZoneWithAbbreviation:@"GMT"] retain];
|
||||
gmt = [[NSTimeZone timeZoneForSecondsFromGMT: 0] retain];
|
||||
}
|
||||
|
||||
@implementation NSCalendarDate(iCalRepresentation)
|
||||
|
||||
@@ -337,7 +337,7 @@
|
||||
second = 0;
|
||||
}
|
||||
|
||||
utc = [NSTimeZone timeZoneWithAbbreviation: @"GMT"];
|
||||
utc = [NSTimeZone timeZoneForSecondsFromGMT: 0];
|
||||
date = [NSCalendarDate dateWithYear: year month: month
|
||||
day: day hour: hour minute: minute
|
||||
second: second
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
NSCalendarDate *utcStart, *utcEnd;
|
||||
NSTimeZone *uTZ;
|
||||
|
||||
uTZ = [NSTimeZone timeZoneWithAbbreviation: @"GMT"];
|
||||
uTZ = [NSTimeZone timeZoneForSecondsFromGMT: 0];
|
||||
utcStart = [start copy];
|
||||
utcEnd = [end copy];
|
||||
[utcStart setTimeZone: uTZ];
|
||||
|
||||
@@ -99,12 +99,12 @@ static BOOL NGMonthDaySet_fillWithByMonthDay (NGMonthDaySet *positiveDaySet,
|
||||
if (dayInMonth > 31)
|
||||
{
|
||||
ok = NO;
|
||||
continue; /* error, value to large */
|
||||
continue; /* error, value too large */
|
||||
}
|
||||
if (dayInMonth < -31)
|
||||
{
|
||||
ok = NO;
|
||||
continue; /* error, value to large */
|
||||
continue; /* error, value too large */
|
||||
}
|
||||
|
||||
/* adjust negative days */
|
||||
@@ -160,7 +160,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
|
||||
/* create range and check whether its in the requested range */
|
||||
|
||||
r = [[NGCalendarDateRange alloc] initWithStartDate: _startDate endDate: end];
|
||||
if ([_r containsDateRange: r])
|
||||
if ([_r doesIntersectWithDateRange: r])
|
||||
[_ranges addObject: r];
|
||||
[r release];
|
||||
r = nil;
|
||||
|
||||
@@ -300,7 +300,7 @@
|
||||
= [(iCalDateTime *) [self uniqueChildWithTag: @"dtstart"] dateTime];
|
||||
else if ([rrule untilDate] == nil || [refDate compare: [rrule untilDate]] == NSOrderedAscending)
|
||||
tmpDate = [self _occurrenceForDate: refDate byRRule: rrule];
|
||||
else if ([refDate compare: [rrule untilDate]] == NSOrderedDescending)
|
||||
else if ([[self _occurrenceForDate: refDate byRRule: rrule] compare: [rrule untilDate] ] == NSOrderedAscending)
|
||||
tmpDate = [rrule untilDate];
|
||||
|
||||
return tmpDate;
|
||||
|
||||
@@ -4,9 +4,9 @@ set -o pipefail
|
||||
#set -x
|
||||
PROGNAME="$(basename $0)"
|
||||
|
||||
BACKUP_DIR=~sogo/backups
|
||||
BACKUP_DIR=${BACKUP_DIR:-~sogo/backups}
|
||||
SOGO_TOOL=/usr/sbin/sogo-tool
|
||||
DAYS_TO_KEEP="30"
|
||||
DAYS_TO_KEEP=${DAYS_TO_KEEP:-30}
|
||||
|
||||
DATE=$(date +%F_%H%M)
|
||||
LOG="logger -t $PROGNAME -p daemon.info"
|
||||
|
||||
+6
-5
@@ -24,9 +24,10 @@
|
||||
//SOGoDraftsFolderName = Drafts;
|
||||
//SOGoSentFolderName = Sent;
|
||||
//SOGoTrashFolderName = Trash;
|
||||
//SOGoIMAPServer = localhost;
|
||||
//SOGoSieveServer = sieve://127.0.0.1:4190;
|
||||
//SOGoSMTPServer = 127.0.0.1;
|
||||
//SOGoJunkFolderName = Junk;
|
||||
//SOGoIMAPServer = "localhost";
|
||||
//SOGoSieveServer = "sieve://127.0.0.1:4190";
|
||||
//SOGoSMTPServer = "smtp://127.0.0.1";
|
||||
//SOGoMailDomain = acme.com;
|
||||
//SOGoMailingMechanism = smtp;
|
||||
//SOGoForceExternalLoginWithEmail = NO;
|
||||
@@ -54,7 +55,7 @@
|
||||
// bindPassword = qwerty;
|
||||
// canAuthenticate = YES;
|
||||
// displayName = "Shared Addresses";
|
||||
// hostname = ldap://127.0.0.1:389;
|
||||
// hostname = "ldap://127.0.0.1:389";
|
||||
// id = public;
|
||||
// isAddressBook = YES;
|
||||
// }
|
||||
@@ -72,7 +73,7 @@
|
||||
// bindPassword = password;
|
||||
// canAuthenticate = YES;
|
||||
// displayName = "Public";
|
||||
// hostname = ldap://127.0.0.1:389;
|
||||
// hostname = "ldap://127.0.0.1:389";
|
||||
// filter = "mail = '*'";
|
||||
// id = directory;
|
||||
// isAddressBook = YES;
|
||||
|
||||
@@ -76,7 +76,7 @@ fi
|
||||
|
||||
if ! [ "$CHECK" = "1" ]; then
|
||||
cat >> $SQLFILE <<HERE
|
||||
ALTER TABLE $TABLE MODIFY COLUMN c_location integer NULL;
|
||||
ALTER TABLE $TABLE MODIFY COLUMN c_location varchar(2048) NULL;
|
||||
|
||||
CREATE TABLE sogo_store
|
||||
(
|
||||
@@ -195,7 +195,7 @@ do
|
||||
echo "DROP TABLE $QUICKTABLE;" >> $SQLFILE
|
||||
echo "DROP TABLE $STORETABLE;" >> $SQLFILE
|
||||
echo "DROP TABLE $ACLTABLE;" >> $SQLFILE
|
||||
echo "UPDATE sogo_folder_info SET c_location = NULL, c_quick_location = NULL, c_acl_location = NULL WHERE c_folder_id = $FOLDERID;" >> $SQLFILE
|
||||
echo "UPDATE $TABLE SET c_location = NULL, c_quick_location = NULL, c_acl_location = NULL WHERE c_folder_id = $FOLDERID;" >> $SQLFILE
|
||||
echo >> $SQLFILE
|
||||
done
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ do
|
||||
echo "DROP TABLE $QUICKTABLE;" >> $SQLFILE
|
||||
echo "DROP TABLE $STORETABLE;" >> $SQLFILE
|
||||
echo "DROP TABLE $ACLTABLE;" >> $SQLFILE
|
||||
echo "UPDATE sogo_folder_info SET c_location = NULL, c_quick_location = NULL, c_acl_location = NULL WHERE c_folder_id = $FOLDERID;" >> $SQLFILE
|
||||
echo "UPDATE $TABLE SET c_location = NULL, c_quick_location = NULL, c_acl_location = NULL WHERE c_folder_id = $FOLDERID;" >> $SQLFILE
|
||||
echo >> $SQLFILE
|
||||
done < $TABLEFILE
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ vevent_class2 = "(Skrytá událost)";
|
||||
vtodo_class0 = "(Veřejný úkol)";
|
||||
vtodo_class1 = "(Soukromý úkol)";
|
||||
vtodo_class2 = "(Skrytý úkol)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "Událost \"%{Summary}\" byla vytvořena";
|
||||
"The event \"%{Summary}\" was deleted" = "Událost \"%{Summary}\" byla smazána";
|
||||
@@ -14,6 +15,7 @@ vtodo_class2 = "(Skrytý úkol)";
|
||||
"The following attendees(s) were notified" = "Následující účastníci byli upozorněni";
|
||||
"The following attendees(s) were added" = "Následující účastníci byli přidáni";
|
||||
"The following attendees(s) were removed" = "Následující účastníci byli odebráni";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Kalendář";
|
||||
"startDate_label" = "Začátek";
|
||||
@@ -26,17 +28,20 @@ vtodo_class2 = "(Skrytý úkol)";
|
||||
"comment_label" = "Komentář:";
|
||||
"organizer_label" = "Organizátor";
|
||||
"attendee_label" = "Účastník";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Pozvání na událost: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(poslal/a %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}Vás pozval/a na %{Summary}.\n\nZačátek: %{StartDate}\nKonec: %{EndDate}\nPopis: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}Vás pozval/a na %{Summary}.\n\nZačátek: %{StartDate} v %{StartTime}\nKonec: %{EndDate} v %{EndTime}\nPopis: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Zrušení události: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}zrušil/a událost: %{Summary}.\n\nZačátek: %{StartDate}\nKonec: %{EndDate}\nPopis: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}zrušil/a událost: %{Summary}.\n\nZačátek: %{StartDate} v %{StartTime}\nKonec: %{EndDate} v %{EndTime}\nPopis: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "Schůzka \"%{Summary}\" na %{OldStartDate} byla změněna";
|
||||
@@ -46,6 +51,7 @@ vtodo_class2 = "(Skrytý úkol)";
|
||||
= "Následující parametry byly ve schůzce \"%{Summary}\" změněny:";
|
||||
"Please accept or decline those changes."
|
||||
= "Prosím přijměte nebo odmítněte tyto změny.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Přijaté pozvání: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Odmítnuté pozvání: \"%{Summary}\"";
|
||||
@@ -59,6 +65,15 @@ vtodo_class2 = "(Skrytý úkol)";
|
||||
= "%{Attendee} %{SentByText}delegoval/a Vaše pozvání na %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}dosud o Vašem pozvání k události nerozhodl/a.";
|
||||
|
||||
/* Reminder */
|
||||
"Reminder: \"%{Summary}\" - %{StartDate}" = "Připomenutí: \"%{Summary}\" - %{StartDate}";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Nedostupný zdroj: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Byl dosažen maximální počet současných rezervací\n(%{NumberOfSimultaneousBookings}) pro zdroj \"%{Cn} %{SystemEmail}\". Konfliktní událost je \"%{EventTitle}\" a začíná %{StartDate}.";
|
||||
|
||||
/* Participation role */
|
||||
"Your participation is required to this event" = "Vaše účast je vyžadována";
|
||||
"Your participation is optional to this event" = "Vaše účast je dobrovolná";
|
||||
"Your participation is not required to this event" = "Vaše účast není vyžadována";
|
||||
@@ -40,7 +40,7 @@
|
||||
if ((self = [super init]))
|
||||
{
|
||||
address = nil;
|
||||
timeZone = [NSTimeZone timeZoneWithAbbreviation: @"GMT"];
|
||||
timeZone = [NSTimeZone timeZoneForSecondsFromGMT: 0];
|
||||
[timeZone retain];
|
||||
startDate = nil;
|
||||
endDate = nil;
|
||||
|
||||
@@ -7,6 +7,7 @@ vevent_class2 = "(Eveniment confidenţial)";
|
||||
vtodo_class0 = "(Sarcină publică)";
|
||||
vtodo_class1 = "(Sarcină privată)";
|
||||
vtodo_class2 = "(Sarcină confidenţială)";
|
||||
|
||||
/* Receipts */
|
||||
"The event \"%{Summary}\" was created" = "Evenimentul \"%{Summary}\" a fost creat";
|
||||
"The event \"%{Summary}\" was deleted" = "Evenimentul \"%{Summary}\" a fost șters";
|
||||
@@ -14,6 +15,7 @@ vtodo_class2 = "(Sarcină confidenţială)";
|
||||
"The following attendees(s) were notified" = "Următori participan(t/ți) au fost notificați";
|
||||
"The following attendees(s) were added" = "Următori participan(t/ți) au fost adăugați";
|
||||
"The following attendees(s) were removed" = "Următori participan(t/ți) au fost eliminați";
|
||||
|
||||
/* IMIP messages */
|
||||
"calendar_label" = "Calendar";
|
||||
"startDate_label" = "Start";
|
||||
@@ -26,17 +28,20 @@ vtodo_class2 = "(Sarcină confidenţială)";
|
||||
"comment_label" = "Comentariu";
|
||||
"organizer_label" = "Organizator";
|
||||
"attendee_label" = "Participant";
|
||||
|
||||
/* Invitation */
|
||||
"Event Invitation: \"%{Summary}\"" = "Invitație la eveniment: \"%{Summary}\"";
|
||||
"(sent by %{SentBy}) " = "(trimis de %{SentBy}) ";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}va invitat la %{Summary}.\n\nÎnceput: %{StartDate}\nSfârșit: %{EndDate}\nDescriere: %{Description}";
|
||||
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}va invitat la %{Summary}.\n\nÎnceput: %{StartDate} la %{StartTime}\nSfârșit: %{EndDate} la %{EndTime}\nDescriere: %{Description}";
|
||||
|
||||
/* Deletion */
|
||||
"Event Cancelled: \"%{Summary}\"" = "Eveniment Anulat: \"%{Summary}\"";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}a anulat acest eveniment: %{Summary}.\n\nÎnceput: %{StartDate}\nSfârșit: %{EndDate}\nDescriere: %{Description}";
|
||||
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
|
||||
= "%{Organizer} %{SentByText}a anulat acest eveniment: %{Summary}.\n\nÎnceput: %{StartDate} la %{StartTime}\nSfârșit: %{EndDate} la %{EndTime}\nDescriere: %{Description}";
|
||||
|
||||
/* Update */
|
||||
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
|
||||
= "întâlnirea \"%{Summary}\" pentru %{OldStartDate} s-a schimbat";
|
||||
@@ -46,6 +51,7 @@ vtodo_class2 = "(Sarcină confidenţială)";
|
||||
= "Următorii parametri s-au schimbat în întâlnirea \"%{Summary}\":";
|
||||
"Please accept or decline those changes."
|
||||
= "Vă rugăm să acceptați sau să refuzați aceste modificări.";
|
||||
|
||||
/* Reply */
|
||||
"Accepted invitation: \"%{Summary}\"" = "Invitație acceptată: \"%{Summary}\"";
|
||||
"Declined invitation: \"%{Summary}\"" = "Invitație refuzată: \"%{Summary}\"";
|
||||
@@ -59,6 +65,15 @@ vtodo_class2 = "(Sarcină confidenţială)";
|
||||
= "%{Attendee} %{SentByText}a delegat invitația catre %{Delegate}.";
|
||||
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
|
||||
= "%{Attendee} %{SentByText}nu a decis încă cu privire la invitația dvs.";
|
||||
|
||||
/* Reminder */
|
||||
"Reminder: \"%{Summary}\" - %{StartDate}" = " Memento: \"%{Summary}\" - %{StartDate}";
|
||||
|
||||
/* Resources */
|
||||
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Nu pot accesa resursa: \"%{Cn} %{SystemEmail}\"";
|
||||
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Numărul maxim de rezervările simultane(%{NumberOfSimultaneousBookings}) a fost atins pentru resursa \"%{Cn} %{SystemEmail}\". Evenimentul în conflict este \"%{EventTitle}\", și începe pe %{StartDate}.";
|
||||
|
||||
/* Participation role */
|
||||
"Your participation is required to this event" = "Participarea ta este necesară pentru acest eveniment";
|
||||
"Your participation is optional to this event" = " Participarea ta este opțională pentru acest eveniment";
|
||||
"Your participation is not required to this event" = "Participarea ta nu este necesară pentru acest eveniment";
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2007-2014 Inverse inc.
|
||||
Copyright (C) 2007-2019 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
|
||||
This file is part of SOGo.
|
||||
@@ -37,6 +37,7 @@
|
||||
#import <NGCards/iCalTimeZonePeriod.h>
|
||||
#import <NGCards/iCalToDo.h>
|
||||
#import <NGCards/NSString+NGCards.h>
|
||||
#import <NGExtensions/NSCalendarDate+misc.h>
|
||||
#import <NGExtensions/NGCalendarDateRange.h>
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
@@ -59,6 +60,7 @@
|
||||
#import <SOGo/SOGoWebDAVValue.h>
|
||||
#import <SOGo/WORequest+SOGo.h>
|
||||
#import <SOGo/WOResponse+SOGo.h>
|
||||
#import <SOGo/SOGoSource.h>
|
||||
|
||||
#import "iCalCalendar+SOGo.h"
|
||||
#import "iCalRepeatableEntityObject+SOGo.h"
|
||||
@@ -70,7 +72,6 @@
|
||||
#import "SOGoTaskObject.h"
|
||||
#import "SOGoWebAppointmentFolder.h"
|
||||
|
||||
|
||||
#define defaultColor @"#AAAAAA"
|
||||
|
||||
@interface SOGoGCSFolder (SOGoPrivate)
|
||||
@@ -425,11 +426,10 @@ static Class iCalEventK = nil;
|
||||
response: (WOResponse *) theResponse
|
||||
{
|
||||
NSMutableDictionary *moduleSettings, *folderShowAlarms, *freeBusyExclusions;
|
||||
NSString *subscriptionPointer;
|
||||
NSString *subscriptionPointer, *domain;
|
||||
NSMutableArray *allUsers;
|
||||
SOGoUserSettings *us;
|
||||
NSDictionary *dict;
|
||||
SOGoUser *sogoUser;
|
||||
BOOL rc;
|
||||
int i;
|
||||
|
||||
@@ -437,34 +437,48 @@ static Class iCalEventK = nil;
|
||||
|
||||
if (rc)
|
||||
{
|
||||
dict = [[SOGoUserManager sharedUserManager] contactInfosForUserWithUIDorEmail: theIdentifier];
|
||||
#warning Duplicated code from SOGoGCSFolder subscribeUserOrGroup
|
||||
domain = [[context activeUser] domain];
|
||||
dict = [[SOGoUserManager sharedUserManager] contactInfosForUserWithUIDorEmail: theIdentifier
|
||||
inDomain: domain];
|
||||
|
||||
if ([[dict objectForKey: @"isGroup"] boolValue])
|
||||
{
|
||||
SOGoGroup *aGroup;
|
||||
if (dict && [[dict objectForKey: @"isGroup"] boolValue])
|
||||
{
|
||||
id <SOGoSource> source;
|
||||
|
||||
aGroup = [SOGoGroup groupWithIdentifier: theIdentifier
|
||||
inDomain: [[context activeUser] domain]];
|
||||
allUsers = [NSMutableArray arrayWithArray: [aGroup members]];
|
||||
source = [[SOGoUserManager sharedUserManager] sourceWithID: [dict objectForKey: @"SOGoSource"]];
|
||||
if ([source conformsToProtocol:@protocol(SOGoMembershipSource)])
|
||||
{
|
||||
NSArray *members;
|
||||
|
||||
// We remove the active user from the group (if present) in order to
|
||||
// not subscribe him to their own resource!
|
||||
[allUsers removeObject: [context activeUser]];
|
||||
}
|
||||
else
|
||||
{
|
||||
sogoUser = [SOGoUser userWithLogin: theIdentifier roles: nil];
|
||||
members = [(id<SOGoMembershipSource>)(source) membersForGroupWithUID: [dict objectForKey: @"c_uid"]];
|
||||
allUsers = [NSMutableArray array];
|
||||
|
||||
if (sogoUser)
|
||||
allUsers = [NSArray arrayWithObject: sogoUser];
|
||||
else
|
||||
allUsers = [NSArray array];
|
||||
}
|
||||
for (i = 0; i < [members count]; i++)
|
||||
{
|
||||
[allUsers addObject: [[members objectAtIndex: i] objectForKey: @"c_uid"]];
|
||||
}
|
||||
// We remove the active user from the group (if present) in order to
|
||||
// not subscribe him to their own resource!
|
||||
[allUsers removeObject: [[context activeUser] login]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Inconsistency error - got group identifier (%@) from a source (%@) that does not support groups (%@).", theIdentifier, [dict objectForKey: @"SOGoSource"], NSStringFromClass([source class])];
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dict)
|
||||
allUsers = [NSArray arrayWithObject: [dict objectForKey: @"c_uid"]];
|
||||
else
|
||||
allUsers = [NSArray array];
|
||||
}
|
||||
|
||||
for (i = 0; i < [allUsers count]; i++)
|
||||
{
|
||||
sogoUser = [allUsers objectAtIndex: i];
|
||||
us = [sogoUser userSettings];
|
||||
us = [SOGoUserSettings settingsForUser: [allUsers objectAtIndex: i]];
|
||||
moduleSettings = [us objectForKey: [container nameInContainer]];
|
||||
if (!(moduleSettings
|
||||
&& [moduleSettings isKindOfClass: [NSMutableDictionary class]]))
|
||||
@@ -1467,8 +1481,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
#warning we do not take the participation status into account
|
||||
while ((currentRecord = [ma nextObject]))
|
||||
{
|
||||
accessClass
|
||||
= [[currentRecord objectForKey: @"c_classification"] intValue];
|
||||
accessClass = [[currentRecord objectForKey: @"c_classification"] intValue];
|
||||
role = roles[accessClass];
|
||||
if (!role)
|
||||
{
|
||||
@@ -1497,13 +1510,17 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
EOQualifier *qualifier;
|
||||
GCSFolder *folder;
|
||||
NSMutableArray *fields, *ma;
|
||||
NSMutableDictionary *currentRecord;
|
||||
NSArray *records;
|
||||
NSEnumerator *matchingRecords;
|
||||
NSMutableArray *baseWhere;
|
||||
NSString *where, *dateSqlString, *privacySQLString, *currentLogin;
|
||||
NSCalendarDate *endDate;
|
||||
NGCalendarDateRange *r;
|
||||
BOOL rememberRecords, canCycle;
|
||||
SOGoUser *ownerUser;
|
||||
|
||||
ownerUser = [SOGoUser userWithLogin: owner roles: nil];
|
||||
rememberRecords = [self _checkIfWeCanRememberRecords: _fields];
|
||||
canCycle = [_component isEqualToString: @"vevent"] || [_component isEqualToString: @"vtodo"];
|
||||
// if (rememberRecords)
|
||||
@@ -1661,11 +1678,21 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
}
|
||||
|
||||
currentLogin = [[context activeUser] login];
|
||||
if (![currentLogin isEqualToString: owner]
|
||||
&& !_includeProtectedInformation)
|
||||
[self _fixupProtectedInformation: [ma objectEnumerator]
|
||||
inFields: _fields
|
||||
forUser: currentLogin];
|
||||
if (![currentLogin isEqualToString: owner])
|
||||
{
|
||||
|
||||
if (!_includeProtectedInformation)
|
||||
[self _fixupProtectedInformation: [ma objectEnumerator]
|
||||
inFields: _fields
|
||||
forUser: currentLogin];
|
||||
}
|
||||
|
||||
// Add owner to each record. It will be used when generating freebusy.
|
||||
matchingRecords = [ma objectEnumerator];
|
||||
while ((currentRecord = [matchingRecords nextObject]))
|
||||
{
|
||||
[currentRecord setObject: ownerUser forKey: @"owner"];
|
||||
}
|
||||
|
||||
if (rememberRecords)
|
||||
[self _rememberRecords: ma];
|
||||
@@ -3392,7 +3419,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
NSMutableDictionary *timezones, *uids;
|
||||
NSString *tzId, *uid, *originalUid;
|
||||
iCalEntityObject *element;
|
||||
iCalDateTime *startDate;
|
||||
iCalTimeZone *timezone;
|
||||
iCalCalendar *masterCalendar;
|
||||
iCalEvent *event;
|
||||
@@ -3436,92 +3462,29 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
|
||||
timezone = nil;
|
||||
element = [components objectAtIndex: i];
|
||||
// Use the timezone of the start date.
|
||||
startDate = (iCalDateTime *) [element uniqueChildWithTag: @"dtstart"];
|
||||
if (startDate)
|
||||
{
|
||||
tzId = [startDate value: 0 ofAttribute: @"tzid"];
|
||||
if ([tzId length])
|
||||
timezone = [timezones valueForKey: tzId];
|
||||
else
|
||||
{
|
||||
// If the start date is a "floating time", let's use the user's timezone
|
||||
// during the import for both the start and end dates. This is similar
|
||||
// to what we do in SOGoAppointmentObject: -_adjustFloatingTimeInRequestCalendar:
|
||||
NSString *s;
|
||||
|
||||
s = [[startDate valuesAtIndex: 0 forKey: @""] objectAtIndex: 0];
|
||||
|
||||
if ([element isKindOfClass: iCalEventK] &&
|
||||
![(iCalEvent *)element isAllDay] &&
|
||||
![s hasSuffix: @"Z"] &&
|
||||
![s hasSuffix: @"z"])
|
||||
{
|
||||
iCalDateTime *endDate;
|
||||
int delta;
|
||||
|
||||
timezone = [iCalTimeZone timeZoneForName: [[[self->context activeUser] userDefaults] timeZoneName]];
|
||||
[calendar addTimeZone: timezone];
|
||||
|
||||
delta = [[timezone periodForDate: [startDate dateTime]] secondsOffsetFromGMT];
|
||||
event = (iCalEvent *)element;
|
||||
|
||||
[event setStartDate: [[event startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
|
||||
[startDate setTimeZone: timezone];
|
||||
|
||||
endDate = (iCalDateTime *) [element uniqueChildWithTag: @"dtend"];
|
||||
|
||||
if (endDate)
|
||||
{
|
||||
[event setEndDate: [[event endDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
|
||||
[endDate setTimeZone: timezone];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([element isKindOfClass: iCalEventK])
|
||||
if ([element isKindOfClass: iCalEventK])
|
||||
{
|
||||
event = (iCalEvent *)element;
|
||||
timezone = [event adjustInContext: self->context withTimezones: timezones];
|
||||
|
||||
if ([event recurrenceId])
|
||||
{
|
||||
event = (iCalEvent *)element;
|
||||
if (![event hasEndDate] && ![event hasDuration])
|
||||
// Event is an occurrence of a repeating event
|
||||
if ((uid = [uids valueForKey: [event uid]]))
|
||||
{
|
||||
// No end date, no duration
|
||||
if ([event isAllDay])
|
||||
[event setDuration: @"P1D"];
|
||||
else
|
||||
[event setDuration: @"PT1H"];
|
||||
|
||||
[self errorWithFormat: @"Importing event with no end date; setting duration to %@ for UID = %@", [event duration], [event uid]];
|
||||
}
|
||||
//
|
||||
// We check for broken all-day events (like the ones coming from the "WebCalendar" tool) where
|
||||
// the start date is equal to the end date. This clearly violates the RFC:
|
||||
//
|
||||
// 3.8.2.2. Date-Time End
|
||||
// The value MUST be later in time than the value of the "DTSTART" property.
|
||||
//
|
||||
if ([event isAllDay] && [[event startDate] isEqual: [event endDate]])
|
||||
{
|
||||
[event setEndDate: [[event startDate] dateByAddingYears: 0 months: 0 days: 1 hours: 0 minutes: 0 seconds: 0]];
|
||||
[self errorWithFormat: @"Fixed broken all-day event; setting end date to %@ for UID = %@", [event endDate], [event uid]];
|
||||
}
|
||||
if ([event recurrenceId])
|
||||
{
|
||||
// Event is an occurrence of a repeating event
|
||||
if ((uid = [uids valueForKey: [event uid]]))
|
||||
SOGoAppointmentObject *master = [self lookupName: uid
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
if (master)
|
||||
{
|
||||
SOGoAppointmentObject *master = [self lookupName: uid
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
if (master)
|
||||
{
|
||||
// Associate the occurrence to the master event and skip the actual import process
|
||||
masterCalendar = [master calendar: NO secure: NO];
|
||||
[masterCalendar addToEvents: event];
|
||||
if (timezone)
|
||||
[masterCalendar addTimeZone: timezone];
|
||||
[master saveCalendar: masterCalendar];
|
||||
continue;
|
||||
}
|
||||
// Associate the occurrence to the master event and skip the actual import process
|
||||
masterCalendar = [master calendar: NO secure: NO];
|
||||
[masterCalendar addToEvents: event];
|
||||
if (timezone)
|
||||
[masterCalendar addTimeZone: timezone];
|
||||
[master saveCalendar: masterCalendar];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* SOGoAppointmentFolderObject.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2010-2012 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2010-2019 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
|
||||
|
||||
@@ -378,7 +378,7 @@ static SoSecurityManager *sm = nil;
|
||||
methodSel = SOGoSelectorForPropertySetter (currentName);
|
||||
if ([newFolder respondsToSelector: methodSel])
|
||||
[newFolder performSelector: methodSel
|
||||
withObject: value];
|
||||
withObject: value];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2007-2018 Inverse inc.
|
||||
Copyright (C) 2007-2020 Inverse inc.
|
||||
|
||||
This file is part of SOGo
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
fromRequest: (WORequest *) rq;
|
||||
|
||||
- (BOOL) resourceHasAutoAccepted;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* __Appointments_SOGoAppointmentObject_H__ */
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2007-2019 Inverse inc.
|
||||
Copyright (C) 2007-2020 Inverse inc.
|
||||
|
||||
This file is part of SOGo
|
||||
|
||||
@@ -47,13 +47,14 @@
|
||||
#import <SOGo/NSObject+DAV.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoDateFormatter.h>
|
||||
#import <SOGo/SOGoGroup.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
#import <SOGo/SOGoDomainDefaults.h>
|
||||
#import <SOGo/WORequest+SOGo.h>
|
||||
|
||||
#import "iCalCalendar+SOGo.h"
|
||||
#import "iCalEvent+SOGo.h"
|
||||
#import "iCalEventChanges+SOGo.h"
|
||||
#import "iCalEntityObject+SOGo.h"
|
||||
#import "iCalPerson+SOGo.h"
|
||||
@@ -768,13 +769,15 @@
|
||||
|
||||
if ([user isResource])
|
||||
{
|
||||
// If we always force the auto-accept if numberOfSimultaneousBookings <= 0 (ie., no limit
|
||||
// We always force the auto-accept if numberOfSimultaneousBookings <= 0 (ie., no limit
|
||||
// is imposed) or if numberOfSimultaneousBookings is greater than the number of
|
||||
// overlapping events
|
||||
// overlapping events.
|
||||
// When numberOfSimultaneousBookings is set to -1, only force the auto-accept
|
||||
// once the conflict has been raised and the action is forced by the user.
|
||||
if ([user numberOfSimultaneousBookings] <= 0 ||
|
||||
[user numberOfSimultaneousBookings] > [fbInfo count])
|
||||
{
|
||||
if (currentAttendee)
|
||||
if (currentAttendee && ([user numberOfSimultaneousBookings] >= 0 || forceSave))
|
||||
{
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
@@ -811,7 +814,7 @@
|
||||
// We are dealing with a normal attendee. Lets check if we have conflicts, unless
|
||||
// we are being asked to force the save anyway
|
||||
//
|
||||
else if (!forceSave)
|
||||
if (!forceSave && !_resourceHasAutoAccepted)
|
||||
{
|
||||
NSMutableDictionary *info;
|
||||
NSMutableArray *conflicts;
|
||||
@@ -865,7 +868,7 @@
|
||||
// set the resource as one!
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
_resourceHasAutoAccepted = YES;
|
||||
_resourceHasAutoAccepted = YES;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1624,7 +1627,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
iCalPerson *attendee;
|
||||
NSException *ex;
|
||||
SOGoUser *ownerUser, *delegatedUser;
|
||||
NSString *recurrenceTime, *delegatedUid;
|
||||
NSString *recurrenceTime, *delegatedUid, *domain;
|
||||
|
||||
event = nil;
|
||||
ex = nil;
|
||||
@@ -1670,10 +1673,15 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
if ([event isAttendee: [[delegate email] rfc822Email]])
|
||||
ex = [NSException exceptionWithHTTPStatus: 409
|
||||
reason: @"delegate is a participant"];
|
||||
else if ([SOGoGroup groupWithEmail: [[delegate email] rfc822Email]
|
||||
inDomain: [ownerUser domain]])
|
||||
ex = [NSException exceptionWithHTTPStatus: 409
|
||||
reason: @"delegate is a group"];
|
||||
else {
|
||||
NSDictionary *dict;
|
||||
domain = [[context activeUser] domain];
|
||||
dict = [[SOGoUserManager sharedUserManager] contactInfosForUserWithUIDorEmail: [[delegate email] rfc822Email]
|
||||
inDomain: domain];
|
||||
if (dict && [[dict objectForKey: @"isGroup"] boolValue])
|
||||
ex = [NSException exceptionWithHTTPStatus: 409
|
||||
reason: @"delegate is a group"];
|
||||
}
|
||||
}
|
||||
if (ex == nil)
|
||||
{
|
||||
@@ -1930,6 +1938,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
{
|
||||
NSArray *allEvents;
|
||||
iCalEvent *event;
|
||||
iCalTimeZone *tz;
|
||||
NSUInteger i;
|
||||
int j;
|
||||
|
||||
@@ -1939,15 +1948,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
{
|
||||
event = [allEvents objectAtIndex: i];
|
||||
|
||||
if (![event hasEndDate] && ![event hasDuration])
|
||||
{
|
||||
// No end date, no duration
|
||||
if ([event isAllDay])
|
||||
[event setDuration: @"P1D"];
|
||||
else
|
||||
[event setDuration: @"PT1H"];
|
||||
[self warnWithFormat: @"Invalid event: no end date; setting duration to %@", [event duration]];
|
||||
}
|
||||
tz = [event adjustInContext: context withTimezones: nil];
|
||||
if (tz)
|
||||
[rqCalendar addTimeZone: tz];
|
||||
|
||||
if ([event organizer])
|
||||
{
|
||||
@@ -1994,53 +1997,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// This is similar to what we do in SOGoAppointmentFolder: -importCalendar:
|
||||
//
|
||||
- (void) _adjustFloatingTimeInRequestCalendar: (iCalCalendar *) rqCalendar
|
||||
{
|
||||
iCalDateTime *startDate, *endDate;
|
||||
NSString *startDateAsString;
|
||||
SOGoUserDefaults *ud;
|
||||
NSArray *allEvents;
|
||||
iCalTimeZone *tz;
|
||||
iCalEvent *event;
|
||||
int i, delta;
|
||||
|
||||
allEvents = [rqCalendar events];
|
||||
for (i = 0; i < [allEvents count]; i++)
|
||||
{
|
||||
event = [allEvents objectAtIndex: i];
|
||||
|
||||
if ([event isAllDay])
|
||||
continue;
|
||||
|
||||
startDate = (iCalDateTime *)[event uniqueChildWithTag: @"dtstart"];
|
||||
startDateAsString = [[startDate valuesAtIndex: 0 forKey: @""] objectAtIndex: 0];
|
||||
|
||||
if (![startDate timeZone] &&
|
||||
![startDateAsString hasSuffix: @"Z"] &&
|
||||
![startDateAsString hasSuffix: @"z"])
|
||||
{
|
||||
ud = [[context activeUser] userDefaults];
|
||||
tz = [iCalTimeZone timeZoneForName: [ud timeZoneName]];
|
||||
if ([rqCalendar addTimeZone: tz])
|
||||
{
|
||||
delta = [[tz periodForDate: [startDate dateTime]] secondsOffsetFromGMT];
|
||||
|
||||
[event setStartDate: [[event startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
|
||||
[startDate setTimeZone: tz];
|
||||
|
||||
endDate = (iCalDateTime *) [event uniqueChildWithTag: @"dtend"];
|
||||
if (endDate)
|
||||
{
|
||||
[event setEndDate: [[event endDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
|
||||
[endDate setTimeZone: tz];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) _decomposeGroupsInRequestCalendar: (iCalCalendar *) rqCalendar
|
||||
{
|
||||
@@ -2163,7 +2119,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
[self _adjustEventsInRequestCalendar: calendar];
|
||||
[self adjustClassificationInRequestCalendar: calendar];
|
||||
[self _adjustPartStatInRequestCalendar: calendar];
|
||||
[self _adjustFloatingTimeInRequestCalendar: calendar];
|
||||
}
|
||||
|
||||
//
|
||||
@@ -2308,7 +2263,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
if (!oldEvent && !newEvent)
|
||||
{
|
||||
// We check if we only have to deal with the MASTER event
|
||||
if ([newEvents count] == [oldEvents count])
|
||||
if ([oldEvents count] && [newEvents count] == [oldEvents count])
|
||||
{
|
||||
oldEvent = [oldEvents objectAtIndex: 0];
|
||||
newEvent = [newEvents objectAtIndex: 0];
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* SOGoAptMailICalReply.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2007-2020 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
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* SOGoAptMailICalReply - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2010-2012 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2010-2020 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2006-2019 Inverse inc.
|
||||
Copyright (C) 2006-2020 Inverse inc.
|
||||
Copyright (C) 2000-2005 SKYRIX Software AG
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoBuild.h>
|
||||
#import <SOGo/SOGoMailer.h>
|
||||
#import <SOGo/SOGoGroup.h>
|
||||
#import <SOGo/SOGoSource.h>
|
||||
#import <SOGo/SOGoPermissions.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
@@ -235,7 +235,8 @@
|
||||
tags = [NSArray arrayWithObjects: @"DTSTAMP", @"DTSTART", @"DTEND", @"DUE", @"EXDATE", @"EXRULE", @"RRULE", @"RECURRENCE-ID", nil];
|
||||
uid = [[component uid] asCryptedPassUsingScheme: @"ssha256"
|
||||
withSalt: [[settings userSalt] dataUsingEncoding: NSASCIIStringEncoding]
|
||||
andEncoding: encHex];
|
||||
andEncoding: encHex
|
||||
keyPath: nil];
|
||||
|
||||
children = [[[[component children] copy] autorelease] objectEnumerator];
|
||||
|
||||
@@ -525,7 +526,7 @@
|
||||
iCalPerson *currentAttendee;
|
||||
NSEnumerator *enumerator;
|
||||
NSAutoreleasePool *pool;
|
||||
SOGoGroup *group;
|
||||
NSDictionary *dict;
|
||||
|
||||
BOOL eventWasModified;
|
||||
unsigned int i, j;
|
||||
@@ -549,37 +550,49 @@
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
}
|
||||
|
||||
group = [SOGoGroup groupWithEmail: [currentAttendee rfc822Email]
|
||||
inDomain: domain];
|
||||
if (group)
|
||||
{
|
||||
dict = [[SOGoUserManager sharedUserManager] contactInfosForUserWithUIDorEmail: [currentAttendee rfc822Email]
|
||||
inDomain: domain];
|
||||
|
||||
if (dict && [[dict objectForKey: @"isGroup"] boolValue])
|
||||
{
|
||||
iCalPerson *person;
|
||||
NSArray *members;
|
||||
SOGoUser *user;
|
||||
NSDictionary *user;
|
||||
id <SOGoSource> source;
|
||||
|
||||
// We did decompose a group...
|
||||
[allAttendees removeObject: currentAttendee];
|
||||
|
||||
members = [group members];
|
||||
for (i = 0; i < [members count]; i++)
|
||||
{
|
||||
user = [members objectAtIndex: i];
|
||||
eventWasModified = YES;
|
||||
source = [[SOGoUserManager sharedUserManager] sourceWithID: [dict objectForKey: @"SOGoSource"]];
|
||||
if ([source conformsToProtocol:@protocol(SOGoMembershipSource)])
|
||||
{
|
||||
members = [(id<SOGoMembershipSource>)(source) membersForGroupWithUID: [dict objectForKey: @"c_uid"]];
|
||||
for (i = 0; i < [members count]; i++)
|
||||
{
|
||||
user = [members objectAtIndex: i];
|
||||
eventWasModified = YES;
|
||||
|
||||
// If the organizer is part of the group, we skip it from
|
||||
// the addition to the attendees' list
|
||||
if ([user hasEmail: organizerEmail])
|
||||
continue;
|
||||
|
||||
person = [self iCalPersonWithUID: [user login]];
|
||||
[person setTag: @"ATTENDEE"];
|
||||
[person setParticipationStatus: [currentAttendee participationStatus]];
|
||||
[person setRsvp: [currentAttendee rsvp]];
|
||||
[person setRole: [currentAttendee role]];
|
||||
|
||||
if (![allAttendees containsObject: person])
|
||||
[allAttendees addObject: person];
|
||||
}
|
||||
// If the organizer is part of the group, we skip it from
|
||||
// the addition to the attendees' list
|
||||
if ([[user objectForKey: @"c_emails"] containsObject: organizerEmail])
|
||||
continue;
|
||||
|
||||
person = [self iCalPersonWithUID: [user objectForKey: @"c_uid"]];
|
||||
[person setTag: @"ATTENDEE"];
|
||||
[person setParticipationStatus: [currentAttendee participationStatus]];
|
||||
[person setRsvp: [currentAttendee rsvp]];
|
||||
[person setRole: [currentAttendee role]];
|
||||
|
||||
if (![allAttendees containsObject: person])
|
||||
[allAttendees addObject: person];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Inconsistency error - got group identifier (%@) from a source (%@) that does not support groups.", [currentAttendee rfc822Email], [dict objectForKey: @"SOGoSource"]];
|
||||
RELEASE(pool);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
j++;
|
||||
@@ -968,7 +981,8 @@
|
||||
[headerMap setObject: @"1.0" forKey: @"MIME-Version"];
|
||||
|
||||
if (textOnly)
|
||||
[headerMap setObject: @"text/html" forKey: @"content-type"];
|
||||
[headerMap setObject: @"text/html; charset=utf-8"
|
||||
forKey: @"content-type"];
|
||||
else
|
||||
[headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
|
||||
|
||||
|
||||
@@ -372,10 +372,23 @@
|
||||
if ([endDate compare: currentEndDate] == NSOrderedAscending)
|
||||
currentEndDate = endDate;
|
||||
|
||||
[infos addObject: [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool: YES], @"c_isopaque",
|
||||
([currentStartDate compare: startDate] == NSOrderedAscending)? startDate : currentStartDate, @"startDate",
|
||||
currentEndDate, @"endDate", nil]];
|
||||
if ([currentStartDate compare: startDate] == NSOrderedAscending)
|
||||
{
|
||||
if ([startDate compare: currentEndDate] == NSOrderedAscending)
|
||||
{
|
||||
[infos addObject: [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool: YES], @"c_isopaque",
|
||||
startDate, @"startDate",
|
||||
currentEndDate, @"endDate", nil]];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[infos addObject: [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool: YES], @"c_isopaque",
|
||||
currentStartDate, @"startDate",
|
||||
currentEndDate, @"endDate", nil]];
|
||||
}
|
||||
|
||||
if (currentEndDate != endDate
|
||||
&& ([currentEndDate dayOfWeek] == 6 || [currentEndDate dayOfWeek] == 0))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* iCalEvent+SOGo.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
* Copyright (C) 2007-2019 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
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#import <NGCards/iCalEvent.h>
|
||||
|
||||
@class iCalTimeZone;
|
||||
@class NSMutableDictionary;
|
||||
|
||||
@interface iCalEvent (SOGoExtensions)
|
||||
@@ -30,6 +31,7 @@
|
||||
- (BOOL) isStillRelevant;
|
||||
- (NSTimeInterval) occurenceInterval;
|
||||
- (void) updateRecurrenceRulesUntilDate: (NSCalendarDate *) previousEndDate;
|
||||
- (iCalTimeZone *) adjustInContext: (WOContext *) context withTimezones: (NSDictionary *) timezones;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -27,9 +27,10 @@
|
||||
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/iCalRecurrenceRule.h>
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
#import <NGCards/iCalTimeZonePeriod.h>
|
||||
#import <NGCards/NSString+NGCards.h>
|
||||
|
||||
#import <SoObjects/SOGo/CardElement+SOGo.h>
|
||||
@@ -68,7 +69,7 @@
|
||||
//
|
||||
- (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent
|
||||
container: (id) theContainer
|
||||
nameInContainer: (NSString *) nameInContainer
|
||||
nameInContainer: (NSString *) nameInContainer
|
||||
{
|
||||
NSMutableDictionary *row;
|
||||
NSCalendarDate *startDate, *endDate;
|
||||
@@ -125,15 +126,15 @@
|
||||
|
||||
boolTmp = ((isAllDay) ? 1 : 0);
|
||||
[row setObject: [NSNumber numberWithInt: boolTmp]
|
||||
forKey: @"c_isallday"];
|
||||
forKey: @"c_isallday"];
|
||||
boolTmp = ((([self isRecurrent] || [self recurrenceId])) ? 1 : 0);
|
||||
[row setObject: [NSNumber numberWithInt: boolTmp]
|
||||
forKey: @"c_iscycle"];
|
||||
forKey: @"c_iscycle"];
|
||||
boolTmp = (([self isOpaque]) ? 1 : 0);
|
||||
[row setObject: [NSNumber numberWithInt: boolTmp]
|
||||
forKey: @"c_isopaque"];
|
||||
forKey: @"c_isopaque"];
|
||||
[row setObject: [NSNumber numberWithInt: [self priorityNumber]]
|
||||
forKey: @"c_priority"];
|
||||
forKey: @"c_priority"];
|
||||
|
||||
[row setObject: title forKey: @"c_title"];
|
||||
if ([location isNotNull]) [row setObject: location forKey: @"c_location"];
|
||||
@@ -151,8 +152,8 @@
|
||||
startDate = [timeZone computedDateForDate: startDate];
|
||||
}
|
||||
[row setObject: [self quickRecordDateAsNumber: startDate
|
||||
withOffset: 0
|
||||
forAllDay: isAllDay]
|
||||
withOffset: 0
|
||||
forAllDay: isAllDay]
|
||||
forKey: @"c_startdate"];
|
||||
}
|
||||
if ([endDate isNotNull])
|
||||
@@ -167,8 +168,8 @@
|
||||
endDate = [timeZone computedDateForDate: endDate];
|
||||
}
|
||||
[row setObject: [self quickRecordDateAsNumber: endDate
|
||||
withOffset: ((isAllDay) ? -1 : 0)
|
||||
forAllDay: isAllDay]
|
||||
withOffset: ((isAllDay) ? -1 : 0)
|
||||
forAllDay: isAllDay]
|
||||
forKey: @"c_enddate"];
|
||||
}
|
||||
|
||||
@@ -274,7 +275,7 @@
|
||||
[row setObject: [self comment] forKey: @"c_description"];
|
||||
else
|
||||
[row setObject: [NSNull null] forKey: @"c_description"];
|
||||
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
@@ -305,11 +306,11 @@
|
||||
// The until date must match the time of the end date
|
||||
offset = [[self endDate] timeIntervalSinceDate: previousEndDate];
|
||||
untilDate = [untilDate dateByAddingYears:0
|
||||
months:0
|
||||
days:0
|
||||
hours:0
|
||||
minutes:0
|
||||
seconds:offset];
|
||||
months:0
|
||||
days:0
|
||||
hours:0
|
||||
minutes:0
|
||||
seconds:offset];
|
||||
[rule setUntilDate: untilDate];
|
||||
}
|
||||
}
|
||||
@@ -324,11 +325,11 @@
|
||||
// The until date must match the time of the end date
|
||||
offset = [[self endDate] timeIntervalSinceDate: previousEndDate];
|
||||
untilDate = [untilDate dateByAddingYears:0
|
||||
months:0
|
||||
days:0
|
||||
hours:0
|
||||
minutes:0
|
||||
seconds:offset];
|
||||
months:0
|
||||
days:0
|
||||
hours:0
|
||||
minutes:0
|
||||
seconds:offset];
|
||||
[rule setUntilDate: untilDate];
|
||||
}
|
||||
}
|
||||
@@ -444,4 +445,121 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (iCalTimeZone *) adjustInContext: (WOContext *) context
|
||||
withTimezones: (NSDictionary *) timezones
|
||||
{
|
||||
iCalDateTime *startDate, *endDate;
|
||||
iCalTimeZone *timezone;
|
||||
NSCalendarDate *date;
|
||||
NSString *startDateAsString, *timezoneId;
|
||||
SOGoUserDefaults *ud;
|
||||
int delta;
|
||||
|
||||
delta = 0;
|
||||
timezone = nil;
|
||||
|
||||
startDate = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"];
|
||||
endDate = (iCalDateTime *) [self uniqueChildWithTag: @"dtend"];
|
||||
|
||||
if (![startDate dateTime])
|
||||
{
|
||||
if ([endDate dateTime])
|
||||
{
|
||||
// End date but no start date
|
||||
delta = 60*60; // 1 hour
|
||||
[self setStartDate: [[self endDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// No start date, no end date; start the event at the first "working" hour
|
||||
date = [[NSCalendarDate calendarDate] beginOfDayForUser: [context activeUser]];
|
||||
[self setStartDate: date];
|
||||
}
|
||||
startDate = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"];
|
||||
[self errorWithFormat: @"Event with no start date; setting start date to %@ for UID %@", [startDate dateTime], [self uid]];
|
||||
}
|
||||
|
||||
if ([startDate dateTime])
|
||||
{
|
||||
timezoneId = [startDate value: 0 ofAttribute: @"tzid"];
|
||||
if ([timezoneId length])
|
||||
{
|
||||
timezone = [iCalTimeZone timeZoneForName: timezoneId];
|
||||
// Fallback to tz (VTIMEZONE) found in the ics.
|
||||
if (!timezone)
|
||||
timezone = [timezones valueForKey: timezoneId];
|
||||
}
|
||||
else
|
||||
{
|
||||
startDateAsString = [[startDate valuesAtIndex: 0 forKey: @""] objectAtIndex: 0];
|
||||
if (!([self isAllDay] ||
|
||||
[startDateAsString hasSuffix: @"Z"] ||
|
||||
[startDateAsString hasSuffix: @"z"]))
|
||||
{
|
||||
// The start date is a "floating time", let's use the user's timezone
|
||||
// for both the start and end dates.
|
||||
ud = [[context activeUser] userDefaults];
|
||||
timezone = [iCalTimeZone timeZoneForName: [ud timeZoneName]];
|
||||
delta = [[timezone periodForDate: [startDate dateTime]] secondsOffsetFromGMT];
|
||||
|
||||
[self setStartDate: [[self startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
|
||||
[startDate setTimeZone: timezone];
|
||||
[self errorWithFormat: @"Event with no timezone; setting timezone %@ to start date for UID %@", [timezone tzId], [self uid]];
|
||||
|
||||
if ([endDate dateTime])
|
||||
{
|
||||
[self setEndDate: [[self endDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
|
||||
[endDate setTimeZone: timezone];
|
||||
[self errorWithFormat: @"Event with no timezone; setting timezone %@ to end date for UID %@", [timezone tzId], [self uid]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (![endDate dateTime] && ![self hasDuration])
|
||||
{
|
||||
// No end date, no duration
|
||||
if ([self isAllDay])
|
||||
[self setDuration: @"P1D"];
|
||||
else
|
||||
[self setDuration: @"PT1H"];
|
||||
|
||||
[self errorWithFormat: @"Event with no end date; setting duration to %@ for UID %@", [self duration], [self uid]];
|
||||
}
|
||||
|
||||
//
|
||||
// We check for broken all-day events (like the ones coming from the "WebCalendar" tool) where
|
||||
// the start date is equal to the end date. This clearly violates the RFC:
|
||||
//
|
||||
// 3.8.2.2. Date-Time End
|
||||
// The value MUST be later in time than the value of the "DTSTART" property.
|
||||
//
|
||||
if ([self isAllDay] && [[self startDate] isEqual: [self endDate]])
|
||||
{
|
||||
[self setEndDate: [[self startDate] dateByAddingYears: 0 months: 0 days: 1 hours: 0 minutes: 0 seconds: 0]];
|
||||
[self errorWithFormat: @"Broken all-day event; setting end date to %@ for UID %@", [self endDate], [self uid]];
|
||||
}
|
||||
|
||||
if ([self isRecurrent])
|
||||
{
|
||||
NSArray *rules;
|
||||
iCalRecurrenceRule *rule;
|
||||
|
||||
rules = [self recurrenceRules];
|
||||
if ([rules count] > 0)
|
||||
{
|
||||
// Consider first rule only
|
||||
rule = [rules objectAtIndex: 0];
|
||||
if ([rule repeatCount] && [rule untilDate])
|
||||
{
|
||||
[self errorWithFormat: @"Recurrent event with both repeat count (%i) and until date (%@); dropping until date", [rule repeatCount], [rule untilDate]];
|
||||
[rule setUntil: nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return timezone;
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,2 +1,5 @@
|
||||
"OtherUsersFolderName" = "Ostatní uživatelé";
|
||||
"SharedFoldersName" = "Sdílené složky";
|
||||
|
||||
"Report: Marked messages as junk" = "Hlášení: Označené zprávy jsou nevyžádané";
|
||||
"Report: Marked messages as not junk" = "Hlášení: Označené zprávy nejsou nevyžádané";
|
||||
@@ -1,2 +1,5 @@
|
||||
"OtherUsersFolderName" = "Alți utilizatori";
|
||||
"SharedFoldersName" = "Foldere partajate";
|
||||
|
||||
"Report: Marked messages as junk" = "Raport: Marcare mesaje selectate ca junk ";
|
||||
"Report: Marked messages as not junk" = "Raport: Marcare mesaje selectate ca non-junk ";
|
||||
@@ -995,7 +995,7 @@ static NSString *userAgent = nil;
|
||||
- (void) fetchMailForForwarding: (SOGoMailObject *) sourceMail
|
||||
{
|
||||
NSDictionary *info, *attachment;
|
||||
NSString *signature, *nl;
|
||||
NSString *signature, *nl, *space;
|
||||
SOGoUserDefaults *ud;
|
||||
|
||||
[sourceMail fetchCoreInfos];
|
||||
@@ -1031,8 +1031,9 @@ static NSString *userAgent = nil;
|
||||
signature = [[self mailAccountFolder] signature];
|
||||
if ([signature length])
|
||||
{
|
||||
nl = (isHTML ? @"<br/>" : @"\n");
|
||||
[self setText: [NSString stringWithFormat: @"%@%@-- %@%@", nl, nl, nl, signature]];
|
||||
nl = (isHTML ? @"<br />" : @"\n");
|
||||
space = (isHTML ? @" " : @" ");
|
||||
[self setText: [NSString stringWithFormat: @"%@%@--%@%@%@", nl, nl, space, nl, signature]];
|
||||
}
|
||||
attachment = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[sourceMail filenameForForward], @"filename",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2009-2017 Inverse inc.
|
||||
Copyright (C) 2009-2020 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
*/
|
||||
|
||||
@class NSArray;
|
||||
@class NSException;
|
||||
@class NSMutableDictionary;
|
||||
@class NSMutableArray;
|
||||
@class NSString;
|
||||
@@ -75,16 +76,18 @@ typedef enum {
|
||||
|
||||
/* capabilities */
|
||||
- (BOOL) hasCapability: (NSString *) capability;
|
||||
- (NSString *) imap4Separator;
|
||||
- (BOOL) supportsQuotas;
|
||||
- (BOOL) supportsQResync;
|
||||
|
||||
- (id) getInboxQuota;
|
||||
- (BOOL) updateFilters;
|
||||
- (BOOL) updateFiltersWithUsername: (NSString *) theUsername
|
||||
andPassword: (NSString *) thePassword;
|
||||
- (NSException *) updateFilters;
|
||||
- (NSException *) updateFiltersAndForceActivation: (BOOL) forceActivation;
|
||||
- (NSException *) updateFiltersWithUsername: (NSString *) theUsername
|
||||
andPassword: (NSString *) thePassword
|
||||
forceActivation: (BOOL) forceActivation;
|
||||
|
||||
- (NSArray *) identities;
|
||||
- (NSDictionary *) defaultIdentity;
|
||||
- (NSString *) signature;
|
||||
- (NSString *) encryption;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2007-2016 Inverse inc.
|
||||
Copyright (C) 2007-2020 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -257,18 +257,6 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
return [capabilities containsObject: capability];
|
||||
}
|
||||
|
||||
- (NSString *) imap4Separator
|
||||
{
|
||||
NGImap4Client *imapClient;
|
||||
|
||||
imapClient = [[self imap4Connection] client];
|
||||
|
||||
if (![imapClient delimiter])
|
||||
[imapClient list: @"INBOX" pattern: @""];
|
||||
|
||||
return [imapClient delimiter];
|
||||
}
|
||||
|
||||
- (BOOL) supportsQuotas
|
||||
{
|
||||
return [self hasCapability: @"quota"];
|
||||
@@ -312,19 +300,32 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
return inboxQuota;
|
||||
}
|
||||
|
||||
- (BOOL) updateFilters
|
||||
- (NSException *) updateFiltersAndForceActivation: (BOOL) forceActivation
|
||||
{
|
||||
return [self updateFiltersWithUsername: nil andPassword: nil];
|
||||
return [self updateFiltersWithUsername: nil
|
||||
andPassword: nil
|
||||
forceActivation: forceActivation];
|
||||
}
|
||||
|
||||
- (BOOL) updateFiltersWithUsername: (NSString *) theUsername
|
||||
- (NSException *) updateFilters
|
||||
{
|
||||
return [self updateFiltersWithUsername: nil
|
||||
andPassword: nil
|
||||
forceActivation: NO];
|
||||
}
|
||||
|
||||
- (NSException *) updateFiltersWithUsername: (NSString *) theUsername
|
||||
andPassword: (NSString *) thePassword
|
||||
forceActivation: (BOOL) forceActivation
|
||||
{
|
||||
SOGoSieveManager *manager;
|
||||
|
||||
manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]];
|
||||
|
||||
return [manager updateFiltersForAccount: self withUsername: theUsername andPassword: thePassword];
|
||||
return [manager updateFiltersForAccount: self
|
||||
withUsername: theUsername
|
||||
andPassword: thePassword
|
||||
forceActivation: forceActivation];
|
||||
}
|
||||
|
||||
|
||||
@@ -666,13 +667,37 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
return identities;
|
||||
}
|
||||
|
||||
- (NSDictionary *) defaultIdentity
|
||||
{
|
||||
NSDictionary *defaultIdentity, *currentIdentity;
|
||||
unsigned int count, max;
|
||||
|
||||
defaultIdentity = nil;
|
||||
[self identities];
|
||||
|
||||
max = [identities count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
currentIdentity = [identities objectAtIndex: count];
|
||||
if ([[currentIdentity objectForKey: @"isDefault"] boolValue])
|
||||
{
|
||||
defaultIdentity = currentIdentity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultIdentity; // can be nil
|
||||
}
|
||||
|
||||
- (NSString *) signature
|
||||
{
|
||||
NSDictionary *identity;
|
||||
NSString *signature;
|
||||
|
||||
[self identities];
|
||||
if ([identities count] > 0)
|
||||
signature = [[identities objectAtIndex: 0] objectForKey: @"signature"];
|
||||
identity = [self defaultIdentity];
|
||||
|
||||
if (identity)
|
||||
signature = [identity objectForKey: @"signature"];
|
||||
else
|
||||
signature = nil;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
#import <Foundation/NSValue.h>
|
||||
#import <Foundation/NSTask.h>
|
||||
#import <Foundation/NSFileHandle.h>
|
||||
|
||||
#import <NGObjWeb/NSException+HTTP.h>
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
@@ -58,7 +58,6 @@
|
||||
#import <SOGo/NSObject+DAV.h>
|
||||
#import <SOGo/SOGoPermissions.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/SOGoGroup.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserFolder.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
@@ -66,6 +65,7 @@
|
||||
#import <SOGo/WORequest+SOGo.h>
|
||||
#import <SOGo/WOResponse+SOGo.h>
|
||||
#import <SOGo/SOGoMailer.h>
|
||||
#import <SOGo/SOGoZipArchiver.h>
|
||||
|
||||
#import "EOQualifier+MailDAV.h"
|
||||
#import "SOGoMailAccount.h"
|
||||
@@ -524,9 +524,9 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
NSDictionary *msgs;
|
||||
NSArray *messages;
|
||||
NSData *content, *zipContent;
|
||||
NSTask *zipTask;
|
||||
NSMutableArray *zipTaskArguments;
|
||||
WOResponse *response;
|
||||
SOGoZipArchiver *archiver;
|
||||
NSFileHandle *zipFileHandle;
|
||||
int i;
|
||||
|
||||
if (!archiveName)
|
||||
@@ -541,21 +541,15 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
return (WOResponse *)error;
|
||||
}
|
||||
|
||||
zipPath = [[SOGoSystemDefaults sharedSystemDefaults] zipPath];
|
||||
fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath: zipPath])
|
||||
{
|
||||
zipPath = [NSString stringWithFormat: @"%@/%@", spoolPath, archiveName];
|
||||
archiver = [SOGoZipArchiver archiverAtPath: zipPath];
|
||||
if (archiver == nil) {
|
||||
[self errorWithFormat: @"Failed to create zip archive at %@", spoolPath];
|
||||
error = [NSException exceptionWithHTTPStatus: 500
|
||||
reason: @"zip not available"];
|
||||
reason: @"Internal server error"];
|
||||
return (WOResponse *)error;
|
||||
}
|
||||
|
||||
zipTask = [[NSTask alloc] init];
|
||||
[zipTask setCurrentDirectoryPath: spoolPath];
|
||||
[zipTask setLaunchPath: zipPath];
|
||||
|
||||
zipTaskArguments = [NSMutableArray arrayWithObjects: nil];
|
||||
[zipTaskArguments addObject: @"SavedMessages.zip"];
|
||||
}
|
||||
|
||||
msgs = (NSDictionary *)[self fetchUIDs: uids
|
||||
parts: [NSArray arrayWithObject: @"BODY.PEEK[]"]];
|
||||
@@ -564,30 +558,26 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
for (i = 0; i < [messages count]; i++)
|
||||
{
|
||||
content = [[[messages objectAtIndex: i] objectForKey: @"body[]"] objectForKey: @"data"];
|
||||
fileName = [NSString stringWithFormat:@"%@/%@.eml", spoolPath, [uids objectAtIndex: i]];;
|
||||
[content writeToFile: fileName atomically: YES];
|
||||
|
||||
[zipTaskArguments addObject:
|
||||
[NSString stringWithFormat:@"%@.eml", [uids objectAtIndex: i]]];
|
||||
}
|
||||
|
||||
[zipTask setArguments: zipTaskArguments];
|
||||
[zipTask launch];
|
||||
[zipTask waitUntilExit];
|
||||
|
||||
[zipTask release];
|
||||
|
||||
zipContent = [[NSData alloc] initWithContentsOfFile:
|
||||
[NSString stringWithFormat: @"%@/SavedMessages.zip", spoolPath]];
|
||||
|
||||
for (i = 0; i < [zipTaskArguments count]; i++)
|
||||
{
|
||||
fileName = [zipTaskArguments objectAtIndex: i];
|
||||
[fm removeFileAtPath:
|
||||
[NSString stringWithFormat: @"%@/%@", spoolPath, fileName] handler: nil];
|
||||
fileName = [NSString stringWithFormat:@"%@.eml", [uids objectAtIndex: i]];
|
||||
[archiver putFileWithName: fileName andData: content];
|
||||
}
|
||||
|
||||
[archiver close];
|
||||
|
||||
response = [context response];
|
||||
|
||||
// Check if SOPE has support for serving files directly
|
||||
if ([response respondsToSelector: @selector(setContentFile:)]) {
|
||||
zipFileHandle = [NSFileHandle fileHandleForReadingAtPath: zipPath];
|
||||
[response setContentFile: zipFileHandle];
|
||||
} else {
|
||||
zipContent = [[NSData alloc] initWithContentsOfFile:zipPath];
|
||||
[response setContent:zipContent];
|
||||
[zipContent release];
|
||||
}
|
||||
|
||||
[fm removeFileAtPath: zipPath handler: nil];
|
||||
|
||||
baseName = [archiveName stringByDeletingPathExtension];
|
||||
extension = [archiveName pathExtension];
|
||||
if ([extension length] > 0)
|
||||
@@ -605,9 +595,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
[response setHeader: [NSString stringWithFormat: @"attachment; filename=\"%@\"",
|
||||
qpFileName]
|
||||
forKey: @"Content-Disposition"];
|
||||
[response setContent: zipContent];
|
||||
|
||||
[zipContent release];
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -884,7 +871,6 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
// We reached the limit or the end of the array
|
||||
if ((i%limit) == 0 || i == [uids count]-1)
|
||||
{
|
||||
id <SOGoAuthenticator> authenticator;
|
||||
NGMimeMessageGenerator *generator;
|
||||
|
||||
[messageToSend setBody: body];
|
||||
@@ -892,13 +878,11 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
|
||||
data = [generator generateMimeFromPart: messageToSend];
|
||||
|
||||
authenticator = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator];
|
||||
|
||||
error = [[SOGoMailer mailerWithDomainDefaults: [[context activeUser] domainDefaults]]
|
||||
sendMailData: data
|
||||
toRecipients: [NSArray arrayWithObject: recipient]
|
||||
sender: [[identities objectAtIndex: 0] objectForKey: @"email"]
|
||||
withAuthenticator: authenticator
|
||||
withAuthenticator: [self authenticatorInContext: context]
|
||||
inContext: context];
|
||||
|
||||
if (error)
|
||||
@@ -1425,15 +1409,18 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
|
||||
- (NSString *) _sogoACLUIDToIMAPUID: (NSString *) uid
|
||||
{
|
||||
SOGoGroup *group;
|
||||
NSString *domain;
|
||||
NSDictionary *dict;
|
||||
SOGoUser *user;
|
||||
|
||||
if ([uid isEqualToString: defaultUserID])
|
||||
return uid;
|
||||
|
||||
group = [SOGoGroup groupWithIdentifier: uid
|
||||
inDomain: [[context activeUser] domain]];
|
||||
if (group)
|
||||
domain = [[context activeUser] domain];
|
||||
dict = [[SOGoUserManager sharedUserManager] contactInfosForUserWithUIDorEmail: uid
|
||||
inDomain: domain];
|
||||
|
||||
if (dict && [[dict objectForKey: @"isGroup"] boolValue])
|
||||
return [[[[context activeUser] domainDefaults] imapAclGroupIdPrefix]
|
||||
stringByAppendingString: uid];
|
||||
|
||||
|
||||
@@ -234,14 +234,15 @@
|
||||
|
||||
- (NSString *) signature
|
||||
{
|
||||
NSString *signature, *mailSignature, *nl;
|
||||
NSString *signature, *mailSignature, *nl, *space;
|
||||
|
||||
signature = [[sourceMail mailAccountFolder] signature];
|
||||
|
||||
if ([signature length])
|
||||
{
|
||||
nl = (htmlComposition ? @"<br/>" : @"\n");
|
||||
mailSignature = [NSString stringWithFormat: @"-- %@%@", nl, signature];
|
||||
nl = (htmlComposition ? @"<br />" : @"\n");
|
||||
space = (htmlComposition ? @" " : @" ");
|
||||
mailSignature = [NSString stringWithFormat: @"--%@%@%@", space, nl, signature];
|
||||
}
|
||||
else
|
||||
mailSignature = @"";
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
02111-1307, USA.
|
||||
*/
|
||||
|
||||
#import <Foundation/NSTask.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
#import <Foundation/NSFileHandle.h>
|
||||
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
#import <NGObjWeb/WORequest.h>
|
||||
@@ -49,6 +49,7 @@
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <SOGo/NSCalendarDate+SOGo.h>
|
||||
#import <SOGo/SOGoZipArchiver.h>
|
||||
|
||||
#import "NSString+Mail.h"
|
||||
#import "NSData+Mail.h"
|
||||
@@ -962,17 +963,18 @@ static BOOL debugSoParts = NO;
|
||||
|
||||
- (WOResponse *) archiveAllFilesinArchiveNamed: (NSString *) archiveName
|
||||
{
|
||||
#warning duplicated code from [SOGoMailFolder archiveUIDs]
|
||||
NSArray *attachments;
|
||||
NSData *body, *zipContent;
|
||||
NSDictionary *currentAttachment;
|
||||
NSException *error;
|
||||
NSFileManager *fm;
|
||||
NSMutableArray *zipTaskArguments;
|
||||
NSString *spoolPath, *name, *fileName, *baseName, *extension, *zipPath, *qpFileName;
|
||||
NSTask *zipTask;
|
||||
NSString *spoolPath, *name, *baseName, *extension, *zipPath, *qpFileName;
|
||||
SOGoMailFolder *folder;
|
||||
WOResponse *response;
|
||||
unsigned int max, count;
|
||||
SOGoZipArchiver *archiver;
|
||||
NSFileHandle *zipFileHandle;;
|
||||
|
||||
if (!archiveName)
|
||||
archiveName = @"attachments.zip";
|
||||
@@ -988,22 +990,15 @@ static BOOL debugSoParts = NO;
|
||||
return (WOResponse *)error;
|
||||
}
|
||||
|
||||
// Prepare execution of zip
|
||||
zipPath = [[SOGoSystemDefaults sharedSystemDefaults] zipPath];
|
||||
fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath: zipPath])
|
||||
{
|
||||
zipPath = [NSString stringWithFormat: @"%@/%@", spoolPath, archiveName];
|
||||
archiver = [SOGoZipArchiver archiverAtPath: zipPath];
|
||||
if (archiver == nil) {
|
||||
[self errorWithFormat: @"Failed to create zip archive at %@", spoolPath];
|
||||
error = [NSException exceptionWithHTTPStatus: 500
|
||||
reason: @"zip not available"];
|
||||
reason: @"Internal server error"];
|
||||
return (WOResponse *)error;
|
||||
}
|
||||
|
||||
zipTask = [[NSTask alloc] init];
|
||||
[zipTask setCurrentDirectoryPath: spoolPath];
|
||||
[zipTask setLaunchPath: zipPath];
|
||||
|
||||
zipTaskArguments = [NSMutableArray arrayWithObjects: nil];
|
||||
[zipTaskArguments addObject: @"attachments.zip"];
|
||||
}
|
||||
|
||||
// Fetch attachments and write them on disk
|
||||
attachments = [self fetchFileAttachments];
|
||||
@@ -1013,32 +1008,25 @@ static BOOL debugSoParts = NO;
|
||||
currentAttachment = [attachments objectAtIndex: count];
|
||||
body = [currentAttachment objectForKey: @"body"];
|
||||
name = [[currentAttachment objectForKey: @"filename"] asSafeFilename];
|
||||
|
||||
fileName = [NSString stringWithFormat:@"%@/%@", spoolPath, name];
|
||||
[body writeToFile: fileName atomically: YES];
|
||||
|
||||
[zipTaskArguments addObject: [NSString stringWithFormat: @"./%@", name]];
|
||||
[archiver putFileWithName: name andData: body];
|
||||
}
|
||||
|
||||
// Zip files
|
||||
[zipTask setArguments: zipTaskArguments];
|
||||
[zipTask launch];
|
||||
[zipTask waitUntilExit];
|
||||
[zipTask release];
|
||||
zipContent = [[NSData alloc] initWithContentsOfFile:
|
||||
[NSString stringWithFormat: @"%@/attachments.zip", spoolPath]];
|
||||
[archiver close];
|
||||
|
||||
// Delete attachments from disk
|
||||
max = [zipTaskArguments count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
fileName = [zipTaskArguments objectAtIndex: count];
|
||||
[fm removeFileAtPath:
|
||||
[NSString stringWithFormat: @"%@/%@", spoolPath, fileName] handler: nil];
|
||||
}
|
||||
|
||||
// Prepare response
|
||||
response = [context response];
|
||||
|
||||
// Check if SOPE has support for serving files directly
|
||||
if ([response respondsToSelector: @selector(setContentFile:)]) {
|
||||
zipFileHandle = [NSFileHandle fileHandleForReadingAtPath: zipPath];
|
||||
[response setContentFile: zipFileHandle];
|
||||
} else {
|
||||
zipContent = [[NSData alloc] initWithContentsOfFile:zipPath];
|
||||
[response setContent:zipContent];
|
||||
[zipContent release];
|
||||
}
|
||||
|
||||
[fm removeFileAtPath: zipPath handler: nil];
|
||||
|
||||
baseName = [archiveName stringByDeletingPathExtension];
|
||||
extension = [archiveName pathExtension];
|
||||
if ([extension length] > 0)
|
||||
@@ -1054,9 +1042,6 @@ static BOOL debugSoParts = NO;
|
||||
[response setHeader: [NSString stringWithFormat: @"attachment; filename=\"%@\"",
|
||||
qpFileName]
|
||||
forKey: @"Content-Disposition"];
|
||||
[response setContent: zipContent];
|
||||
|
||||
[zipContent release];
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@ SOGo_HEADER_FILES = \
|
||||
SOGoWebDAVAclManager.h \
|
||||
SOGoWebDAVValue.h \
|
||||
SOGoMailer.h \
|
||||
SOGoGroup.h \
|
||||
SOGoUser.h \
|
||||
\
|
||||
CardElement+SOGo.h \
|
||||
@@ -87,7 +86,8 @@ SOGo_HEADER_FILES = \
|
||||
WOContext+SOGo.h \
|
||||
\
|
||||
SOGoCredentialsFile.h \
|
||||
SOGoTextTemplateFile.h
|
||||
SOGoTextTemplateFile.h \
|
||||
SOGoZipArchiver.h
|
||||
|
||||
all::
|
||||
@touch SOGoBuild.m
|
||||
@@ -155,7 +155,6 @@ SOGo_OBJC_FILES = \
|
||||
SOGoWebDAVAclManager.m \
|
||||
SOGoWebDAVValue.m \
|
||||
SOGoMailer.m \
|
||||
SOGoGroup.m \
|
||||
SOGoUser.m \
|
||||
\
|
||||
CardElement+SOGo.m \
|
||||
@@ -167,9 +166,10 @@ SOGo_OBJC_FILES = \
|
||||
WOContext+SOGo.m \
|
||||
\
|
||||
SOGoCredentialsFile.m \
|
||||
SOGoTextTemplateFile.m
|
||||
SOGoTextTemplateFile.m \
|
||||
SOGoZipArchiver.m
|
||||
|
||||
SOGo_C_FILES += lmhash.c
|
||||
SOGo_C_FILES += lmhash.c aes.c crypt_blowfish.c pkcs5_pbkdf2.c
|
||||
|
||||
SOGo_RESOURCE_FILES = \
|
||||
SOGoDefaults.plist \
|
||||
@@ -206,7 +206,7 @@ endif
|
||||
ADDITIONAL_TOOL_LIBS += -Lobj -lSOGo$(LIBRARY_NAME_SUFFIX)
|
||||
ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/
|
||||
ADDITIONAL_LIB_DIRS += -L../../SOPE/GDLContentStore/obj/
|
||||
ADDITIONAL_LDFLAGS += -lmemcached
|
||||
ADDITIONAL_LDFLAGS += -lmemcached -lzip
|
||||
|
||||
-include GNUmakefile.preamble
|
||||
ifneq ($(FHS_INSTALL_ROOT),)
|
||||
|
||||
@@ -43,6 +43,10 @@ ADDITIONAL_CPPFLAGS += $(LASSO_CFLAGS)
|
||||
SOGo_LIBRARIES_DEPEND_UPON += $(LASSO_LIBS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAS_LIBRARY_oath), yes)
|
||||
SOGo_LIBRARIES_DEPEND_UPON += $(MFA_LIBS)
|
||||
endif
|
||||
|
||||
ifeq ($(findstring openbsd, $(GNUSTEP_HOST_OS)), openbsd)
|
||||
SOGo_LIBRARIES_DEPEND_UPON += -lcrypto
|
||||
else
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
@class NSMutableDictionary;
|
||||
@class NSString;
|
||||
|
||||
@interface LDAPSource : NSObject <SOGoDNSource>
|
||||
@interface LDAPSource : NSObject <SOGoDNSource, SOGoMembershipSource>
|
||||
{
|
||||
int _queryLimit;
|
||||
int _queryTimeout;
|
||||
@@ -74,6 +74,7 @@
|
||||
NSArray *_groupObjectClasses;
|
||||
|
||||
NSDictionary *_modulesConstraints;
|
||||
NSArray *_lookupFields;
|
||||
|
||||
BOOL _passwordPolicy;
|
||||
BOOL _updateSambaNTLMPasswords;
|
||||
@@ -109,6 +110,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
IMAPLoginField: (NSString *) newIMAPLoginField
|
||||
SieveHostField: (NSString *) newSieveHostField
|
||||
bindFields: (id) newBindFields
|
||||
lookupFields: (NSArray *) newLookupFields
|
||||
kindField: (NSString *) newKindField
|
||||
andMultipleBookingsField: (NSString *) newMultipleBookingsField;
|
||||
|
||||
|
||||
+200
-34
@@ -34,6 +34,8 @@
|
||||
#import "NSString+Crypto.h"
|
||||
#import "SOGoCache.h"
|
||||
#import "SOGoSystemDefaults.h"
|
||||
#import "SOGoUser.h"
|
||||
#import "SOGoUserManager.h"
|
||||
|
||||
#import "LDAPSource.h"
|
||||
|
||||
@@ -107,6 +109,8 @@ static Class NSStringK;
|
||||
|
||||
_passwordPolicy = NO;
|
||||
_updateSambaNTLMPasswords = NO;
|
||||
_lookupFields = [NSArray arrayWithObject: @"*"];
|
||||
[_lookupFields retain];
|
||||
|
||||
_kindField = nil;
|
||||
_multipleBookingsField = nil;
|
||||
@@ -155,6 +159,7 @@ static Class NSStringK;
|
||||
[_MSExchangeHostname release];
|
||||
[_modifiers release];
|
||||
[_displayName release];
|
||||
[_lookupFields release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -190,6 +195,7 @@ static Class NSStringK;
|
||||
IMAPLoginField: [udSource objectForKey: @"IMAPLoginFieldName"]
|
||||
SieveHostField: [udSource objectForKey: @"SieveHostFieldName"]
|
||||
bindFields: [udSource objectForKey: @"bindFields"]
|
||||
lookupFields: [udSource objectForKey: @"lookupFields"]
|
||||
kindField: [udSource objectForKey: @"KindFieldName"]
|
||||
andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]];
|
||||
|
||||
@@ -225,7 +231,7 @@ static Class NSStringK;
|
||||
if (!_contactInfoAttribute)
|
||||
_contactInfoAttribute = [dd ldapContactInfoAttribute];
|
||||
[_contactInfoAttribute retain];
|
||||
|
||||
|
||||
udQueryLimit = [udSource objectForKey: @"SOGoLDAPQueryLimit"];
|
||||
if (udQueryLimit)
|
||||
_queryLimit = [udQueryLimit intValue];
|
||||
@@ -261,7 +267,7 @@ static Class NSStringK;
|
||||
|
||||
if ([udSource objectForKey: @"updateSambaNTLMPasswords"])
|
||||
_updateSambaNTLMPasswords = [[udSource objectForKey: @"updateSambaNTLMPasswords"] boolValue];
|
||||
|
||||
|
||||
ASSIGN(_MSExchangeHostname, [udSource objectForKey: @"MSExchangeHostname"]);
|
||||
}
|
||||
|
||||
@@ -329,6 +335,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
IMAPLoginField: (NSString *) newIMAPLoginField
|
||||
SieveHostField: (NSString *) newSieveHostField
|
||||
bindFields: (id) newBindFields
|
||||
lookupFields: (NSArray *) newLookupFields
|
||||
kindField: (NSString *) newKindField
|
||||
andMultipleBookingsField: (NSString *) newMultipleBookingsField
|
||||
{
|
||||
@@ -375,6 +382,8 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
ASSIGN(_bindFields, [newBindFields componentsSeparatedByString: @","]);
|
||||
}
|
||||
}
|
||||
if (newLookupFields)
|
||||
ASSIGN(_lookupFields, newLookupFields);
|
||||
if (newKindField)
|
||||
ASSIGN(_kindField, [newKindField lowercaseString]);
|
||||
if (newMultipleBookingsField)
|
||||
@@ -583,7 +592,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
didBind = [bindConnection bindWithMethod: @"simple"
|
||||
binddn: userDN
|
||||
credentials: _pwd];
|
||||
else
|
||||
else
|
||||
didBind = [bindConnection bindWithMethod: @"simple"
|
||||
binddn: userDN
|
||||
credentials: _pwd
|
||||
@@ -617,7 +626,8 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
- (NSString *) _encryptPassword: (NSString *) thePassword
|
||||
{
|
||||
NSString *pass;
|
||||
pass = [thePassword asCryptedPassUsingScheme: _userPasswordAlgorithm];
|
||||
pass = [thePassword asCryptedPassUsingScheme: _userPasswordAlgorithm
|
||||
keyPath: nil];
|
||||
|
||||
if (pass == nil)
|
||||
{
|
||||
@@ -639,14 +649,14 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
NSArray *changes;
|
||||
|
||||
BOOL didChange;
|
||||
|
||||
|
||||
attr = [[NGLdapAttribute alloc] initWithAttributeName: theAttribute];
|
||||
[attr addStringValue: theValue];
|
||||
|
||||
|
||||
mod = [NGLdapModification replaceModification: attr];
|
||||
|
||||
|
||||
changes = [NSArray arrayWithObject: mod];
|
||||
|
||||
|
||||
if ([bindConnection bindWithMethod: @"simple"
|
||||
binddn: theUserDN
|
||||
credentials: theUserPassword])
|
||||
@@ -656,7 +666,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
}
|
||||
else
|
||||
didChange = NO;
|
||||
|
||||
|
||||
RELEASE(attr);
|
||||
|
||||
return didChange;
|
||||
@@ -669,7 +679,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
oldPassword: (NSString *) oldPassword
|
||||
newPassword: (NSString *) newPassword
|
||||
perr: (SOGoPasswordPolicyError *) perr
|
||||
|
||||
|
||||
{
|
||||
NGLdapConnection *bindConnection;
|
||||
NSString *userDN;
|
||||
@@ -718,7 +728,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
// We don't use a password policy - we simply use
|
||||
// a modify-op to change the password
|
||||
NSString* encryptedPass;
|
||||
|
||||
|
||||
if ([_userPasswordAlgorithm isEqualToString: @"none"])
|
||||
{
|
||||
encryptedPass = newPassword;
|
||||
@@ -727,7 +737,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
{
|
||||
encryptedPass = [self _encryptPassword: newPassword];
|
||||
}
|
||||
|
||||
|
||||
if (encryptedPass != nil)
|
||||
{
|
||||
*perr = PolicyNoError;
|
||||
@@ -747,7 +757,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
userDN: userDN
|
||||
password: newPassword
|
||||
connection: bindConnection];
|
||||
|
||||
|
||||
[self _ldapModifyAttribute: @"sambaLMPassword"
|
||||
withValue: [newPassword asLMHash]
|
||||
userDN: userDN
|
||||
@@ -770,7 +780,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
}
|
||||
}
|
||||
NS_ENDHANDLER ;
|
||||
|
||||
|
||||
[bindConnection release];
|
||||
return didChange;
|
||||
}
|
||||
@@ -914,11 +924,11 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
[qs appendFormat: @" AND %@", _filter];
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
|
||||
|
||||
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
|
||||
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
|
||||
entries = [ldapConnection baseSearchAtBaseDN: _baseDN
|
||||
qualifier: qualifier
|
||||
attributes: attributes];
|
||||
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
|
||||
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
|
||||
entries = [ldapConnection flatSearchAtBaseDN: _baseDN
|
||||
qualifier: qualifier
|
||||
attributes: attributes];
|
||||
@@ -1122,7 +1132,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
ldifRecord = [ldapEntry asDictionary];
|
||||
[ldifRecord setObject: self forKey: @"source"];
|
||||
[ldifRecord setObject: [ldapEntry dn] forKey: @"dn"];
|
||||
|
||||
|
||||
// We get our objectClass attribute values. We lowercase
|
||||
// everything for ease of search after.
|
||||
o = [ldapEntry objectClasses];
|
||||
@@ -1250,7 +1260,6 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
NSEnumerator *entries;
|
||||
NSMutableArray *contacts;
|
||||
EOQualifier *qualifier;
|
||||
NSArray *attributes;
|
||||
|
||||
contacts = [NSMutableArray array];
|
||||
|
||||
@@ -1258,20 +1267,19 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
{
|
||||
ldapConnection = [self _ldapConnection];
|
||||
qualifier = [self _qualifierForFilter: match onCriteria: criteria];
|
||||
attributes = [NSArray arrayWithObject: @"*"];
|
||||
|
||||
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
|
||||
entries = [ldapConnection baseSearchAtBaseDN: _baseDN
|
||||
qualifier: qualifier
|
||||
attributes: attributes];
|
||||
attributes: _lookupFields];
|
||||
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
|
||||
entries = [ldapConnection flatSearchAtBaseDN: _baseDN
|
||||
qualifier: qualifier
|
||||
attributes: attributes];
|
||||
else /* we do it like before */
|
||||
attributes: _lookupFields];
|
||||
else /* we do it like before */
|
||||
entries = [ldapConnection deepSearchAtBaseDN: _baseDN
|
||||
qualifier: qualifier
|
||||
attributes: attributes];
|
||||
attributes: _lookupFields];
|
||||
while ((currentEntry = [entries nextObject]))
|
||||
[contacts addObject:
|
||||
[self _convertLDAPEntryToContact: currentEntry]];
|
||||
@@ -1284,23 +1292,21 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
{
|
||||
NGLdapConnection *ldapConnection;
|
||||
NSEnumerator *entries;
|
||||
NSArray *attributes;
|
||||
|
||||
ldapConnection = [self _ldapConnection];
|
||||
attributes = [NSArray arrayWithObject: @"*"];
|
||||
|
||||
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
|
||||
entries = [ldapConnection baseSearchAtBaseDN: _baseDN
|
||||
qualifier: theQualifier
|
||||
attributes: attributes];
|
||||
attributes: _lookupFields];
|
||||
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
|
||||
entries = [ldapConnection flatSearchAtBaseDN: _baseDN
|
||||
qualifier: theQualifier
|
||||
attributes: attributes];
|
||||
attributes: _lookupFields];
|
||||
else
|
||||
entries = [ldapConnection deepSearchAtBaseDN: _baseDN
|
||||
qualifier: theQualifier
|
||||
attributes: attributes];
|
||||
attributes: _lookupFields];
|
||||
|
||||
return [entries nextObject];
|
||||
}
|
||||
@@ -1354,7 +1360,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
NGLdapEntry *entry;
|
||||
EOQualifier *qualifier;
|
||||
NSString *login;
|
||||
|
||||
|
||||
login = nil;
|
||||
qualifier = nil;
|
||||
|
||||
@@ -1372,6 +1378,30 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
return login;
|
||||
}
|
||||
|
||||
- (NSDictionary *) lookupContactEntryByDN: (NSString *) theDN
|
||||
{
|
||||
NGLdapConnection *ldapConnection;
|
||||
NGLdapEntry *ldapEntry;
|
||||
EOQualifier *qualifier;
|
||||
NSDictionary *ldifRecord;
|
||||
|
||||
ldifRecord = nil;
|
||||
qualifier = nil;
|
||||
|
||||
ldapConnection = [self _ldapConnection];
|
||||
|
||||
if (_filter)
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat: _filter];
|
||||
|
||||
ldapEntry = [ldapConnection entryAtDN: theDN
|
||||
qualifier: qualifier
|
||||
attributes: [NSArray arrayWithObject: @"*"]];
|
||||
if (ldapEntry)
|
||||
ldifRecord = [self _convertLDAPEntryToContact: ldapEntry];
|
||||
|
||||
return ldifRecord;
|
||||
}
|
||||
|
||||
- (NSString *) lookupDNByLogin: (NSString *) theLogin
|
||||
{
|
||||
return [[SOGoCache sharedCache] distinguishedNameForLogin: theLogin];
|
||||
@@ -1383,24 +1413,24 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
EOQualifier *qualifier;
|
||||
NGLdapEntry *ldapEntry;
|
||||
NSString *s;
|
||||
|
||||
|
||||
if ([theValue length] > 0 && [theAttributes count] > 0)
|
||||
{
|
||||
if ([theAttributes count] == 1)
|
||||
{
|
||||
s = [NSString stringWithFormat: @"(%@='%@')",
|
||||
[theAttributes lastObject], SafeLDAPCriteria(theValue)];
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString *fieldFormat;
|
||||
|
||||
|
||||
fieldFormat = [NSString stringWithFormat: @"(%%@='%@')", SafeLDAPCriteria(theValue)];
|
||||
s = [[theAttributes stringsWithFormat: fieldFormat]
|
||||
componentsJoinedByString: @" OR "];
|
||||
}
|
||||
|
||||
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat: s];
|
||||
ldapEntry = [self _lookupLDAPEntry: qualifier];
|
||||
}
|
||||
@@ -1797,6 +1827,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||
IMAPLoginField: nil
|
||||
SieveHostField: nil
|
||||
bindFields: nil
|
||||
lookupFields: nil
|
||||
kindField: nil
|
||||
andMultipleBookingsField: nil];
|
||||
[ab setListRequiresDot: NO];
|
||||
@@ -1927,7 +1958,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||
reason: @"user addressbooks"
|
||||
@" are not supported"
|
||||
userInfo: nil];
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1990,4 +2021,139 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECK_CLASS(o) ({ \
|
||||
if ([o isKindOfClass: [NSString class]]) \
|
||||
o = [NSArray arrayWithObject: o]; \
|
||||
})
|
||||
|
||||
- (NSArray *) membersForGroupWithUID: (NSString *) uid
|
||||
{
|
||||
NSMutableArray *dns, *uids, *logins;
|
||||
NSString *dn, *login;
|
||||
SOGoUserManager *um;
|
||||
NSDictionary *d;
|
||||
SOGoUser *user;
|
||||
NSArray *o;
|
||||
NSAutoreleasePool *pool;
|
||||
int i, c;
|
||||
NGLdapEntry *entry;
|
||||
NSMutableArray *members = nil;
|
||||
|
||||
if ([uid hasPrefix: @"@"])
|
||||
uid = [uid substringFromIndex: 1];
|
||||
|
||||
entry = [self lookupGroupEntryByUID: uid inDomain: nil];
|
||||
|
||||
if (entry)
|
||||
{
|
||||
members = [NSMutableArray new];
|
||||
uids = [NSMutableArray array];
|
||||
dns = [NSMutableArray array];
|
||||
logins = [NSMutableArray array];
|
||||
|
||||
// We check if it's a static group
|
||||
// Fetch "members" - we get DNs
|
||||
d = [entry asDictionary];
|
||||
o = [d objectForKey: @"member"];
|
||||
CHECK_CLASS(o);
|
||||
if (o) [dns addObjectsFromArray: o];
|
||||
|
||||
// Fetch "uniqueMembers" - we get DNs
|
||||
o = [d objectForKey: @"uniquemember"];
|
||||
CHECK_CLASS(o);
|
||||
if (o) [dns addObjectsFromArray: o];
|
||||
|
||||
// Fetch "memberUid" - we get UID (like login names)
|
||||
o = [d objectForKey: @"memberuid"];
|
||||
CHECK_CLASS(o);
|
||||
if (o) [uids addObjectsFromArray: o];
|
||||
|
||||
c = [dns count] + [uids count];
|
||||
|
||||
// We deal with a static group, let's add the members
|
||||
if (c)
|
||||
{
|
||||
um = [SOGoUserManager sharedUserManager];
|
||||
|
||||
// We add members for whom we have their associated DN
|
||||
for (i = 0; i < [dns count]; i++)
|
||||
{
|
||||
pool = [NSAutoreleasePool new];
|
||||
dn = [dns objectAtIndex: i];
|
||||
login = [um getLoginForDN: [dn lowercaseString]];
|
||||
user = [SOGoUser userWithLogin: login roles: nil];
|
||||
if (user)
|
||||
{
|
||||
[logins addObject: login];
|
||||
[members addObject: [NSDictionary dictionaryWithObject: login
|
||||
forKey: @"c_uid"]];
|
||||
}
|
||||
[pool release];
|
||||
}
|
||||
|
||||
// We add members for whom we have their associated login name
|
||||
for (i = 0; i < [uids count]; i++)
|
||||
{
|
||||
pool = [NSAutoreleasePool new];
|
||||
login = [uids objectAtIndex: i];
|
||||
user = [SOGoUser userWithLogin: login roles: nil];
|
||||
if (user)
|
||||
{
|
||||
[logins addObject: login];
|
||||
[members addObject: [NSDictionary dictionaryWithObject: login
|
||||
forKey: @"c_uid"]];
|
||||
}
|
||||
[pool release];
|
||||
}
|
||||
|
||||
|
||||
// We are done fetching members, let's cache the members of the group
|
||||
// (ie., their UIDs) in memcached to speed up -hasMemberWithUID.
|
||||
[[SOGoCache sharedCache] setValue: [logins componentsJoinedByString: @","]
|
||||
forKey: [NSString stringWithFormat: @"%@+%@", uid, _domain]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// We deal with a dynamic group, let's search all users for whom
|
||||
// memberOf is equal to our group's DN.
|
||||
// We also need to look for labelelURI?
|
||||
}
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (BOOL) groupWithUIDHasMemberWithUID: (NSString *) uid
|
||||
memberUid: (NSString *) memberUid
|
||||
{
|
||||
|
||||
BOOL rc;
|
||||
NSString *key, *value;;
|
||||
NSArray *a;
|
||||
|
||||
rc = NO;
|
||||
|
||||
if ([uid hasPrefix: @"@"])
|
||||
uid = [uid substringFromIndex: 1];
|
||||
|
||||
key = [NSString stringWithFormat: @"%@+%@", uid, _domain];
|
||||
value = [[SOGoCache sharedCache] valueForKey: key];
|
||||
|
||||
// If the value isn't in memcached, that probably means -members was never called.
|
||||
// We call it only once here.
|
||||
if (!value)
|
||||
{
|
||||
[self membersForGroupWithUID: uid];
|
||||
value = [[SOGoCache sharedCache] valueForKey: key];
|
||||
}
|
||||
|
||||
a = [value componentsSeparatedByString: @","];
|
||||
rc = [a containsObject: memberUid];
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,31 +1,32 @@
|
||||
/* NSCalendarDate+SOGo.h - this file is part of SOGo
|
||||
Copyright (C) 2000-2004 SKYRIX Software AG
|
||||
|
||||
This file is part of OGo
|
||||
|
||||
OGo is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation; either version 2, or (at your option) any
|
||||
later version.
|
||||
|
||||
OGo 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 Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with OGo; see the file COPYING. If not, write to the
|
||||
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
||||
02111-1307, USA.
|
||||
*/
|
||||
*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
#ifndef NSCALENDARDATE_SCHEDULER_H
|
||||
#define NSCALENDARDATE_SCHEDULER_H
|
||||
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
|
||||
@class NSCalendarDate;
|
||||
@class NSString;
|
||||
@class NSTimeZone;
|
||||
@class SOGoUser;
|
||||
|
||||
@interface NSCalendarDate (SOGoExtensions)
|
||||
|
||||
@@ -34,6 +35,7 @@
|
||||
inTimeZone: (NSTimeZone *) timeZone;
|
||||
|
||||
- (BOOL) isDateInSameMonth: (NSCalendarDate *) _other;
|
||||
- (NSCalendarDate *) beginOfDayForUser: (SOGoUser *) user;
|
||||
|
||||
- (NSString *) shortDateString;
|
||||
- (NSString *) rfc822DateString;
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
/* NSCalendarDate+SOGo.m - this file is part of SOGo
|
||||
Copyright (C) 2000-2004 SKYRIX Software AG
|
||||
|
||||
This file is part of OGo
|
||||
|
||||
OGo is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation; either version 2, or (at your option) any
|
||||
later version.
|
||||
|
||||
OGo 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 Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with OGo; see the file COPYING. If not, write to the
|
||||
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
||||
02111-1307, USA.
|
||||
*/
|
||||
*
|
||||
* Copyright (C) 2019 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 <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <NGExtensions/NSCalendarDate+misc.h>
|
||||
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
|
||||
#import "NSCalendarDate+SOGo.h"
|
||||
|
||||
static NSString *rfc822Days[] = {@"Sun", @"Mon", @"Tue", @"Wed", @"Thu",
|
||||
@@ -96,6 +98,26 @@ static NSString *rfc822Months[] = {@"", @"Jan", @"Feb", @"Mar", @"Apr",
|
||||
return str;
|
||||
}
|
||||
|
||||
- (NSCalendarDate *) beginOfDayForUser: (SOGoUser *) user
|
||||
{
|
||||
NSCalendarDate *date;
|
||||
NSTimeZone *timeZone;
|
||||
SOGoUserDefaults *ud;
|
||||
|
||||
ud = [user userDefaults];
|
||||
timeZone = [ud timeZone];
|
||||
[self setTimeZone: timeZone];
|
||||
date = [self beginOfDay];
|
||||
date = [date addYear: 0
|
||||
month: 0
|
||||
day: 0
|
||||
hour: 0 - [date hourOfDay] + [ud dayStartHour]
|
||||
minute: 0 - [date minuteOfHour]
|
||||
second: 0];
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
- (NSString *) rfc822DateString
|
||||
{
|
||||
int timeZoneShift, tzSeconds;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* NSData+Crypto.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2012 Nicolas Höft
|
||||
* Copyright (C) 2012-2016 Inverse inc.
|
||||
* Copyright (C) 2012-2020 Inverse inc.
|
||||
*
|
||||
* Author: Nicolas Höft
|
||||
* Inverse inc.
|
||||
@@ -32,7 +32,12 @@
|
||||
@interface NSData (SOGoCryptoExtension)
|
||||
|
||||
- (NSData *) asCryptedPassUsingScheme: (NSString *) passwordScheme
|
||||
withSalt: (NSData *) theSalt;
|
||||
withSalt: (NSData *) theSalt
|
||||
keyPath: (NSString *) theKeyPath;
|
||||
|
||||
- (BOOL) verifyUsingScheme: (NSString *) passwordScheme
|
||||
withPassword: (NSData *) thePassword
|
||||
keyPath: (NSString *) theKeyPath;
|
||||
|
||||
- (NSData *) asLM;
|
||||
- (NSData *) asMD4;
|
||||
@@ -46,19 +51,22 @@
|
||||
- (NSData *) asSSHA512UsingSalt: (NSData *) theSalt;
|
||||
- (NSData *) asSHA256CryptUsingSalt: (NSData *) theSalt;
|
||||
- (NSData *) asSHA512CryptUsingSalt: (NSData *) theSalt;
|
||||
- (NSData *) asSymAES128CBCUsingIV: (NSString *) theIV
|
||||
keyPath: (NSString *) theKeyPath;
|
||||
- (NSData *) asCramMD5;
|
||||
|
||||
- (NSData *) asPBKDF2SHA1UsingSalt: (NSData *) theSalt;
|
||||
- (NSData *) asCryptUsingSalt: (NSData *) theSalt;
|
||||
- (NSData *) asMD5CryptUsingSalt: (NSData *) theSalt;
|
||||
- (NSData *) asBlowfishCryptUsingSalt: (NSData *) theSalt;
|
||||
|
||||
- (NSData *) extractSalt: (NSString *) theScheme;
|
||||
|
||||
+ (NSData *) generateSaltForLength: (unsigned int) theLength
|
||||
withBase64: (BOOL) doBase64;
|
||||
withPrintable: (BOOL) doPrintable;
|
||||
+ (NSData *) generateSaltForLength: (unsigned int) theLength;
|
||||
|
||||
+ (NSString *) encodeDataAsHexString: (NSData* ) theData;
|
||||
+ (NSData *) decodeDataFromHexString: (NSString* ) theString;
|
||||
+ (NSString *) encodeDataAsHexString: (NSData *) theData;
|
||||
+ (NSData *) decodeDataFromHexString: (NSString *) theString;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
+281
-39
@@ -1,7 +1,7 @@
|
||||
/* NSData+Crypto.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2012 Nicolas Höft
|
||||
* Copyright (C) 2012-2016 Inverse inc.
|
||||
* Copyright (C) 2012, 2020 Nicolas Höft
|
||||
* Copyright (C) 2012-2020 Inverse inc.
|
||||
* Copyright (C) 2012 Jeroen Dekkers
|
||||
*
|
||||
* Author: Nicolas Höft
|
||||
@@ -49,7 +49,10 @@
|
||||
#error this module requires either gnutls or openssl
|
||||
#endif
|
||||
|
||||
#include "aes.h"
|
||||
#include "crypt_blowfish.h"
|
||||
#include "lmhash.h"
|
||||
#include "pkcs5_pbkdf2.h"
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <NGExtensions/NGBase64Coding.h>
|
||||
@@ -61,6 +64,15 @@ static BOOL check_gnutls_init(void);
|
||||
static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
#endif
|
||||
|
||||
#define BLF_CRYPT_DEFAULT_COMPLEXITY (5)
|
||||
#define BLF_CRYPT_SALT_LEN (16)
|
||||
#define BLF_CRYPT_BUFFER_LEN (128)
|
||||
#define BLF_CRYPT_PREFIX_LEN (7+22+1) /* $2.$nn$ + salt */
|
||||
#define BLF_CRYPT_PREFIX "$2y"
|
||||
|
||||
static const char salt_chars[] =
|
||||
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
|
||||
@implementation NSData (SOGoCryptoExtension)
|
||||
|
||||
@@ -133,7 +145,7 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
*/
|
||||
+ (NSData *) generateSaltForLength: (unsigned int) theLength
|
||||
{
|
||||
return [NSData generateSaltForLength: theLength withBase64: NO];
|
||||
return [NSData generateSaltForLength: theLength withPrintable: NO];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,15 +153,16 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
* with doBase64 == YES then the data will be longer than theLength
|
||||
*
|
||||
* @param theLength Length of the binary data to be generated in bytes
|
||||
* @param doBase64 Convert the data into Base-64 before retuning it, be aware that this makes the binary data longer
|
||||
* @param doPrintable Use only printable characters
|
||||
* @return Pseudo-random binary data with length theLength or nil, if an error occured
|
||||
*/
|
||||
+ (NSData *) generateSaltForLength: (unsigned int) theLength
|
||||
withBase64: (BOOL) doBase64
|
||||
withPrintable: (BOOL) doPrintable
|
||||
{
|
||||
char *buf;
|
||||
int fd;
|
||||
NSData *data;
|
||||
unsigned int i;
|
||||
|
||||
fd = open("/dev/urandom", O_RDONLY);
|
||||
|
||||
@@ -158,12 +171,14 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
buf = (char *)malloc(theLength);
|
||||
read(fd, buf, theLength);
|
||||
close(fd);
|
||||
|
||||
data = [NSData dataWithBytesNoCopy: buf length: theLength freeWhenDone: YES];
|
||||
if(doBase64 == YES)
|
||||
if (doPrintable == YES)
|
||||
{
|
||||
return [data dataByEncodingBase64WithLineLength: 1024];
|
||||
for (i = 0; i < theLength; i++)
|
||||
{
|
||||
buf[i] = salt_chars[buf[i] % (sizeof(salt_chars)-1)];
|
||||
}
|
||||
}
|
||||
data = [NSData dataWithBytesNoCopy: buf length: theLength freeWhenDone: YES];
|
||||
return data;
|
||||
}
|
||||
return nil;
|
||||
@@ -174,10 +189,11 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
*
|
||||
* @param passwordScheme The scheme to use for hashing/encryption.
|
||||
* @param theSalt The salt to be used. If none is given but needed, it will be generated
|
||||
* @return Binary data from the encryption by the specified scheme. On error the funciton returns nil.
|
||||
* @return Binary data from the encryption by the specified scheme. On error the function returns nil.
|
||||
*/
|
||||
- (NSData *) asCryptedPassUsingScheme: (NSString *) passwordScheme
|
||||
withSalt: (NSData *) theSalt
|
||||
keyPath: (NSString *) theKeyPath
|
||||
{
|
||||
if ([passwordScheme caseInsensitiveCompare: @"none"] == NSOrderedSame ||
|
||||
[passwordScheme caseInsensitiveCompare: @"plain"] == NSOrderedSame ||
|
||||
@@ -243,18 +259,88 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
{
|
||||
return [self asSHA512CryptUsingSalt: theSalt];
|
||||
}
|
||||
else if ([passwordScheme caseInsensitiveCompare: @"blf-crypt"] == NSOrderedSame)
|
||||
{
|
||||
return [self asBlowfishCryptUsingSalt: theSalt];
|
||||
}
|
||||
else if ([passwordScheme caseInsensitiveCompare: @"pbkdf2"] == NSOrderedSame)
|
||||
{
|
||||
return [self asPBKDF2SHA1UsingSalt: theSalt];
|
||||
}
|
||||
else if ([[passwordScheme lowercaseString] hasPrefix: @"sym"])
|
||||
{
|
||||
// We first support one sym cipher, AES-128-CBC. If something else is provided
|
||||
// we return nil for now. Example of what theSalt might contain:
|
||||
// $AES-128-CBC$cinlbHKnyBApySphVCz6yA==$Z9hjCXfMhz4xbXkW+aMkAw==
|
||||
// If theSalt is empty, that means we are not validating a password
|
||||
// but rather changing it. In this case, we generate an IV.
|
||||
NSString *cipher, *iv;
|
||||
|
||||
cipher = nil;
|
||||
iv = nil;
|
||||
|
||||
if ([theSalt length])
|
||||
{
|
||||
NSString *s;
|
||||
NSArray *a;
|
||||
|
||||
s = [[NSString alloc] initWithData: theSalt encoding: NSUTF8StringEncoding];
|
||||
[s autorelease];
|
||||
a = [s componentsSeparatedByString: @"$"];
|
||||
cipher = [a objectAtIndex: 1];
|
||||
iv = [a objectAtIndex: 2];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([passwordScheme caseInsensitiveCompare: @"sym-aes-128-cbc"] == NSOrderedSame)
|
||||
cipher = @"AES-128-CBC";
|
||||
}
|
||||
|
||||
if ([cipher caseInsensitiveCompare: @"AES-128-CBC"] == NSOrderedSame)
|
||||
return [self asSymAES128CBCUsingIV: iv
|
||||
keyPath: theKeyPath];
|
||||
}
|
||||
// in case the scheme was not detected, return nil
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the given password data is equivalent with the
|
||||
* clear text password using the passed encryption scheme
|
||||
*
|
||||
* @param passwordScheme The password scheme to use for comparison
|
||||
* @param thePassword
|
||||
*/
|
||||
- (BOOL) verifyUsingScheme: (NSString *) passwordScheme
|
||||
withPassword: (NSData *) thePassword
|
||||
keyPath: (NSString *) theKeyPath
|
||||
{
|
||||
NSData *passwordCrypted;
|
||||
NSData *salt;
|
||||
|
||||
salt = [self extractSalt: passwordScheme];
|
||||
if (salt == nil)
|
||||
return NO;
|
||||
// encrypt self with the salt an compare the results
|
||||
passwordCrypted = [thePassword asCryptedPassUsingScheme: passwordScheme
|
||||
withSalt: salt
|
||||
keyPath: theKeyPath];
|
||||
|
||||
// return always false when there was a problem
|
||||
if (passwordCrypted == nil)
|
||||
return NO;
|
||||
|
||||
return [self isEqual: passwordCrypted];
|
||||
}
|
||||
|
||||
- (NSData *) asLM
|
||||
{
|
||||
NSData *out;
|
||||
|
||||
|
||||
unsigned char buf[14];
|
||||
unsigned char *o;
|
||||
unsigned int len;
|
||||
|
||||
|
||||
memset(buf, 0, 14);
|
||||
len = ([self length] >= 14 ? 14 : [self length]);
|
||||
[self getBytes: buf length: len];
|
||||
@@ -282,7 +368,7 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
#if defined(HAVE_GNUTLS)
|
||||
if (!check_gnutls_init())
|
||||
return nil;
|
||||
|
||||
|
||||
md4_buffer([self bytes], [self length], md4);
|
||||
#elif defined(HAVE_OPENSSL)
|
||||
MD4([self bytes], [self length], md4);
|
||||
@@ -339,7 +425,7 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
int i;
|
||||
int len;
|
||||
NSData *key;
|
||||
|
||||
|
||||
if ([self length] > 64)
|
||||
{
|
||||
key = [self asMD5];
|
||||
@@ -355,7 +441,7 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
// make sure the rest of the bytes is zero
|
||||
memset(inner + len, 0, 64 - len);
|
||||
memcpy(outer, inner, 64);
|
||||
|
||||
|
||||
for (i = 0; i < 64; i++)
|
||||
{
|
||||
inner[i] ^= 0x36;
|
||||
@@ -476,6 +562,48 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
return [NSData dataWithBytes: sha length: SHA512_DIGEST_LENGTH];
|
||||
}
|
||||
|
||||
- (NSData *) asSymAES128CBCUsingIV: (NSString *) theIV
|
||||
keyPath: (NSString *) theKeyPath
|
||||
{
|
||||
NSData *iv_d, *key_d, *cipherdata;
|
||||
NSMutableString *result;
|
||||
NSString *s;
|
||||
|
||||
char ciphertext[256], *iv_s, *key_s, *pass;
|
||||
unsigned int len;
|
||||
|
||||
len = ceil((double)[self length]/16) * 16;
|
||||
|
||||
if (theIV)
|
||||
iv_d = [theIV dataByDecodingBase64];
|
||||
else
|
||||
{
|
||||
iv_d = [NSData generateSaltForLength: len];
|
||||
theIV = [iv_d stringByEncodingBase64];
|
||||
}
|
||||
|
||||
iv_s = calloc([iv_d length]+1, sizeof(char));
|
||||
strncpy(iv_s, [iv_d bytes], [iv_d length]);
|
||||
|
||||
key_d = [NSData dataWithContentsOfFile: theKeyPath];
|
||||
key_s = calloc([key_d length]+1, sizeof(char));
|
||||
strncpy(key_s, [key_d bytes], [key_d length]);
|
||||
|
||||
pass = calloc(len, sizeof(char));
|
||||
strncpy(pass, [self bytes], [self length]);
|
||||
AES128_CBC_encrypt_buffer((uint8_t*)ciphertext, (uint8_t*)pass, (uint32_t)len, (const uint8_t*)key_s, (const uint8_t*)iv_s);
|
||||
|
||||
cipherdata = [NSData dataWithBytes: ciphertext length: 16];
|
||||
s = [[NSString alloc] initWithData: [cipherdata dataByEncodingBase64WithLineLength: 1024]
|
||||
encoding: NSASCIIStringEncoding];
|
||||
|
||||
result = [NSMutableString string];
|
||||
[result appendFormat: @"$AES-128-CBC$%@$%@", theIV, s];
|
||||
RELEASE(s);
|
||||
|
||||
return [result dataUsingEncoding: NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the data with SSHA. Uses openssl functions to generate it.
|
||||
*
|
||||
@@ -486,11 +614,12 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
*/
|
||||
- (NSData *) asSSHAUsingSalt: (NSData *) theSalt
|
||||
{
|
||||
//
|
||||
//
|
||||
NSMutableData *sshaData;
|
||||
|
||||
// generate salt, if not available
|
||||
if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8];
|
||||
if ([theSalt length] == 0)
|
||||
theSalt = [NSData generateSaltForLength: 8];
|
||||
|
||||
// put the pass and salt together as one data array
|
||||
sshaData = [NSMutableData dataWithData: self];
|
||||
@@ -517,7 +646,8 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
NSMutableData *sshaData;
|
||||
|
||||
// generate salt, if not available
|
||||
if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8];
|
||||
if ([theSalt length] == 0)
|
||||
theSalt = [NSData generateSaltForLength: 8];
|
||||
|
||||
// put the pass and salt together as one data array
|
||||
sshaData = [NSMutableData dataWithData: self];
|
||||
@@ -544,7 +674,8 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
NSMutableData *sshaData;
|
||||
|
||||
// generate salt, if not available
|
||||
if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8];
|
||||
if ([theSalt length] == 0)
|
||||
theSalt = [NSData generateSaltForLength: 8];
|
||||
|
||||
// put the pass and salt together as one data array
|
||||
sshaData = [NSMutableData dataWithData: self];
|
||||
@@ -571,7 +702,8 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
NSMutableData *smdData;
|
||||
|
||||
// generate salt, if not available
|
||||
if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8];
|
||||
if ([theSalt length] == 0)
|
||||
theSalt = [NSData generateSaltForLength: 8];
|
||||
|
||||
// put the pass and salt together as one data array
|
||||
smdData = [NSMutableData dataWithData: self];
|
||||
@@ -597,9 +729,10 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
|
||||
if ([theSalt length] == 0)
|
||||
{
|
||||
// make sure these characters are all printable by using base64
|
||||
theSalt = [NSData generateSaltForLength: 8 withBase64: YES];
|
||||
// make sure these characters are all printable
|
||||
theSalt = [NSData generateSaltForLength: 8 withPrintable: YES];
|
||||
}
|
||||
|
||||
cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding];
|
||||
|
||||
saltData = [NSMutableData dataWithData: [[NSString stringWithFormat:@"$%@$", magic] dataUsingEncoding: NSUTF8StringEncoding]];
|
||||
@@ -668,7 +801,8 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
// crypt() works with strings, so convert NSData to strings
|
||||
cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding];
|
||||
|
||||
if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8 withBase64: YES];
|
||||
if ([theSalt length] == 0)
|
||||
theSalt = [NSData generateSaltForLength: 8 withPrintable: YES];
|
||||
|
||||
saltString = [[NSString alloc] initWithData: theSalt encoding: NSUTF8StringEncoding];
|
||||
|
||||
@@ -682,6 +816,106 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
return [NSData dataWithBytes: buf length: strlen(buf)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the data using blowfish-crypt
|
||||
* @param theSalt The salt to be used must not be nil, if empty, one will be generated
|
||||
*/
|
||||
- (NSData *) asBlowfishCryptUsingSalt: (NSData *) theSalt
|
||||
{
|
||||
NSString *cleartext;
|
||||
char hashed_password[BLF_CRYPT_BUFFER_LEN];
|
||||
char magic_salt[BLF_CRYPT_PREFIX_LEN]; // contains $2.$nn$ + salt
|
||||
|
||||
if ([theSalt length] == 0)
|
||||
{
|
||||
// generate a salt with default complexity if none was provided
|
||||
NSData* salt = [NSData generateSaltForLength: BLF_CRYPT_SALT_LEN];
|
||||
if (_crypt_gensalt_blowfish_rn(BLF_CRYPT_PREFIX, BLF_CRYPT_DEFAULT_COMPLEXITY,
|
||||
[salt bytes], BLF_CRYPT_SALT_LEN,
|
||||
magic_salt, BLF_CRYPT_PREFIX_LEN) == NULL)
|
||||
return nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* salt = [theSalt bytes];
|
||||
if ([theSalt length] < BLF_CRYPT_PREFIX_LEN ||
|
||||
salt[0] != '$' || salt[1] != '2' ||
|
||||
salt[2] < 'a' || salt[2] > 'z' ||
|
||||
salt[3] != '$')
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
memcpy(magic_salt, salt, BLF_CRYPT_PREFIX_LEN);
|
||||
}
|
||||
|
||||
cleartext = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding];
|
||||
const char* password = [cleartext UTF8String];
|
||||
|
||||
char* bf_res = _crypt_blowfish_rn(password, magic_salt,
|
||||
hashed_password, BLF_CRYPT_BUFFER_LEN);
|
||||
[cleartext autorelease];
|
||||
|
||||
if (bf_res == NULL)
|
||||
return nil;
|
||||
|
||||
return [NSData dataWithBytes: hashed_password length: strlen(hashed_password)];
|
||||
}
|
||||
|
||||
- (NSData *) asPBKDF2SHA1UsingSalt: (NSData *) theSalt
|
||||
{
|
||||
NSString *saltString;
|
||||
unsigned char hashed_password[PBKDF2_KEY_SIZE_SHA1] = {0};
|
||||
int rounds = 0;
|
||||
|
||||
if ([theSalt length] == 0)
|
||||
{
|
||||
// generate a salt with default complexity if none was provided
|
||||
NSData* saltData = [NSData generateSaltForLength: PBKDF2_SALT_LEN withPrintable: YES];
|
||||
saltString = [[NSString alloc] initWithData: saltData encoding: NSUTF8StringEncoding];
|
||||
[saltString autorelease];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString *saltAndRounds;
|
||||
NSArray *saltAndRoundsComponents;
|
||||
saltAndRounds = [[NSString alloc] initWithData: theSalt encoding: NSUTF8StringEncoding];
|
||||
// salt is expected to be of the form salt$rounds
|
||||
saltAndRoundsComponents = [saltAndRounds componentsSeparatedByString: @"$"];
|
||||
AUTORELEASE(saltAndRounds);
|
||||
|
||||
if ([saltAndRoundsComponents count] != 2)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
saltString = [saltAndRoundsComponents objectAtIndex: 0];
|
||||
|
||||
rounds = [[saltAndRoundsComponents objectAtIndex: 1] intValue];
|
||||
}
|
||||
|
||||
if (rounds == 0)
|
||||
rounds = PBKDF2_DEFAULT_ROUNDS;
|
||||
|
||||
const char* password = [self bytes];
|
||||
const unsigned char* salt = (const unsigned char*)[saltString UTF8String];
|
||||
#if defined(HAVE_GNUTLS)
|
||||
if (!check_gnutls_init())
|
||||
return nil;
|
||||
#endif
|
||||
if (pkcs5_pbkdf2(password, [self length], salt, PBKDF2_SALT_LEN,
|
||||
hashed_password, PBKDF2_KEY_SIZE_SHA1,
|
||||
rounds) != 0)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *passwordData =
|
||||
[NSData dataWithBytesNoCopy: hashed_password length: PBKDF2_KEY_SIZE_SHA1 freeWhenDone: NO];
|
||||
NSString *hexHash = [NSData encodeDataAsHexString: passwordData];
|
||||
|
||||
NSString* result = [NSString stringWithFormat: @"$1$%@$%u$%@", saltString, rounds, hexHash];
|
||||
return [result dataUsingEncoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the salt from a password encrypted with a specied scheme
|
||||
*
|
||||
@@ -698,19 +932,22 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
|
||||
// for the ssha schemes the salt is appended at the endif
|
||||
// so the range with the salt are bytes after each digest length
|
||||
if ([theScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame)
|
||||
if ([theScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame ||
|
||||
[theScheme caseInsensitiveCompare: @"blf-crypt"] == NSOrderedSame)
|
||||
{
|
||||
// for crypt schemes simply use the whole string
|
||||
// for (blf-)crypt schemes simply use the whole string
|
||||
// the crypt() function is able to extract it by itself
|
||||
r = NSMakeRange(0, len);
|
||||
}
|
||||
else if ([theScheme caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame ||
|
||||
[theScheme caseInsensitiveCompare: @"sha256-crypt"] == NSOrderedSame ||
|
||||
[theScheme caseInsensitiveCompare: @"sha512-crypt"] == NSOrderedSame)
|
||||
[theScheme caseInsensitiveCompare: @"sha256-crypt"] == NSOrderedSame ||
|
||||
[theScheme caseInsensitiveCompare: @"sha512-crypt"] == NSOrderedSame ||
|
||||
[theScheme caseInsensitiveCompare: @"pbkdf2"] == NSOrderedSame)
|
||||
{
|
||||
// md5-crypt is generated the following "$1$<salt>$<encrypted pass>"
|
||||
// sha256-crypt is generated the following "$5$<salt>$<encrypted pass>"
|
||||
// sha512-crypt is generated the following "$6$<salt>$<encrypted pass>"
|
||||
// pbkdf2 is generated as "$1$<salt>$<rounds>$<encrypted pass>"
|
||||
NSString *cryptString;
|
||||
NSArray *cryptParts;
|
||||
|
||||
@@ -725,21 +962,21 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
}
|
||||
// second is the identifier of md5-crypt/sha256-crypt or sha512-crypt
|
||||
else if ([[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"1"] == NSOrderedSame ||
|
||||
[[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"5"] == NSOrderedSame ||
|
||||
[[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"6"] == NSOrderedSame)
|
||||
[[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"5"] == NSOrderedSame ||
|
||||
[[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"6"] == NSOrderedSame)
|
||||
{
|
||||
// third is the salt; convert it to NSData
|
||||
if ([cryptParts count] == 4)
|
||||
return [[cryptParts objectAtIndex: 2] dataUsingEncoding: NSUTF8StringEncoding];
|
||||
else
|
||||
{
|
||||
NSString *saltWithRounds;
|
||||
// third is the salt; convert it to NSData
|
||||
if ([cryptParts count] == 4)
|
||||
return [[cryptParts objectAtIndex: 2] dataUsingEncoding: NSUTF8StringEncoding];
|
||||
else
|
||||
{
|
||||
NSString *saltWithRounds;
|
||||
|
||||
saltWithRounds = [NSString stringWithFormat: @"%@$%@", [cryptParts objectAtIndex: 2], [cryptParts objectAtIndex: 3]];
|
||||
saltWithRounds = [NSString stringWithFormat: @"%@$%@", [cryptParts objectAtIndex: 2], [cryptParts objectAtIndex: 3]];
|
||||
|
||||
return [saltWithRounds dataUsingEncoding: NSUTF8StringEncoding];
|
||||
}
|
||||
}
|
||||
return [saltWithRounds dataUsingEncoding: NSUTF8StringEncoding];
|
||||
}
|
||||
}
|
||||
// nothing good
|
||||
return [NSData data];
|
||||
}
|
||||
@@ -759,6 +996,11 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
{
|
||||
r = NSMakeRange(MD5_DIGEST_LENGTH, len - MD5_DIGEST_LENGTH);
|
||||
}
|
||||
else if ([[theScheme lowercaseString] hasPrefix: @"sym"])
|
||||
{
|
||||
// For sym we return everything
|
||||
r = NSMakeRange(0, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
// return empty string on unknown scheme
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* NSString+Crypto.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2012 Nicolas Höft
|
||||
* Copyright (C) 2012-2016 Inverse inc.
|
||||
* Copyright (C) 2012-2020 Inverse inc.
|
||||
*
|
||||
* Author: Nicolas Höft
|
||||
* Inverse inc.
|
||||
@@ -39,15 +39,18 @@ typedef enum {
|
||||
@interface NSString (SOGoCryptoExtension)
|
||||
|
||||
- (BOOL) isEqualToCrypted: (NSString *) cryptedPassword
|
||||
withDefaultScheme: (NSString *) theScheme;
|
||||
withDefaultScheme: (NSString *) theScheme
|
||||
keyPath: (NSString *) theKeyPath;
|
||||
|
||||
- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme
|
||||
withSalt: (NSData *) theSalt
|
||||
andEncoding: (keyEncoding) encoding;
|
||||
andEncoding: (keyEncoding) encoding
|
||||
keyPath: (NSString *) theKeyPath;
|
||||
|
||||
// this method uses the default encoding (base64, plain, hex)
|
||||
// and generates a salt when necessary
|
||||
- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme;
|
||||
- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme
|
||||
keyPath: (NSString *) theKeyPath;
|
||||
|
||||
- (NSArray *) splitPasswordWithDefaultScheme: (NSString *) defaultScheme;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* NSString+Crypto.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2012 Nicolas Höft
|
||||
* Copyright (C) 2012-2015 Inverse inc.
|
||||
* Copyright (C) 2012-2019 Inverse inc.
|
||||
*
|
||||
* Author: Nicolas Höft
|
||||
* Inverse inc.
|
||||
@@ -41,17 +41,17 @@
|
||||
{
|
||||
NSRange r;
|
||||
int len;
|
||||
|
||||
|
||||
len = [self length];
|
||||
if (len == 0)
|
||||
return @"";
|
||||
if ([self characterAtIndex:0] != '{')
|
||||
return @"";
|
||||
|
||||
|
||||
r = [self rangeOfString:@"}" options:(NSLiteralSearch)];
|
||||
if (r.length == 0)
|
||||
return @"";
|
||||
|
||||
|
||||
r.length = (r.location - 1);
|
||||
r.location = 1;
|
||||
return [[self substringWithRange:r] lowercaseString];
|
||||
@@ -73,7 +73,7 @@
|
||||
NSString *scheme;
|
||||
NSString *pass;
|
||||
NSArray *encodingAndScheme;
|
||||
|
||||
|
||||
NSRange range;
|
||||
int selflen, len;
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
encodingAndScheme = [NSString getDefaultEncodingForScheme: scheme];
|
||||
|
||||
pass = [self substringWithRange: range];
|
||||
|
||||
|
||||
// Returns an array with [scheme, password, encoding]
|
||||
return [NSArray arrayWithObjects: [encodingAndScheme objectAtIndex: 1], pass, [encodingAndScheme objectAtIndex: 0], nil];
|
||||
}
|
||||
@@ -106,14 +106,13 @@
|
||||
*/
|
||||
- (BOOL) isEqualToCrypted: (NSString *) cryptedPassword
|
||||
withDefaultScheme: (NSString *) theScheme
|
||||
keyPath: (NSString *) theKeyPath
|
||||
{
|
||||
NSArray *passInfo;
|
||||
NSString *selfCrypted;
|
||||
NSString *pass;
|
||||
NSString *scheme;
|
||||
NSData *salt;
|
||||
NSData *decodedData;
|
||||
NSNumber *encodingNumber;
|
||||
NSData *passwordData;
|
||||
keyEncoding encoding;
|
||||
|
||||
// split scheme and pass
|
||||
@@ -121,8 +120,7 @@
|
||||
|
||||
scheme = [passInfo objectAtIndex: 0];
|
||||
pass = [passInfo objectAtIndex: 1];
|
||||
encodingNumber = [passInfo objectAtIndex: 2];
|
||||
encoding = [encodingNumber intValue];
|
||||
encoding = [[passInfo objectAtIndex: 2] intValue];
|
||||
|
||||
if (encoding == encHex)
|
||||
{
|
||||
@@ -153,35 +151,26 @@
|
||||
decodedData = [pass dataUsingEncoding: NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
salt = [decodedData extractSalt: scheme];
|
||||
|
||||
// encrypt self with the salt an compare the results
|
||||
selfCrypted = [self asCryptedPassUsingScheme: scheme
|
||||
withSalt: salt
|
||||
andEncoding: encoding];
|
||||
|
||||
// return always false when there was a problem
|
||||
if (selfCrypted == nil)
|
||||
return NO;
|
||||
|
||||
if ([selfCrypted isEqualToString: pass] == YES)
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
passwordData = [self dataUsingEncoding: NSUTF8StringEncoding];
|
||||
return [decodedData verifyUsingScheme: scheme
|
||||
withPassword: passwordData
|
||||
keyPath: theKeyPath];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls asCryptedPassUsingScheme:withSalt:andEncoding: with an empty salt and uses
|
||||
* the default encoding.
|
||||
*
|
||||
* @param passwordScheme
|
||||
* @param passwordScheme: The password scheme to hash the cleartext password.
|
||||
* @return If successful, the encrypted and encoded NSString of the format {scheme}pass, or nil if the scheme did not exists or an error occured
|
||||
*/
|
||||
- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme
|
||||
keyPath: (NSString *) theKeyPath
|
||||
{
|
||||
return [self asCryptedPassUsingScheme: passwordScheme
|
||||
withSalt: [NSData data]
|
||||
andEncoding: encDefault];
|
||||
andEncoding: encDefault
|
||||
keyPath: theKeyPath];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,6 +187,7 @@
|
||||
- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme
|
||||
withSalt: (NSData *) theSalt
|
||||
andEncoding: (keyEncoding) userEncoding
|
||||
keyPath: (NSString *) theKeyPath
|
||||
{
|
||||
keyEncoding dataEncoding;
|
||||
NSData* cryptedData;
|
||||
@@ -219,7 +209,10 @@
|
||||
|
||||
// convert NSString to NSData and apply encryption scheme
|
||||
cryptedData = [self dataUsingEncoding: NSUTF8StringEncoding];
|
||||
cryptedData = [cryptedData asCryptedPassUsingScheme: passwordScheme withSalt: theSalt];
|
||||
cryptedData = [cryptedData asCryptedPassUsingScheme: passwordScheme
|
||||
withSalt: theSalt
|
||||
keyPath: theKeyPath];
|
||||
|
||||
// abort on unsupported scheme or error
|
||||
if (cryptedData == nil)
|
||||
return nil;
|
||||
@@ -229,7 +222,7 @@
|
||||
// hex encoding
|
||||
return [NSData encodeDataAsHexString: cryptedData];
|
||||
}
|
||||
else if(dataEncoding == encBase64)
|
||||
else if (dataEncoding == encBase64)
|
||||
{
|
||||
// base64 encoding
|
||||
NSString *s = [[NSString alloc] initWithData: [cryptedData dataByEncodingBase64WithLineLength: 1024]
|
||||
@@ -352,7 +345,7 @@
|
||||
|
||||
// See http://en.wikipedia.org/wiki/LM_hash#Algorithm
|
||||
d = [[self uppercaseString] dataUsingEncoding: NSWindowsCP1252StringEncoding];
|
||||
|
||||
|
||||
return [[NSData encodeDataAsHexString: [d asLM]] uppercaseString];
|
||||
}
|
||||
|
||||
|
||||
@@ -739,7 +739,7 @@ static int cssEscapingCount;
|
||||
substrLen = [substring length];
|
||||
|
||||
matchRange = NSMakeRange (0, selfLen);
|
||||
while (!done)
|
||||
while (!done && matchRange.length > 0)
|
||||
{
|
||||
substrRange = [self rangeOfString: substring options: 0 range: matchRange];
|
||||
if (substrRange.location == NSNotFound)
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
|
||||
@class NGImap4Connection;
|
||||
|
||||
@class SOGoGroup;
|
||||
@class SOGoObject;
|
||||
@class SOGoUser;
|
||||
@class SOGoUserDefaults;
|
||||
@@ -67,13 +66,6 @@
|
||||
withName: (NSString *) userName;
|
||||
- (id) userNamed: (NSString *) name;
|
||||
|
||||
- (void) registerGroup: (SOGoGroup *) group
|
||||
withName: (NSString *) groupName
|
||||
inDomain: (NSString *) domainName;
|
||||
|
||||
- (id) groupNamed: (NSString *) groupName
|
||||
inDomain: (NSString *) domainName;
|
||||
|
||||
- (void) registerIMAP4Connection: (NGImap4Connection *) connection
|
||||
forKey: (NSString *) key;
|
||||
- (NGImap4Connection *) imap4ConnectionForKey: (NSString *) key;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SOGoCache.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2008-2014 Inverse inc.
|
||||
* Copyright (C) 2008-2020 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
|
||||
@@ -260,22 +260,6 @@ static memcached_st *handle = NULL;
|
||||
return [users objectForKey: name];
|
||||
}
|
||||
|
||||
- (void) registerGroup: (SOGoGroup *) group
|
||||
withName: (NSString *) groupName
|
||||
inDomain: (NSString *) domainName
|
||||
|
||||
{
|
||||
if (group)
|
||||
[groups setObject: group forKey: [NSString stringWithFormat: @"%@+%@", groupName, domainName]];
|
||||
}
|
||||
|
||||
- (id) groupNamed: (NSString *) groupName
|
||||
inDomain: (NSString *) domainName
|
||||
|
||||
{
|
||||
return [groups objectForKey: [NSString stringWithFormat: @"%@+%@", groupName, domainName]];
|
||||
}
|
||||
|
||||
- (void) registerIMAP4Connection: (NGImap4Connection *) connection
|
||||
forKey: (NSString *) key
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
SOGoLDAPContactInfoAttribute = "description";
|
||||
|
||||
SOGoMailingMechanism = "sendmail";
|
||||
SOGoSMTPServer = "localhost";
|
||||
SOGoSMTPServer = "smtp://localhost";
|
||||
SOGoMailSpoolPath = "/var/spool/sogo";
|
||||
|
||||
SOGoWebAccessEnabled = YES;
|
||||
@@ -103,7 +103,6 @@
|
||||
SOGoMailReplyPlacement = "below";
|
||||
SOGoMailSignaturePlacement = "below";
|
||||
SOGoRefreshViewIntervals = ( 1, 2, 5, 10, 20, 30, 60 );
|
||||
SOGoMailComposeMessageType = "text";
|
||||
SOGoMailListViewColumnsOrder = ( "Thread", "Flagged", "Attachment", "Subject",
|
||||
"From", "Unread", "Date", "Priority",
|
||||
"Size" );
|
||||
|
||||
@@ -48,10 +48,13 @@
|
||||
- (BOOL) forceExternalLoginWithEmail;
|
||||
- (BOOL) externalAvatarsEnabled;
|
||||
- (BOOL) sieveScriptsEnabled;
|
||||
- (NSString *) sieveScriptHeaderTemplateFile;
|
||||
- (NSString *) sieveScriptFooterTemplateFile;
|
||||
- (BOOL) forwardEnabled;
|
||||
- (int) forwardConstraints;
|
||||
- (NSArray *) forwardConstraintsDomains;
|
||||
- (BOOL) vacationEnabled;
|
||||
- (BOOL) vacationPeriodEnabled;
|
||||
- (NSString *) vacationDefaultSubject;
|
||||
- (NSString *) vacationHeaderTemplateFile;
|
||||
- (NSString *) vacationFooterTemplateFile;
|
||||
|
||||
@@ -196,6 +196,16 @@
|
||||
return [self boolForKey: @"SOGoSieveScriptsEnabled"];
|
||||
}
|
||||
|
||||
- (NSString *) sieveScriptHeaderTemplateFile
|
||||
{
|
||||
return [self stringForKey: @"SOGoSieveScriptHeaderTemplateFile"];
|
||||
}
|
||||
|
||||
- (NSString *) sieveScriptFooterTemplateFile
|
||||
{
|
||||
return [self stringForKey: @"SOGoSieveScriptFooterTemplateFile"];
|
||||
}
|
||||
|
||||
- (BOOL) forwardEnabled
|
||||
{
|
||||
return [self boolForKey: @"SOGoForwardEnabled"];
|
||||
@@ -220,6 +230,18 @@
|
||||
return [self boolForKey: @"SOGoVacationEnabled"];
|
||||
}
|
||||
|
||||
- (BOOL) vacationPeriodEnabled
|
||||
{
|
||||
id o;
|
||||
|
||||
o = [self stringForKey: @"SOGoVacationPeriodEnabled"];
|
||||
|
||||
if (o)
|
||||
return [o boolValue];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *) vacationDefaultSubject
|
||||
{
|
||||
return [self stringForKey: @"SOGoVacationDefaultSubject"];
|
||||
@@ -259,7 +281,15 @@
|
||||
|
||||
- (NSString *) smtpServer
|
||||
{
|
||||
return [self stringForKey: @"SOGoSMTPServer"];
|
||||
NSString *server;
|
||||
server = [self stringForKey: @"SOGoSMTPServer"];
|
||||
// backwards compatibility
|
||||
if (![server hasPrefix: @"smtp://"] &&
|
||||
![server hasPrefix: @"smtps://"])
|
||||
{
|
||||
return [NSString stringWithFormat: @"smtp://%@", server];
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
- (NSString *) smtpAuthenticationType
|
||||
|
||||
+102
-57
@@ -49,7 +49,7 @@
|
||||
#import "SOGoCache.h"
|
||||
#import "SOGoContentObject.h"
|
||||
#import "SOGoDomainDefaults.h"
|
||||
#import "SOGoGroup.h"
|
||||
#import "SOGoSource.h"
|
||||
#import "SOGoParentFolder.h"
|
||||
#import "SOGoPermissions.h"
|
||||
#import "SOGoUser.h"
|
||||
@@ -477,11 +477,18 @@ static NSArray *childRecordFields = nil;
|
||||
|
||||
if ([newName length])
|
||||
{
|
||||
[self renameTo: newName];
|
||||
error = nil;
|
||||
NS_DURING
|
||||
{
|
||||
[self renameTo: newName];
|
||||
error = nil;
|
||||
}
|
||||
NS_HANDLER
|
||||
error = [NSException exceptionWithHTTPStatus: 409
|
||||
reason: @"Existing name"];
|
||||
NS_ENDHANDLER;
|
||||
}
|
||||
else
|
||||
error = [NSException exceptionWithHTTPStatus: 400
|
||||
error = [NSException exceptionWithHTTPStatus: 403
|
||||
reason: @"Empty string"];
|
||||
|
||||
return error;
|
||||
@@ -645,6 +652,12 @@ static NSArray *childRecordFields = nil;
|
||||
|
||||
- (void) renameTo: (NSString *) newName
|
||||
{
|
||||
if (!displayName)
|
||||
[self displayName];
|
||||
|
||||
if ([displayName isEqualToString: newName])
|
||||
return;
|
||||
|
||||
#warning SOGoFolder should have the corresponding method
|
||||
[displayName release];
|
||||
displayName = nil;
|
||||
@@ -910,34 +923,47 @@ static NSArray *childRecordFields = nil;
|
||||
{
|
||||
NSMutableDictionary *moduleSettings, *folderShowAlarms;
|
||||
NSMutableArray *folderSubscription;
|
||||
NSString *subscriptionPointer;
|
||||
NSString *subscriptionPointer, *domain;
|
||||
NSMutableArray *allUsers;
|
||||
SOGoUserSettings *us;
|
||||
NSDictionary *dict;
|
||||
SOGoUser *sogoUser;
|
||||
BOOL rc;
|
||||
int i;
|
||||
|
||||
dict = [[SOGoUserManager sharedUserManager] contactInfosForUserWithUIDorEmail: theIdentifier];
|
||||
domain = [[context activeUser] domain];
|
||||
dict = [[SOGoUserManager sharedUserManager] contactInfosForUserWithUIDorEmail: theIdentifier
|
||||
inDomain: domain];
|
||||
|
||||
if ([[dict objectForKey: @"isGroup"] boolValue])
|
||||
if (dict && [[dict objectForKey: @"isGroup"] boolValue])
|
||||
{
|
||||
SOGoGroup *aGroup;
|
||||
id <SOGoSource> source;
|
||||
|
||||
aGroup = [SOGoGroup groupWithIdentifier: theIdentifier
|
||||
inDomain: [[context activeUser] domain]];
|
||||
allUsers = [NSMutableArray arrayWithArray: [aGroup members]];
|
||||
source = [[SOGoUserManager sharedUserManager] sourceWithID: [dict objectForKey: @"SOGoSource"]];
|
||||
if ([source conformsToProtocol:@protocol(SOGoMembershipSource)])
|
||||
{
|
||||
NSArray *members;
|
||||
|
||||
// We remove the active user from the group (if present) in order to
|
||||
// not subscribe him to their own resource!
|
||||
[allUsers removeObject: [context activeUser]];
|
||||
members = [(id<SOGoMembershipSource>)(source) membersForGroupWithUID: [dict objectForKey: @"c_uid"]];
|
||||
allUsers = [NSMutableArray array];
|
||||
|
||||
for (i = 0; i < [members count]; i++)
|
||||
{
|
||||
[allUsers addObject: [[members objectAtIndex: i] objectForKey: @"c_uid"]];
|
||||
}
|
||||
// We remove the active user from the group (if present) in order to
|
||||
// not subscribe him to their own resource!
|
||||
[allUsers removeObject: [[context activeUser] login]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Inconsistency (subscribeUserOrGroup:reallyDo:response:) error - got group identifier (%@) from a source (%@) that does not support groups (%@).", theIdentifier, [dict objectForKey: @"SOGoSource"], NSStringFromClass([source class])];
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sogoUser = [SOGoUser userWithLogin: theIdentifier roles: nil];
|
||||
|
||||
if (sogoUser)
|
||||
allUsers = [NSArray arrayWithObject: sogoUser];
|
||||
if (dict)
|
||||
allUsers = [NSArray arrayWithObject: [dict objectForKey: @"c_uid"]];
|
||||
else
|
||||
allUsers = [NSArray array];
|
||||
}
|
||||
@@ -950,8 +976,7 @@ static NSArray *childRecordFields = nil;
|
||||
|
||||
for (i = 0; i < [allUsers count]; i++)
|
||||
{
|
||||
sogoUser = [allUsers objectAtIndex: i];
|
||||
us = [sogoUser userSettings];
|
||||
us = [SOGoUserSettings settingsForUser: [allUsers objectAtIndex: i]];
|
||||
moduleSettings = [us objectForKey: [container nameInContainer]];
|
||||
if (!(moduleSettings
|
||||
&& [moduleSettings isKindOfClass: [NSMutableDictionary class]]))
|
||||
@@ -1608,14 +1633,11 @@ static NSArray *childRecordFields = nil;
|
||||
matchingUID: (NSString *) uid
|
||||
{
|
||||
int count, max;
|
||||
NSDictionary *record;
|
||||
NSDictionary *record, *dict;
|
||||
NSString *currentUID, *domain;
|
||||
SOGoGroup *group;
|
||||
NSMutableArray *acls;
|
||||
|
||||
acls = [NSMutableArray array];
|
||||
#warning should it be the domain of the ownerUser instead?
|
||||
domain = [[context activeUser] domain];
|
||||
|
||||
max = [records count];
|
||||
for (count = 0; count < max; count++)
|
||||
@@ -1624,17 +1646,27 @@ static NSArray *childRecordFields = nil;
|
||||
currentUID = [record valueForKey: @"c_uid"];
|
||||
if ([currentUID hasPrefix: @"@"])
|
||||
{
|
||||
group = [[SOGoCache sharedCache] groupNamed: currentUID inDomain: domain];
|
||||
domain = [[context activeUser] domain];
|
||||
dict = [[SOGoUserManager sharedUserManager] contactInfosForUserWithUIDorEmail: currentUID
|
||||
inDomain: domain];
|
||||
if (dict)
|
||||
{
|
||||
id <SOGoSource> source;
|
||||
|
||||
if (!group)
|
||||
{
|
||||
group = [SOGoGroup groupWithIdentifier: currentUID
|
||||
inDomain: domain];
|
||||
[[SOGoCache sharedCache] registerGroup: group withName: currentUID inDomain: domain];
|
||||
}
|
||||
|
||||
if (group && [group hasMemberWithUID: uid])
|
||||
[acls addObject: [record valueForKey: @"c_role"]];
|
||||
source = [[SOGoUserManager sharedUserManager] sourceWithID: [dict objectForKey: @"SOGoSource"]];
|
||||
if ([source conformsToProtocol:@protocol(SOGoMembershipSource)])
|
||||
{
|
||||
if ([(id<SOGoMembershipSource>)(source) groupWithUIDHasMemberWithUID: currentUID memberUid: uid])
|
||||
[acls addObject: [record valueForKey: @"c_role"]];
|
||||
else
|
||||
[self errorWithFormat: @"Group %@ has no member with UID %@", currentUID, uid];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Inconsistency (_aclsFromGroupRoles:matchingUID:) error - got group identifier (%@) from a source (%@) that does not support groups (%@).", currentUID, [dict objectForKey: @"SOGoSource"], NSStringFromClass([source class])];
|
||||
return [NSArray array];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1744,36 +1776,49 @@ static NSArray *childRecordFields = nil;
|
||||
NSString *uid, *uids, *qs, *objectPath, *domain;
|
||||
NSMutableArray *usersAndGroups, *groupsMembers;
|
||||
NSMutableDictionary *aclsForObject;
|
||||
SOGoGroup *group;
|
||||
|
||||
unsigned int i;
|
||||
|
||||
if ([users count] > 0)
|
||||
{
|
||||
domain = [[context activeUser] domain];
|
||||
usersAndGroups = [NSMutableArray arrayWithArray: users];
|
||||
groupsMembers = [NSMutableArray array];
|
||||
for (i = 0; i < [usersAndGroups count]; i++)
|
||||
{
|
||||
NSDictionary *dict;
|
||||
|
||||
uid = [usersAndGroups objectAtIndex: i];
|
||||
group = [SOGoGroup groupWithIdentifier: uid inDomain: domain];
|
||||
if (group)
|
||||
domain = [[context activeUser] domain];
|
||||
dict = [[SOGoUserManager sharedUserManager] contactInfosForUserWithUIDorEmail: uid
|
||||
inDomain: domain];
|
||||
if (dict && [[dict objectForKey: @"isGroup"] boolValue])
|
||||
{
|
||||
NSArray *members;
|
||||
SOGoUser *user;
|
||||
unsigned int j;
|
||||
|
||||
// Fetch members to remove them from the cache along the group
|
||||
members = [group members];
|
||||
for (j = 0; j < [members count]; j++)
|
||||
id <SOGoSource> source;
|
||||
source = [[SOGoUserManager sharedUserManager] sourceWithID: [dict objectForKey: @"SOGoSource"]];
|
||||
if ([source conformsToProtocol:@protocol(SOGoMembershipSource)])
|
||||
{
|
||||
user = [members objectAtIndex: j];
|
||||
[groupsMembers addObject: [user login]];
|
||||
}
|
||||
NSDictionary *user;
|
||||
NSArray *members;
|
||||
unsigned int j;
|
||||
|
||||
if (![uid hasPrefix: @"@"])
|
||||
// Prefix the UID with the character "@" when dealing with a group
|
||||
[usersAndGroups replaceObjectAtIndex: i
|
||||
withObject: [NSString stringWithFormat: @"@%@", uid]];
|
||||
// Fetch members to remove them from the cache along the group
|
||||
members = [(id<SOGoMembershipSource>)(source) membersForGroupWithUID: uid];
|
||||
for (j = 0; j < [members count]; j++)
|
||||
{
|
||||
user = [members objectAtIndex: j];
|
||||
[groupsMembers addObject: [user objectForKey: @"c_uid"]];
|
||||
}
|
||||
|
||||
if (![uid hasPrefix: @"@"])
|
||||
// Prefix the UID with the character "@" when dealing with a group
|
||||
[usersAndGroups replaceObjectAtIndex: i
|
||||
withObject: [NSString stringWithFormat: @"@%@", uid]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Inconsistency error (removeAclsForUsers:forObjectAtPath:) - got group identifier (%@) from a source (%@) that does not support groups (%@).", uid, [dict objectForKey: @"SOGoSource"], NSStringFromClass([source class])];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
objectPath = [objectPathArray componentsJoinedByString: @"/"];
|
||||
@@ -1835,7 +1880,6 @@ static NSArray *childRecordFields = nil;
|
||||
{
|
||||
NSString *objectPath, *aUID, *domain;
|
||||
NSMutableArray *newRoles;
|
||||
SOGoGroup *group;
|
||||
|
||||
objectPath = [objectPathArray componentsJoinedByString: @"/"];
|
||||
|
||||
@@ -1845,10 +1889,11 @@ static NSArray *childRecordFields = nil;
|
||||
aUID = uid;
|
||||
if (![uid hasPrefix: @"@"])
|
||||
{
|
||||
// Prefix the UID with the character "@" when dealing with a group
|
||||
NSDictionary *dict;
|
||||
domain = [[context activeUser] domain];
|
||||
group = [SOGoGroup groupWithIdentifier: uid inDomain: domain];
|
||||
if (group)
|
||||
dict = [[SOGoUserManager sharedUserManager] contactInfosForUserWithUIDorEmail: uid
|
||||
inDomain: domain];
|
||||
if ([[dict objectForKey: @"isGroup"] boolValue])
|
||||
{
|
||||
aUID = [NSString stringWithFormat: @"@%@", uid];
|
||||
// Remove all roles when defining ACLs for a group
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/* SOGoGroup.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2012 Inverse inc.
|
||||
*
|
||||
* Author: Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __SOGOGROUP_H__
|
||||
#define __SOGOGROUP_H__
|
||||
|
||||
#import <Foundation/NSObject.h>
|
||||
|
||||
@class LDAPSource;
|
||||
@class NSArray;
|
||||
@class NSMutableArray;
|
||||
@class NSString;
|
||||
@class NGLdapEntry;
|
||||
|
||||
@protocol SOGoSource;
|
||||
|
||||
@interface SOGoGroup : NSObject
|
||||
{
|
||||
@private
|
||||
NSString *_identifier;
|
||||
NSString *_domain;
|
||||
NGLdapEntry *_entry;
|
||||
NSObject <SOGoSource> *_source;
|
||||
NSMutableArray *_members;
|
||||
}
|
||||
|
||||
+ (id) groupWithIdentifier: (NSString *) theID
|
||||
inDomain: (NSString *) domain;
|
||||
+ (id) groupWithEmail: (NSString *) theEmail
|
||||
inDomain: (NSString *) domain;
|
||||
+ (id) groupWithValue: (NSString *) theValue
|
||||
andSourceSelector: (SEL) theSelector
|
||||
inDomain: (NSString *) domain;
|
||||
|
||||
- (NSArray *) members;
|
||||
|
||||
- (BOOL) hasMemberWithUID: (NSString *) memberUID;
|
||||
|
||||
@end
|
||||
|
||||
#endif // __SOGOGROUP_H__
|
||||
@@ -1,370 +0,0 @@
|
||||
/* SOGoGroup.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2014 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
Here are some group samples:
|
||||
|
||||
[ POSIX group ]
|
||||
|
||||
dn: cn=it-staff,ou=Group,dc=zzz,dc=xxx,dc=yyy
|
||||
objectClass: posixGroup
|
||||
objectClass: top
|
||||
cn: it-staff
|
||||
userPassword: {crypt}x
|
||||
gidNumber: 8000
|
||||
memberUid: lsa
|
||||
memberUid: mrm
|
||||
memberUid: ij
|
||||
memberUid: no
|
||||
memberUid: ld
|
||||
memberUid: db
|
||||
memberUid: rgl
|
||||
memberUid: ja
|
||||
memberUid: hbt
|
||||
memberUid: hossein
|
||||
|
||||
dn: cn=inverse,ou=groups,dc=inverse,dc=ca
|
||||
objectClass: groupOfUniqueNames
|
||||
objectClass: top
|
||||
objectClass: extensibleObject
|
||||
uniqueMember: uid=flachapelle,ou=users,dc=inverse,dc=ca
|
||||
uniqueMember: uid=lmarcotte,ou=users,dc=inverse,dc=ca
|
||||
uniqueMember: uid=wsourdeau,ou=users,dc=inverse,dc=ca
|
||||
cn: inverse
|
||||
mail: inverse@inverse.ca
|
||||
|
||||
*/
|
||||
|
||||
#include "SOGoGroup.h"
|
||||
|
||||
|
||||
#include "SOGoCache.h"
|
||||
#include "SOGoSource.h"
|
||||
#include "SOGoSystemDefaults.h"
|
||||
#include "SOGoUserManager.h"
|
||||
#include "SOGoUser.h"
|
||||
|
||||
#import <NGLdap/NGLdapEntry.h>
|
||||
|
||||
#define CHECK_CLASS(o) ({ \
|
||||
if ([o isKindOfClass: [NSString class]]) \
|
||||
o = [NSArray arrayWithObject: o]; \
|
||||
})
|
||||
|
||||
@implementation SOGoGroup
|
||||
|
||||
- (id) initWithIdentifier: (NSString *) theID
|
||||
domain: (NSString *) theDomain
|
||||
source: (NSObject <SOGoSource> *) theSource
|
||||
entry: (NGLdapEntry *) theEntry
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
ASSIGN(_identifier, theID);
|
||||
ASSIGN(_domain, theDomain);
|
||||
ASSIGN(_source, theSource);
|
||||
ASSIGN(_entry, theEntry);
|
||||
_members = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(_identifier);
|
||||
RELEASE(_domain);
|
||||
RELEASE(_source);
|
||||
RELEASE(_entry);
|
||||
RELEASE(_members);
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
+ (id) groupWithIdentifier: (NSString *) theID
|
||||
inDomain: (NSString *) domain
|
||||
{
|
||||
NSRange r;
|
||||
NSString *uid, *inDomain;
|
||||
SOGoSystemDefaults *sd;
|
||||
|
||||
uid = [theID hasPrefix: @"@"] ? [theID substringFromIndex: 1] : theID;
|
||||
inDomain = domain;
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
if ([sd enableDomainBasedUID])
|
||||
{
|
||||
/* Split domain from uid */
|
||||
r = [uid rangeOfString: @"@" options: NSBackwardsSearch];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
if (!domain)
|
||||
inDomain = [uid substringFromIndex: r.location + 1];
|
||||
uid = [uid substringToIndex: r.location];
|
||||
}
|
||||
}
|
||||
|
||||
return [SOGoGroup groupWithValue: uid
|
||||
andSourceSelector: @selector (lookupGroupEntryByUID:inDomain:)
|
||||
inDomain: inDomain];
|
||||
}
|
||||
|
||||
+ (id) groupWithEmail: (NSString *) theEmail
|
||||
inDomain: (NSString *) domain
|
||||
{
|
||||
return [SOGoGroup groupWithValue: theEmail
|
||||
andSourceSelector: @selector (lookupGroupEntryByEmail:inDomain:)
|
||||
inDomain: domain];
|
||||
}
|
||||
|
||||
//
|
||||
// Returns nil if theValue doesn't match to a group
|
||||
// (so its objectClass isn't a group)
|
||||
//
|
||||
+ (id) groupWithValue: (NSString *) theValue
|
||||
andSourceSelector: (SEL) theSelector
|
||||
inDomain: (NSString *) domain
|
||||
{
|
||||
NSArray *allSources;
|
||||
NGLdapEntry *entry;
|
||||
NSObject <SOGoSource, SOGoDNSource> *source;
|
||||
id o;
|
||||
NSEnumerator *gclasses;
|
||||
NSString *gclass;
|
||||
|
||||
int i;
|
||||
|
||||
// Don't bother looking in all sources if the
|
||||
// supplied value is nil.
|
||||
if (!theValue)
|
||||
return nil;
|
||||
|
||||
allSources = [[SOGoUserManager sharedUserManager]
|
||||
sourceIDsInDomain: domain];
|
||||
entry = nil;
|
||||
o = nil;
|
||||
|
||||
for (i = 0; i < [allSources count]; i++)
|
||||
{
|
||||
source = (NSObject <SOGoSource, SOGoDNSource> *) [[SOGoUserManager sharedUserManager] sourceWithID: [allSources objectAtIndex: i]];
|
||||
|
||||
// Our different sources might not all implements groups support
|
||||
if ([source respondsToSelector: theSelector])
|
||||
entry = [source performSelector: theSelector
|
||||
withObject: theValue
|
||||
withObject: domain];
|
||||
if (entry)
|
||||
break;
|
||||
|
||||
entry = nil;
|
||||
}
|
||||
|
||||
if (entry)
|
||||
{
|
||||
NSArray *classes;
|
||||
|
||||
// We check to see if it's a group
|
||||
classes = [[entry asDictionary] objectForKey: @"objectclass"];
|
||||
|
||||
if (classes)
|
||||
{
|
||||
/* LDAP records returned as dictionaries may contain NSString or
|
||||
NSArray values, depending on whether the amount of values
|
||||
assigned to a key is 1 or more. Since this can occur with
|
||||
"objectclass" too, we need to check whether "classes" is actually
|
||||
an NSString instance... */
|
||||
if ([classes isKindOfClass: [NSString class]])
|
||||
classes = [NSArray arrayWithObject:
|
||||
[(NSString *) classes lowercaseString]];
|
||||
else
|
||||
{
|
||||
int i, c;
|
||||
|
||||
classes = [NSMutableArray arrayWithArray: classes];
|
||||
c = [classes count];
|
||||
for (i = 0; i < c; i++)
|
||||
[(id)classes replaceObjectAtIndex: i
|
||||
withObject: [[classes objectAtIndex: i] lowercaseString]];
|
||||
}
|
||||
}
|
||||
|
||||
gclasses = [[source groupObjectClasses] objectEnumerator];
|
||||
while ((gclass = [gclasses nextObject]))
|
||||
if ([classes containsObject: gclass])
|
||||
{
|
||||
// Found a group, let's return it.
|
||||
o = [[self alloc] initWithIdentifier: theValue
|
||||
domain: domain
|
||||
source: source
|
||||
entry: entry];
|
||||
AUTORELEASE(o);
|
||||
}
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
//
|
||||
// This method actually try to obtain all members
|
||||
// from either dynamic of static groups.
|
||||
//
|
||||
- (NSArray *) members
|
||||
{
|
||||
NSMutableArray *dns, *uids, *logins;
|
||||
NSString *dn, *login;
|
||||
SOGoUserManager *um;
|
||||
NSDictionary *d;
|
||||
SOGoUser *user;
|
||||
NSArray *o;
|
||||
NSAutoreleasePool *pool;
|
||||
int i, c;
|
||||
|
||||
if (!_members)
|
||||
{
|
||||
_members = [NSMutableArray new];
|
||||
uids = [NSMutableArray array];
|
||||
dns = [NSMutableArray array];
|
||||
logins = [NSMutableArray array];
|
||||
|
||||
// We check if it's a static group
|
||||
// Fetch "members" - we get DNs
|
||||
d = [_entry asDictionary];
|
||||
o = [d objectForKey: @"member"];
|
||||
CHECK_CLASS(o);
|
||||
if (o) [dns addObjectsFromArray: o];
|
||||
|
||||
// Fetch "uniqueMembers" - we get DNs
|
||||
o = [d objectForKey: @"uniquemember"];
|
||||
CHECK_CLASS(o);
|
||||
if (o) [dns addObjectsFromArray: o];
|
||||
|
||||
// Fetch "memberUid" - we get UID (like login names)
|
||||
o = [d objectForKey: @"memberuid"];
|
||||
CHECK_CLASS(o);
|
||||
if (o) [uids addObjectsFromArray: o];
|
||||
|
||||
c = [dns count] + [uids count];
|
||||
|
||||
// We deal with a static group, let's add the members
|
||||
if (c)
|
||||
{
|
||||
um = [SOGoUserManager sharedUserManager];
|
||||
|
||||
// We add members for whom we have their associated DN
|
||||
for (i = 0; i < [dns count]; i++)
|
||||
{
|
||||
pool = [NSAutoreleasePool new];
|
||||
dn = [dns objectAtIndex: i];
|
||||
login = [um getLoginForDN: [dn lowercaseString]];
|
||||
user = [SOGoUser userWithLogin: login roles: nil];
|
||||
if (user)
|
||||
{
|
||||
[logins addObject: login];
|
||||
[_members addObject: user];
|
||||
}
|
||||
[pool release];
|
||||
}
|
||||
|
||||
// We add members for whom we have their associated login name
|
||||
for (i = 0; i < [uids count]; i++)
|
||||
{
|
||||
pool = [NSAutoreleasePool new];
|
||||
login = [uids objectAtIndex: i];
|
||||
user = [SOGoUser userWithLogin: login roles: nil];
|
||||
|
||||
if (user)
|
||||
{
|
||||
[logins addObject: [user loginInDomain]];
|
||||
[_members addObject: user];
|
||||
}
|
||||
[pool release];
|
||||
}
|
||||
|
||||
|
||||
// We are done fetching members, let's cache the members of the group
|
||||
// (ie., their UIDs) in memcached to speed up -hasMemberWithUID.
|
||||
[[SOGoCache sharedCache] setValue: [logins componentsJoinedByString: @","]
|
||||
forKey: [NSString stringWithFormat: @"%@+%@", _identifier, _domain]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// We deal with a dynamic group, let's search all users for whom
|
||||
// memberOf is equal to our group's DN.
|
||||
// We also need to look for labelelURI?
|
||||
}
|
||||
}
|
||||
|
||||
return _members;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (BOOL) hasMemberWithUID: (NSString *) memberUID
|
||||
{
|
||||
|
||||
BOOL rc;
|
||||
|
||||
rc = NO;
|
||||
|
||||
// If _members is initialized, we use it as it's very accurate.
|
||||
// Otherwise, we fallback on memcached in order to avoid
|
||||
// decomposing the group all the time just to see if a user
|
||||
// is a member of it.
|
||||
if (_members)
|
||||
{
|
||||
NSString *currentUID;
|
||||
|
||||
int count, max;
|
||||
max = [_members count];
|
||||
for (count = 0; !rc && count < max; count++)
|
||||
{
|
||||
currentUID = [[_members objectAtIndex: count] login];
|
||||
rc = [memberUID isEqualToString: currentUID];
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString *key, *value;;
|
||||
NSArray *a;
|
||||
|
||||
key = [NSString stringWithFormat: @"%@+%@", _identifier, _domain];
|
||||
value = [[SOGoCache sharedCache] valueForKey: key];
|
||||
|
||||
|
||||
// If the value isn't in memcached, that probably means -members was never called.
|
||||
// We call it only once here.
|
||||
if (!value)
|
||||
{
|
||||
[self members];
|
||||
value = [[SOGoCache sharedCache] valueForKey: key];
|
||||
}
|
||||
|
||||
a = [value componentsSeparatedByString: @","];
|
||||
rc = [a containsObject: memberUID];
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@end
|
||||
+11
-23
@@ -22,6 +22,7 @@
|
||||
|
||||
#import <NGObjWeb/NSException+HTTP.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSURL+misc.h>
|
||||
#import <NGMail/NGSendMail.h>
|
||||
#import <NGMail/NGSmtpClient.h>
|
||||
#import <NGMime/NGMimePartGenerator.h>
|
||||
@@ -216,40 +217,27 @@
|
||||
}
|
||||
|
||||
- (NSException *) _smtpSendData: (NSData *) mailData
|
||||
toRecipients: (NSArray *) recipients
|
||||
sender: (NSString *) sender
|
||||
toRecipients: (NSArray *) recipients
|
||||
sender: (NSString *) sender
|
||||
withAuthenticator: (id <SOGoAuthenticator>) authenticator
|
||||
inContext: (WOContext *) woContext
|
||||
{
|
||||
NSString *currentTo, *host, *login, *password;
|
||||
NGInternetSocketAddress *addr;
|
||||
NSString *currentTo, *login, *password;
|
||||
NSMutableArray *toErrors;
|
||||
NSEnumerator *addresses;
|
||||
NSEnumerator *addresses;
|
||||
NGSmtpClient *client;
|
||||
NSException *result;
|
||||
NSRange r;
|
||||
unsigned int port;
|
||||
NSURL * smtpUrl;
|
||||
|
||||
client = [NGSmtpClient smtpClient];
|
||||
host = smtpServer;
|
||||
result = nil;
|
||||
port = 25;
|
||||
|
||||
// We check if there is a port specified in the smtpServer ivar value
|
||||
r = [smtpServer rangeOfString: @":"];
|
||||
|
||||
if (r.length)
|
||||
{
|
||||
port = [[smtpServer substringFromIndex: r.location+1] intValue];
|
||||
host = [smtpServer substringToIndex: r.location];
|
||||
}
|
||||
smtpUrl = [[[NSURL alloc] initWithString: smtpServer] autorelease];
|
||||
|
||||
addr = [NGInternetSocketAddress addressWithPort: port
|
||||
onHost: host];
|
||||
client = [NGSmtpClient clientWithURL: smtpUrl];
|
||||
|
||||
NS_DURING
|
||||
{
|
||||
[client connectToAddress: addr];
|
||||
[client connect];
|
||||
if ([authenticationType isEqualToString: @"plain"])
|
||||
{
|
||||
/* XXX Allow static credentials by peeking at the classname */
|
||||
@@ -297,7 +285,7 @@
|
||||
@" (smtp) all recipients discarded"];
|
||||
else if ([toErrors count] > 0)
|
||||
result = [NSException exceptionWithHTTPStatus: 500
|
||||
reason: [NSString stringWithFormat:
|
||||
reason: [NSString stringWithFormat:
|
||||
@"cannot send message (smtp) - recipients discarded:\n%@",
|
||||
[toErrors componentsJoinedByString: @", "]]];
|
||||
else
|
||||
@@ -313,7 +301,7 @@
|
||||
}
|
||||
NS_HANDLER
|
||||
{
|
||||
[self errorWithFormat: @"Could not connect to the SMTP server %@ on port %d", host, port];
|
||||
[self errorWithFormat: @"Could not connect to the SMTP server %@", smtpServer];
|
||||
result = [NSException exceptionWithHTTPStatus: 500
|
||||
reason: @"cannot send message:"
|
||||
@" (smtp) error when connecting"];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SOGoSieveManager.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2010-2014 Inverse inc.
|
||||
* Copyright (C) 2010-2019 Inverse inc.
|
||||
*
|
||||
* Author: Inverse <info@inverse.ca>
|
||||
*
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
|
||||
@class NSDictionary;
|
||||
@class NSException;
|
||||
@class NSMutableArray;
|
||||
@class NSString;
|
||||
@class NGSieveClient;
|
||||
@@ -41,7 +42,8 @@
|
||||
+ (id) sieveManagerForUser: (SOGoUser *) user;
|
||||
- (id) initForUser: (SOGoUser *) newUser;
|
||||
|
||||
- (NSString *) sieveScriptWithRequirements: (NSMutableArray *) newRequirements;
|
||||
- (NSString *) sieveScriptWithRequirements: (NSMutableArray *) newRequirements
|
||||
delimiter: (NSString *) delimiter;
|
||||
- (NSString *) lastScriptError;
|
||||
|
||||
- (NGSieveClient *) clientForAccount: (SOGoMailAccount *) theAccount;
|
||||
@@ -49,10 +51,13 @@
|
||||
withUsername: (NSString *) theUsername
|
||||
andPassword: (NSString *) thePassword;
|
||||
|
||||
- (BOOL) updateFiltersForAccount: (SOGoMailAccount *) theAccount;
|
||||
- (BOOL) updateFiltersForAccount: (SOGoMailAccount *) theAccount
|
||||
- (BOOL) hasActiveExternalSieveScripts: (NGSieveClient *) client;
|
||||
|
||||
- (NSException *) updateFiltersForAccount: (SOGoMailAccount *) theAccount;
|
||||
- (NSException *) updateFiltersForAccount: (SOGoMailAccount *) theAccount
|
||||
withUsername: (NSString *) theUsername
|
||||
andPassword: (NSString *) thePassword;
|
||||
andPassword: (NSString *) thePassword
|
||||
forceActivation: (BOOL) forceActivation;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSCharacterSet.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
@@ -32,7 +33,11 @@
|
||||
#import <SOGo/SOGoTextTemplateFile.h>
|
||||
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSString+Ext.h>
|
||||
#import <NGImap4/NGImap4Connection.h>
|
||||
#import <NGImap4/NGImap4Client.h>
|
||||
#import <NGImap4/NGSieveClient.h>
|
||||
#import <NGObjWeb/NSException+HTTP.h>
|
||||
|
||||
#import "../Mailer/SOGoMailAccount.h"
|
||||
|
||||
@@ -251,6 +256,44 @@ static NSString *sieveScriptName = @"sogo";
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *) _extractRequirementsFromContent: (NSString *) theContent
|
||||
intoArray: (NSMutableArray *) theRequirements
|
||||
{
|
||||
NSString *line, *v;
|
||||
NSArray *lines;
|
||||
id o;
|
||||
|
||||
int i, count;
|
||||
|
||||
lines = [theContent componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]];
|
||||
count = [lines count];
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
line = [[lines objectAtIndex: i] stringByTrimmingSpaces];
|
||||
if ([line hasPrefix: @"require "])
|
||||
{
|
||||
line = [line substringFromIndex: 8];
|
||||
// Handle lines like: require "imapflags";
|
||||
if ([line characterAtIndex: 0] == '"')
|
||||
{
|
||||
v = [line substringToIndex: [line length]-2];
|
||||
[theRequirements addObject: v];
|
||||
}
|
||||
// Else handle lines like: require ["imapflags","vacation"];
|
||||
else if ([line characterAtIndex: 0] == '[')
|
||||
{
|
||||
o = [[line substringToIndex: [line length]-1] objectFromJSONString];
|
||||
[theRequirements addObjectsFromArray: o];
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return [[lines subarrayWithRange: NSMakeRange(i, count-i)] componentsJoinedByString: @"\n"];
|
||||
}
|
||||
|
||||
- (BOOL) _extractRuleField: (NSString **) field
|
||||
fromRule: (NSDictionary *) rule
|
||||
andType: (UIxFilterFieldType *) type
|
||||
@@ -450,7 +493,6 @@ static NSString *sieveScriptName = @"sogo";
|
||||
{
|
||||
NSString *sieveAction, *method, *requirement, *argument, *flag, *mailbox;
|
||||
NSDictionary *mailLabels;
|
||||
SOGoDomainDefaults *dd;
|
||||
|
||||
sieveAction = nil;
|
||||
|
||||
@@ -486,7 +528,6 @@ static NSString *sieveScriptName = @"sogo";
|
||||
}
|
||||
else if ([method isEqualToString: @"fileinto"])
|
||||
{
|
||||
dd = [user domainDefaults];
|
||||
mailbox
|
||||
= [[argument componentsSeparatedByString: @"/"]
|
||||
componentsJoinedByString: delimiter];
|
||||
@@ -592,7 +633,7 @@ static NSString *sieveScriptName = @"sogo";
|
||||
|
||||
sieveScript = [NSMutableString string];
|
||||
|
||||
ASSIGN (requirements, newRequirements);
|
||||
ASSIGN(requirements, newRequirements);
|
||||
[scriptError release];
|
||||
scriptError = nil;
|
||||
|
||||
@@ -613,8 +654,7 @@ static NSString *sieveScriptName = @"sogo";
|
||||
}
|
||||
|
||||
[scriptError retain];
|
||||
[requirements release];
|
||||
requirements = nil;
|
||||
DESTROY(requirements);
|
||||
|
||||
if (scriptError)
|
||||
sieveScript = nil;
|
||||
@@ -765,44 +805,75 @@ static NSString *sieveScriptName = @"sogo";
|
||||
return [client autorelease];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (BOOL) updateFiltersForAccount: (SOGoMailAccount *) theAccount
|
||||
- (BOOL) hasActiveExternalSieveScripts: (NGSieveClient *) client
|
||||
{
|
||||
return [self updateFiltersForAccount: theAccount
|
||||
withUsername: nil
|
||||
andPassword: nil];
|
||||
NSDictionary *scripts;
|
||||
NSEnumerator *keys;
|
||||
NSString *key;
|
||||
|
||||
scripts = [client listScripts];
|
||||
|
||||
keys = [scripts keyEnumerator];
|
||||
while ((key = [keys nextObject]))
|
||||
{
|
||||
if ([key caseInsensitiveCompare: @"sogo"] != NSOrderedSame &&
|
||||
[[scripts objectForKey: key] intValue] > 0)
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (BOOL) updateFiltersForAccount: (SOGoMailAccount *) theAccount
|
||||
- (NSException *) updateFiltersForAccount: (SOGoMailAccount *) theAccount
|
||||
{
|
||||
return [self updateFiltersForAccount: theAccount
|
||||
withUsername: nil
|
||||
andPassword: nil
|
||||
forceActivation: NO];
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (NSException *) updateFiltersForAccount: (SOGoMailAccount *) theAccount
|
||||
withUsername: (NSString *) theUsername
|
||||
andPassword: (NSString *) thePassword
|
||||
forceActivation: (BOOL) forceActivation
|
||||
{
|
||||
NSString *filterScript, *v, *delimiter, *content, *message;
|
||||
NSMutableArray *req;
|
||||
NSMutableString *script, *header;
|
||||
NSDictionary *result, *values;
|
||||
NSException *error;
|
||||
SOGoUserDefaults *ud;
|
||||
SOGoDomainDefaults *dd;
|
||||
NGSieveClient *client;
|
||||
NSString *filterScript, *v;
|
||||
BOOL b, dateCapability;
|
||||
NGImap4Client *imapClient;
|
||||
BOOL b, activate, dateCapability;
|
||||
unsigned int now;
|
||||
|
||||
error = nil;
|
||||
dd = [user domainDefaults];
|
||||
if (!([dd sieveScriptsEnabled] || [dd vacationEnabled] || [dd forwardEnabled]))
|
||||
return YES;
|
||||
return error;
|
||||
|
||||
req = [NSMutableArray arrayWithCapacity: 15];
|
||||
ud = [user userDefaults];
|
||||
|
||||
client = [self clientForAccount: theAccount withUsername: theUsername andPassword: thePassword];
|
||||
if (!client)
|
||||
return NO;
|
||||
{
|
||||
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
|
||||
reason: @"Error while connecting to Sieve server."];
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
// Activate script Sieve when forced or when no external script is enabled
|
||||
activate = forceActivation || ![self hasActiveExternalSieveScripts: client];
|
||||
|
||||
// We adjust the "methodRequirements" based on the server's
|
||||
// capabilities. Cyrus exposes "imapflags" while Dovecot (and
|
||||
@@ -821,9 +892,26 @@ static NSString *sieveScriptName = @"sogo";
|
||||
//
|
||||
script = [NSMutableString string];
|
||||
|
||||
// We grab the IMAP4 delimiter using the supplied username/password
|
||||
if (thePassword)
|
||||
{
|
||||
imapClient = [NGImap4Client clientWithURL: [theAccount imap4URL]];
|
||||
[imapClient login: theUsername password: thePassword];
|
||||
}
|
||||
else
|
||||
imapClient = [[theAccount imap4Connection] client];
|
||||
|
||||
delimiter = [imapClient delimiter];
|
||||
|
||||
if (!delimiter)
|
||||
[imapClient list: @"INBOX" pattern: @""];
|
||||
|
||||
if (!delimiter)
|
||||
delimiter = [dd stringForKey: @"NGImap4ConnectionStringSeparator"];
|
||||
|
||||
// We first handle filters
|
||||
filterScript = [self sieveScriptWithRequirements: req
|
||||
delimiter: [theAccount imap4Separator]];
|
||||
delimiter: delimiter];
|
||||
if (filterScript)
|
||||
{
|
||||
if ([filterScript length])
|
||||
@@ -834,9 +922,12 @@ static NSString *sieveScriptName = @"sogo";
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Sieve generation failure: %@", [self lastScriptError]];
|
||||
message = [NSString stringWithFormat: @"Sieve generation failure: %@", [self lastScriptError]];
|
||||
[self errorWithFormat: message];
|
||||
[client closeConnection];
|
||||
return NO;
|
||||
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
|
||||
reason: message];
|
||||
return error;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -914,7 +1005,9 @@ static NSString *sieveScriptName = @"sogo";
|
||||
}
|
||||
|
||||
// Start date of auto-reply
|
||||
if ([[values objectForKey: @"startDateEnabled"] boolValue] && dateCapability)
|
||||
if ([dd vacationPeriodEnabled] &&
|
||||
[[values objectForKey: @"startDateEnabled"] boolValue] &&
|
||||
dateCapability)
|
||||
{
|
||||
[req addObjectUniquely: @"date"];
|
||||
[req addObjectUniquely: @"relational"];
|
||||
@@ -925,7 +1018,9 @@ static NSString *sieveScriptName = @"sogo";
|
||||
}
|
||||
|
||||
// End date of auto-reply
|
||||
if ([[values objectForKey: @"endDateEnabled"] boolValue] && dateCapability)
|
||||
if ([dd vacationPeriodEnabled] &&
|
||||
[[values objectForKey: @"endDateEnabled"] boolValue] &&
|
||||
dateCapability)
|
||||
{
|
||||
[req addObjectUniquely: @"date"];
|
||||
[req addObjectUniquely: @"relational"];
|
||||
@@ -1012,46 +1107,87 @@ static NSString *sieveScriptName = @"sogo";
|
||||
[script appendString: @"keep;\r\n"];
|
||||
}
|
||||
|
||||
// We handle header/footer Sieve scripts
|
||||
if ((v = [dd sieveScriptHeaderTemplateFile]))
|
||||
{
|
||||
content = [NSString stringWithContentsOfFile: v
|
||||
encoding: NSUTF8StringEncoding
|
||||
error: NULL];
|
||||
if (content)
|
||||
{
|
||||
v = [self _extractRequirementsFromContent: content
|
||||
intoArray: req];
|
||||
[script insertString: v atIndex: 0];
|
||||
b = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if ((v = [dd sieveScriptFooterTemplateFile]))
|
||||
{
|
||||
content = [NSString stringWithContentsOfFile: v
|
||||
encoding: NSUTF8StringEncoding
|
||||
error: NULL];
|
||||
if (content)
|
||||
{
|
||||
v = [self _extractRequirementsFromContent: content
|
||||
intoArray: req];
|
||||
[script appendString: @"\n"];
|
||||
[script appendString: v];
|
||||
b = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if ([req count])
|
||||
{
|
||||
header = [NSString stringWithFormat: @"require [\"%@\"];\r\n",
|
||||
[req componentsJoinedByString: @"\",\""]];
|
||||
[[req uniqueObjects] componentsJoinedByString: @"\",\""]];
|
||||
[script insertString: header atIndex: 0];
|
||||
}
|
||||
|
||||
|
||||
/* We ensure to deactive the current active script since it could prevent
|
||||
its deletion from the server. */
|
||||
result = [client setActiveScript: @""];
|
||||
if (activate)
|
||||
result = [client setActiveScript: @""];
|
||||
// We delete the existing Sieve script
|
||||
result = [client deleteScript: sieveScriptName];
|
||||
|
||||
if (![[result valueForKey:@"result"] boolValue]) {
|
||||
[self logWithFormat: @"WARNING: Could not delete Sieve script - continuing...: %@", result];
|
||||
}
|
||||
if (![[result valueForKey:@"result"] boolValue])
|
||||
[self warnWithFormat: @"Could not delete Sieve script: %@", [[result objectForKey: @"RawResponse"] objectForKey: @"reason"]];
|
||||
|
||||
// We put and activate the script only if we actually have a script
|
||||
// that does something...
|
||||
/* We put and activate the script only if we actually have a script
|
||||
that does something... */
|
||||
if (b && [script length])
|
||||
{
|
||||
result = [client putScript: sieveScriptName script: script];
|
||||
|
||||
if (![[result valueForKey:@"result"] boolValue]) {
|
||||
[self logWithFormat: @"Could not upload Sieve script: %@", result];
|
||||
[client closeConnection];
|
||||
return NO;
|
||||
}
|
||||
if (![[result valueForKey:@"result"] boolValue])
|
||||
{
|
||||
message = [NSString stringWithFormat: @"Could not upload Sieve script: %@", [[result objectForKey: @"RawResponse"] objectForKey: @"reason"]];
|
||||
[self errorWithFormat: message];
|
||||
[client closeConnection];
|
||||
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
|
||||
reason: message];
|
||||
return error;
|
||||
}
|
||||
|
||||
result = [client setActiveScript: sieveScriptName];
|
||||
if (![[result valueForKey:@"result"] boolValue]) {
|
||||
[self logWithFormat: @"Could not enable Sieve script: %@", result];
|
||||
[client closeConnection];
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
if (activate)
|
||||
{
|
||||
result = [client setActiveScript: sieveScriptName];
|
||||
if (![[result valueForKey:@"result"] boolValue])
|
||||
{
|
||||
message = [NSString stringWithFormat: @"Could not enable Sieve script: %@", [[result objectForKey: @"RawResponse"] objectForKey: @"reason"]];
|
||||
[self errorWithFormat: message];
|
||||
[client closeConnection];
|
||||
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
|
||||
reason: message];
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[client closeConnection];
|
||||
return YES;
|
||||
return error;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -118,4 +118,10 @@
|
||||
- (void) updateBaseDNFromLogin: (NSString *) theLogin;
|
||||
@end
|
||||
|
||||
@protocol SOGoMembershipSource <SOGoSource>
|
||||
- (NSArray *) membersForGroupWithUID: (NSString *) uid;
|
||||
- (BOOL) groupWithUIDHasMemberWithUID: (NSString *) uid
|
||||
memberUid: (NSString *) memberUid;
|
||||
@end
|
||||
|
||||
#endif /* SOGOSOURCE_H */
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2006-2016 Inverse inc.
|
||||
Copyright (C) 2006-2020 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
|
||||
- (BOOL) isSuperUser;
|
||||
- (BOOL) canAuthenticate;
|
||||
- (NSString *) googleAuthenticatorKey;
|
||||
|
||||
/* resource */
|
||||
- (BOOL) isResource;
|
||||
|
||||
+134
-91
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2006-2016 Inverse inc.
|
||||
Copyright (C) 2006-2020 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
#import "SOGoUserSettings.h"
|
||||
#import "WOResourceManager+SOGo.h"
|
||||
|
||||
#if defined(MFA_CONFIG)
|
||||
#include <liboath/oath.h>
|
||||
#endif
|
||||
|
||||
#import "SOGoUser.h"
|
||||
|
||||
@implementation SoUser (SOGoExtension)
|
||||
@@ -338,18 +342,34 @@
|
||||
|
||||
- (NSMutableDictionary *) defaultIdentity
|
||||
{
|
||||
NSMutableDictionary *currentIdentity, *defaultIdentity;
|
||||
NSEnumerator *identities;
|
||||
NSDictionary *defaultAccount, *currentIdentity;
|
||||
NSMutableDictionary *defaultIdentity;
|
||||
NSArray *identities;
|
||||
NSString *defaultEmail;
|
||||
unsigned int count, max;
|
||||
|
||||
defaultEmail = [NSString stringWithFormat: @"%@@%@", [self loginInDomain], [self domain]];
|
||||
defaultAccount = [[self mailAccounts] objectAtIndex: 0];
|
||||
defaultIdentity = nil;
|
||||
|
||||
identities = [[self allIdentities] objectEnumerator];
|
||||
while (!defaultIdentity
|
||||
&& (currentIdentity = [identities nextObject]))
|
||||
if ([[currentIdentity objectForKey: @"isDefault"] boolValue])
|
||||
defaultIdentity = currentIdentity;
|
||||
identities = [defaultAccount objectForKey: @"identities"];
|
||||
max = [identities count];
|
||||
|
||||
return defaultIdentity;
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
currentIdentity = [identities objectAtIndex: count];
|
||||
if ([[currentIdentity objectForKey: @"isDefault"] boolValue])
|
||||
{
|
||||
defaultIdentity = [NSMutableDictionary dictionaryWithDictionary: currentIdentity];
|
||||
break;
|
||||
}
|
||||
else if ([[currentIdentity objectForKey: @"email"] caseInsensitiveCompare: defaultEmail] == NSOrderedSame)
|
||||
{
|
||||
defaultIdentity = [NSMutableDictionary dictionaryWithDictionary: currentIdentity];
|
||||
}
|
||||
}
|
||||
|
||||
return defaultIdentity; // can be nil
|
||||
}
|
||||
|
||||
- (SOGoDateFormatter *) dateFormatterInContext: (WOContext *) context
|
||||
@@ -622,19 +642,20 @@
|
||||
|
||||
- (void) _appendSystemMailAccountWithDelegatedIdentities: (BOOL) appendDeletegatedIdentities
|
||||
{
|
||||
NSString *fullName, *replyTo, *imapLogin, *imapServer, *cImapServer, *signature,
|
||||
*encryption, *scheme, *action, *query, *customEmail, *defaultEmail, *sieveServer;
|
||||
NSString *fullName, *imapLogin, *imapServer, *cImapServer,
|
||||
*encryption, *scheme, *action, *query, *customEmail, *sieveServer;
|
||||
NSMutableDictionary *mailAccount, *identity, *mailboxes, *receipts, *security, *mailSettings;
|
||||
NSNumber *port;
|
||||
NSMutableArray *identities, *mails;
|
||||
NSArray *delegators, *delegates;
|
||||
NSURL *url, *cUrl;
|
||||
unsigned int count, max, default_identity;
|
||||
unsigned int count, max; //, default_identity;
|
||||
NSInteger defaultPort;
|
||||
NSUInteger index;
|
||||
BOOL hasDefaultIdentity;
|
||||
|
||||
[self userDefaults];
|
||||
[self userSettings];
|
||||
[self userDefaults]; // set _defaults
|
||||
[self userSettings]; // set _settings
|
||||
|
||||
mailSettings = [_settings objectForKey: @"Mail"];
|
||||
mailAccount = [NSMutableDictionary new];
|
||||
@@ -711,89 +732,75 @@
|
||||
}
|
||||
|
||||
// 5. Identities
|
||||
defaultEmail = [NSString stringWithFormat: @"%@@%@", [self loginInDomain], [self domain]];
|
||||
default_identity = 0;
|
||||
identities = [NSMutableArray new];
|
||||
[identities addObjectsFromArray: [_defaults mailIdentities]];
|
||||
mails = [NSMutableArray arrayWithArray: [self allEmails]];
|
||||
[mailAccount setObject: [mails objectAtIndex: 0] forKey: @"name"];
|
||||
max = [identities count];
|
||||
hasDefaultIdentity = NO;
|
||||
fullName = [self cn];
|
||||
if ([fullName length] == 0)
|
||||
fullName = login;
|
||||
|
||||
replyTo = [_defaults mailReplyTo];
|
||||
|
||||
max = [mails count];
|
||||
|
||||
/* custom from */
|
||||
if ([[self domainDefaults] mailCustomFromEnabled])
|
||||
{
|
||||
[self userDefaults];
|
||||
customEmail = [_defaults mailCustomEmail];
|
||||
fullName = [_defaults mailCustomFullName];
|
||||
if ([customEmail length] > 0 || [fullName length] > 0)
|
||||
{
|
||||
if ([customEmail length] == 0)
|
||||
customEmail = [mails objectAtIndex: 0];
|
||||
else if ([fullName length] == 0)
|
||||
{
|
||||
// Custom email but default fullname; if the custom email is
|
||||
// one of the user's emails, remove the duplicated entry
|
||||
index = [mails indexOfObject: customEmail];
|
||||
if (index != NSNotFound)
|
||||
{
|
||||
[mails removeObjectAtIndex: index];
|
||||
max--;
|
||||
}
|
||||
}
|
||||
|
||||
if ([fullName length] == 0)
|
||||
{
|
||||
fullName = [self cn];
|
||||
if ([fullName length] == 0)
|
||||
fullName = login;
|
||||
}
|
||||
|
||||
identity = [NSMutableDictionary new];
|
||||
[identity setObject: customEmail forKey: @"email"];
|
||||
[identity setObject: fullName forKey: @"fullName"];
|
||||
|
||||
if ([replyTo length] > 0)
|
||||
[identity setObject: replyTo forKey: @"replyTo"];
|
||||
|
||||
signature = [_defaults mailSignature];
|
||||
if (signature)
|
||||
[identity setObject: signature forKey: @"signature"];
|
||||
[identities addObject: identity];
|
||||
|
||||
if ([[identity objectForKey: @"email"] caseInsensitiveCompare: defaultEmail] == NSOrderedSame)
|
||||
default_identity = [identities count]-1;
|
||||
|
||||
[identity release];
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitize identities
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
identity = [NSMutableDictionary new];
|
||||
fullName = [self cn];
|
||||
if (![fullName length])
|
||||
fullName = login;
|
||||
[identity setObject: fullName forKey: @"fullName"];
|
||||
[identity setObject: [[mails objectAtIndex: count] stringByTrimmingSpaces]
|
||||
forKey: @"email"];
|
||||
identity = [NSMutableDictionary dictionaryWithDictionary: [identities objectAtIndex: count]];
|
||||
customEmail = [identity objectForKey: @"email"];
|
||||
if ([customEmail length])
|
||||
{
|
||||
if (![[self domainDefaults] mailCustomFromEnabled])
|
||||
{
|
||||
// No custom from -- enforce a valid email
|
||||
index = [mails indexOfObject: customEmail];
|
||||
if (index == NSNotFound)
|
||||
{
|
||||
[identity setObject: [self systemEmail] forKey: @"email"];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Email must be defined
|
||||
[identity setObject: [self systemEmail] forKey: @"email"];
|
||||
}
|
||||
if (![[self domainDefaults] mailCustomFromEnabled])
|
||||
{
|
||||
// No custom from -- enforce a valid fullname and remove reply-to
|
||||
[identity setObject: fullName forKey: @"fullName"];
|
||||
[identity removeObjectForKey: @"replyTo"];
|
||||
}
|
||||
if (!appendDeletegatedIdentities)
|
||||
{
|
||||
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isReadOnly"];
|
||||
}
|
||||
if ([[identity objectForKey: @"isDefault"] boolValue])
|
||||
{
|
||||
if (hasDefaultIdentity || !appendDeletegatedIdentities)
|
||||
[identity removeObjectForKey: @"isDefault"]; // only one possible default identity
|
||||
else
|
||||
hasDefaultIdentity = YES;
|
||||
}
|
||||
[identities replaceObjectAtIndex: count withObject: identity];
|
||||
}
|
||||
|
||||
if ([replyTo length] > 0)
|
||||
[identity setObject: replyTo forKey: @"replyTo"];
|
||||
|
||||
signature = [_defaults mailSignature];
|
||||
if (signature)
|
||||
[identity setObject: signature forKey: @"signature"];
|
||||
if (![identities count])
|
||||
{
|
||||
// Create a default identity
|
||||
identity = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
fullName, @"fullName",
|
||||
[self systemEmail], @"email", nil];
|
||||
if (appendDeletegatedIdentities)
|
||||
{
|
||||
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isDefault"];
|
||||
hasDefaultIdentity = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isReadOnly"];
|
||||
}
|
||||
[identities addObject: identity];
|
||||
|
||||
if ([[identity objectForKey: @"email"] caseInsensitiveCompare: defaultEmail] == NSOrderedSame)
|
||||
default_identity = [identities count]-1;
|
||||
|
||||
[identity release];
|
||||
}
|
||||
[[identities objectAtIndex: default_identity] setObject: [NSNumber numberWithBool: YES]
|
||||
forKey: @"isDefault"];
|
||||
|
||||
/* identities from delegators */
|
||||
if (appendDeletegatedIdentities)
|
||||
@@ -976,11 +983,19 @@
|
||||
|
||||
- (NSDictionary *) primaryIdentity
|
||||
{
|
||||
NSDictionary *defaultAccount;
|
||||
NSArray *identities;
|
||||
NSDictionary *defaultIdentity, *defaultAccount;
|
||||
|
||||
defaultAccount = [[self mailAccounts] objectAtIndex: 0];
|
||||
defaultIdentity = [self defaultIdentity];
|
||||
|
||||
return [[defaultAccount objectForKey: @"identities"] objectAtIndex: 0];
|
||||
if (!defaultIdentity && [[self mailAccounts] count])
|
||||
{
|
||||
defaultAccount = [[self mailAccounts] objectAtIndex: 0];
|
||||
identities = [defaultAccount objectForKey: @"identities"];
|
||||
defaultIdentity = [identities objectAtIndex: 0];
|
||||
}
|
||||
|
||||
return defaultIdentity;
|
||||
}
|
||||
|
||||
/* folders */
|
||||
@@ -1079,6 +1094,34 @@
|
||||
return [authValue boolValue];
|
||||
}
|
||||
|
||||
- (NSString *) googleAuthenticatorKey
|
||||
{
|
||||
#if defined(MFA_CONFIG)
|
||||
NSString *key, *result;
|
||||
const char *s;
|
||||
char *secret;
|
||||
|
||||
size_t s_len, secret_len;
|
||||
|
||||
key = [[[self userSettings] userSalt] substringToIndex: 12];
|
||||
s = [key UTF8String];
|
||||
s_len = strlen(s);
|
||||
|
||||
oath_init();
|
||||
oath_base32_encode(s,s_len, &secret, &secret_len);
|
||||
oath_done();
|
||||
|
||||
result = [[NSString alloc] initWithBytesNoCopy: secret
|
||||
length: secret_len
|
||||
encoding: NSASCIIStringEncoding
|
||||
freeWhenDone: YES];
|
||||
|
||||
return [result autorelease];
|
||||
#else
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* resource */
|
||||
- (BOOL) isResource
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SOGoUserDefaults.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2011-2017 Inverse inc.
|
||||
* Copyright (C) 2011-2020 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
|
||||
@@ -133,6 +133,9 @@ extern NSString *SOGoWeekStartFirstFullWeek;
|
||||
- (void) setAnimationMode: (NSString *) newValue;
|
||||
- (NSString *) animationMode;
|
||||
|
||||
- (BOOL) googleAuthenticatorEnabled;
|
||||
- (void) setGoogleAuthenticatorEnabled: (BOOL) newValue;
|
||||
|
||||
- (void) setMailComposeWindow: (NSString *) newValue;
|
||||
- (NSString *) mailComposeWindow;
|
||||
|
||||
@@ -154,21 +157,9 @@ extern NSString *SOGoWeekStartFirstFullWeek;
|
||||
- (void) setMailReplyPlacement: (NSString *) newValue;
|
||||
- (NSString *) mailReplyPlacement;
|
||||
|
||||
- (void) setMailSignature: (NSString *) newValue;
|
||||
- (NSString *) mailSignature;
|
||||
|
||||
- (void) setMailSignaturePlacement: (NSString *) newValue;
|
||||
- (NSString *) mailSignaturePlacement;
|
||||
|
||||
- (void) setMailCustomFullName: (NSString *) newValue;
|
||||
- (NSString *) mailCustomFullName;
|
||||
|
||||
- (void) setMailCustomEmail: (NSString *) newValue;
|
||||
- (NSString *) mailCustomEmail;
|
||||
|
||||
- (void) setMailReplyTo: (NSString *) newValue;
|
||||
- (NSString *) mailReplyTo;
|
||||
|
||||
- (void) setAllowUserReceipt: (BOOL) allow;
|
||||
- (BOOL) allowUserReceipt;
|
||||
- (void) setUserReceiptNonRecipientAction: (NSString *) action;
|
||||
@@ -191,6 +182,9 @@ extern NSString *SOGoWeekStartFirstFullWeek;
|
||||
- (void) setMailCertificateAlwaysEncrypt: (BOOL) newValue;
|
||||
- (BOOL) mailCertificateAlwaysEncrypt;
|
||||
|
||||
- (void) setMailIdentities: (NSArray *) newIdentites;
|
||||
- (NSArray *) mailIdentities;
|
||||
|
||||
- (void) setAuxiliaryMailAccounts: (NSArray *) newAccounts;
|
||||
- (NSArray *) auxiliaryMailAccounts;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SOGoUserDefaults.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2017 Inverse inc.
|
||||
* Copyright (C) 2009-2020 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
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <NGImap4/NSString+Imap4.h>
|
||||
#import <NGObjWeb/WOApplication.h>
|
||||
@@ -114,33 +115,55 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (BOOL) _migrateSignature
|
||||
- (BOOL) _migrateMailIdentities
|
||||
{
|
||||
BOOL rc;
|
||||
NSString *signature;
|
||||
NSArray *mailAccounts, *identities;
|
||||
NSDictionary *identity;
|
||||
NSArray *mailIdentities;
|
||||
NSMutableDictionary *identity;
|
||||
NSString *fullName, *email, *replyTo, *signature;
|
||||
|
||||
mailAccounts = [self arrayForKey: @"MailAccounts"];
|
||||
if (mailAccounts)
|
||||
mailIdentities = [self mailIdentities];
|
||||
if (mailIdentities)
|
||||
{
|
||||
rc = YES;
|
||||
if ([mailAccounts count] > 0)
|
||||
{
|
||||
identities = [[mailAccounts objectAtIndex: 0]
|
||||
objectForKey: @"identifies"];
|
||||
if ([identities count] > 0)
|
||||
{
|
||||
identity = [identities objectAtIndex: 0];
|
||||
signature = [identity objectForKey: @"signature"];
|
||||
if ([signature length])
|
||||
[self setObject: signature forKey: @"MailSignature"];
|
||||
}
|
||||
}
|
||||
[self removeObjectForKey: @"MailAccounts"];
|
||||
rc = NO;
|
||||
}
|
||||
else
|
||||
rc = NO;
|
||||
{
|
||||
identity = [NSMutableDictionary dictionaryWithCapacity: 4];
|
||||
fullName = [self stringForKey: @"SOGoMailCustomFullName"];
|
||||
email = [self stringForKey: @"SOGoMailCustomEmail"];
|
||||
replyTo = [self stringForKey: @"SOGoMailReplyTo"];
|
||||
signature = [self stringForKey: @"SOGoMailSignature"];
|
||||
|
||||
if ([fullName length])
|
||||
[identity setObject: fullName forKey: @"fullName"];
|
||||
if ([email length])
|
||||
[identity setObject: email forKey: @"email"];
|
||||
if ([replyTo length])
|
||||
[identity setObject: replyTo forKey: @"replyTo"];
|
||||
if ([signature length])
|
||||
[identity setObject: signature forKey: @"signature"];
|
||||
|
||||
if ([identity count])
|
||||
{
|
||||
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isDefault"];
|
||||
[self setMailIdentities: [NSArray arrayWithObject: identity]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep old attributes for now because v2 doesn't handle identities
|
||||
*
|
||||
if (fullName)
|
||||
[self removeObjectForKey: @"SOGoMailCustomFullName"];
|
||||
if (email)
|
||||
[self removeObjectForKey: @"SOGoMailCustomEmail"];
|
||||
if (replyTo)
|
||||
[self removeObjectForKey: @"SOGoMailReplyTo"];
|
||||
if (signature)
|
||||
[self removeObjectForKey: @"SOGoMailSignature"];
|
||||
*/
|
||||
rc = YES;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -211,7 +234,8 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
|
||||
/* we must not use a boolean operation, otherwise subsequent migrations will
|
||||
not be invoked in the case where rc = YES. */
|
||||
return ([self _migrateLastModule]
|
||||
| [self _migrateSignature]
|
||||
// | [self _migrateSignature]
|
||||
| [self _migrateMailIdentities]
|
||||
| [self _migrateCalendarCategories]
|
||||
| [self migrateOldDefaultsWithDictionary: migratedKeys]
|
||||
| [super migrate]);
|
||||
@@ -542,6 +566,16 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
|
||||
return [self stringForKey: @"SOGoAnimationMode"];
|
||||
}
|
||||
|
||||
- (BOOL) googleAuthenticatorEnabled
|
||||
{
|
||||
return [self boolForKey: @"SOGoGoogleAuthenticatorEnabled"];
|
||||
}
|
||||
|
||||
- (void) setGoogleAuthenticatorEnabled: (BOOL) newValue
|
||||
{
|
||||
[self setBool: newValue forKey: @"SOGoGoogleAuthenticatorEnabled"];
|
||||
}
|
||||
|
||||
- (void) setMailComposeWindow: (NSString *) newValue
|
||||
{
|
||||
[self setObject: newValue forKey: @"SOGoMailComposeWindow"];
|
||||
@@ -619,18 +653,6 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
|
||||
return [self stringForKey: @"SOGoMailReplyPlacement"];
|
||||
}
|
||||
|
||||
- (void) setMailSignature: (NSString *) newValue
|
||||
{
|
||||
if ([newValue length] == 0)
|
||||
newValue = nil;
|
||||
[self setObject: newValue forKey: @"SOGoMailSignature"];
|
||||
}
|
||||
|
||||
- (NSString *) mailSignature
|
||||
{
|
||||
return [self stringForKey: @"SOGoMailSignature"];
|
||||
}
|
||||
|
||||
- (void) setMailSignaturePlacement: (NSString *) newValue
|
||||
{
|
||||
[self setObject: newValue forKey: @"SOGoMailSignaturePlacement"];
|
||||
@@ -650,45 +672,6 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
|
||||
return signaturePlacement;
|
||||
}
|
||||
|
||||
- (void) setMailCustomFullName: (NSString *) newValue
|
||||
{
|
||||
if ([newValue length] == 0)
|
||||
newValue = nil;
|
||||
[self setObject: [newValue stringByTrimmingSpaces]
|
||||
forKey: @"SOGoMailCustomFullName"];
|
||||
}
|
||||
|
||||
- (NSString *) mailCustomFullName
|
||||
{
|
||||
return [self stringForKey: @"SOGoMailCustomFullName"];
|
||||
}
|
||||
|
||||
- (void) setMailCustomEmail: (NSString *) newValue
|
||||
{
|
||||
if ([newValue length] == 0)
|
||||
newValue = nil;
|
||||
[self setObject: [newValue stringByTrimmingSpaces]
|
||||
forKey: @"SOGoMailCustomEmail"];
|
||||
}
|
||||
|
||||
- (NSString *) mailCustomEmail
|
||||
{
|
||||
return [self stringForKey: @"SOGoMailCustomEmail"];
|
||||
}
|
||||
|
||||
- (void) setMailReplyTo: (NSString *) newValue
|
||||
{
|
||||
if ([newValue length] == 0)
|
||||
newValue = nil;
|
||||
[self setObject: [newValue stringByTrimmingSpaces]
|
||||
forKey: @"SOGoMailReplyTo"];
|
||||
}
|
||||
|
||||
- (NSString *) mailReplyTo
|
||||
{
|
||||
return [self stringForKey: @"SOGoMailReplyTo"];
|
||||
}
|
||||
|
||||
- (void) setAllowUserReceipt: (BOOL) allow
|
||||
{
|
||||
[self setBool: allow forKey: @"SOGoMailReceiptAllow"];
|
||||
@@ -774,6 +757,16 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
|
||||
return [self boolForKey: @"SOGoMailCertificateAlwaysEncrypt"];
|
||||
}
|
||||
|
||||
- (void) setMailIdentities: (NSArray *) newIdentites
|
||||
{
|
||||
[self setObject: newIdentites forKey: @"SOGoMailIdentities"];
|
||||
}
|
||||
|
||||
- (NSArray *) mailIdentities
|
||||
{
|
||||
return [self arrayForKey: @"SOGoMailIdentities"];
|
||||
}
|
||||
|
||||
- (void) setAuxiliaryMailAccounts: (NSArray *) newAccounts
|
||||
{
|
||||
[self setObject: newAccounts forKey: @"AuxiliaryMailAccounts"];
|
||||
|
||||
@@ -674,18 +674,18 @@
|
||||
return [NSString stringWithFormat: @"urn:uuid:%@", nameInContainer];
|
||||
}
|
||||
|
||||
- (NSException *) setDavSignature: (NSString *) newSignature
|
||||
{
|
||||
SOGoUserDefaults *ud;
|
||||
SOGoUser *user;
|
||||
// - (NSException *) setDavSignature: (NSString *) newSignature
|
||||
// {
|
||||
// SOGoUserDefaults *ud;
|
||||
// SOGoUser *user;
|
||||
|
||||
user = [SOGoUser userWithLogin: [self ownerInContext: nil]];
|
||||
ud = [user userDefaults];
|
||||
[ud setMailSignature: newSignature];
|
||||
[ud synchronize];
|
||||
// user = [SOGoUser userWithLogin: [self ownerInContext: nil]];
|
||||
// ud = [user userDefaults];
|
||||
// [ud setMailSignature: newSignature];
|
||||
// [ud synchronize];
|
||||
|
||||
return nil;
|
||||
}
|
||||
// return nil;
|
||||
// }
|
||||
|
||||
#warning unused stub
|
||||
- (BOOL) collectionDavKey: (NSString *) key
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/* SOGoZipArchiver.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef SOGOZIPARCHIVER_H
|
||||
#define SOGOZIPARCHIVER_H
|
||||
|
||||
#include <zip.h>
|
||||
|
||||
@interface SOGoZipArchiver : NSObject
|
||||
{
|
||||
zip_t *zip;
|
||||
}
|
||||
|
||||
- (id) initFromFile: (NSString *) file;
|
||||
+ (id) archiverAtPath: (NSString *) file;
|
||||
|
||||
- (BOOL) putFileWithName: (NSString *) filename andData: (NSData *) data;
|
||||
- (BOOL) close;
|
||||
@end
|
||||
|
||||
#endif /* SOGOZIPARCHIVER_H */
|
||||
@@ -0,0 +1,107 @@
|
||||
/* SOGoZipArchiver.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2020 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 <Foundation/NSCharacterSet.h>
|
||||
#import <Foundation/NSData.h>
|
||||
|
||||
#import "SOGoZipArchiver.h"
|
||||
|
||||
@implementation SOGoZipArchiver
|
||||
|
||||
+ (id)archiverAtPath:(NSString *)file
|
||||
{
|
||||
id newArchiver = [[self alloc] initFromFile: file];
|
||||
[newArchiver autorelease];
|
||||
return newArchiver;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
zip = NULL;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self close];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id)initFromFile:(NSString *)file
|
||||
{
|
||||
id ret;
|
||||
|
||||
ret = nil;
|
||||
if (file) {
|
||||
if ((self = [self init])) {
|
||||
int errorp;
|
||||
self->zip = zip_open([file cString], ZIP_CREATE | ZIP_EXCL, &errorp);
|
||||
if (self->zip == NULL) {
|
||||
zip_error_t ziperror;
|
||||
zip_error_init_with_code(&ziperror, errorp);
|
||||
NSLog(@"Failed to open zip output file %@: %@", file,
|
||||
[NSString stringWithCString: zip_error_strerror(&ziperror)]);
|
||||
} else {
|
||||
ret = self;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (BOOL)putFileWithName:(NSString *)filename andData:(NSData *)data
|
||||
{
|
||||
if (self->zip == NULL) {
|
||||
NSLog(@"Failed to add file, archive is not open");
|
||||
return NO;
|
||||
}
|
||||
|
||||
zip_source_t *source = zip_source_buffer(self->zip, [data bytes], [data length], 0);
|
||||
if (source == NULL) {
|
||||
NSLog(@"Failed to create zip source from buffer: %@", [NSString stringWithCString: zip_strerror(self->zip)]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (zip_file_add(self->zip, [filename UTF8String], source, ZIP_FL_ENC_UTF_8) < 0) {
|
||||
NSLog(@"Failed to add file %@: %@", filename, [NSString stringWithCString: zip_strerror(self->zip)]);
|
||||
zip_source_free(source);
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)close
|
||||
{
|
||||
BOOL success = YES;
|
||||
if (self->zip != NULL) {
|
||||
if (zip_close(zip) != 0) {
|
||||
NSLog(@"Failed to close zip archive: %@", [NSString stringWithCString: zip_strerror(self->zip)]);
|
||||
zip_discard(self->zip);
|
||||
success = NO;
|
||||
}
|
||||
self->zip = NULL;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SQLSource.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2019 Inverse inc.
|
||||
* Copyright (C) 2009-2020 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
|
||||
@@ -42,6 +42,7 @@
|
||||
NSString *_imapHostField;
|
||||
NSString *_sieveHostField;
|
||||
NSString *_userPasswordAlgorithm;
|
||||
NSString *_keyPath;
|
||||
NSURL *_viewURL;
|
||||
BOOL _prependPasswordScheme;
|
||||
|
||||
|
||||
+82
-77
@@ -1,6 +1,6 @@
|
||||
/* SQLSource.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2017 Inverse inc.
|
||||
* Copyright (C) 2009-2020 Inverse inc.
|
||||
*
|
||||
* This file is part of SOGo.
|
||||
*
|
||||
@@ -94,6 +94,7 @@
|
||||
_searchFields = [NSArray arrayWithObjects: @"c_cn", @"mail", nil];
|
||||
[_searchFields retain];
|
||||
_userPasswordAlgorithm = nil;
|
||||
_keyPath = nil;
|
||||
_viewURL = nil;
|
||||
_kindField = nil;
|
||||
_multipleBookingsField = nil;
|
||||
@@ -114,6 +115,7 @@
|
||||
[_mailFields release];
|
||||
[_searchFields release];
|
||||
[_userPasswordAlgorithm release];
|
||||
[_keyPath release];
|
||||
[_viewURL release];
|
||||
[_kindField release];
|
||||
[_multipleBookingsField release];
|
||||
@@ -137,6 +139,7 @@
|
||||
ASSIGN(_loginFields, [udSource objectForKey: @"LoginFieldNames"]);
|
||||
ASSIGN(_mailFields, [udSource objectForKey: @"MailFieldNames"]);
|
||||
ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]);
|
||||
ASSIGN(_keyPath, [udSource objectForKey: @"keyPath"]);
|
||||
ASSIGN(_imapLoginField, [udSource objectForKey: @"IMAPLoginFieldName"]);
|
||||
ASSIGN(_imapHostField, [udSource objectForKey: @"IMAPHostFieldName"]);
|
||||
ASSIGN(_sieveHostField, [udSource objectForKey: @"SieveHostFieldName"]);
|
||||
@@ -191,7 +194,8 @@
|
||||
return NO;
|
||||
|
||||
return [plainPassword isEqualToCrypted: encryptedPassword
|
||||
withDefaultScheme: _userPasswordAlgorithm];
|
||||
withDefaultScheme: _userPasswordAlgorithm
|
||||
keyPath: _keyPath];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,7 +209,8 @@
|
||||
NSString *pass;
|
||||
NSString* result;
|
||||
|
||||
pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm];
|
||||
pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm
|
||||
keyPath: _keyPath];
|
||||
|
||||
if (pass == nil)
|
||||
{
|
||||
@@ -228,10 +233,10 @@
|
||||
// kind of logic in this module.
|
||||
//
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
password: (NSString *) _pwd
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
{
|
||||
EOAdaptorChannel *channel;
|
||||
EOQualifier *qualifier;
|
||||
@@ -259,8 +264,8 @@
|
||||
{
|
||||
field = [_loginFields objectAtIndex: i];
|
||||
loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: field
|
||||
operatorSelector: EOQualifierOperatorEqual
|
||||
value: _login];
|
||||
operatorSelector: EOQualifierOperatorEqual
|
||||
value: _login];
|
||||
[loginQualifier autorelease];
|
||||
[qualifiers addObject: loginQualifier];
|
||||
}
|
||||
@@ -299,7 +304,7 @@
|
||||
value = [row objectForKey: @"c_password"];
|
||||
|
||||
rc = [self _isPassword: _pwd equalTo: value];
|
||||
[channel cancelFetch];
|
||||
[channel cancelFetch];
|
||||
}
|
||||
else
|
||||
[self errorWithFormat: @"could not run SQL '%@': %@", qualifier, ex];
|
||||
@@ -322,9 +327,9 @@
|
||||
* @return YES if the password was successfully changed.
|
||||
*/
|
||||
- (BOOL) changePasswordForLogin: (NSString *) login
|
||||
oldPassword: (NSString *) oldPassword
|
||||
newPassword: (NSString *) newPassword
|
||||
perr: (SOGoPasswordPolicyError *) perr
|
||||
oldPassword: (NSString *) oldPassword
|
||||
newPassword: (NSString *) newPassword
|
||||
perr: (SOGoPasswordPolicyError *) perr
|
||||
{
|
||||
EOAdaptorChannel *channel;
|
||||
GCSChannelManager *cm;
|
||||
@@ -351,23 +356,23 @@
|
||||
cm = [GCSChannelManager defaultChannelManager];
|
||||
channel = [cm acquireOpenChannelForURL: _viewURL];
|
||||
if (channel)
|
||||
{
|
||||
sqlstr = [NSString stringWithFormat: (@"UPDATE %@"
|
||||
@" SET c_password = '%@'"
|
||||
@" WHERE c_uid = '%@'"),
|
||||
[_viewURL gcsTableName], encryptedPassword, login];
|
||||
{
|
||||
sqlstr = [NSString stringWithFormat: (@"UPDATE %@"
|
||||
@" SET c_password = '%@'"
|
||||
@" WHERE c_uid = '%@'"),
|
||||
[_viewURL gcsTableName], encryptedPassword, login];
|
||||
|
||||
ex = [channel evaluateExpressionX: sqlstr];
|
||||
if (!ex)
|
||||
{
|
||||
didChange = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"could not run SQL '%@': %@", sqlstr, ex];
|
||||
}
|
||||
[cm releaseChannel: channel];
|
||||
}
|
||||
ex = [channel evaluateExpressionX: sqlstr];
|
||||
if (!ex)
|
||||
{
|
||||
didChange = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"could not run SQL '%@': %@", sqlstr, ex];
|
||||
}
|
||||
[cm releaseChannel: channel];
|
||||
}
|
||||
}
|
||||
|
||||
return didChange;
|
||||
@@ -489,8 +494,8 @@
|
||||
[loginQualifier autorelease];
|
||||
[qualifiers addObject: loginQualifier];
|
||||
|
||||
if (_mailFields)
|
||||
{
|
||||
if (_mailFields)
|
||||
{
|
||||
for (i = 0; i < [_mailFields count]; i++)
|
||||
{
|
||||
field = [_mailFields objectAtIndex: i];
|
||||
@@ -505,7 +510,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sql = [NSMutableString stringWithFormat: @"SELECT *"
|
||||
@" FROM %@"
|
||||
@@ -519,12 +524,12 @@
|
||||
ex = [channel evaluateExpressionX: sql];
|
||||
if (!ex)
|
||||
{
|
||||
NSMutableArray *emails;
|
||||
NSMutableArray *emails;
|
||||
|
||||
response = [[channel fetchAttributes: [channel describeResults: NO]
|
||||
withZone: NULL] mutableCopy];
|
||||
[response autorelease];
|
||||
[channel cancelFetch];
|
||||
[channel cancelFetch];
|
||||
|
||||
/* Convert all c_ fields to obtain their ldif equivalent */
|
||||
fieldNames = [response allKeys];
|
||||
@@ -540,35 +545,35 @@
|
||||
[self _fillConstraintsForModule: @"Mail" intoRecord: response];
|
||||
[self _fillConstraintsForModule: @"ActiveSync" intoRecord: response];
|
||||
|
||||
// We set the domain, if any
|
||||
// We set the domain, if any
|
||||
value = nil;
|
||||
if (_domain)
|
||||
value = _domain;
|
||||
else if (_domainField)
|
||||
if (_domain)
|
||||
value = _domain;
|
||||
else if (_domainField)
|
||||
value = [response objectForKey: _domainField];
|
||||
if (![value isNotNull])
|
||||
value = @"";
|
||||
[response setObject: value forKey: @"c_domain"];
|
||||
value = @"";
|
||||
[response setObject: value forKey: @"c_domain"];
|
||||
|
||||
// We populate all mail fields
|
||||
emails = [NSMutableArray array];
|
||||
// We populate all mail fields
|
||||
emails = [NSMutableArray array];
|
||||
|
||||
if ([response objectForKey: @"mail"])
|
||||
[emails addObject: [response objectForKey: @"mail"]];
|
||||
if ([response objectForKey: @"mail"])
|
||||
[emails addObject: [response objectForKey: @"mail"]];
|
||||
|
||||
if (_mailFields && [_mailFields count] > 0)
|
||||
{
|
||||
NSString *s;
|
||||
int i;
|
||||
if (_mailFields && [_mailFields count] > 0)
|
||||
{
|
||||
NSString *s;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < [_mailFields count]; i++)
|
||||
if ((s = [response objectForKey: [_mailFields objectAtIndex: i]]) &&
|
||||
for (i = 0; i < [_mailFields count]; i++)
|
||||
if ((s = [response objectForKey: [_mailFields objectAtIndex: i]]) &&
|
||||
[s isNotNull] &&
|
||||
[[s stringByTrimmingSpaces] length] > 0)
|
||||
[emails addObjectsFromArray: [s componentsSeparatedByString: @" "]];
|
||||
}
|
||||
[[s stringByTrimmingSpaces] length] > 0)
|
||||
[emails addObjectsFromArray: [s componentsSeparatedByString: @" "]];
|
||||
}
|
||||
|
||||
[response setObject: [emails uniqueObjects] forKey: @"c_emails"];
|
||||
[response setObject: [emails uniqueObjects] forKey: @"c_emails"];
|
||||
if (_imapHostField)
|
||||
{
|
||||
value = [response objectForKey: _imapHostField];
|
||||
@@ -626,29 +631,29 @@
|
||||
[response setObject: [response objectForKey: _imapLoginField] forKey: @"c_imaplogin"];
|
||||
}
|
||||
|
||||
// We check if it's a resource of not
|
||||
if (_kindField)
|
||||
{
|
||||
if ((value = [response objectForKey: _kindField]) && [value isNotNull])
|
||||
{
|
||||
if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame ||
|
||||
[value caseInsensitiveCompare: @"thing"] == NSOrderedSame ||
|
||||
[value caseInsensitiveCompare: @"group"] == NSOrderedSame)
|
||||
{
|
||||
[response setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"isResource"];
|
||||
}
|
||||
}
|
||||
}
|
||||
// We check if it's a resource of not
|
||||
if (_kindField)
|
||||
{
|
||||
if ((value = [response objectForKey: _kindField]) && [value isNotNull])
|
||||
{
|
||||
if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame ||
|
||||
[value caseInsensitiveCompare: @"thing"] == NSOrderedSame ||
|
||||
[value caseInsensitiveCompare: @"group"] == NSOrderedSame)
|
||||
{
|
||||
[response setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"isResource"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_multipleBookingsField)
|
||||
{
|
||||
if ((value = [response objectForKey: _multipleBookingsField]))
|
||||
{
|
||||
[response setObject: [NSNumber numberWithInt: [value intValue]]
|
||||
forKey: @"numberOfSimultaneousBookings"];
|
||||
}
|
||||
}
|
||||
if (_multipleBookingsField)
|
||||
{
|
||||
if ((value = [response objectForKey: _multipleBookingsField]))
|
||||
{
|
||||
[response setObject: [NSNumber numberWithInt: [value intValue]]
|
||||
forKey: @"numberOfSimultaneousBookings"];
|
||||
}
|
||||
}
|
||||
|
||||
[response setObject: self forKey: @"source"];
|
||||
}
|
||||
@@ -742,7 +747,7 @@
|
||||
if (channel)
|
||||
{
|
||||
sql = [NSMutableString stringWithFormat: @"SELECT c_uid FROM %@",
|
||||
[_viewURL gcsTableName]];
|
||||
[_viewURL gcsTableName]];
|
||||
|
||||
if (_domainField)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,659 @@
|
||||
/*
|
||||
|
||||
This is an implementation of the AES128 algorithm, specifically ECB and CBC mode.
|
||||
|
||||
The implementation is verified against the test vectors in:
|
||||
National Institute of Standards and Technology Special Publication 800-38A 2001 ED
|
||||
|
||||
ECB-AES128
|
||||
----------
|
||||
|
||||
plain-text:
|
||||
6bc1bee22e409f96e93d7e117393172a
|
||||
ae2d8a571e03ac9c9eb76fac45af8e51
|
||||
30c81c46a35ce411e5fbc1191a0a52ef
|
||||
f69f2445df4f9b17ad2b417be66c3710
|
||||
|
||||
key:
|
||||
2b7e151628aed2a6abf7158809cf4f3c
|
||||
|
||||
resulting cipher
|
||||
3ad77bb40d7a3660a89ecaf32466ef97
|
||||
f5d3d58503b9699de785895a96fdbaaf
|
||||
43b1cd7f598ece23881b00e3ed030688
|
||||
7b0c785e27e8ad3f8223207104725dd4
|
||||
|
||||
|
||||
NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0)
|
||||
You should pad the end of the string with zeros if this is not the case.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Includes: */
|
||||
/*****************************************************************************/
|
||||
#include <stdint.h>
|
||||
#include <string.h> // CBC mode, for memset
|
||||
#include "aes.h"
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Defines: */
|
||||
/*****************************************************************************/
|
||||
// The number of columns comprising a state in AES. This is a constant in AES. Value=4
|
||||
#define Nb 4
|
||||
// The number of 32 bit words in a key.
|
||||
#define Nk 4
|
||||
// Key length in bytes [128 bit]
|
||||
#define KEYLEN 16
|
||||
// The number of rounds in AES Cipher.
|
||||
#define Nr 10
|
||||
|
||||
// jcallan@github points out that declaring Multiply as a function
|
||||
// reduces code size considerably with the Keil ARM compiler.
|
||||
// See this link for more information: https://github.com/kokke/tiny-AES128-C/pull/3
|
||||
#ifndef MULTIPLY_AS_A_FUNCTION
|
||||
#define MULTIPLY_AS_A_FUNCTION 0
|
||||
#endif
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Private variables: */
|
||||
/*****************************************************************************/
|
||||
// state - array holding the intermediate results during decryption.
|
||||
typedef uint8_t state_t[4][4];
|
||||
static state_t* state;
|
||||
|
||||
// The array that stores the round keys.
|
||||
static uint8_t RoundKey[176];
|
||||
|
||||
// The Key input to the AES Program
|
||||
static const uint8_t* Key;
|
||||
|
||||
#if defined(CBC) && CBC
|
||||
// Initial Vector used only for CBC mode
|
||||
static uint8_t* Iv;
|
||||
#endif
|
||||
|
||||
// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM
|
||||
// The numbers below can be computed dynamically trading ROM for RAM -
|
||||
// This can be useful in (embedded) bootloader applications, where ROM is often limited.
|
||||
static const uint8_t sbox[256] = {
|
||||
//0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
||||
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
||||
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
|
||||
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
|
||||
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
|
||||
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
|
||||
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
|
||||
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
|
||||
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
|
||||
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
||||
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
|
||||
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
|
||||
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
|
||||
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
||||
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
|
||||
|
||||
static const uint8_t rsbox[256] =
|
||||
{ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
|
||||
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
|
||||
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
|
||||
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
|
||||
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
|
||||
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
|
||||
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
|
||||
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
|
||||
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
|
||||
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
|
||||
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
|
||||
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
|
||||
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
|
||||
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
|
||||
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
|
||||
|
||||
|
||||
// The round constant word array, Rcon[i], contains the values given by
|
||||
// x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8)
|
||||
// Note that i starts at 1, not 0).
|
||||
static const uint8_t Rcon[255] = {
|
||||
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
|
||||
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
|
||||
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
|
||||
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
|
||||
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
|
||||
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
|
||||
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
|
||||
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
|
||||
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
|
||||
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
|
||||
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
|
||||
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
|
||||
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
|
||||
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
|
||||
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
|
||||
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb };
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Private functions: */
|
||||
/*****************************************************************************/
|
||||
static uint8_t getSBoxValue(uint8_t num)
|
||||
{
|
||||
return sbox[num];
|
||||
}
|
||||
|
||||
static uint8_t getSBoxInvert(uint8_t num)
|
||||
{
|
||||
return rsbox[num];
|
||||
}
|
||||
|
||||
// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states.
|
||||
static void KeyExpansion(void)
|
||||
{
|
||||
uint32_t i, j, k;
|
||||
uint8_t tempa[4]; // Used for the column/row operations
|
||||
|
||||
// The first round key is the key itself.
|
||||
for(i = 0; i < Nk; ++i)
|
||||
{
|
||||
RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
|
||||
RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
|
||||
RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
|
||||
RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
|
||||
}
|
||||
|
||||
// All other round keys are found from the previous round keys.
|
||||
for(; (i < (Nb * (Nr + 1))); ++i)
|
||||
{
|
||||
for(j = 0; j < 4; ++j)
|
||||
{
|
||||
tempa[j]=RoundKey[(i-1) * 4 + j];
|
||||
}
|
||||
if (i % Nk == 0)
|
||||
{
|
||||
// This function rotates the 4 bytes in a word to the left once.
|
||||
// [a0,a1,a2,a3] becomes [a1,a2,a3,a0]
|
||||
|
||||
// Function RotWord()
|
||||
{
|
||||
k = tempa[0];
|
||||
tempa[0] = tempa[1];
|
||||
tempa[1] = tempa[2];
|
||||
tempa[2] = tempa[3];
|
||||
tempa[3] = k;
|
||||
}
|
||||
|
||||
// SubWord() is a function that takes a four-byte input word and
|
||||
// applies the S-box to each of the four bytes to produce an output word.
|
||||
|
||||
// Function Subword()
|
||||
{
|
||||
tempa[0] = getSBoxValue(tempa[0]);
|
||||
tempa[1] = getSBoxValue(tempa[1]);
|
||||
tempa[2] = getSBoxValue(tempa[2]);
|
||||
tempa[3] = getSBoxValue(tempa[3]);
|
||||
}
|
||||
|
||||
tempa[0] = tempa[0] ^ Rcon[i/Nk];
|
||||
}
|
||||
else if (Nk > 6 && i % Nk == 4)
|
||||
{
|
||||
// Function Subword()
|
||||
{
|
||||
tempa[0] = getSBoxValue(tempa[0]);
|
||||
tempa[1] = getSBoxValue(tempa[1]);
|
||||
tempa[2] = getSBoxValue(tempa[2]);
|
||||
tempa[3] = getSBoxValue(tempa[3]);
|
||||
}
|
||||
}
|
||||
RoundKey[i * 4 + 0] = RoundKey[(i - Nk) * 4 + 0] ^ tempa[0];
|
||||
RoundKey[i * 4 + 1] = RoundKey[(i - Nk) * 4 + 1] ^ tempa[1];
|
||||
RoundKey[i * 4 + 2] = RoundKey[(i - Nk) * 4 + 2] ^ tempa[2];
|
||||
RoundKey[i * 4 + 3] = RoundKey[(i - Nk) * 4 + 3] ^ tempa[3];
|
||||
}
|
||||
}
|
||||
|
||||
// This function adds the round key to state.
|
||||
// The round key is added to the state by an XOR function.
|
||||
static void AddRoundKey(uint8_t round)
|
||||
{
|
||||
uint8_t i,j;
|
||||
for(i=0;i<4;++i)
|
||||
{
|
||||
for(j = 0; j < 4; ++j)
|
||||
{
|
||||
(*state)[i][j] ^= RoundKey[round * Nb * 4 + i * Nb + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The SubBytes Function Substitutes the values in the
|
||||
// state matrix with values in an S-box.
|
||||
static void SubBytes(void)
|
||||
{
|
||||
uint8_t i, j;
|
||||
for(i = 0; i < 4; ++i)
|
||||
{
|
||||
for(j = 0; j < 4; ++j)
|
||||
{
|
||||
(*state)[j][i] = getSBoxValue((*state)[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The ShiftRows() function shifts the rows in the state to the left.
|
||||
// Each row is shifted with different offset.
|
||||
// Offset = Row number. So the first row is not shifted.
|
||||
static void ShiftRows(void)
|
||||
{
|
||||
uint8_t temp;
|
||||
|
||||
// Rotate first row 1 columns to left
|
||||
temp = (*state)[0][1];
|
||||
(*state)[0][1] = (*state)[1][1];
|
||||
(*state)[1][1] = (*state)[2][1];
|
||||
(*state)[2][1] = (*state)[3][1];
|
||||
(*state)[3][1] = temp;
|
||||
|
||||
// Rotate second row 2 columns to left
|
||||
temp = (*state)[0][2];
|
||||
(*state)[0][2] = (*state)[2][2];
|
||||
(*state)[2][2] = temp;
|
||||
|
||||
temp = (*state)[1][2];
|
||||
(*state)[1][2] = (*state)[3][2];
|
||||
(*state)[3][2] = temp;
|
||||
|
||||
// Rotate third row 3 columns to left
|
||||
temp = (*state)[0][3];
|
||||
(*state)[0][3] = (*state)[3][3];
|
||||
(*state)[3][3] = (*state)[2][3];
|
||||
(*state)[2][3] = (*state)[1][3];
|
||||
(*state)[1][3] = temp;
|
||||
}
|
||||
|
||||
static uint8_t xtime(uint8_t x)
|
||||
{
|
||||
return ((x<<1) ^ (((x>>7) & 1) * 0x1b));
|
||||
}
|
||||
|
||||
// MixColumns function mixes the columns of the state matrix
|
||||
static void MixColumns(void)
|
||||
{
|
||||
uint8_t i;
|
||||
uint8_t Tmp,Tm,t;
|
||||
for(i = 0; i < 4; ++i)
|
||||
{
|
||||
t = (*state)[i][0];
|
||||
Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ;
|
||||
Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ;
|
||||
Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ;
|
||||
Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ;
|
||||
Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ;
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply is used to multiply numbers in the field GF(2^8)
|
||||
#if MULTIPLY_AS_A_FUNCTION
|
||||
static uint8_t Multiply(uint8_t x, uint8_t y)
|
||||
{
|
||||
return (((y & 1) * x) ^
|
||||
((y>>1 & 1) * xtime(x)) ^
|
||||
((y>>2 & 1) * xtime(xtime(x))) ^
|
||||
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^
|
||||
((y>>4 & 1) * xtime(xtime(xtime(xtime(x))))));
|
||||
}
|
||||
#else
|
||||
#define Multiply(x, y) \
|
||||
( ((y & 1) * x) ^ \
|
||||
((y>>1 & 1) * xtime(x)) ^ \
|
||||
((y>>2 & 1) * xtime(xtime(x))) ^ \
|
||||
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \
|
||||
((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \
|
||||
|
||||
#endif
|
||||
|
||||
// MixColumns function mixes the columns of the state matrix.
|
||||
// The method used to multiply may be difficult to understand for the inexperienced.
|
||||
// Please use the references to gain more information.
|
||||
static void InvMixColumns(void)
|
||||
{
|
||||
int i;
|
||||
uint8_t a,b,c,d;
|
||||
for(i=0;i<4;++i)
|
||||
{
|
||||
a = (*state)[i][0];
|
||||
b = (*state)[i][1];
|
||||
c = (*state)[i][2];
|
||||
d = (*state)[i][3];
|
||||
|
||||
(*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09);
|
||||
(*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d);
|
||||
(*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b);
|
||||
(*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The SubBytes Function Substitutes the values in the
|
||||
// state matrix with values in an S-box.
|
||||
static void InvSubBytes(void)
|
||||
{
|
||||
uint8_t i,j;
|
||||
for(i=0;i<4;++i)
|
||||
{
|
||||
for(j=0;j<4;++j)
|
||||
{
|
||||
(*state)[j][i] = getSBoxInvert((*state)[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void InvShiftRows(void)
|
||||
{
|
||||
uint8_t temp;
|
||||
|
||||
// Rotate first row 1 columns to right
|
||||
temp=(*state)[3][1];
|
||||
(*state)[3][1]=(*state)[2][1];
|
||||
(*state)[2][1]=(*state)[1][1];
|
||||
(*state)[1][1]=(*state)[0][1];
|
||||
(*state)[0][1]=temp;
|
||||
|
||||
// Rotate second row 2 columns to right
|
||||
temp=(*state)[0][2];
|
||||
(*state)[0][2]=(*state)[2][2];
|
||||
(*state)[2][2]=temp;
|
||||
|
||||
temp=(*state)[1][2];
|
||||
(*state)[1][2]=(*state)[3][2];
|
||||
(*state)[3][2]=temp;
|
||||
|
||||
// Rotate third row 3 columns to right
|
||||
temp=(*state)[0][3];
|
||||
(*state)[0][3]=(*state)[1][3];
|
||||
(*state)[1][3]=(*state)[2][3];
|
||||
(*state)[2][3]=(*state)[3][3];
|
||||
(*state)[3][3]=temp;
|
||||
}
|
||||
|
||||
|
||||
// Cipher is the main function that encrypts the PlainText.
|
||||
static void Cipher(void)
|
||||
{
|
||||
uint8_t round = 0;
|
||||
|
||||
// Add the First round key to the state before starting the rounds.
|
||||
AddRoundKey(0);
|
||||
|
||||
// There will be Nr rounds.
|
||||
// The first Nr-1 rounds are identical.
|
||||
// These Nr-1 rounds are executed in the loop below.
|
||||
for(round = 1; round < Nr; ++round)
|
||||
{
|
||||
SubBytes();
|
||||
ShiftRows();
|
||||
MixColumns();
|
||||
AddRoundKey(round);
|
||||
}
|
||||
|
||||
// The last round is given below.
|
||||
// The MixColumns function is not here in the last round.
|
||||
SubBytes();
|
||||
ShiftRows();
|
||||
AddRoundKey(Nr);
|
||||
}
|
||||
|
||||
static void InvCipher(void)
|
||||
{
|
||||
uint8_t round=0;
|
||||
|
||||
// Add the First round key to the state before starting the rounds.
|
||||
AddRoundKey(Nr);
|
||||
|
||||
// There will be Nr rounds.
|
||||
// The first Nr-1 rounds are identical.
|
||||
// These Nr-1 rounds are executed in the loop below.
|
||||
for(round=Nr-1;round>0;round--)
|
||||
{
|
||||
InvShiftRows();
|
||||
InvSubBytes();
|
||||
AddRoundKey(round);
|
||||
InvMixColumns();
|
||||
}
|
||||
|
||||
// The last round is given below.
|
||||
// The MixColumns function is not here in the last round.
|
||||
InvShiftRows();
|
||||
InvSubBytes();
|
||||
AddRoundKey(0);
|
||||
}
|
||||
|
||||
static void BlockCopy(uint8_t* output, uint8_t* input)
|
||||
{
|
||||
uint8_t i;
|
||||
for (i=0;i<KEYLEN;++i)
|
||||
{
|
||||
output[i] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Public functions: */
|
||||
/*****************************************************************************/
|
||||
#if defined(ECB) && ECB
|
||||
|
||||
|
||||
void AES128_ECB_encrypt(uint8_t* input, const uint8_t* key, uint8_t* output)
|
||||
{
|
||||
// Copy input to output, and work in-memory on output
|
||||
BlockCopy(output, input);
|
||||
state = (state_t*)output;
|
||||
|
||||
Key = key;
|
||||
KeyExpansion();
|
||||
|
||||
// The next function call encrypts the PlainText with the Key using AES algorithm.
|
||||
Cipher();
|
||||
}
|
||||
|
||||
void AES128_ECB_decrypt(uint8_t* input, const uint8_t* key, uint8_t *output)
|
||||
{
|
||||
// Copy input to output, and work in-memory on output
|
||||
BlockCopy(output, input);
|
||||
state = (state_t*)output;
|
||||
|
||||
// The KeyExpansion routine must be called before encryption.
|
||||
Key = key;
|
||||
KeyExpansion();
|
||||
|
||||
InvCipher();
|
||||
}
|
||||
|
||||
|
||||
#endif // #if defined(ECB) && ECB
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if defined(CBC) && CBC
|
||||
|
||||
|
||||
static void XorWithIv(uint8_t* buf)
|
||||
{
|
||||
uint8_t i;
|
||||
for(i = 0; i < KEYLEN; ++i)
|
||||
{
|
||||
buf[i] ^= Iv[i];
|
||||
}
|
||||
}
|
||||
|
||||
void AES128_CBC_encrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv)
|
||||
{
|
||||
size_t i;
|
||||
uint8_t remainders = length % KEYLEN; /* Remaining bytes in the last non-full block */
|
||||
|
||||
BlockCopy(output, input);
|
||||
state = (state_t*)output;
|
||||
|
||||
// Skip the key expansion if key is passed as 0
|
||||
if(0 != key)
|
||||
{
|
||||
Key = key;
|
||||
KeyExpansion();
|
||||
}
|
||||
|
||||
if(iv != 0)
|
||||
{
|
||||
Iv = (uint8_t*)iv;
|
||||
}
|
||||
|
||||
for(i = 0; i < length; i += KEYLEN)
|
||||
{
|
||||
XorWithIv(input);
|
||||
BlockCopy(output, input);
|
||||
state = (state_t*)output;
|
||||
Cipher();
|
||||
Iv = output;
|
||||
input += KEYLEN;
|
||||
output += KEYLEN;
|
||||
}
|
||||
|
||||
if(remainders)
|
||||
{
|
||||
BlockCopy(output, input);
|
||||
memset(output + remainders, 0, KEYLEN - remainders); /* add 0-padding */
|
||||
state = (state_t*)output;
|
||||
Cipher();
|
||||
}
|
||||
}
|
||||
|
||||
void AES128_CBC_decrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv)
|
||||
{
|
||||
size_t i;
|
||||
uint8_t remainders = length % KEYLEN; /* Remaining bytes in the last non-full block */
|
||||
|
||||
BlockCopy(output, input);
|
||||
state = (state_t*)output;
|
||||
|
||||
// Skip the key expansion if key is passed as 0
|
||||
if(0 != key)
|
||||
{
|
||||
Key = key;
|
||||
KeyExpansion();
|
||||
}
|
||||
|
||||
// If iv is passed as 0, we continue to encrypt without re-setting the Iv
|
||||
if(iv != 0)
|
||||
{
|
||||
Iv = (uint8_t*)iv;
|
||||
}
|
||||
|
||||
for(i = 0; i < length; i += KEYLEN)
|
||||
{
|
||||
BlockCopy(output, input);
|
||||
state = (state_t*)output;
|
||||
InvCipher();
|
||||
XorWithIv(output);
|
||||
Iv = input;
|
||||
input += KEYLEN;
|
||||
output += KEYLEN;
|
||||
}
|
||||
|
||||
if(remainders)
|
||||
{
|
||||
BlockCopy(output, input);
|
||||
memset(output+remainders, 0, KEYLEN - remainders); /* add 0-padding */
|
||||
state = (state_t*)output;
|
||||
InvCipher();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t AES128_CBC_encrypt_inplace( uint8_t* data, size_t length, const uint8_t* key, const uint8_t* iv){
|
||||
size_t i;
|
||||
state = NULL;
|
||||
|
||||
/* Check for valid length. Must be > 0 and a multiple of KEYLEN */
|
||||
if( length % KEYLEN != 0 || length == 0){
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Skip the key expansion if key is passed as 0
|
||||
if(0 != key)
|
||||
{
|
||||
Key = key;
|
||||
KeyExpansion();
|
||||
}
|
||||
|
||||
if(iv != 0)
|
||||
{
|
||||
Iv = (uint8_t*)iv;
|
||||
}
|
||||
|
||||
for(i = 0; i < length; i += KEYLEN)
|
||||
{
|
||||
XorWithIv(data);
|
||||
state = (state_t*)data;
|
||||
Cipher();
|
||||
Iv = data;
|
||||
data += KEYLEN;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We must have a writable iv pointer in this case, as we need the storage for holding the next decryption IV */
|
||||
uint8_t AES128_CBC_decrypt_inplace( uint8_t* data, size_t length, const uint8_t* key, uint8_t* iv){
|
||||
size_t i;
|
||||
state = NULL;
|
||||
uint8_t next_iv[KEYLEN];
|
||||
|
||||
/* Check for valid length. Must be > 0 and a multiple of KEYLEN */
|
||||
if( length % KEYLEN != 0 || length == 0){
|
||||
return 1;
|
||||
}
|
||||
|
||||
if( 0 == iv )
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
Iv = (uint8_t*)iv;
|
||||
|
||||
// Skip the key expansion if key is passed as 0
|
||||
if(0 != key)
|
||||
{
|
||||
Key = key;
|
||||
KeyExpansion();
|
||||
}
|
||||
|
||||
BlockCopy(next_iv,data);
|
||||
|
||||
for(i = 0; i < length; i += KEYLEN)
|
||||
{
|
||||
state = (state_t*)data;
|
||||
InvCipher();
|
||||
XorWithIv(data);
|
||||
data += KEYLEN;
|
||||
/* use the last buffered IV */
|
||||
BlockCopy(iv,next_iv);
|
||||
/* and buffer the next */
|
||||
BlockCopy(next_iv,data);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // #if defined(CBC) && CBC
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef _AES_H_
|
||||
#define _AES_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// #define the macros below to 1/0 to enable/disable the mode of operation.
|
||||
//
|
||||
// CBC enables AES128 encryption in CBC-mode of operation and handles 0-padding.
|
||||
// ECB enables the basic ECB 16-byte block algorithm. Both can be enabled simultaneously.
|
||||
|
||||
// The #ifndef-guard allows it to be configured before #include'ing or at compile time.
|
||||
#ifndef CBC
|
||||
#define CBC 1
|
||||
#endif
|
||||
|
||||
#ifndef ECB
|
||||
#define ECB 1
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if defined(ECB) && ECB
|
||||
|
||||
void AES128_ECB_encrypt(uint8_t* input, const uint8_t* key, uint8_t *output);
|
||||
void AES128_ECB_decrypt(uint8_t* input, const uint8_t* key, uint8_t *output);
|
||||
|
||||
#endif // #if defined(ECB) && ECB
|
||||
|
||||
|
||||
#if defined(CBC) && CBC
|
||||
|
||||
void AES128_CBC_encrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv);
|
||||
void AES128_CBC_decrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv);
|
||||
|
||||
/* These variants encrypt and decrypt the data block in-place.
|
||||
* The data block length MUST be a multiple of the algorithm block size (16 bytes)
|
||||
* The return value will be non-zero if the length is incorrect.
|
||||
* For the decypt function, the iv data must be writable, and will be modified on return.
|
||||
*/
|
||||
uint8_t AES128_CBC_encrypt_inplace( uint8_t* data, size_t length, const uint8_t* key, const uint8_t* iv);
|
||||
uint8_t AES128_CBC_decrypt_inplace( uint8_t* data, size_t length, const uint8_t* key, uint8_t* iv);
|
||||
|
||||
|
||||
#endif // #if defined(CBC) && CBC
|
||||
|
||||
|
||||
|
||||
#endif //_AES_H_
|
||||
@@ -0,0 +1,907 @@
|
||||
/*
|
||||
* The crypt_blowfish homepage is:
|
||||
*
|
||||
* http://www.openwall.com/crypt/
|
||||
*
|
||||
* This code comes from John the Ripper password cracker, with reentrant
|
||||
* and crypt(3) interfaces added, but optimizations specific to password
|
||||
* cracking removed.
|
||||
*
|
||||
* Written by Solar Designer <solar at openwall.com> in 1998-2014.
|
||||
* No copyright is claimed, and the software is hereby placed in the public
|
||||
* domain. In case this attempt to disclaim copyright and place the software
|
||||
* in the public domain is deemed null and void, then the software is
|
||||
* Copyright (c) 1998-2014 Solar Designer and it is hereby released to the
|
||||
* general public under the following terms:
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted.
|
||||
*
|
||||
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||
*
|
||||
* It is my intent that you should be able to use this on your system,
|
||||
* as part of a software package, or anywhere else to improve security,
|
||||
* ensure compatibility, or for any other purpose. I would appreciate
|
||||
* it if you give credit where it is due and keep your modifications in
|
||||
* the public domain as well, but I don't require that in order to let
|
||||
* you place this code and any modifications you make under a license
|
||||
* of your choice.
|
||||
*
|
||||
* This implementation is fully compatible with OpenBSD's bcrypt.c for prefix
|
||||
* "$2b$", originally by Niels Provos <provos at citi.umich.edu>, and it uses
|
||||
* some of his ideas. The password hashing algorithm was designed by David
|
||||
* Mazieres <dm at lcs.mit.edu>. For information on the level of
|
||||
* compatibility for bcrypt hash prefixes other than "$2b$", please refer to
|
||||
* the comments in BF_set_key() below and to the included crypt(3) man page.
|
||||
*
|
||||
* There's a paper on the algorithm that explains its design decisions:
|
||||
*
|
||||
* http://www.usenix.org/events/usenix99/provos.html
|
||||
*
|
||||
* Some of the tricks in BF_ROUND might be inspired by Eric Young's
|
||||
* Blowfish library (I can't be sure if I would think of something if I
|
||||
* hadn't seen his code).
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <errno.h>
|
||||
#ifndef __set_errno
|
||||
#define __set_errno(val) errno = (val)
|
||||
#endif
|
||||
|
||||
/* Just to make sure the prototypes match the actual definitions */
|
||||
#include "crypt_blowfish.h"
|
||||
|
||||
#ifdef __i386__
|
||||
#define BF_ASM 0
|
||||
#define BF_SCALE 1
|
||||
#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__)
|
||||
#define BF_ASM 0
|
||||
#define BF_SCALE 1
|
||||
#else
|
||||
#define BF_ASM 0
|
||||
#define BF_SCALE 0
|
||||
#endif
|
||||
|
||||
typedef unsigned int BF_word;
|
||||
typedef signed int BF_word_signed;
|
||||
|
||||
/* Number of Blowfish rounds, this is also hardcoded into a few places */
|
||||
#define BF_N 16
|
||||
|
||||
typedef BF_word BF_key[BF_N + 2];
|
||||
|
||||
typedef struct {
|
||||
BF_word S[4][0x100];
|
||||
BF_key P;
|
||||
} BF_ctx;
|
||||
|
||||
/*
|
||||
* Magic IV for 64 Blowfish encryptions that we do at the end.
|
||||
* The string is "OrpheanBeholderScryDoubt" on big-endian.
|
||||
*/
|
||||
static BF_word BF_magic_w[6] = {
|
||||
0x4F727068, 0x65616E42, 0x65686F6C,
|
||||
0x64657253, 0x63727944, 0x6F756274
|
||||
};
|
||||
|
||||
/*
|
||||
* P-box and S-box tables initialized with digits of Pi.
|
||||
*/
|
||||
static BF_ctx BF_init_state = {
|
||||
{
|
||||
{
|
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
|
||||
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
||||
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
|
||||
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
|
||||
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
|
||||
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
|
||||
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
|
||||
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
|
||||
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
|
||||
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
|
||||
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
|
||||
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
|
||||
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
|
||||
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
|
||||
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
|
||||
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
|
||||
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
|
||||
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
|
||||
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
|
||||
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
|
||||
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
|
||||
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
|
||||
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
|
||||
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
|
||||
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
|
||||
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
|
||||
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
|
||||
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
|
||||
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
|
||||
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
|
||||
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
|
||||
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
|
||||
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
|
||||
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
|
||||
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
|
||||
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
|
||||
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
|
||||
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
|
||||
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
|
||||
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
|
||||
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
|
||||
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
|
||||
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
|
||||
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
|
||||
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
|
||||
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
|
||||
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
|
||||
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
|
||||
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
|
||||
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
|
||||
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
|
||||
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
|
||||
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
|
||||
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
|
||||
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
|
||||
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
|
||||
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
|
||||
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
|
||||
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
|
||||
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
|
||||
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
|
||||
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
|
||||
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
|
||||
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
|
||||
}, {
|
||||
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
|
||||
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
|
||||
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
|
||||
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
|
||||
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
|
||||
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
|
||||
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
|
||||
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
|
||||
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
|
||||
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
|
||||
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
|
||||
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
|
||||
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
|
||||
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
|
||||
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
|
||||
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
|
||||
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
|
||||
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
|
||||
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
|
||||
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
|
||||
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
|
||||
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
|
||||
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
|
||||
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
|
||||
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
|
||||
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
|
||||
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
|
||||
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
|
||||
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
|
||||
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
|
||||
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
|
||||
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
|
||||
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
|
||||
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
|
||||
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
|
||||
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
|
||||
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
|
||||
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
|
||||
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
|
||||
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
|
||||
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
|
||||
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
|
||||
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
|
||||
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
|
||||
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
|
||||
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
|
||||
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
|
||||
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
|
||||
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
|
||||
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
|
||||
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
|
||||
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
|
||||
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
|
||||
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
|
||||
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
|
||||
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
|
||||
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
|
||||
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
|
||||
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
|
||||
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
|
||||
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
|
||||
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
|
||||
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
|
||||
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
|
||||
}, {
|
||||
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
|
||||
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
|
||||
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
|
||||
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
|
||||
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
|
||||
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
|
||||
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
|
||||
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
|
||||
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
|
||||
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
|
||||
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
|
||||
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
|
||||
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
|
||||
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
|
||||
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
|
||||
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
|
||||
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
|
||||
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
|
||||
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
|
||||
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
|
||||
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
|
||||
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
|
||||
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
|
||||
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
|
||||
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
|
||||
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
|
||||
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
|
||||
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
|
||||
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
|
||||
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
|
||||
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
|
||||
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
|
||||
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
|
||||
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
|
||||
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
|
||||
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
|
||||
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
|
||||
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
|
||||
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
|
||||
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
|
||||
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
|
||||
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
|
||||
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
|
||||
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
|
||||
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
|
||||
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
|
||||
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
|
||||
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
|
||||
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
|
||||
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
|
||||
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
|
||||
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
|
||||
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
|
||||
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
|
||||
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
|
||||
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
|
||||
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
|
||||
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
|
||||
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
|
||||
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
|
||||
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
|
||||
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
|
||||
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
|
||||
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
|
||||
}, {
|
||||
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
|
||||
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
|
||||
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
|
||||
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
|
||||
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
|
||||
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
|
||||
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
|
||||
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
|
||||
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
|
||||
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
|
||||
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
|
||||
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
|
||||
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
|
||||
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
|
||||
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
|
||||
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
|
||||
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
|
||||
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
|
||||
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
|
||||
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
|
||||
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
|
||||
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
|
||||
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
|
||||
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
|
||||
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
|
||||
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
|
||||
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
|
||||
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
|
||||
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
|
||||
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
|
||||
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
|
||||
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
|
||||
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
|
||||
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
|
||||
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
|
||||
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
|
||||
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
|
||||
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
|
||||
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
|
||||
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
|
||||
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
|
||||
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
|
||||
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
|
||||
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
|
||||
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
|
||||
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
|
||||
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
|
||||
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
|
||||
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
|
||||
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
|
||||
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
|
||||
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
|
||||
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
|
||||
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
|
||||
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
|
||||
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
|
||||
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
|
||||
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
|
||||
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
|
||||
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
|
||||
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
|
||||
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
|
||||
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
|
||||
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
|
||||
}
|
||||
}, {
|
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
|
||||
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
||||
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
|
||||
0x9216d5d9, 0x8979fb1b
|
||||
}
|
||||
};
|
||||
|
||||
static unsigned char BF_itoa64[64 + 1] =
|
||||
"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
static unsigned char BF_atoi64[0x60] = {
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1,
|
||||
54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64,
|
||||
64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64,
|
||||
64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
|
||||
43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64
|
||||
};
|
||||
|
||||
#define BF_safe_atoi64(dst, src) \
|
||||
{ \
|
||||
tmp = (unsigned char)(src); \
|
||||
if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \
|
||||
tmp = BF_atoi64[tmp]; \
|
||||
if (tmp > 63) return -1; \
|
||||
(dst) = tmp; \
|
||||
}
|
||||
|
||||
static int BF_decode(BF_word *dst, const char *src, int size)
|
||||
{
|
||||
unsigned char *dptr = (unsigned char *)dst;
|
||||
unsigned char *end = dptr + size;
|
||||
const unsigned char *sptr = (const unsigned char *)src;
|
||||
unsigned int tmp, c1, c2, c3, c4;
|
||||
|
||||
do {
|
||||
BF_safe_atoi64(c1, *sptr++);
|
||||
BF_safe_atoi64(c2, *sptr++);
|
||||
*dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4);
|
||||
if (dptr >= end) break;
|
||||
|
||||
BF_safe_atoi64(c3, *sptr++);
|
||||
*dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2);
|
||||
if (dptr >= end) break;
|
||||
|
||||
BF_safe_atoi64(c4, *sptr++);
|
||||
*dptr++ = ((c3 & 0x03) << 6) | c4;
|
||||
} while (dptr < end);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void BF_encode(char *dst, const BF_word *src, int size)
|
||||
{
|
||||
const unsigned char *sptr = (const unsigned char *)src;
|
||||
const unsigned char *end = sptr + size;
|
||||
unsigned char *dptr = (unsigned char *)dst;
|
||||
unsigned int c1, c2;
|
||||
|
||||
do {
|
||||
c1 = *sptr++;
|
||||
*dptr++ = BF_itoa64[c1 >> 2];
|
||||
c1 = (c1 & 0x03) << 4;
|
||||
if (sptr >= end) {
|
||||
*dptr++ = BF_itoa64[c1];
|
||||
break;
|
||||
}
|
||||
|
||||
c2 = *sptr++;
|
||||
c1 |= c2 >> 4;
|
||||
*dptr++ = BF_itoa64[c1];
|
||||
c1 = (c2 & 0x0f) << 2;
|
||||
if (sptr >= end) {
|
||||
*dptr++ = BF_itoa64[c1];
|
||||
break;
|
||||
}
|
||||
|
||||
c2 = *sptr++;
|
||||
c1 |= c2 >> 6;
|
||||
*dptr++ = BF_itoa64[c1];
|
||||
*dptr++ = BF_itoa64[c2 & 0x3f];
|
||||
} while (sptr < end);
|
||||
}
|
||||
|
||||
static void BF_swap(BF_word *x, int count)
|
||||
{
|
||||
static int endianness_check = 1;
|
||||
char *is_little_endian = (char *)&endianness_check;
|
||||
BF_word tmp;
|
||||
|
||||
if (*is_little_endian)
|
||||
do {
|
||||
tmp = *x;
|
||||
tmp = (tmp << 16) | (tmp >> 16);
|
||||
*x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF);
|
||||
} while (--count);
|
||||
}
|
||||
|
||||
#if BF_SCALE
|
||||
/* Architectures which can shift addresses left by 2 bits with no extra cost */
|
||||
#define BF_ROUND(L, R, N) \
|
||||
tmp1 = L & 0xFF; \
|
||||
tmp2 = L >> 8; \
|
||||
tmp2 &= 0xFF; \
|
||||
tmp3 = L >> 16; \
|
||||
tmp3 &= 0xFF; \
|
||||
tmp4 = L >> 24; \
|
||||
tmp1 = data.ctx.S[3][tmp1]; \
|
||||
tmp2 = data.ctx.S[2][tmp2]; \
|
||||
tmp3 = data.ctx.S[1][tmp3]; \
|
||||
tmp3 += data.ctx.S[0][tmp4]; \
|
||||
tmp3 ^= tmp2; \
|
||||
R ^= data.ctx.P[N + 1]; \
|
||||
tmp3 += tmp1; \
|
||||
R ^= tmp3;
|
||||
#else
|
||||
/* Architectures with no complicated addressing modes supported */
|
||||
#define BF_INDEX(S, i) \
|
||||
(*((BF_word *)(((unsigned char *)S) + (i))))
|
||||
#define BF_ROUND(L, R, N) \
|
||||
tmp1 = L & 0xFF; \
|
||||
tmp1 <<= 2; \
|
||||
tmp2 = L >> 6; \
|
||||
tmp2 &= 0x3FC; \
|
||||
tmp3 = L >> 14; \
|
||||
tmp3 &= 0x3FC; \
|
||||
tmp4 = L >> 22; \
|
||||
tmp4 &= 0x3FC; \
|
||||
tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \
|
||||
tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \
|
||||
tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \
|
||||
tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \
|
||||
tmp3 ^= tmp2; \
|
||||
R ^= data.ctx.P[N + 1]; \
|
||||
tmp3 += tmp1; \
|
||||
R ^= tmp3;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Encrypt one block, BF_N is hardcoded here.
|
||||
*/
|
||||
#define BF_ENCRYPT \
|
||||
L ^= data.ctx.P[0]; \
|
||||
BF_ROUND(L, R, 0); \
|
||||
BF_ROUND(R, L, 1); \
|
||||
BF_ROUND(L, R, 2); \
|
||||
BF_ROUND(R, L, 3); \
|
||||
BF_ROUND(L, R, 4); \
|
||||
BF_ROUND(R, L, 5); \
|
||||
BF_ROUND(L, R, 6); \
|
||||
BF_ROUND(R, L, 7); \
|
||||
BF_ROUND(L, R, 8); \
|
||||
BF_ROUND(R, L, 9); \
|
||||
BF_ROUND(L, R, 10); \
|
||||
BF_ROUND(R, L, 11); \
|
||||
BF_ROUND(L, R, 12); \
|
||||
BF_ROUND(R, L, 13); \
|
||||
BF_ROUND(L, R, 14); \
|
||||
BF_ROUND(R, L, 15); \
|
||||
tmp4 = R; \
|
||||
R = L; \
|
||||
L = tmp4 ^ data.ctx.P[BF_N + 1];
|
||||
|
||||
#if BF_ASM
|
||||
#define BF_body() \
|
||||
_BF_body_r(&data.ctx);
|
||||
#else
|
||||
#define BF_body() \
|
||||
L = R = 0; \
|
||||
ptr = data.ctx.P; \
|
||||
do { \
|
||||
ptr += 2; \
|
||||
BF_ENCRYPT; \
|
||||
*(ptr - 2) = L; \
|
||||
*(ptr - 1) = R; \
|
||||
} while (ptr < &data.ctx.P[BF_N + 2]); \
|
||||
\
|
||||
ptr = data.ctx.S[0]; \
|
||||
do { \
|
||||
ptr += 2; \
|
||||
BF_ENCRYPT; \
|
||||
*(ptr - 2) = L; \
|
||||
*(ptr - 1) = R; \
|
||||
} while (ptr < &data.ctx.S[3][0xFF]);
|
||||
#endif
|
||||
|
||||
static void BF_set_key(const char *key, BF_key expanded, BF_key initial,
|
||||
unsigned char flags)
|
||||
{
|
||||
const char *ptr = key;
|
||||
unsigned int bug, i, j;
|
||||
BF_word safety, sign, diff, tmp[2];
|
||||
|
||||
/*
|
||||
* There was a sign extension bug in older revisions of this function. While
|
||||
* we would have liked to simply fix the bug and move on, we have to provide
|
||||
* a backwards compatibility feature (essentially the bug) for some systems and
|
||||
* a safety measure for some others. The latter is needed because for certain
|
||||
* multiple inputs to the buggy algorithm there exist easily found inputs to
|
||||
* the correct algorithm that produce the same hash. Thus, we optionally
|
||||
* deviate from the correct algorithm just enough to avoid such collisions.
|
||||
* While the bug itself affected the majority of passwords containing
|
||||
* characters with the 8th bit set (although only a percentage of those in a
|
||||
* collision-producing way), the anti-collision safety measure affects
|
||||
* only a subset of passwords containing the '\xff' character (not even all of
|
||||
* those passwords, just some of them). This character is not found in valid
|
||||
* UTF-8 sequences and is rarely used in popular 8-bit character encodings.
|
||||
* Thus, the safety measure is unlikely to cause much annoyance, and is a
|
||||
* reasonable tradeoff to use when authenticating against existing hashes that
|
||||
* are not reliably known to have been computed with the correct algorithm.
|
||||
*
|
||||
* We use an approach that tries to minimize side-channel leaks of password
|
||||
* information - that is, we mostly use fixed-cost bitwise operations instead
|
||||
* of branches or table lookups. (One conditional branch based on password
|
||||
* length remains. It is not part of the bug aftermath, though, and is
|
||||
* difficult and possibly unreasonable to avoid given the use of C strings by
|
||||
* the caller, which results in similar timing leaks anyway.)
|
||||
*
|
||||
* For actual implementation, we set an array index in the variable "bug"
|
||||
* (0 means no bug, 1 means sign extension bug emulation) and a flag in the
|
||||
* variable "safety" (bit 16 is set when the safety measure is requested).
|
||||
* Valid combinations of settings are:
|
||||
*
|
||||
* Prefix "$2a$": bug = 0, safety = 0x10000
|
||||
* Prefix "$2b$": bug = 0, safety = 0
|
||||
* Prefix "$2x$": bug = 1, safety = 0
|
||||
* Prefix "$2y$": bug = 0, safety = 0
|
||||
*/
|
||||
bug = (unsigned int)flags & 1;
|
||||
safety = ((BF_word)flags & 2) << 15;
|
||||
|
||||
sign = diff = 0;
|
||||
|
||||
for (i = 0; i < BF_N + 2; i++) {
|
||||
tmp[0] = tmp[1] = 0;
|
||||
for (j = 0; j < 4; j++) {
|
||||
tmp[0] <<= 8;
|
||||
tmp[0] |= (unsigned char)*ptr; /* correct */
|
||||
tmp[1] <<= 8;
|
||||
tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */
|
||||
/*
|
||||
* Sign extension in the first char has no effect - nothing to overwrite yet,
|
||||
* and those extra 24 bits will be fully shifted out of the 32-bit word. For
|
||||
* chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign
|
||||
* extension in tmp[1] occurs. Once this flag is set, it remains set.
|
||||
*/
|
||||
if (j)
|
||||
sign |= tmp[1] & 0x80;
|
||||
if (!*ptr)
|
||||
ptr = key;
|
||||
else
|
||||
ptr++;
|
||||
}
|
||||
diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */
|
||||
|
||||
expanded[i] = tmp[bug];
|
||||
initial[i] = BF_init_state.P[i] ^ tmp[bug];
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point, "diff" is zero iff the correct and buggy algorithms produced
|
||||
* exactly the same result. If so and if "sign" is non-zero, which indicates
|
||||
* that there was a non-benign sign extension, this means that we have a
|
||||
* collision between the correctly computed hash for this password and a set of
|
||||
* passwords that could be supplied to the buggy algorithm. Our safety measure
|
||||
* is meant to protect from such many-buggy to one-correct collisions, by
|
||||
* deviating from the correct algorithm in such cases. Let's check for this.
|
||||
*/
|
||||
diff |= diff >> 16; /* still zero iff exact match */
|
||||
diff &= 0xffff; /* ditto */
|
||||
diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */
|
||||
sign <<= 9; /* move the non-benign sign extension flag to bit 16 */
|
||||
sign &= ~diff & safety; /* action needed? */
|
||||
|
||||
/*
|
||||
* If we have determined that we need to deviate from the correct algorithm,
|
||||
* flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but
|
||||
* let's stick to it now. It came out of the approach we used above, and it's
|
||||
* not any worse than any other choice we could make.)
|
||||
*
|
||||
* It is crucial that we don't do the same to the expanded key used in the main
|
||||
* Eksblowfish loop. By doing it to only one of these two, we deviate from a
|
||||
* state that could be directly specified by a password to the buggy algorithm
|
||||
* (and to the fully correct one as well, but that's a side-effect).
|
||||
*/
|
||||
initial[0] ^= sign;
|
||||
}
|
||||
|
||||
static const unsigned char flags_by_subtype[26] =
|
||||
{2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0};
|
||||
|
||||
static char *BF_crypt(const char *key, const char *setting,
|
||||
char *output, int size,
|
||||
BF_word min)
|
||||
{
|
||||
#if BF_ASM
|
||||
extern void _BF_body_r(BF_ctx *ctx);
|
||||
#endif
|
||||
struct {
|
||||
BF_ctx ctx;
|
||||
BF_key expanded_key;
|
||||
union {
|
||||
BF_word salt[4];
|
||||
BF_word output[6];
|
||||
} binary;
|
||||
} data;
|
||||
BF_word L, R;
|
||||
BF_word tmp1, tmp2, tmp3, tmp4;
|
||||
BF_word *ptr;
|
||||
BF_word count;
|
||||
int i;
|
||||
|
||||
if (size < 7 + 22 + 31 + 1) {
|
||||
__set_errno(ERANGE);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (setting[0] != '$' ||
|
||||
setting[1] != '2' ||
|
||||
setting[2] < 'a' || setting[2] > 'z' ||
|
||||
!flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] ||
|
||||
setting[3] != '$' ||
|
||||
setting[4] < '0' || setting[4] > '3' ||
|
||||
setting[5] < '0' || setting[5] > '9' ||
|
||||
(setting[4] == '3' && setting[5] > '1') ||
|
||||
setting[6] != '$') {
|
||||
__set_errno(EINVAL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0'));
|
||||
if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) {
|
||||
__set_errno(EINVAL);
|
||||
return NULL;
|
||||
}
|
||||
BF_swap(data.binary.salt, 4);
|
||||
|
||||
BF_set_key(key, data.expanded_key, data.ctx.P,
|
||||
flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']);
|
||||
|
||||
memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S));
|
||||
|
||||
L = R = 0;
|
||||
for (i = 0; i < BF_N + 2; i += 2) {
|
||||
L ^= data.binary.salt[i & 2];
|
||||
R ^= data.binary.salt[(i & 2) + 1];
|
||||
BF_ENCRYPT;
|
||||
data.ctx.P[i] = L;
|
||||
data.ctx.P[i + 1] = R;
|
||||
}
|
||||
|
||||
ptr = data.ctx.S[0];
|
||||
do {
|
||||
ptr += 4;
|
||||
L ^= data.binary.salt[(BF_N + 2) & 3];
|
||||
R ^= data.binary.salt[(BF_N + 3) & 3];
|
||||
BF_ENCRYPT;
|
||||
*(ptr - 4) = L;
|
||||
*(ptr - 3) = R;
|
||||
|
||||
L ^= data.binary.salt[(BF_N + 4) & 3];
|
||||
R ^= data.binary.salt[(BF_N + 5) & 3];
|
||||
BF_ENCRYPT;
|
||||
*(ptr - 2) = L;
|
||||
*(ptr - 1) = R;
|
||||
} while (ptr < &data.ctx.S[3][0xFF]);
|
||||
|
||||
do {
|
||||
int done;
|
||||
|
||||
for (i = 0; i < BF_N + 2; i += 2) {
|
||||
data.ctx.P[i] ^= data.expanded_key[i];
|
||||
data.ctx.P[i + 1] ^= data.expanded_key[i + 1];
|
||||
}
|
||||
|
||||
done = 0;
|
||||
do {
|
||||
BF_body();
|
||||
if (done)
|
||||
break;
|
||||
done = 1;
|
||||
|
||||
tmp1 = data.binary.salt[0];
|
||||
tmp2 = data.binary.salt[1];
|
||||
tmp3 = data.binary.salt[2];
|
||||
tmp4 = data.binary.salt[3];
|
||||
for (i = 0; i < BF_N; i += 4) {
|
||||
data.ctx.P[i] ^= tmp1;
|
||||
data.ctx.P[i + 1] ^= tmp2;
|
||||
data.ctx.P[i + 2] ^= tmp3;
|
||||
data.ctx.P[i + 3] ^= tmp4;
|
||||
}
|
||||
data.ctx.P[16] ^= tmp1;
|
||||
data.ctx.P[17] ^= tmp2;
|
||||
} while (1);
|
||||
} while (--count);
|
||||
|
||||
for (i = 0; i < 6; i += 2) {
|
||||
L = BF_magic_w[i];
|
||||
R = BF_magic_w[i + 1];
|
||||
|
||||
count = 64;
|
||||
do {
|
||||
BF_ENCRYPT;
|
||||
} while (--count);
|
||||
|
||||
data.binary.output[i] = L;
|
||||
data.binary.output[i + 1] = R;
|
||||
}
|
||||
|
||||
memcpy(output, setting, 7 + 22 - 1);
|
||||
output[7 + 22 - 1] = BF_itoa64[(int)
|
||||
BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30];
|
||||
|
||||
/* This has to be bug-compatible with the original implementation, so
|
||||
* only encode 23 of the 24 bytes. :-) */
|
||||
BF_swap(data.binary.output, 6);
|
||||
BF_encode(&output[7 + 22], data.binary.output, 23);
|
||||
output[7 + 22 + 31] = '\0';
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
int _crypt_output_magic(const char *setting, char *output, int size)
|
||||
{
|
||||
if (size < 3)
|
||||
return -1;
|
||||
|
||||
output[0] = '*';
|
||||
output[1] = '0';
|
||||
output[2] = '\0';
|
||||
|
||||
if (setting[0] == '*' && setting[1] == '0')
|
||||
output[1] = '1';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Please preserve the runtime self-test. It serves two purposes at once:
|
||||
*
|
||||
* 1. We really can't afford the risk of producing incompatible hashes e.g.
|
||||
* when there's something like gcc bug 26587 again, whereas an application or
|
||||
* library integrating this code might not also integrate our external tests or
|
||||
* it might not run them after every build. Even if it does, the miscompile
|
||||
* might only occur on the production build, but not on a testing build (such
|
||||
* as because of different optimization settings). It is painful to recover
|
||||
* from incorrectly-computed hashes - merely fixing whatever broke is not
|
||||
* enough. Thus, a proactive measure like this self-test is needed.
|
||||
*
|
||||
* 2. We don't want to leave sensitive data from our actual password hash
|
||||
* computation on the stack or in registers. Previous revisions of the code
|
||||
* would do explicit cleanups, but simply running the self-test after hash
|
||||
* computation is more reliable.
|
||||
*
|
||||
* The performance cost of this quick self-test is around 0.6% at the "$2a$08"
|
||||
* setting.
|
||||
*/
|
||||
char *_crypt_blowfish_rn(const char *key, const char *setting,
|
||||
char *output, int size)
|
||||
{
|
||||
const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8";
|
||||
const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu";
|
||||
static const char * const test_hashes[2] =
|
||||
{"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */
|
||||
"VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */
|
||||
const char *test_hash = test_hashes[0];
|
||||
char *retval;
|
||||
const char *p;
|
||||
int save_errno, ok;
|
||||
struct {
|
||||
char s[7 + 22 + 1];
|
||||
char o[7 + 22 + 31 + 1 + 1 + 1];
|
||||
} buf;
|
||||
|
||||
/* Hash the supplied password */
|
||||
_crypt_output_magic(setting, output, size);
|
||||
retval = BF_crypt(key, setting, output, size, 16);
|
||||
save_errno = errno;
|
||||
|
||||
/*
|
||||
* Do a quick self-test. It is important that we make both calls to BF_crypt()
|
||||
* from the same scope such that they likely use the same stack locations,
|
||||
* which makes the second call overwrite the first call's sensitive data on the
|
||||
* stack and makes it more likely that any alignment related issues would be
|
||||
* detected by the self-test.
|
||||
*/
|
||||
memcpy(buf.s, test_setting, sizeof(buf.s));
|
||||
if (retval) {
|
||||
unsigned int flags = flags_by_subtype[
|
||||
(unsigned int)(unsigned char)setting[2] - 'a'];
|
||||
test_hash = test_hashes[flags & 1];
|
||||
buf.s[2] = setting[2];
|
||||
}
|
||||
memset(buf.o, 0x55, sizeof(buf.o));
|
||||
buf.o[sizeof(buf.o) - 1] = 0;
|
||||
p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1);
|
||||
|
||||
ok = (p == buf.o &&
|
||||
!memcmp(p, buf.s, 7 + 22) &&
|
||||
!memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1));
|
||||
|
||||
{
|
||||
const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345";
|
||||
BF_key ae, ai, ye, yi;
|
||||
BF_set_key(k, ae, ai, 2); /* $2a$ */
|
||||
BF_set_key(k, ye, yi, 4); /* $2y$ */
|
||||
ai[0] ^= 0x10000; /* undo the safety (for comparison) */
|
||||
ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 &&
|
||||
!memcmp(ae, ye, sizeof(ae)) &&
|
||||
!memcmp(ai, yi, sizeof(ai));
|
||||
}
|
||||
|
||||
__set_errno(save_errno);
|
||||
if (ok)
|
||||
return retval;
|
||||
|
||||
/* Should not happen */
|
||||
_crypt_output_magic(setting, output, size);
|
||||
__set_errno(EINVAL); /* pretend we don't support this hash type */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count,
|
||||
const char *input, int size, char *output, int output_size)
|
||||
{
|
||||
if (size < 16 || output_size < 7 + 22 + 1 ||
|
||||
(count && (count < 4 || count > 31)) ||
|
||||
prefix[0] != '$' || prefix[1] != '2' ||
|
||||
(prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) {
|
||||
if (output_size > 0) output[0] = '\0';
|
||||
__set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!count) count = 5;
|
||||
|
||||
output[0] = '$';
|
||||
output[1] = '2';
|
||||
output[2] = prefix[2];
|
||||
output[3] = '$';
|
||||
output[4] = '0' + count / 10;
|
||||
output[5] = '0' + count % 10;
|
||||
output[6] = '$';
|
||||
|
||||
BF_encode(&output[7], (const BF_word *)input, 16);
|
||||
output[7 + 22] = '\0';
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Written by Solar Designer <solar at openwall.com> in 2000-2011.
|
||||
* No copyright is claimed, and the software is hereby placed in the public
|
||||
* domain. In case this attempt to disclaim copyright and place the software
|
||||
* in the public domain is deemed null and void, then the software is
|
||||
* Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
|
||||
* general public under the following terms:
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted.
|
||||
*
|
||||
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||
*
|
||||
* See crypt_blowfish.c for more information.
|
||||
*/
|
||||
|
||||
#ifndef _CRYPT_BLOWFISH_H
|
||||
#define _CRYPT_BLOWFISH_H
|
||||
|
||||
extern int _crypt_output_magic(const char *setting, char *output, int size);
|
||||
extern char *_crypt_blowfish_rn(const char *key, const char *setting,
|
||||
char *output, int size);
|
||||
extern char *_crypt_gensalt_blowfish_rn(const char *prefix,
|
||||
unsigned long count,
|
||||
const char *input, int size, char *output, int output_size);
|
||||
|
||||
#endif
|
||||
@@ -106,7 +106,7 @@ def FindHFile(args, filename):
|
||||
found = full_filename
|
||||
|
||||
if found is None:
|
||||
raise Exception, "'%s' not found in include dirs" % filename
|
||||
raise Exception("'%s' not found in include dirs" % filename)
|
||||
|
||||
return found
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/* $OpenBSD: pkcs5_pbkdf2.c,v 1.11 2019/11/21 16:07:24 tedu Exp $ */
|
||||
|
||||
/*-
|
||||
* Copyright (c) 2008 Damien Bergamini <damien.bergamini@free.fr>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_GNUTLS
|
||||
#include <gnutls/gnutls.h>
|
||||
#include <gnutls/crypto.h>
|
||||
#define SHA_CTX gnutls_hash_hd_t
|
||||
#define SHA1_Init(c) gnutls_hash_init(c, GNUTLS_DIG_SHA1)
|
||||
#define SHA1_Update(c,b,l) gnutls_hash(*c, b, l)
|
||||
#define SHA1_Final(b,c) gnutls_hash_deinit(*c, b);
|
||||
#elif defined(HAVE_OPENSSL)
|
||||
#include <openssl/sha.h>
|
||||
#endif
|
||||
|
||||
#define MINIMUM(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
#define bcopy(b1,b2,len) (memmove((b2), (b1), (len)), (void) 0)
|
||||
|
||||
#define SHA1_BLOCK_LENGTH 64
|
||||
#define SHA1_DIGEST_LENGTH 20
|
||||
#define SHA1_DIGEST_STRING_LENGTH (SHA1_DIGEST_LENGTH * 2 + 1)
|
||||
|
||||
/*
|
||||
* HMAC-SHA-1 (from RFC 2202).
|
||||
*/
|
||||
static void
|
||||
hmac_sha1(const u_int8_t *text, size_t text_len, const u_int8_t *key,
|
||||
size_t key_len, u_int8_t digest[SHA1_DIGEST_LENGTH])
|
||||
{
|
||||
SHA_CTX ctx;
|
||||
u_int8_t k_pad[SHA1_BLOCK_LENGTH];
|
||||
u_int8_t tk[SHA1_DIGEST_LENGTH];
|
||||
int i;
|
||||
|
||||
if (key_len > SHA1_BLOCK_LENGTH) {
|
||||
SHA1_Init(&ctx);
|
||||
SHA1_Update(&ctx, key, key_len);
|
||||
SHA1_Final(tk, &ctx);
|
||||
|
||||
key = tk;
|
||||
key_len = SHA1_DIGEST_LENGTH;
|
||||
}
|
||||
|
||||
memset(k_pad, 0, sizeof k_pad);
|
||||
bcopy(key, k_pad, key_len);
|
||||
for (i = 0; i < SHA1_BLOCK_LENGTH; i++)
|
||||
k_pad[i] ^= 0x36;
|
||||
|
||||
SHA1_Init(&ctx);
|
||||
SHA1_Update(&ctx, k_pad, SHA1_BLOCK_LENGTH);
|
||||
SHA1_Update(&ctx, text, text_len);
|
||||
SHA1_Final(digest, &ctx);
|
||||
|
||||
memset(k_pad, 0, sizeof k_pad);
|
||||
bcopy(key, k_pad, key_len);
|
||||
for (i = 0; i < SHA1_BLOCK_LENGTH; i++)
|
||||
k_pad[i] ^= 0x5c;
|
||||
|
||||
SHA1_Init(&ctx);
|
||||
SHA1_Update(&ctx, k_pad, SHA1_BLOCK_LENGTH);
|
||||
SHA1_Update(&ctx, digest, SHA1_DIGEST_LENGTH);
|
||||
SHA1_Final(digest, &ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Password-Based Key Derivation Function 2 (PKCS #5 v2.0).
|
||||
* Code based on IEEE Std 802.11-2007, Annex H.4.2.
|
||||
*/
|
||||
int
|
||||
pkcs5_pbkdf2(const char *pass, size_t pass_len, const uint8_t *salt,
|
||||
size_t salt_len, uint8_t *key, size_t key_len, unsigned int rounds)
|
||||
{
|
||||
uint8_t *asalt, obuf[SHA1_DIGEST_LENGTH];
|
||||
uint8_t d1[SHA1_DIGEST_LENGTH], d2[SHA1_DIGEST_LENGTH];
|
||||
unsigned int i, j;
|
||||
unsigned int count;
|
||||
size_t r;
|
||||
|
||||
if (rounds < 1 || key_len == 0)
|
||||
goto bad;
|
||||
if (salt_len == 0 || salt_len > SIZE_MAX - 4)
|
||||
goto bad;
|
||||
if ((asalt = malloc(salt_len + 4)) == NULL)
|
||||
goto bad;
|
||||
|
||||
memcpy(asalt, salt, salt_len);
|
||||
|
||||
for (count = 1; key_len > 0; count++) {
|
||||
asalt[salt_len + 0] = (count >> 24) & 0xff;
|
||||
asalt[salt_len + 1] = (count >> 16) & 0xff;
|
||||
asalt[salt_len + 2] = (count >> 8) & 0xff;
|
||||
asalt[salt_len + 3] = count & 0xff;
|
||||
hmac_sha1(asalt, salt_len + 4, (const u_int8_t *)pass, pass_len, d1);
|
||||
memcpy(obuf, d1, sizeof(obuf));
|
||||
|
||||
for (i = 1; i < rounds; i++) {
|
||||
hmac_sha1(d1, sizeof(d1), (const u_int8_t *)pass, pass_len, d2);
|
||||
memcpy(d1, d2, sizeof(d1));
|
||||
for (j = 0; j < sizeof(obuf); j++)
|
||||
obuf[j] ^= d1[j];
|
||||
}
|
||||
|
||||
r = MINIMUM(key_len, SHA1_DIGEST_LENGTH);
|
||||
memcpy(key, obuf, r);
|
||||
key += r;
|
||||
key_len -= r;
|
||||
};
|
||||
bzero(asalt, salt_len + 4);
|
||||
free(asalt);
|
||||
bzero(d1, sizeof(d1));
|
||||
bzero(d2, sizeof(d2));
|
||||
bzero(obuf, sizeof(obuf));
|
||||
|
||||
return 0;
|
||||
|
||||
bad:
|
||||
/* overwrite with random in case caller doesn't check return code */
|
||||
//arc4random_buf(key, key_len);
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef PKCS5_PBKDF2_H
|
||||
#define PKCS5_PBKDF2_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PBKDF2_KEY_SIZE_SHA1 (20)
|
||||
#define PBKDF2_SALT_LEN (16)
|
||||
#define PBKDF2_DEFAULT_ROUNDS (5000)
|
||||
|
||||
int
|
||||
pkcs5_pbkdf2(const char *pass, size_t pass_len, const uint8_t *salt,
|
||||
size_t salt_len, uint8_t *key, size_t key_len, unsigned int rounds);
|
||||
|
||||
#endif /* ! PKCS5_PBKDF2_H */
|
||||
@@ -1,6 +1,7 @@
|
||||
/* TestNSString+MD5SHA1.m - this file is part of SOGo
|
||||
/* TestNSString+Crypto.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2011, 2012 Jeroen Dekkers
|
||||
* Copyright (C) 2020 Nicolas Höft
|
||||
*
|
||||
* Author: Jeroen Dekkers <jeroen@dekkers.ch>
|
||||
*
|
||||
@@ -60,4 +61,55 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void) test_blowfish
|
||||
{
|
||||
NSString *error;
|
||||
// well-known comparison
|
||||
NSString *blf_key = @"123456";
|
||||
NSString *blf_hash = @"{BLF-CRYPT}$2a$05$tLVuFQTgdwrZmixu.QMxoedUAUEeIFIBv89Ur5mQ6F1vBL8Vw1mXO";
|
||||
error = [NSString stringWithFormat:
|
||||
@"string '%@' wrong BLF-CRYPT: '%@'",
|
||||
blf_key, blf_hash];
|
||||
testWithMessage([blf_key isEqualToCrypted:blf_hash withDefaultScheme: @"CRYPT" keyPath: nil], error);
|
||||
|
||||
// generate a new blowfish-crypt key
|
||||
NSString *blf_prefix = @"$2y$05$";
|
||||
|
||||
NSString *blf_result = [blf_key asCryptedPassUsingScheme: @"blf-crypt" keyPath: nil];
|
||||
|
||||
error = [NSString stringWithFormat:
|
||||
@"returned hash '%@' has incorrect BLF-CRYPT prefix: '%@'",
|
||||
blf_result, blf_prefix];
|
||||
|
||||
testWithMessage([blf_result hasPrefix: blf_prefix], error);
|
||||
|
||||
test([blf_key isEqualToCrypted:blf_result withDefaultScheme: @"BLF-CRYPT" keyPath: nil]);
|
||||
}
|
||||
|
||||
- (void) test_pbkdf2
|
||||
{
|
||||
NSString *error;
|
||||
// well-known comparison
|
||||
NSString *pbkdf2_key = @"123456";
|
||||
NSString *pbkdf2_hash = @"{PBKDF2}$1$xbhnwhLxltdS9L5M$5001$f1699047a6132383490817d6e58a5284f13339f0";
|
||||
NSString *pkbf2_prefix;
|
||||
NSString *pkbf2_result;
|
||||
|
||||
error = [NSString stringWithFormat:
|
||||
@"string '%@' wrong PBKDF2: '%@'",
|
||||
pbkdf2_key, pbkdf2_hash];
|
||||
testWithMessage([pbkdf2_key isEqualToCrypted:pbkdf2_hash withDefaultScheme: @"CRYPT" keyPath: nil], error);
|
||||
|
||||
// generate a new pbkdf2-crypt key
|
||||
pkbf2_prefix = @"$1$";
|
||||
pkbf2_result = [pbkdf2_key asCryptedPassUsingScheme: @"PBKDF2" keyPath: nil];
|
||||
|
||||
error = [NSString stringWithFormat:
|
||||
@"returned hash '%@' has incorrect PBKDF2 prefix: '%@'",
|
||||
pkbf2_result, pkbf2_prefix];
|
||||
|
||||
testWithMessage([pkbf2_result hasPrefix: pkbf2_prefix], error);
|
||||
test([pbkdf2_key isEqualToCrypted:pkbf2_result withDefaultScheme: @"PBKDF2" keyPath: nil]);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
#import <Foundation/NSAutoreleasePool.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSEnumerator.h>
|
||||
#import <Foundation/NSString.h>
|
||||
@@ -36,6 +37,8 @@
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalEvent.h>
|
||||
#import <NGCards/NGVCard.h>
|
||||
|
||||
#import "SOGoTool.h"
|
||||
@@ -248,6 +251,48 @@
|
||||
[gcsFolder deleteContentWithName: c_name];
|
||||
rc = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
iCalEvent *event;
|
||||
|
||||
event = (iCalEvent *) [calendar firstChildWithTag: @"vevent"];
|
||||
if (event)
|
||||
{
|
||||
iCalDateTime *startDate, *endDate;
|
||||
|
||||
startDate = (iCalDateTime *) [event uniqueChildWithTag: @"dtstart"];
|
||||
if (![startDate dateTime])
|
||||
{
|
||||
NSLog(@"Missing start date of event in path %@ with c_name = %@ (%@)", folder, c_name, [event summary]);
|
||||
if (delete)
|
||||
[gcsFolder deleteContentWithName: c_name];
|
||||
rc = NO;
|
||||
}
|
||||
endDate = (iCalDateTime *) [event uniqueChildWithTag: @"dtend"];
|
||||
if (![endDate dateTime] && ![event hasDuration])
|
||||
{
|
||||
NSLog(@"Missing end date of event in path %@ with c_name = %@ (%@)", folder, c_name, [event summary]);
|
||||
if (delete)
|
||||
[gcsFolder deleteContentWithName: c_name];
|
||||
rc = NO;
|
||||
}
|
||||
if ([startDate dateTime] && [endDate dateTime])
|
||||
{
|
||||
NSComparisonResult comparison;
|
||||
|
||||
comparison = [[startDate dateTime] compare: [endDate dateTime]];
|
||||
if (([event isAllDay] && comparison == NSOrderedDescending) ||
|
||||
(![event isAllDay] && comparison != NSOrderedAscending))
|
||||
{
|
||||
NSLog(@"Start date (%@) is not before end date (%@) for event in path %@ with c_name = %@ (%@)",
|
||||
[startDate dateTime], [endDate dateTime], folder, c_name, [event summary]);
|
||||
if (delete)
|
||||
[gcsFolder deleteContentWithName: c_name];
|
||||
rc = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#import <GDLContentStore/NSURL+GCS.h>
|
||||
|
||||
#import <SOGo/SOGoCache.h>
|
||||
#import <SOGo/SOGoSource.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
@@ -116,7 +117,7 @@ typedef enum
|
||||
" folder the folder - Calendar/<ID> or Contacts/<ID>\n"
|
||||
" user the user to get/set rights for - 'ALL', '<default>', 'anonymous' are supported\n"
|
||||
" rights rights to add\n\n"
|
||||
"Example: sogo-tool manage-acl get jdoe Calendar/personal\n\n"
|
||||
"Example: sogo-tool manage-acl get jdoe Calendar/personal ALL\n\n"
|
||||
"Note: You can add only one access right at the time. To set them all at once,\n"
|
||||
" invoke 'remove' first to remove them all.\n\n");
|
||||
}
|
||||
@@ -215,7 +216,30 @@ typedef enum
|
||||
[allSQLUsers addObject: @"anonymous"];
|
||||
}
|
||||
else
|
||||
[allSQLUsers addObject: user];
|
||||
{
|
||||
NSDictionary *dict;
|
||||
|
||||
dict = [[SOGoUserManager sharedUserManager] contactInfosForUserWithUIDorEmail: user];
|
||||
|
||||
if (dict && [[dict objectForKey: @"isGroup"] boolValue])
|
||||
{
|
||||
id <SOGoSource> source;
|
||||
NSArray *members;
|
||||
int i;
|
||||
|
||||
source = [[SOGoUserManager sharedUserManager] sourceWithID: [dict objectForKey: @"SOGoSource"]];
|
||||
if ([source conformsToProtocol: @protocol(SOGoMembershipSource)])
|
||||
{
|
||||
members = [(id<SOGoMembershipSource>)(source) membersForGroupWithUID: [dict objectForKey: @"c_uid"]];
|
||||
for (i = 0; i < [members count]; i++)
|
||||
{
|
||||
[allSQLUsers addObject: [[members objectAtIndex: i] objectForKey: @"c_uid"]];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
[allSQLUsers addObject: user];
|
||||
}
|
||||
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
max = [allSQLUsers count];
|
||||
@@ -413,7 +437,9 @@ typedef enum
|
||||
fm = [GCSFolderManager defaultFolderManager];
|
||||
f = [fm folderAtPath: [NSString stringWithFormat: @"/Users/%@/%@", owner, folder]];
|
||||
|
||||
if (!f)
|
||||
// Skip the folder existence check so we can auto-create the personal folder
|
||||
// for users that have never logged-in
|
||||
if (!f && command != ManageACLSubscribe)
|
||||
{
|
||||
NSLog(@"No folder %@ found for user %@", folder, owner);
|
||||
rc = NO;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SOGoToolRestore.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2015 Inverse inc.
|
||||
* Copyright (C) 2009-2019 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
|
||||
@@ -614,6 +614,7 @@
|
||||
- (BOOL) _updateSieveScripsForLogin: (NSString *) theLogin
|
||||
{
|
||||
/* credentials file handling */
|
||||
NSException *error;
|
||||
NSString *credsFilename, *authname=nil, *authpwd=nil;
|
||||
SOGoCredentialsFile *cf;
|
||||
SOGoUser *user;
|
||||
@@ -650,7 +651,13 @@
|
||||
account = [folder lookupName: @"0" inContext: localContext acquire: NO];
|
||||
[account setContext: localContext];
|
||||
|
||||
return [account updateFiltersWithUsername: authname andPassword: authpwd];
|
||||
error = [account updateFiltersWithUsername: authname
|
||||
andPassword: authpwd
|
||||
forceActivation: NO];
|
||||
if (error)
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL) restoreUserPreferencesFromUserRecord: (NSDictionary *) userRecord
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SOGoToolUpdateAutoReply.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2011-2016 Inverse inc.
|
||||
* Copyright (C) 2011-2019 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
|
||||
@@ -81,8 +81,10 @@
|
||||
SOGoUserDefaults *userDefaults;
|
||||
SOGoUser *user;
|
||||
BOOL result;
|
||||
NSException *error;
|
||||
|
||||
user = [SOGoUser userWithLogin: theLogin];
|
||||
|
||||
userDefaults = [user userDefaults];
|
||||
vacationOptions = [[userDefaults vacationOptions] mutableCopy];
|
||||
[vacationOptions autorelease];
|
||||
@@ -93,6 +95,14 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
// We do NOT enable the vacation message automatically if the domain
|
||||
// preference is disabled by default.
|
||||
if (![[user domainDefaults] vacationPeriodEnabled])
|
||||
{
|
||||
NSLog(@"SOGoVacationPeriodEnabled set to NO for the domain - ignoring.");
|
||||
return NO;
|
||||
}
|
||||
|
||||
[vacationOptions setObject: [NSNumber numberWithBool: NO] forKey: @"startDateEnabled"];
|
||||
}
|
||||
|
||||
@@ -118,8 +128,10 @@
|
||||
account = [folder lookupName: @"0" inContext: localContext acquire: NO];
|
||||
[account setContext: localContext];
|
||||
|
||||
result = [account updateFiltersWithUsername: theUsername andPassword: thePassword];
|
||||
if (!result)
|
||||
error = [account updateFiltersWithUsername: theUsername
|
||||
andPassword: thePassword
|
||||
forceActivation: NO];
|
||||
if (error)
|
||||
{
|
||||
// Can't update Sieve script -- Reactivate auto-reply
|
||||
if (disabling)
|
||||
@@ -128,6 +140,7 @@
|
||||
[vacationOptions setObject: [NSNumber numberWithBool: YES] forKey: @"startDateEnabled"];
|
||||
[userDefaults setVacationOptions: vacationOptions];
|
||||
[userDefaults synchronize];
|
||||
result = NO;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 352 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user