Compare commits

...

121 Commits

Author SHA1 Message Date
Aleksander Machniak 1972037274 Fix vulnerability in handling _session argument of utils/save-prefs (#1489382)
Conflicts:

	CHANGELOG
	program/lib/Roundcube/rcube_plugin_api.php
	program/steps/utils/save_pref.inc
11 years ago
Aleksander Machniak 6a0eed5066 Update changelog 11 years ago
Thomas Bruederli 6233a6911f Complete list of previous versions 11 years ago
Thomas Bruederli ce0c1f44a3 Bump version for 0.7.4 release 11 years ago
Thomas Bruederli 95ac3e217a Sanity check the file path for generic message footer before adding it 11 years ago
Aleksander Machniak 395b74051c Whitelist configuration options (user preferences) that can be changed using save-pref command
Conflicts:

	program/lib/Roundcube/rcube_plugin.php
	program/lib/Roundcube/rcube_plugin_api.php

Conflicts:

	CHANGELOG
	program/steps/utils/save_pref.inc
11 years ago
Aleksander Machniak 268a28efb2 Don't force 'stop' action on last rule in a script 12 years ago
Aleksander Machniak 377793d67c Fix XSS vulnerability in handling of text/enriched messages (#1488806)
Conflicts:

	CHANGELOG
12 years ago
Thomas Bruederli a5c8786a34 Merge branch 'release-0.7' of github.com:roundcube/roundcubemail into release-0.7 12 years ago
Thomas Bruederli f9a25bd418 Backported commit 1078a6f099 to 0.7 release branch 12 years ago
Aleksander Machniak b1263ab253 Update changelog 12 years ago
Remi Collet ccbf0da6eb fix call to MDB2::isError for MDB2 2.5.0b4 12 years ago
Aleksander Machniak b39e8e4174 Remove duplicates from get_col_values() result in flat mode 12 years ago
Aleksander Machniak 76d3b47145 Workaround possible PHP Fatal error: Class 'PEAR' not found 12 years ago
Aleksander Machniak 639a825b2e Fix post-filtering vlv results, fixes warning "mb_strtolower() expects parameter 1 to be a string, array given"
Conflicts:

	program/include/rcube_ldap.php
12 years ago
Aleksander Machniak 9bfaf5f070 Lower the limit of folders list size (to 100, 25 for IE) when the names abbreviation is in action 12 years ago
Aleksander Machniak 1ff03fb29d Fix lower-casing email address on replies (#1488598)
Conflicts:

	CHANGELOG
12 years ago
Aleksander Machniak c2c162c240 Fix so subscribed non-existing/non-accessible shared folder can be unsubscribed
Conflicts:

	CHANGELOG
12 years ago
Thomas Bruederli da0c480b9d Merge branch 'release-0.7' of github.com:roundcube/roundcubemail into release-0.7 12 years ago
Thomas Bruederli d063993a4b Update changelog & bump version 12 years ago
Aleksander Machniak 872d209bcd Fixed issue with DBMail bug [http://pear.php.net/bugs/bug.php?id=19077] (#1488594)
Conflicts:

	plugins/managesieve/Changelog
12 years ago
Thomas Bruederli bf73e037d6 Add fix for http://pear.php.net/bugs/bug.php?id=18819 12 years ago
Aleksander Machniak 51fcb01f0a Added separate From and To columns apart from smart From/To column (#1486891)
Conflicts:

	CHANGELOG
	program/steps/mail/check_recent.inc
	program/steps/mail/func.inc
	program/steps/mail/list.inc
	program/steps/mail/mark.inc
	program/steps/mail/move_del.inc
	program/steps/mail/pagenav.inc
	program/steps/mail/search.inc
	skins/larry/mail.css
	skins/larry/templates/mail.html
	skins/larry/ui.js
12 years ago
Thomas Bruederli a8e478c07c Merge branch 'release-0.7' of github.com:roundcube/roundcubemail into release-0.7 12 years ago
Thomas Bruederli 4e6e3b64c0 Backported some recent improvements to LDAP addressbook 12 years ago
Aleksander Machniak d098e205a0 - Fix (workaround) delete operations with some versions of memcache (#1488592)
Conflicts:

	CHANGELOG
	program/include/rcube_session.php
12 years ago
Thomas Bruederli 2c89ca7298 Backporting: Fix HTML entities handling in HTML editor (#1488483) 12 years ago
Aleksander Machniak e8cab44de0 Set some php ini settings if possible 12 years ago
Aleksander Machniak 358b0f8bbc Fix wrong compose screen elements focus in IE9 (#1488541)
Conflicts:

	CHANGELOG
12 years ago
Aleksander Machniak cc57dad1cd Fix last merge 12 years ago
Aleksander Machniak 9899abc36c Fix handling of MYRIGHTS on private namespace roots - fixes issue where
in ACL plugin it wasn't possible to share INBOX folder (when it was a namespace prefix).

Conflicts:

	program/include/rcube_imap.php

Conflicts:

	plugins/acl/acl.php
	plugins/acl/package.xml
	program/include/rcube_imap.php
12 years ago
alecpl 77799d87ba - Applied some fixes from trunk 12 years ago
alecpl cba69dd977 - Merge r6014 from trunk 12 years ago
alecpl f451a1ca5e - Merge r6012 from trunk 12 years ago
alecpl fe22e87d29 - Merge r6009 from trunk 12 years ago
alecpl a8d90bd175 - Fix counting of messages search results (on refresh when search filter is in use) 12 years ago
thomascube 315993ef0f Bump version to 0.7.2 12 years ago
alecpl 7190667885 - Merge fix r5990 from trunk 12 years ago
alecpl ab5036eef7 - Merge r5969 from trunk 12 years ago
alecpl 053538bf60 - Merge r5966 from trunk (#1488375) 12 years ago
alecpl b1a8dabb1f - Fix displaying of HTML messages from Disqus (#1488372) 12 years ago
alecpl b1fa85b7e8 - Disable E_STRICT warnings on PHP 5.4 12 years ago
alecpl 4ffa559227 - Apply fixes from trunk 12 years ago
alecpl 0411ae5a29 - Fix PHP Fatal error: Using $this when not in object context 12 years ago
thomascube 2d3f635a97 Describe 'scope' option for LDAP groups in config template 12 years ago
thomascube 249303d441 Fix message cache expunge after config options changed 12 years ago
thomascube b8e6ba1908 Backported r5878 to 0.7 release branch 12 years ago
thomascube 4dc22dd634 Backported r5871 to 0.7 release branch 12 years ago
thomascube ba36dd03f6 Backported r5850 to 0.7 branch 12 years ago
alecpl 5ea6e16036 - Fix inconsistent line endings (#1488303) 12 years ago
alecpl 735bf7c3e1 - Backported r5859 fix 12 years ago
alecpl 06ca480d1e - Applied fix from trunk 12 years ago
thomascube 7fb577bc8f Swiss German translation for markasjunk plugin 12 years ago
thomascube ee9ee7f66f Backported r5820 and r5842 12 years ago
alecpl 82775db0a4 - Revert SORT=DISPLAY support, removed by mistake (#1488327) 12 years ago
alecpl 20588763f2 - Remove leftover code 12 years ago
alecpl 40858c00ef - Add lost translation label in de_DE (#1488315) 13 years ago
alecpl 68d3aada14 - Fix drafts update issues when edited from preview pane (#1488314) 13 years ago
alecpl 9ac31a6123 - Fix wrong variable name in rcube_ldap.php (#1488302) 13 years ago
alecpl 3c36bcc329 - Backported r5769 from trunk 13 years ago
alecpl 63b9ff12d2 - Fix possible infinite loop in build_thread_data() 13 years ago
alecpl be431441cb - Applied fixes from trunk up to r5756 13 years ago
thomascube 89730fa4e2 More translations from trunk 13 years ago
thomascube b115ba77d1 Backported translation updates 13 years ago
thomascube 0d114efc93 Bump version 13 years ago
thomascube 8dea9b4c16 Restore lost translations 13 years ago
thomascube 49cdae95f6 Migrate LDAP fieldmap fix from trunk 13 years ago
alecpl fd65421904 - Backported r5718 13 years ago
alecpl d69f8f0611 - Backported r5713 13 years ago
alecpl a33118fd0f - Applied fixes from trunk up to r5711 13 years ago
alecpl 896b2204e8 - Backported r5704-r5705 from trunk 13 years ago
alecpl a128fafbe8 - Backport r5689 from trunk 13 years ago
alecpl bc8437b71c - Fix so editor selector is hidden when 'htmleditor' is listed in 'dont_override' 13 years ago
alecpl e81c00b7b9 - Fix wrong (long) label usage (#1488283) 13 years ago
alecpl 79c6ac9b92 - Fix handling of INBOX's subfolders in special folders config (#1488279) 13 years ago
alecpl 9fc4eb3f82 - Add ifModule statement for setting Options -Indexes in .htaccess file (#1488274) 13 years ago
alecpl d355c04084 - Backport r5637 fix for searching without CHARSET 13 years ago
alecpl 984ee8ce92 - Update tests 13 years ago
alecpl 96c946ee6c - Applied fixes from trunk up to r5633 13 years ago
alecpl 89dc8499c3 - Applied fixes from trunk up to r5628 13 years ago
alecpl e8b2579865 - Aplied fixes from trunk up to r5615 13 years ago
thomascube faf5fb4c07 Remove beta warning 13 years ago
thomascube 62d5be17fd Add some last-minute changes from trunk 13 years ago
alecpl e2f30659a1 - Backported r5598:r5599 from trunk 13 years ago
thomascube f41565354b Fix broken encoding of French localization 13 years ago
thomascube e02694c3a6 Backported CSS sanitization (r5586:r5590) 13 years ago
alecpl 19073428b1 - Fix bug in sk_SK localization (r5584) 13 years ago
thomascube 245a0ebb88 Update version strings and changelog for 0.7 (stable) release 13 years ago
alecpl 4f5257e5eb - Apply fix r5577 from trunk 13 years ago
alecpl 7fde6ff46a - Update plugin localizations from trunk 13 years ago
thomascube 080c74859d Synchronized localization files from trunk 13 years ago
alecpl 3c5099f22b - Applied fixes from trunk up to r5553 13 years ago
thomascube 1112978a82 Remove vulnerable flash player 13 years ago
thomascube fe2773c875 Backported r5544 to release branch 13 years ago
alecpl db4ec58b90 - Apply fixes from trunk up to r5542 13 years ago
thomascube f47d5e2089 Apply fixes r5539 and r5540 to release branch 13 years ago
alecpl e237eec846 - Applied fixes from trunk up to r5526 13 years ago
alecpl a691021389 - Merge commit r5517 from trunk 13 years ago
alecpl 6f98b9100b - Applied r5515 from trunk 13 years ago
alecpl 0597586b89 - Applied fixes from trunk up to r5512 13 years ago
alecpl 230ccbaef0 - Apply fix from trunk (r5500) 13 years ago
alecpl 77449d011b - Applied fixes from trunk up to r5498 13 years ago
alecpl 2a3e02769d - Merge r5489 from trunk 13 years ago
alecpl 3ad2b1b4b0 - Applied fixes from trunk up to r5479 13 years ago
alecpl c994e0e7cd - Applied fixes from trunk up to r5451 13 years ago
thomascube bdb649067d Forgot to bump versions... 13 years ago
alecpl 3fd5e2edfc - Apply fixes from trunk up to r5433 13 years ago
alecpl 3fec6952dd - Applied fixes from trunk up to r5425 13 years ago
alecpl ecfaed571b - Apply fixes fom trunk up to r5414 13 years ago
thomascube c82bf6689f Add memcache debug mode from r5120 13 years ago
thomascube ad71ba4fcd Backporting r5403 to release branch 13 years ago
alecpl 51f7a5b2a0 - Apply fixes from trunk up to r5401 13 years ago
thomascube 69cb80b059 Backported r5392 to release branch 13 years ago
thomascube 2573a872c4 Backport LDAP VLV pseudo-search option to release branch 13 years ago
thomascube 44a352b7b8 Backport r5375 to release branch 13 years ago
alecpl f1654a33b2 - Applied fixes from trunk up to r5371 13 years ago
thomascube 799e120145 Backporting changes trom trunk (r5357-r5365) 13 years ago
thomascube 831fde154d Set version strings to 0.7-beta1 13 years ago
thomascube 5da48a9522 Copy plugins to release branch 13 years ago
thomascube b03854de3a Remove externals 13 years ago
thomascube de1a3f40b1 Create release branch for 0.7 (beta) 13 years ago

@ -17,7 +17,7 @@ php_flag zend.ze1_compatibility_mode Off
php_flag suhosin.session.encrypt Off
#php_value session.cookie_path /
php_value session.auto_start 0
php_flag session.auto_start Off
php_value session.gc_maxlifetime 21600
php_value session.gc_divisor 500
php_value session.gc_probability 1
@ -49,4 +49,7 @@ ExpiresDefault "access plus 1 month"
</IfModule>
FileETag MTime Size
<IfModule mod_autoindex.c>
Options -Indexes
</ifModule>

File diff suppressed because it is too large Load Diff

@ -1,14 +1,6 @@
Roundcube Webmail (http://roundcube.net)
ATTENTION
---------
This is just a snapshot of the current SVN repository and is NOT A STABLE
version of Roundcube. It's not recommended to replace an existing installation
of Roundcube with this version. Also using a separate database for this
installation is highly recommended.
Introduction:
-------------
Roundcube Webmail is a browser-based multilingual IMAP client with an

@ -29,8 +29,8 @@ CREATE TABLE [dbo].[cache_messages] (
[mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[uid] [int] NOT NULL ,
[changed] [datetime] NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
[flags] [int](1) NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL ,
[flags] [int] NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
@ -40,7 +40,7 @@ CREATE TABLE [dbo].[contacts] (
[changed] [datetime] NOT NULL ,
[del] [char] (1) COLLATE Latin1_General_CI_AI NOT NULL ,
[name] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[email] [varchar] (255) COLLATE Latin1_General_CI_AI NOT NULL ,
[email] [text] COLLATE Latin1_General_CI_AI NOT NULL ,
[firstname] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[surname] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[vcard] [text] COLLATE Latin1_General_CI_AI NULL ,
@ -81,7 +81,7 @@ CREATE TABLE [dbo].[identities] (
GO
CREATE TABLE [dbo].[session] (
[sess_id] [varchar] (32) COLLATE Latin1_General_CI_AI NOT NULL ,
[sess_id] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[created] [datetime] NOT NULL ,
[changed] [datetime] NULL ,
[ip] [varchar] (40) COLLATE Latin1_General_CI_AI NOT NULL ,
@ -226,7 +226,7 @@ GO
ALTER TABLE [dbo].[cache_messages] ADD
CONSTRAINT [DF_cache_messages_changed] DEFAULT (getdate()) FOR [changed],
CONSTRAINT [DF_cache_messages_flags] DEFAULT (0) FOR [flags],
CONSTRAINT [DF_cache_messages_flags] DEFAULT (0) FOR [flags]
GO
CREATE INDEX [IX_cache_messages_user_id] ON [dbo].[cache_messages]([user_id]) ON [PRIMARY]
@ -310,7 +310,7 @@ GO
ALTER TABLE [dbo].[searches] ADD
CONSTRAINT [DF_searches_user] DEFAULT (0) FOR [user_id],
CONSTRAINT [DF_searches_type] DEFAULT (0) FOR [type],
CONSTRAINT [DF_searches_type] DEFAULT (0) FOR [type]
GO
CREATE UNIQUE INDEX [IX_searches_user_type_name] ON [dbo].[searches]([user_id],[type],[name]) ON [PRIMARY]

@ -175,8 +175,8 @@ CREATE TABLE [dbo].[cache_messages] (
[mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[uid] [int] NOT NULL ,
[changed] [datetime] NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
[flags] [int] NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL ,
[flags] [int] NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
@ -239,3 +239,13 @@ ALTER TABLE [dbo].[cache_messages] ADD CONSTRAINT [FK_cache_messages_user_id]
ON DELETE CASCADE ON UPDATE CASCADE
GO
-- Updates from version 0.7-beta
ALTER TABLE [dbo].[session] ALTER COLUMN [sess_id] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL
GO
-- Updates from version 0.7
ALTER TABLE [dbo].[contacts] ALTER COLUMN [email] [text] COLLATE Latin1_General_CI_AI NOT NULL
GO

@ -6,7 +6,7 @@
-- Table structure for table `session`
CREATE TABLE `session` (
`sess_id` varchar(40) NOT NULL,
`sess_id` varchar(128) NOT NULL,
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`ip` varchar(40) NOT NULL,
@ -40,7 +40,7 @@ CREATE TABLE `cache` (
`cache_key` varchar(128) /*!40101 CHARACTER SET ascii COLLATE ascii_general_ci */ NOT NULL ,
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`data` longtext NOT NULL,
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`user_id` int(10) UNSIGNED NOT NULL,
PRIMARY KEY(`cache_id`),
CONSTRAINT `user_id_fk_cache` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
@ -52,7 +52,7 @@ CREATE TABLE `cache` (
-- Table structure for table `cache_index`
CREATE TABLE `cache_index` (
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`user_id` int(10) UNSIGNED NOT NULL,
`mailbox` varchar(255) BINARY NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`valid` tinyint(1) NOT NULL DEFAULT '0',
@ -67,7 +67,7 @@ CREATE TABLE `cache_index` (
-- Table structure for table `cache_thread`
CREATE TABLE `cache_thread` (
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`user_id` int(10) UNSIGNED NOT NULL,
`mailbox` varchar(255) BINARY NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`data` longtext NOT NULL,
@ -81,7 +81,7 @@ CREATE TABLE `cache_thread` (
-- Table structure for table `cache_messages`
CREATE TABLE `cache_messages` (
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`user_id` int(10) UNSIGNED NOT NULL,
`mailbox` varchar(255) BINARY NOT NULL,
`uid` int(11) UNSIGNED NOT NULL DEFAULT '0',
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
@ -101,23 +101,23 @@ CREATE TABLE `contacts` (
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`del` tinyint(1) NOT NULL DEFAULT '0',
`name` varchar(128) NOT NULL DEFAULT '',
`email` varchar(255) NOT NULL,
`email` text NOT NULL,
`firstname` varchar(128) NOT NULL DEFAULT '',
`surname` varchar(128) NOT NULL DEFAULT '',
`vcard` longtext NULL,
`words` text NULL,
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`user_id` int(10) UNSIGNED NOT NULL,
PRIMARY KEY(`contact_id`),
CONSTRAINT `user_id_fk_contacts` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `user_contacts_index` (`user_id`,`email`)
INDEX `user_contacts_index` (`user_id`,`del`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `contactgroups`
CREATE TABLE `contactgroups` (
`contactgroup_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`user_id` int(10) UNSIGNED NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`del` tinyint(1) NOT NULL DEFAULT '0',
`name` varchar(128) NOT NULL DEFAULT '',
@ -129,7 +129,7 @@ CREATE TABLE `contactgroups` (
CREATE TABLE `contactgroupmembers` (
`contactgroup_id` int(10) UNSIGNED NOT NULL,
`contact_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`contact_id` int(10) UNSIGNED NOT NULL,
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
PRIMARY KEY (`contactgroup_id`, `contact_id`),
CONSTRAINT `contactgroup_id_fk_contactgroups` FOREIGN KEY (`contactgroup_id`)
@ -144,7 +144,7 @@ CREATE TABLE `contactgroupmembers` (
CREATE TABLE `identities` (
`identity_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`user_id` int(10) UNSIGNED NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`del` tinyint(1) NOT NULL DEFAULT '0',
`standard` tinyint(1) NOT NULL DEFAULT '0',
@ -178,7 +178,7 @@ CREATE TABLE `dictionary` (
CREATE TABLE `searches` (
`search_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`user_id` int(10) UNSIGNED NOT NULL,
`type` int(3) NOT NULL DEFAULT '0',
`name` varchar(128) NOT NULL,
`data` text,

@ -208,3 +208,30 @@ CREATE TABLE `cache_messages` (
INDEX `changed_index` (`changed`),
PRIMARY KEY (`user_id`, `mailbox`, `uid`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Updates from version 0.7-beta
ALTER TABLE `session` CHANGE `sess_id` `sess_id` varchar(128) NOT NULL;
-- Updates from version 0.7
/*!40014 SET FOREIGN_KEY_CHECKS=0 */;
ALTER TABLE `contacts` DROP FOREIGN KEY `user_id_fk_contacts`;
ALTER TABLE `contacts` DROP INDEX `user_contacts_index`;
ALTER TABLE `contacts` MODIFY `email` text NOT NULL;
ALTER TABLE `contacts` ADD INDEX `user_contacts_index` (`user_id`,`del`);
ALTER TABLE `contacts` ADD CONSTRAINT `user_id_fk_contacts` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `cache` ALTER `user_id` DROP DEFAULT;
ALTER TABLE `cache_index` ALTER `user_id` DROP DEFAULT;
ALTER TABLE `cache_thread` ALTER `user_id` DROP DEFAULT;
ALTER TABLE `cache_messages` ALTER `user_id` DROP DEFAULT;
ALTER TABLE `contacts` ALTER `user_id` DROP DEFAULT;
ALTER TABLE `contactgroups` ALTER `user_id` DROP DEFAULT;
ALTER TABLE `contactgroupmembers` ALTER `contact_id` DROP DEFAULT;
ALTER TABLE `identities` ALTER `user_id` DROP DEFAULT;
ALTER TABLE `searches` ALTER `user_id` DROP DEFAULT;
/*!40014 SET FOREIGN_KEY_CHECKS=1 */;

@ -37,7 +37,7 @@ CREATE INDEX users_alias_id_idx ON users (alias);
--
CREATE TABLE "session" (
sess_id varchar(40) DEFAULT '' PRIMARY KEY,
sess_id varchar(128) DEFAULT '' PRIMARY KEY,
created timestamp with time zone DEFAULT now() NOT NULL,
changed timestamp with time zone DEFAULT now() NOT NULL,
ip varchar(41) NOT NULL,
@ -107,14 +107,14 @@ CREATE TABLE contacts (
changed timestamp with time zone DEFAULT now() NOT NULL,
del smallint DEFAULT 0 NOT NULL,
name varchar(128) DEFAULT '' NOT NULL,
email varchar(255) DEFAULT '' NOT NULL,
email text DEFAULT '' NOT NULL,
firstname varchar(128) DEFAULT '' NOT NULL,
surname varchar(128) DEFAULT '' NOT NULL,
vcard text,
words text
);
CREATE INDEX contacts_user_id_idx ON contacts (user_id, email);
CREATE INDEX contacts_user_id_idx ON contacts (user_id, del);
--
-- Sequence "contactgroups_ids"

@ -127,7 +127,7 @@ CREATE TABLE searches (
CONSTRAINT searches_user_id_key UNIQUE (user_id, "type", name)
);
DROP SEQUENCE messages_ids;
DROP SEQUENCE message_ids;
DROP TABLE messages;
CREATE TABLE cache_index (
@ -165,3 +165,13 @@ CREATE TABLE cache_messages (
);
CREATE INDEX cache_messages_changed_idx ON cache_messages (changed);
-- Updates from version 0.7-beta
ALTER TABLE "session" ALTER sess_id TYPE varchar(128);
-- Updates from version 0.7
DROP INDEX contacts_user_id_idx;
CREATE INDEX contacts_user_id_idx ON contacts USING btree (user_id, del);
ALTER TABLE contacts ALTER email TYPE text;

@ -24,18 +24,18 @@ CREATE INDEX ix_cache_created ON cache(created);
CREATE TABLE contacts (
contact_id integer NOT NULL PRIMARY KEY,
user_id integer NOT NULL default '0',
user_id integer NOT NULL,
changed datetime NOT NULL default '0000-00-00 00:00:00',
del tinyint NOT NULL default '0',
name varchar(128) NOT NULL default '',
email varchar(255) NOT NULL default '',
email text NOT NULL default '',
firstname varchar(128) NOT NULL default '',
surname varchar(128) NOT NULL default '',
vcard text NOT NULL default '',
words text NOT NULL default ''
);
CREATE INDEX ix_contacts_user_id ON contacts(user_id, email);
CREATE INDEX ix_contacts_user_id ON contacts(user_id, del);
CREATE TABLE contactgroups (
@ -110,7 +110,7 @@ CREATE INDEX ix_users_alias ON users(alias);
--
CREATE TABLE session (
sess_id varchar(40) NOT NULL PRIMARY KEY,
sess_id varchar(128) NOT NULL PRIMARY KEY,
created datetime NOT NULL default '0000-00-00 00:00:00',
changed datetime NOT NULL default '0000-00-00 00:00:00',
ip varchar(40) NOT NULL default '',
@ -147,7 +147,7 @@ CREATE TABLE searches (
data text NOT NULL
);
CREATE UNIQUE INDEX ix_searches_user_type_name (user_id, type, name);
CREATE UNIQUE INDEX ix_searches_user_type_name ON searches (user_id, type, name);
-- --------------------------------------------------------

@ -246,7 +246,7 @@ CREATE TABLE searches (
data text NOT NULL
);
CREATE UNIQUE INDEX ix_searches_user_type_name (user_id, type, name);
CREATE UNIQUE INDEX ix_searches_user_type_name ON searches (user_id, type, name);
DROP TABLE messages;
@ -282,3 +282,54 @@ CREATE TABLE cache_messages (
);
CREATE INDEX ix_cache_messages_changed ON cache_messages (changed);
-- Updates from version 0.7-beta
DROP TABLE session;
CREATE TABLE session (
sess_id varchar(128) NOT NULL PRIMARY KEY,
created datetime NOT NULL default '0000-00-00 00:00:00',
changed datetime NOT NULL default '0000-00-00 00:00:00',
ip varchar(40) NOT NULL default '',
vars text NOT NULL
);
CREATE INDEX ix_session_changed ON session (changed);
-- Updates from version 0.7
CREATE TABLE contacts_tmp (
contact_id integer NOT NULL PRIMARY KEY,
user_id integer NOT NULL,
changed datetime NOT NULL default '0000-00-00 00:00:00',
del tinyint NOT NULL default '0',
name varchar(128) NOT NULL default '',
email text NOT NULL default '',
firstname varchar(128) NOT NULL default '',
surname varchar(128) NOT NULL default '',
vcard text NOT NULL default '',
words text NOT NULL default ''
);
INSERT INTO contacts_tmp (contact_id, user_id, changed, del, name, email, firstname, surname, vcard, words)
SELECT contact_id, user_id, changed, del, name, email, firstname, surname, vcard, words FROM contacts;
DROP TABLE contacts;
CREATE TABLE contacts (
contact_id integer NOT NULL PRIMARY KEY,
user_id integer NOT NULL,
changed datetime NOT NULL default '0000-00-00 00:00:00',
del tinyint NOT NULL default '0',
name varchar(128) NOT NULL default '',
email text NOT NULL default '',
firstname varchar(128) NOT NULL default '',
surname varchar(128) NOT NULL default '',
vcard text NOT NULL default '',
words text NOT NULL default ''
);
INSERT INTO contacts (contact_id, user_id, changed, del, name, email, firstname, surname, vcard, words)
SELECT contact_id, user_id, changed, del, name, email, firstname, surname, vcard, words FROM contacts_tmp;
CREATE INDEX ix_contacts_user_id ON contacts(user_id, del);
DROP TABLE contacts_tmp;

@ -5,7 +5,7 @@
| Main configuration file |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2010, The Roundcube Dev Team |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
+-----------------------------------------------------------------------+
@ -222,6 +222,9 @@ $rcmail_config['session_lifetime'] = 10;
// session domain: .example.org
$rcmail_config['session_domain'] = '';
// session name. Default: 'roundcube_sessid'
$rcmail_config['session_name'] = null;
// Backend to use for session storage. Can either be 'db' (default) or 'memcache'
// If set to memcache, a list of servers need to be specified in 'memcache_hosts'
// Make sure the Memcache extension (http://pecl.php.net/package/memcache) version >= 2.0.0 is installed
@ -362,15 +365,15 @@ $rcmail_config['plugins'] = array();
// ----------------------------------
// default messages sort column. Use empty value for default server's sorting,
// or 'arrival', 'date', 'subject', 'from', 'to', 'size', 'cc'
// or 'arrival', 'date', 'subject', 'from', 'to', 'fromto', 'size', 'cc'
$rcmail_config['message_sort_col'] = '';
// default messages sort order
$rcmail_config['message_sort_order'] = 'DESC';
// These cols are shown in the message list. Available cols are:
// subject, from, to, cc, replyto, date, size, status, flag, attachment, 'priority'
$rcmail_config['list_cols'] = array('subject', 'status', 'from', 'date', 'size', 'flag', 'attachment');
// subject, from, to, fromto, cc, replyto, date, size, status, flag, attachment, 'priority'
$rcmail_config['list_cols'] = array('subject', 'status', 'fromto', 'date', 'size', 'flag', 'attachment');
// the default locale setting (leave empty for auto-detection)
// RFC1766 formatted language name like en_US, de_DE, de_CH, fr_FR, pt_BR
@ -418,7 +421,7 @@ $rcmail_config['trash_mbox'] = 'Trash';
// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP)
$rcmail_config['default_imap_folders'] = array('INBOX', 'Drafts', 'Sent', 'Junk', 'Trash');
// automatically create the above listed default folders on login
// automatically create the above listed default folders on first login
$rcmail_config['create_default_folders'] = false;
// protect the default folders from renames, deletes, and subscription changes
@ -460,6 +463,9 @@ $rcmail_config['spellcheck_ignore_nums'] = false;
// Makes that words with symbols will be ignored (e.g. g@@gle)
$rcmail_config['spellcheck_ignore_syms'] = false;
// Use this char/string to separate recipients when composing a new message
$rcmail_config['recipients_separator'] = ',';
// don't let users set pagesize to more than this value if set
$rcmail_config['max_pagesize'] = 200;
@ -535,6 +541,9 @@ $rcmail_config['ldap_public']['Verisign'] = array(
// The login name is used to search for the DN to bind with
'search_base_dn' => '',
'search_filter' => '', // e.g. '(&(objectClass=posixAccount)(uid=%u))'
// DN and password to bind as before searching for bind DN, if anonymous search is not allowed
'search_bind_dn' => '',
'search_bind_pw' => '',
// Default for %dn variable if search doesn't return DN value
'search_dn_default' => '',
// Optional authentication identifier to be used as SASL authorization proxy
@ -563,12 +572,14 @@ $rcmail_config['ldap_public']['Verisign'] = array(
'required_fields' => array('cn', 'sn', 'mail'),
'search_fields' => array('mail', 'cn'), // fields to search in
// mapping of contact fields to directory attributes
// for every attribute one can specify the number of values (limit) allowed.
// default is 1, a wildcard * means unlimited
'fieldmap' => array(
// Roundcube => LDAP
// Roundcube => LDAP:limit
'name' => 'cn',
'surname' => 'sn',
'firstname' => 'givenName',
'email' => 'mail',
'email' => 'mail:*',
'phone:home' => 'homePhone',
'phone:work' => 'telephoneNumber',
'phone:mobile' => 'mobile',
@ -586,6 +597,7 @@ $rcmail_config['ldap_public']['Verisign'] = array(
'numsub_filter' => '(objectClass=organizationalUnit)', // with VLV, we also use numSubOrdinates to query the total number of records. Set this filter to get all numSubOrdinates attributes for counting
'sizelimit' => '0', // Enables you to limit the count of entries fetched. Setting this to 0 means no limit.
'timelimit' => '0', // Sets the number of seconds how long is spend on the search. Setting this to 0 means no limit.
'referrals' => true|false, // Sets the LDAP_OPT_REFERRALS option. Mostly used in multi-domain Active Directory setups
// definition for contact groups (uncomment if no groups are supported)
// for the groups base_dn, the user replacements %fu, %u, $d and %dc work as for base_dn (see above)
@ -593,6 +605,7 @@ $rcmail_config['ldap_public']['Verisign'] = array(
// -> in this case, assure that groups and contacts are separated due to the concernig filters!
'groups' => array(
'base_dn' => '',
'scope' => 'sub', // search mode: sub|base|list
'filter' => '(objectClass=groupOfNames)',
'object_classes' => array("top", "groupOfNames"),
'member_attr' => 'member', // name of the member attribute, e.g. uniqueMember
@ -623,6 +636,13 @@ $rcmail_config['autocomplete_max'] = 15;
// available placeholders: {street}, {locality}, {zipcode}, {country}, {region}
$rcmail_config['address_template'] = '{street}<br/>{locality} {zipcode}<br/>{country} {region}';
// Matching mode for addressbook search (including autocompletion)
// 0 - partial (*abc*), default
// 1 - strict (abc)
// 2 - prefix (abc*)
// Note: For LDAP sources fuzzy_search must be enabled to use 'partial' or 'prefix' mode
$rcmail_config['addressbook_search_mode'] = 0;
// ----------------------------------
// USER PREFERENCES
// ----------------------------------
@ -636,11 +656,21 @@ $rcmail_config['skin'] = 'default';
// show up to X items in list view
$rcmail_config['pagesize'] = 40;
// sort contacts by this col (preferably either one of name, firstname, surname)
$rcmail_config['addressbook_sort_col'] = 'surname';
// the way how contact names are displayed in the list
// 0: display name
// 1: (prefix) firstname middlename surname (suffix)
// 2: (prefix) surname firstname middlename (suffix)
// 3: (prefix) surname, firstname middlename (suffix)
$rcmail_config['addressbook_name_listing'] = 0;
// use this timezone to display date/time
$rcmail_config['timezone'] = 'auto';
// is daylight saving On?
$rcmail_config['dst_active'] = (bool)date('I');
// is daylight saving On? Default: (bool)date('I');
$rcmail_config['dst_active'] = null;
// prefer displaying HTML messages
$rcmail_config['prefer_html'] = true;
@ -770,4 +800,7 @@ $rcmail_config['default_addressbook'] = null;
// Enables spell checking before sending a message.
$rcmail_config['spellcheck_before_send'] = false;
// Skip alternative email addresses in autocompletion (show one address per contact)
$rcmail_config['autocomplete_single'] = false;
// end of config file

@ -2,9 +2,9 @@
/*
+-------------------------------------------------------------------------+
| Roundcube Webmail IMAP Client |
| Version 0.7-svn |
| Version 0.7.4 |
| |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
@ -119,6 +119,10 @@ if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') {
// prevent endless looping on login page
if ($query['_task'] == 'login')
unset($query['_task']);
// prevent redirect to compose with specified ID (#1488226)
if ($query['_action'] == 'compose' && !empty($query['_id']))
$query = array();
}
// allow plugins to control the redirect url after login success
@ -195,7 +199,7 @@ else {
// check client X-header to verify request origin
if ($OUTPUT->ajax_call) {
if (rc_request_header('X-Roundcube-Request') != $RCMAIL->get_request_token() && !$RCMAIL->config->get('devel_mode')) {
header('HTTP/1.1 404 Not Found');
header('HTTP/1.1 403 Forbidden');
die("Invalid Request");
}
}

@ -142,20 +142,22 @@ class rcube_install
foreach ($this->config as $prop => $default) {
$value = (isset($_POST["_$prop"]) || $this->bool_config_props[$prop]) ? $_POST["_$prop"] : $default;
$is_default = !isset($_POST["_$prop"]);
$value = !$is_default || $this->bool_config_props[$prop] ? $_POST["_$prop"] : $default;
// convert some form data
if ($prop == 'debug_level') {
$val = 0;
if (is_array($value))
if ($prop == 'debug_level' && !$is_default) {
if (is_array($value)) {
$val = 0;
foreach ($value as $dbgval)
$val += intval($dbgval);
$value = $val;
$value = $val;
}
}
else if ($which == 'db' && $prop == 'db_dsnw' && !empty($_POST['_dbtype'])) {
if ($_POST['_dbtype'] == 'sqlite')
$value = sprintf('%s://%s?mode=0646', $_POST['_dbtype'], $_POST['_dbname']{0} == '/' ? '/' . $_POST['_dbname'] : $_POST['_dbname']);
else
else if ($_POST['_dbtype'])
$value = sprintf('%s://%s:%s@%s/%s', $_POST['_dbtype'],
rawurlencode($_POST['_dbuser']), rawurlencode($_POST['_dbpass']), $_POST['_dbhost'], $_POST['_dbname']);
}
@ -177,9 +179,9 @@ class rcube_install
$value = '%p';
}
else if ($prop == 'default_imap_folders') {
$value = Array();
$value = array();
foreach ($this->config['default_imap_folders'] as $_folder) {
switch($_folder) {
switch ($_folder) {
case 'Drafts': $_folder = $this->config['drafts_mbox']; break;
case 'Sent': $_folder = $this->config['sent_mbox']; break;
case 'Junk': $_folder = $this->config['junk_mbox']; break;
@ -206,7 +208,7 @@ class rcube_install
// replace the matching line in config file
$out = preg_replace(
'/(\$rcmail_config\[\''.preg_quote($prop).'\'\])\s+=\s+(.+);/Uie',
"'\\1 = ' . rcube_install::_dump_var(\$value) . ';'",
"'\\1 = ' . rcube_install::_dump_var(\$value, \$prop) . ';'",
$out);
}
@ -299,7 +301,7 @@ class rcube_install
$current = $this->config;
$this->config = array();
$this->load_defaults();
foreach ($this->replaced_config as $prop => $replacement) {
if (isset($current[$prop])) {
if ($prop == 'skin_path')
@ -328,9 +330,9 @@ class rcube_install
if ($current['keep_alive'] && $current['session_lifetime'] < $current['keep_alive'])
$current['session_lifetime'] = max(10, ceil($current['keep_alive'] / 60) * 2);
$this->config = array_merge($this->config, $current);
foreach ((array)$current['ldap_public'] as $key => $values) {
$this->config['ldap_public'][$key] = $current['ldap_public'][$key];
}
@ -502,17 +504,25 @@ class rcube_install
return $out;
}
/**
* Create a HTML dropdown to select a previous version of Roundcube
*/
function versions_select($attrib = array())
{
$select = new html_select($attrib);
$select->add(array('0.1-stable', '0.1.1', '0.2-alpha', '0.2-beta', '0.2-stable', '0.3-stable', '0.3.1', '0.4-beta', '0.4.2', '0.5-beta', '0.5', '0.5.1'));
$select->add(array(
'0.1-stable', '0.1.1',
'0.2-alpha', '0.2-beta', '0.2-stable',
'0.3-stable', '0.3.1',
'0.4-beta', '0.4.2',
'0.5-beta', '0.5', '0.5.1',
'0.6-beta', '0.6',
'0.7-beta', '0.7', '0.7.1', '0.7.2', '0.7.3',
));
return $select;
}
/**
* Return a list with available subfolders of the skin directory
*/
@ -614,7 +624,22 @@ class rcube_install
}
static function _dump_var($var) {
static function _dump_var($var, $name=null) {
// special values
switch ($name) {
case 'syslog_facility':
$list = array(32 => 'LOG_AUTH', 80 => 'LOG_AUTHPRIV', 72 => ' LOG_CRON',
24 => 'LOG_DAEMON', 0 => 'LOG_KERN', 128 => 'LOG_LOCAL0',
136 => 'LOG_LOCAL1', 144 => 'LOG_LOCAL2', 152 => 'LOG_LOCAL3',
160 => 'LOG_LOCAL4', 168 => 'LOG_LOCAL5', 176 => 'LOG_LOCAL6',
184 => 'LOG_LOCAL7', 48 => 'LOG_LPR', 16 => 'LOG_MAIL',
56 => 'LOG_NEWS', 40 => 'LOG_SYSLOG', 8 => 'LOG_USER', 64 => 'LOG_UUCP');
if ($val = $list[$var])
return $val;
break;
}
if (is_array($var)) {
if (empty($var)) {
return 'array()';

@ -0,0 +1,351 @@
/**
* ACL plugin script
*
* @version @package_version@
* @author Aleksander Machniak <alec@alec.pl>
*/
if (window.rcmail) {
rcmail.addEventListener('init', function() {
if (rcmail.gui_objects.acltable) {
rcmail.acl_list_init();
// enable autocomplete on user input
if (rcmail.env.acl_users_source) {
rcmail.init_address_input_events($('#acluser'), {action:'settings/plugin.acl-autocomplete'});
// fix inserted value
rcmail.addEventListener('autocomplete_insert', function(e) {
if (e.field.id != 'acluser')
return;
var value = e.insert;
// get UID from the entry value
if (value.match(/\s*\(([^)]+)\)[, ]*$/))
value = RegExp.$1;
e.field.value = value;
});
}
}
rcmail.enable_command('acl-create', 'acl-save', 'acl-cancel', 'acl-mode-switch', true);
rcmail.enable_command('acl-delete', 'acl-edit', false);
});
}
// Display new-entry form
rcube_webmail.prototype.acl_create = function()
{
this.acl_init_form();
}
// Display ACL edit form
rcube_webmail.prototype.acl_edit = function()
{
// @TODO: multi-row edition
var id = this.acl_list.get_single_selection();
if (id)
this.acl_init_form(id);
}
// ACL entry delete
rcube_webmail.prototype.acl_delete = function()
{
var users = this.acl_get_usernames();
if (users && users.length && confirm(this.get_label('acl.deleteconfirm'))) {
this.http_request('settings/plugin.acl', '_act=delete&_user='+urlencode(users.join(','))
+ '&_mbox='+urlencode(this.env.mailbox),
this.set_busy(true, 'acl.deleting'));
}
}
// Save ACL data
rcube_webmail.prototype.acl_save = function()
{
var user = $('#acluser').val(), rights = '', type;
$(':checkbox', this.env.acl_advanced ? $('#advancedrights') : sim_ul = $('#simplerights')).map(function() {
if (this.checked)
rights += this.value;
});
if (type = $('input:checked[name=usertype]').val()) {
if (type != 'user')
user = type;
}
if (!user) {
alert(this.get_label('acl.nouser'));
return;
}
if (!rights) {
alert(this.get_label('acl.norights'));
return;
}
this.http_request('settings/plugin.acl', '_act=save'
+ '&_user='+urlencode(user)
+ '&_acl=' +rights
+ '&_mbox='+urlencode(this.env.mailbox)
+ (this.acl_id ? '&_old='+this.acl_id : ''),
this.set_busy(true, 'acl.saving'));
}
// Cancel/Hide form
rcube_webmail.prototype.acl_cancel = function()
{
this.ksearch_blur();
this.acl_form.hide();
}
// Update data after save (and hide form)
rcube_webmail.prototype.acl_update = function(o)
{
// delete old row
if (o.old)
this.acl_remove_row(o.old);
// make sure the same ID doesn't exist
else if (this.env.acl[o.id])
this.acl_remove_row(o.id);
// add new row
this.acl_add_row(o, true);
// hide autocomplete popup
this.ksearch_blur();
// hide form
this.acl_form.hide();
}
// Switch table display mode
rcube_webmail.prototype.acl_mode_switch = function(elem)
{
this.env.acl_advanced = !this.env.acl_advanced;
this.enable_command('acl-delete', 'acl-edit', false);
this.http_request('settings/plugin.acl', '_act=list'
+ '&_mode='+(this.env.acl_advanced ? 'advanced' : 'simple')
+ '&_mbox='+urlencode(this.env.mailbox),
this.set_busy(true, 'loading'));
}
// ACL table initialization
rcube_webmail.prototype.acl_list_init = function()
{
this.acl_list = new rcube_list_widget(this.gui_objects.acltable,
{multiselect:true, draggable:false, keyboard:true, toggleselect:true});
this.acl_list.addEventListener('select', function(o) { rcmail.acl_list_select(o); });
this.acl_list.addEventListener('dblclick', function(o) { rcmail.acl_list_dblclick(o); });
this.acl_list.addEventListener('keypress', function(o) { rcmail.acl_list_keypress(o); });
this.acl_list.init();
}
// ACL table row selection handler
rcube_webmail.prototype.acl_list_select = function(list)
{
rcmail.enable_command('acl-delete', list.selection.length > 0);
rcmail.enable_command('acl-edit', list.selection.length == 1);
list.focus();
}
// ACL table double-click handler
rcube_webmail.prototype.acl_list_dblclick = function(list)
{
this.acl_edit();
}
// ACL table keypress handler
rcube_webmail.prototype.acl_list_keypress = function(list)
{
if (list.key_pressed == list.ENTER_KEY)
this.command('acl-edit');
else if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
if (!this.acl_form || !this.acl_form.is(':visible'))
this.command('acl-delete');
}
// Reloads ACL table
rcube_webmail.prototype.acl_list_update = function(html)
{
$(this.gui_objects.acltable).html(html);
this.acl_list_init();
}
// Returns names of users in selected rows
rcube_webmail.prototype.acl_get_usernames = function()
{
var users = [], n, len, cell, row,
list = this.acl_list,
selection = list.get_selection();
for (n=0, len=selection.length; n<len; n++) {
if (this.env.acl_specials.length && $.inArray(selection[n], this.env.acl_specials) >= 0) {
users.push(selection[n]);
}
else if (row = list.rows[selection[n]]) {
cell = $('td.user', row.obj);
if (cell.length == 1)
users.push(cell.text());
}
}
return users;
}
// Removes ACL table row
rcube_webmail.prototype.acl_remove_row = function(id)
{
var list = this.acl_list;
list.remove_row(id);
list.clear_selection();
// we don't need it anymore (remove id conflict)
$('#rcmrow'+id).remove();
this.env.acl[id] = null;
this.enable_command('acl-delete', list.selection.length > 0);
this.enable_command('acl-edit', list.selection.length == 1);
}
// Adds ACL table row
rcube_webmail.prototype.acl_add_row = function(o, sel)
{
var n, len, ids = [], spec = [], id = o.id, list = this.acl_list,
items = this.env.acl_advanced ? [] : this.env.acl_items,
table = this.gui_objects.acltable,
row = $('thead > tr', table).clone();
// Update new row
$('td', row).map(function() {
var r, cl = this.className.replace(/^acl/, '');
if (items && items[cl])
cl = items[cl];
if (cl == 'user')
$(this).text(o.username);
else
$(this).addClass(rcmail.acl_class(o.acl, cl)).text('');
});
row.attr('id', 'rcmrow'+id);
row = row.get(0);
this.env.acl[id] = o.acl;
// sorting... (create an array of user identifiers, then sort it)
for (n in this.env.acl) {
if (this.env.acl[n]) {
if (this.env.acl_specials.length && $.inArray(n, this.env.acl_specials) >= 0)
spec.push(n);
else
ids.push(n);
}
}
ids.sort();
// specials on the top
ids = spec.concat(ids);
// find current id
for (n=0, len=ids.length; n<len; n++)
if (ids[n] == id)
break;
// add row
if (n && n < len) {
$('#rcmrow'+ids[n-1]).after(row);
list.init_row(row);
list.rowcount++;
}
else
list.insert_row(row);
if (sel)
list.select_row(o.id);
}
// Initializes and shows ACL create/edit form
rcube_webmail.prototype.acl_init_form = function(id)
{
var ul, row, td, val = '', type = 'user', li_elements, body = $('body'),
adv_ul = $('#advancedrights'), sim_ul = $('#simplerights'),
name_input = $('#acluser');
if (!this.acl_form) {
var fn = function () { $('input[value=user]').prop('checked', true); };
name_input.click(fn).keypress(fn);
}
this.acl_form = $('#aclform');
// Hide unused items
if (this.env.acl_advanced) {
adv_ul.show();
sim_ul.hide();
ul = adv_ul;
}
else {
sim_ul.show();
adv_ul.hide();
ul = sim_ul;
}
// initialize form fields
li_elements = $(':checkbox', ul);
li_elements.attr('checked', false);
if (id && (row = this.acl_list.rows[id])) {
row = row.obj;
li_elements.map(function() {
val = this.value;
td = $('td.'+this.id, row);
if (td && td.hasClass('enabled'))
this.checked = true;
});
if (!this.env.acl_specials.length || $.inArray(id, this.env.acl_specials) < 0)
val = $('td.user', row).text();
else
type = id;
}
// mark read (lrs) rights by default
else
li_elements.filter(function() { return this.id.match(/^acl([lrs]|read)$/); }).prop('checked', true);
name_input.val(val);
$('input[value='+type+']').prop('checked', true);
this.acl_id = id;
// position the form horizontally
var bw = body.width(), mw = this.acl_form.width();
if (bw >= mw)
this.acl_form.css({left: parseInt((bw - mw)/2)+'px'});
// display it
this.acl_form.show();
if (type == 'user')
name_input.focus();
// unfocus the list, make backspace key in name input field working
this.acl_list.blur();
}
// Returns class name according to ACL comparision result
rcube_webmail.prototype.acl_class = function(acl1, acl2)
{
var i, len, found = 0;
acl1 = String(acl1);
acl2 = String(acl2);
for (i=0, len=acl2.length; i<len; i++)
if (acl1.indexOf(acl2[i]) > -1)
found++;
if (found == len)
return 'enabled';
else if (found)
return 'partial';
return 'disabled';
}

@ -0,0 +1,707 @@
<?php
/**
* Folders Access Control Lists Management (RFC4314, RFC2086)
*
* @version @package_version@
* @author Aleksander Machniak <alec@alec.pl>
*
*
* Copyright (C) 2011, Kolab Systems AG
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program 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; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
class acl extends rcube_plugin
{
public $task = 'settings|addressbook|calendar';
private $rc;
private $supported = null;
private $mbox;
private $ldap;
private $specials = array('anyone', 'anonymous');
/**
* Plugin initialization
*/
function init()
{
$this->rc = rcmail::get_instance();
// Register hooks
$this->add_hook('folder_form', array($this, 'folder_form'));
// kolab_addressbook plugin
$this->add_hook('addressbook_form', array($this, 'folder_form'));
$this->add_hook('calendar_form_kolab', array($this, 'folder_form'));
// Plugin actions
$this->register_action('plugin.acl', array($this, 'acl_actions'));
$this->register_action('plugin.acl-autocomplete', array($this, 'acl_autocomplete'));
}
/**
* Handler for plugin actions (AJAX)
*/
function acl_actions()
{
$action = trim(get_input_value('_act', RCUBE_INPUT_GPC));
// Connect to IMAP
$this->rc->imap_init();
$this->rc->imap_connect();
// Load localization and configuration
$this->add_texts('localization/');
$this->load_config();
if ($action == 'save') {
$this->action_save();
}
else if ($action == 'delete') {
$this->action_delete();
}
else if ($action == 'list') {
$this->action_list();
}
// Only AJAX actions
$this->rc->output->send();
}
/**
* Handler for user login autocomplete request
*/
function acl_autocomplete()
{
$this->load_config();
$search = get_input_value('_search', RCUBE_INPUT_GPC, true);
$sid = get_input_value('_id', RCUBE_INPUT_GPC);
$users = array();
if ($this->init_ldap()) {
$max = (int) $this->rc->config->get('autocomplete_max', 15);
$mode = (int) $this->rc->config->get('addressbook_search_mode');
$this->ldap->set_pagesize($max);
$result = $this->ldap->search('*', $search, $mode);
foreach ($result->records as $record) {
$user = $record['uid'];
if (is_array($user)) {
$user = array_filter($user);
$user = $user[0];
}
if ($user) {
if ($record['name'])
$user = $record['name'] . ' (' . $user . ')';
$users[] = $user;
}
}
}
sort($users, SORT_LOCALE_STRING);
$this->rc->output->command('ksearch_query_results', $users, $search, $sid);
$this->rc->output->send();
}
/**
* Handler for 'folder_form' hook
*
* @param array $args Hook arguments array (form data)
*
* @return array Hook arguments array
*/
function folder_form($args)
{
$mbox_imap = $args['options']['name'];
$myrights = $args['options']['rights'];
// Edited folder name (empty in create-folder mode)
if (!strlen($mbox_imap)) {
return $args;
}
/*
// Do nothing on protected folders (?)
if ($args['options']['protected']) {
return $args;
}
*/
// Check if myrights is set
if (empty($myrights)) {
return $args;
}
// Load localization and include scripts
$this->load_config();
$this->add_texts('localization/', array('deleteconfirm', 'norights',
'nouser', 'deleting', 'saving'));
$this->include_script('acl.js');
$this->rc->output->include_script('list.js');
$this->include_stylesheet($this->local_skin_path().'/acl.css');
// add Info fieldset if it doesn't exist
if (!isset($args['form']['props']['fieldsets']['info']))
$args['form']['props']['fieldsets']['info'] = array(
'name' => rcube_label('info'),
'content' => array());
// Display folder rights to 'Info' fieldset
$args['form']['props']['fieldsets']['info']['content']['myrights'] = array(
'label' => Q($this->gettext('myrights')),
'value' => $this->acl2text($myrights)
);
// Return if not folder admin
if (!in_array('a', $myrights)) {
return $args;
}
// The 'Sharing' tab
$this->mbox = $mbox_imap;
$this->rc->output->set_env('acl_users_source', (bool) $this->rc->config->get('acl_users_source'));
$this->rc->output->set_env('mailbox', $mbox_imap);
$this->rc->output->add_handlers(array(
'acltable' => array($this, 'templ_table'),
'acluser' => array($this, 'templ_user'),
'aclrights' => array($this, 'templ_rights'),
));
$this->rc->output->set_env('autocomplete_max', (int)$this->rc->config->get('autocomplete_max', 15));
$this->rc->output->set_env('autocomplete_min_length', $this->rc->config->get('autocomplete_min_length'));
$this->rc->output->add_label('autocompletechars', 'autocompletemore');
$args['form']['sharing'] = array(
'name' => Q($this->gettext('sharing')),
'content' => $this->rc->output->parse('acl.table', false, false),
);
return $args;
}
/**
* Creates ACL rights table
*
* @param array $attrib Template object attributes
*
* @return string HTML Content
*/
function templ_table($attrib)
{
if (empty($attrib['id']))
$attrib['id'] = 'acl-table';
$out = $this->list_rights($attrib);
$this->rc->output->add_gui_object('acltable', $attrib['id']);
return $out;
}
/**
* Creates ACL rights form (rights list part)
*
* @param array $attrib Template object attributes
*
* @return string HTML Content
*/
function templ_rights($attrib)
{
// Get supported rights
$supported = $this->rights_supported();
// depending on server capability either use 'te' or 'd' for deleting msgs
$deleteright = implode(array_intersect(str_split('ted'), $supported));
$out = '';
$ul = '';
$input = new html_checkbox();
// Advanced rights
$attrib['id'] = 'advancedrights';
foreach ($supported as $val) {
$id = "acl$val";
$ul .= html::tag('li', null,
$input->show('', array(
'name' => "acl[$val]", 'value' => $val, 'id' => $id))
. html::label(array('for' => $id, 'title' => $this->gettext('longacl'.$val)),
$this->gettext('acl'.$val)));
}
$out = html::tag('ul', $attrib, $ul, html::$common_attrib);
// Simple rights
$ul = '';
$attrib['id'] = 'simplerights';
$items = array(
'read' => 'lrs',
'write' => 'wi',
'delete' => $deleteright,
'other' => preg_replace('/[lrswi'.$deleteright.']/', '', implode($supported)),
);
foreach ($items as $key => $val) {
$id = "acl$key";
$ul .= html::tag('li', null,
$input->show('', array(
'name' => "acl[$val]", 'value' => $val, 'id' => $id))
. html::label(array('for' => $id, 'title' => $this->gettext('longacl'.$key)),
$this->gettext('acl'.$key)));
}
$out .= "\n" . html::tag('ul', $attrib, $ul, html::$common_attrib);
$this->rc->output->set_env('acl_items', $items);
return $out;
}
/**
* Creates ACL rights form (user part)
*
* @param array $attrib Template object attributes
*
* @return string HTML Content
*/
function templ_user($attrib)
{
// Create username input
$attrib['name'] = 'acluser';
$textfield = new html_inputfield($attrib);
$fields['user'] = html::label(array('for' => 'iduser'), $this->gettext('username'))
. ' ' . $textfield->show();
// Add special entries
if (!empty($this->specials)) {
foreach ($this->specials as $key) {
$fields[$key] = html::label(array('for' => 'id'.$key), $this->gettext($key));
}
}
$this->rc->output->set_env('acl_specials', $this->specials);
// Create list with radio buttons
if (count($fields) > 1) {
$ul = '';
$radio = new html_radiobutton(array('name' => 'usertype'));
foreach ($fields as $key => $val) {
$ul .= html::tag('li', null, $radio->show($key == 'user' ? 'user' : '',
array('value' => $key, 'id' => 'id'.$key))
. $val);
}
$out = html::tag('ul', array('id' => 'usertype'), $ul, html::$common_attrib);
}
// Display text input alone
else {
$out = $fields['user'];
}
return $out;
}
/**
* Creates ACL rights table
*
* @param array $attrib Template object attributes
*
* @return string HTML Content
*/
private function list_rights($attrib=array())
{
// Get ACL for the folder
$acl = $this->rc->imap->get_acl($this->mbox);
if (!is_array($acl)) {
$acl = array();
}
// Keep special entries (anyone/anonymous) on top of the list
if (!empty($this->specials) && !empty($acl)) {
foreach ($this->specials as $key) {
if (isset($acl[$key])) {
$acl_special[$key] = $acl[$key];
unset($acl[$key]);
}
}
}
// Sort the list by username
uksort($acl, 'strnatcasecmp');
if (!empty($acl_special)) {
$acl = array_merge($acl_special, $acl);
}
// Get supported rights and build column names
$supported = $this->rights_supported();
// depending on server capability either use 'te' or 'd' for deleting msgs
$deleteright = implode(array_intersect(str_split('ted'), $supported));
// Use advanced or simple (grouped) rights
$advanced = $this->rc->config->get('acl_advanced_mode');
if ($advanced) {
$items = array();
foreach ($supported as $sup) {
$items[$sup] = $sup;
}
}
else {
$items = array(
'read' => 'lrs',
'write' => 'wi',
'delete' => $deleteright,
'other' => preg_replace('/[lrswi'.$deleteright.']/', '', implode($supported)),
);
}
// Create the table
$attrib['noheader'] = true;
$table = new html_table($attrib);
// Create table header
$table->add_header('user', $this->gettext('identifier'));
foreach (array_keys($items) as $key) {
$table->add_header('acl'.$key, $this->gettext('shortacl'.$key));
}
$i = 1;
$js_table = array();
foreach ($acl as $user => $rights) {
if ($this->rc->imap->conn->user == $user) {
continue;
}
// filter out virtual rights (c or d) the server may return
$userrights = array_intersect($rights, $supported);
$userid = html_identifier($user);
if (!empty($this->specials) && in_array($user, $this->specials)) {
$user = $this->gettext($user);
}
$table->add_row(array('id' => 'rcmrow'.$userid));
$table->add('user', Q($user));
foreach ($items as $key => $right) {
$in = $this->acl_compare($userrights, $right);
switch ($in) {
case 2: $class = 'enabled'; break;
case 1: $class = 'partial'; break;
default: $class = 'disabled'; break;
}
$table->add('acl' . $key . ' ' . $class, '');
}
$js_table[$userid] = implode($userrights);
}
$this->rc->output->set_env('acl', $js_table);
$this->rc->output->set_env('acl_advanced', $advanced);
$out = $table->show();
return $out;
}
/**
* Handler for ACL update/create action
*/
private function action_save()
{
$mbox = trim(get_input_value('_mbox', RCUBE_INPUT_GPC, true)); // UTF7-IMAP
$user = trim(get_input_value('_user', RCUBE_INPUT_GPC));
$acl = trim(get_input_value('_acl', RCUBE_INPUT_GPC));
$oldid = trim(get_input_value('_old', RCUBE_INPUT_GPC));
$acl = array_intersect(str_split($acl), $this->rights_supported());
$users = $oldid ? array($user) : explode(',', $user);
foreach ($users as $user) {
$user = trim($user);
if (!empty($this->specials) && in_array($user, $this->specials)) {
$username = $this->gettext($user);
}
else {
if (!strpos($user, '@') && ($realm = $this->get_realm())) {
$user .= '@' . rcube_idn_to_ascii(preg_replace('/^@/', '', $realm));
}
$username = $user;
}
if (!$acl || !$user || !strlen($mbox)) {
continue;
}
if ($user != $_SESSION['username'] && $username != $_SESSION['username']) {
if ($this->rc->imap->set_acl($mbox, $user, $acl)) {
$ret = array('id' => html_identifier($user),
'username' => $username, 'acl' => implode($acl), 'old' => $oldid);
$this->rc->output->command('acl_update', $ret);
$result++;
}
}
}
if ($result) {
$this->rc->output->show_message($oldid ? 'acl.updatesuccess' : 'acl.createsuccess', 'confirmation');
}
else {
$this->rc->output->show_message($oldid ? 'acl.updateerror' : 'acl.createerror', 'error');
}
}
/**
* Handler for ACL delete action
*/
private function action_delete()
{
$mbox = trim(get_input_value('_mbox', RCUBE_INPUT_GPC, true)); //UTF7-IMAP
$user = trim(get_input_value('_user', RCUBE_INPUT_GPC));
$user = explode(',', $user);
foreach ($user as $u) {
$u = trim($u);
if ($this->rc->imap->delete_acl($mbox, $u)) {
$this->rc->output->command('acl_remove_row', html_identifier($u));
}
else {
$error = true;
}
}
if (!$error) {
$this->rc->output->show_message('acl.deletesuccess', 'confirmation');
}
else {
$this->rc->output->show_message('acl.deleteerror', 'error');
}
}
/**
* Handler for ACL list update action (with display mode change)
*/
private function action_list()
{
if (in_array('acl_advanced_mode', (array)$this->rc->config->get('dont_override'))) {
return;
}
$this->mbox = trim(get_input_value('_mbox', RCUBE_INPUT_GPC, true)); // UTF7-IMAP
$advanced = trim(get_input_value('_mode', RCUBE_INPUT_GPC));
$advanced = $advanced == 'advanced' ? true : false;
// Save state in user preferences
$this->rc->user->save_prefs(array('acl_advanced_mode' => $advanced));
$out = $this->list_rights();
$out = preg_replace(array('/^<table[^>]+>/', '/<\/table>$/'), '', $out);
$this->rc->output->command('acl_list_update', $out);
}
/**
* Creates <UL> list with descriptive access rights
*
* @param array $rights MYRIGHTS result
*
* @return string HTML content
*/
function acl2text($rights)
{
if (empty($rights)) {
return '';
}
$supported = $this->rights_supported();
$list = array();
$attrib = array(
'name' => 'rcmyrights',
'style' => 'margin:0; padding:0 15px;',
);
foreach ($supported as $right) {
if (in_array($right, $rights)) {
$list[] = html::tag('li', null, Q($this->gettext('acl' . $right)));
}
}
if (count($list) == count($supported))
return Q($this->gettext('aclfull'));
return html::tag('ul', $attrib, implode("\n", $list));
}
/**
* Compares two ACLs (according to supported rights)
*
* @param array $acl1 ACL rights array (or string)
* @param array $acl2 ACL rights array (or string)
*
* @param int Comparision result, 2 - full match, 1 - partial match, 0 - no match
*/
function acl_compare($acl1, $acl2)
{
if (!is_array($acl1)) $acl1 = str_split($acl1);
if (!is_array($acl2)) $acl2 = str_split($acl2);
$rights = $this->rights_supported();
$acl1 = array_intersect($acl1, $rights);
$acl2 = array_intersect($acl2, $rights);
$res = array_intersect($acl1, $acl2);
$cnt1 = count($res);
$cnt2 = count($acl2);
if ($cnt1 == $cnt2)
return 2;
else if ($cnt1)
return 1;
else
return 0;
}
/**
* Get list of supported access rights (according to RIGHTS capability)
*
* @return array List of supported access rights abbreviations
*/
function rights_supported()
{
if ($this->supported !== null) {
return $this->supported;
}
$capa = $this->rc->imap->get_capability('RIGHTS');
if (is_array($capa)) {
$rights = strtolower($capa[0]);
}
else {
$rights = 'cd';
}
return $this->supported = str_split('lrswi' . $rights . 'pa');
}
/**
* Username realm detection.
*
* @return string Username realm (domain)
*/
private function get_realm()
{
// When user enters a username without domain part, realm
// alows to add it to the username (and display correct username in the table)
if (isset($_SESSION['acl_username_realm'])) {
return $_SESSION['acl_username_realm'];
}
// find realm in username of logged user (?)
list($name, $domain) = explode('@', $_SESSION['username']);
// Use (always existent) ACL entry on the INBOX for the user to determine
// whether or not the user ID in ACL entries need to be qualified and how
// they would need to be qualified.
if (empty($domain)) {
$acl = $this->rc->imap->get_acl('INBOX');
if (is_array($acl)) {
$regexp = '/^' . preg_quote($_SESSION['username'], '/') . '@(.*)$/';
foreach (array_keys($acl) as $name) {
if (preg_match($regexp, $name, $matches)) {
$domain = $matches[1];
break;
}
}
}
}
return $_SESSION['acl_username_realm'] = $domain;
}
/**
* Initializes autocomplete LDAP backend
*/
private function init_ldap()
{
if ($this->ldap)
return $this->ldap->ready;
// get LDAP config
$config = $this->rc->config->get('acl_users_source');
if (empty($config)) {
return false;
}
// not an array, use configured ldap_public source
if (!is_array($config)) {
$ldap_config = (array) $this->rc->config->get('ldap_public');
$config = $ldap_config[$config];
}
$uid_field = $this->rc->config->get('acl_users_field', 'mail');
$filter = $this->rc->config->get('acl_users_filter');
if (empty($uid_field) || empty($config)) {
return false;
}
// get name attribute
if (!empty($config['fieldmap'])) {
$name_field = $config['fieldmap']['name'];
}
// ... no fieldmap, use the old method
if (empty($name_field)) {
$name_field = $config['name_field'];
}
// add UID field to fieldmap, so it will be returned in a record with name
$config['fieldmap'] = array(
'name' => $name_field,
'uid' => $uid_field,
);
// search in UID and name fields
$config['search_fields'] = array_values($config['fieldmap']);
$config['required_fields'] = array($uid_field);
// set search filter
if ($filter)
$config['filter'] = $filter;
// disable vlv
$config['vlv'] = false;
// Initialize LDAP connection
$this->ldap = new rcube_ldap($config,
$this->rc->config->get('ldap_debug'),
$this->rc->config->mail_domain($_SESSION['imap_host']));
return $this->ldap->ready;
}
}

@ -0,0 +1,19 @@
<?php
// Default look of access rights table
// In advanced mode all access rights are displayed separately
// In simple mode access rights are grouped into four groups: read, write, delete, full
$rcmail_config['acl_advanced_mode'] = false;
// LDAP addressbook that would be searched for user names autocomplete.
// That should be an array refering to the $rcmail_config['ldap_public'] array key
// or complete addressbook configuration array.
$rcmail_config['acl_users_source'] = '';
// The LDAP attribute which will be used as ACL user identifier
$rcmail_config['acl_users_field'] = 'mail';
// The LDAP search filter will be &'d with search queries
$rcmail_config['acl_users_filter'] = '';
?>

@ -0,0 +1,83 @@
<?php
$labels['sharing'] = 'Freigabe';
$labels['myrights'] = 'Zugriffsrechte';
$labels['username'] = 'Benutzer:';
$labels['advanced'] = 'erweiterter Modus';
$labels['newuser'] = 'Eintrag hinzufügen';
$labels['actions'] = 'Zugriffsrechte Aktionen...';
$labels['anyone'] = 'Alle Benutzer (anyone)';
$labels['anonymous'] = 'Gäste (anonymous)';
$labels['identifier'] = 'Bezeichnung';
$labels['acll'] = 'Ordner sichtbar';
$labels['aclr'] = 'Nachrichten lesen';
$labels['acls'] = 'Lesestatus ändern';
$labels['aclw'] = 'Flags schreiben';
$labels['acli'] = 'Nachrichten Hinzufügen';
$labels['aclp'] = 'Nachrichten Senden an';
$labels['aclc'] = 'Unterordner erstellen';
$labels['aclk'] = 'Unterordner erstellen';
$labels['acld'] = 'Nachrichten als gelöscht markieren';
$labels['aclt'] = 'Nachrichten als gelöscht markieren';
$labels['acle'] = 'Nachrichten endgültig Löschen';
$labels['aclx'] = 'Ordner löschen';
$labels['acla'] = 'Zugriffsrechte Verwalten';
$labels['aclfull'] = 'Vollzugriff';
$labels['aclother'] = 'Andere';
$labels['aclread'] = 'Lesen';
$labels['aclwrite'] = 'Schreiben';
$labels['acldelete'] = 'Löschen';
$labels['shortacll'] = 'Sichtbar';
$labels['shortaclr'] = 'Lesen';
$labels['shortacls'] = 'Lesestatus';
$labels['shortaclw'] = 'Flags ändern';
$labels['shortacli'] = 'Hinzufügen';
$labels['shortaclp'] = 'Senden an';
$labels['shortaclc'] = 'Erstellen';
$labels['shortaclk'] = 'Erstellen';
$labels['shortacld'] = 'Löschen';
$labels['shortaclt'] = 'Löschen';
$labels['shortacle'] = 'endgültig löschen';
$labels['shortaclx'] = 'Ordner löschen';
$labels['shortacla'] = 'Verwalten';
$labels['shortaclother'] = 'Andere';
$labels['shortaclread'] = 'Lesen';
$labels['shortaclwrite'] = 'Schreiben';
$labels['shortacldelete'] = 'Löschen';
$labels['longacll'] = 'Der Ordner ist sichtbar und kann abonniert werden';
$labels['longaclr'] = 'Nachrichten im Ordner können gelesen werden';
$labels['longacls'] = 'Der Lesestatus von Nachrichten kann geändert werden';
$labels['longaclw'] = 'Alle Nachrichten-Flags und Schlüsselwörter außer "Gelesen" und "Gelöscht" können geändert werden';
$labels['longacli'] = 'Nachrichten können in diesen Ordner kopiert oder verschoben werden';
$labels['longaclp'] = 'Nachrichten können an diesen Ordner gesendet werden';
$labels['longaclc'] = 'Unterordner können in diesem Ordner erstellt oder umbenannt werden';
$labels['longaclk'] = 'Unterordner können in diesem Ordner erstellt oder umbenannt werden';
$labels['longacld'] = 'Der "gelöscht" Status von Nachrichten kann geändert werden';
$labels['longaclt'] = 'Der "gelöscht" Status von Nachrichten kann geändert werden';
$labels['longacle'] = 'Als "gelöscht" markiert Nachrichten können gelöscht werden.';
$labels['longaclx'] = 'Der Ordner kann gelöscht oder umbenannt werden';
$labels['longacla'] = 'Die Zugriffsrechte des Ordners können geändert werden';
$labels['longaclfull'] = 'Vollzugriff inklusive Ordner-Verwaltung';
$labels['longaclread'] = 'Der Ordnerinhalt kann gelesen werden';
$labels['longaclwrite'] = 'Nachrichten können markiert, an den Ordner gesendet und in den Ordner kopiert oder verschoben werden';
$labels['longacldelete'] = 'Nachrichten können gelöscht werden';
$messages['deleting'] = 'Zugriffsrechte werden entzogen...';
$messages['saving'] = 'Zugriffsrechte werden gewährt...';
$messages['updatesuccess'] = 'Zugriffsrechte erfolgreich geändert';
$messages['deletesuccess'] = 'Zugriffsrechte erfolgreich entzogen';
$messages['createsuccess'] = 'Zugriffsrechte erfolgreich gewährt';
$messages['updateerror'] = 'Zugriffsrechte konnten nicht geändert werden';
$messages['deleteerror'] = 'Zugriffsrechte konnten nicht entzogen werden';
$messages['createerror'] = 'Zugriffsrechte konnten nicht gewährt werden';
$messages['deleteconfirm'] = 'Sind Sie sicher, daß Sie die Zugriffsrechte den ausgewählten Benutzern entziehen möchten?';
$messages['norights'] = 'Es wurden keine Zugriffsrechte ausgewählt!';
$messages['nouser'] = 'Es wurde kein Benutzer ausgewählt!';
?>

@ -0,0 +1,83 @@
<?php
$labels['sharing'] = 'Sharing';
$labels['myrights'] = 'Access Rights';
$labels['username'] = 'User:';
$labels['advanced'] = 'advanced mode';
$labels['newuser'] = 'Add entry';
$labels['actions'] = 'Access right actions...';
$labels['anyone'] = 'All users (anyone)';
$labels['anonymous'] = 'Guests (anonymous)';
$labels['identifier'] = 'Identifier';
$labels['acll'] = 'Lookup';
$labels['aclr'] = 'Read messages';
$labels['acls'] = 'Keep Seen state';
$labels['aclw'] = 'Write flags';
$labels['acli'] = 'Insert (Copy into)';
$labels['aclp'] = 'Post';
$labels['aclc'] = 'Create subfolders';
$labels['aclk'] = 'Create subfolders';
$labels['acld'] = 'Delete messages';
$labels['aclt'] = 'Delete messages';
$labels['acle'] = 'Expunge';
$labels['aclx'] = 'Delete folder';
$labels['acla'] = 'Administer';
$labels['aclfull'] = 'Full control';
$labels['aclother'] = 'Other';
$labels['aclread'] = 'Read';
$labels['aclwrite'] = 'Write';
$labels['acldelete'] = 'Delete';
$labels['shortacll'] = 'Lookup';
$labels['shortaclr'] = 'Read';
$labels['shortacls'] = 'Keep';
$labels['shortaclw'] = 'Write';
$labels['shortacli'] = 'Insert';
$labels['shortaclp'] = 'Post';
$labels['shortaclc'] = 'Create';
$labels['shortaclk'] = 'Create';
$labels['shortacld'] = 'Delete';
$labels['shortaclt'] = 'Delete';
$labels['shortacle'] = 'Expunge';
$labels['shortaclx'] = 'Folder delete';
$labels['shortacla'] = 'Administer';
$labels['shortaclother'] = 'Other';
$labels['shortaclread'] = 'Read';
$labels['shortaclwrite'] = 'Write';
$labels['shortacldelete'] = 'Delete';
$labels['longacll'] = 'The folder is visible on lists and can be subscribed to';
$labels['longaclr'] = 'The folder can be opened for reading';
$labels['longacls'] = 'Messages Seen flag can be changed';
$labels['longaclw'] = 'Messages flags and keywords can be changed, except Seen and Deleted';
$labels['longacli'] = 'Messages can be written or copied to the folder';
$labels['longaclp'] = 'Messages can be posted to this folder';
$labels['longaclc'] = 'Folders can be created (or renamed) directly under this folder';
$labels['longaclk'] = 'Folders can be created (or renamed) directly under this folder';
$labels['longacld'] = 'Messages Delete flag can be changed';
$labels['longaclt'] = 'Messages Delete flag can be changed';
$labels['longacle'] = 'Messages can be expunged';
$labels['longaclx'] = 'The folder can be deleted or renamed';
$labels['longacla'] = 'The folder access rights can be changed';
$labels['longaclfull'] = 'Full control including folder administration';
$labels['longaclread'] = 'The folder can be opened for reading';
$labels['longaclwrite'] = 'Messages can be marked, written or copied to the folder';
$labels['longacldelete'] = 'Messages can be deleted';
$messages['deleting'] = 'Deleting access rights...';
$messages['saving'] = 'Saving access rights...';
$messages['updatesuccess'] = 'Successfully changed access rights';
$messages['deletesuccess'] = 'Successfully deleted access rights';
$messages['createsuccess'] = 'Successfully added access rights';
$messages['updateerror'] = 'Ubable to update access rights';
$messages['deleteerror'] = 'Unable to delete access rights';
$messages['createerror'] = 'Unable to add access rights';
$messages['deleteconfirm'] = 'Are you sure, you want to remove access rights of selected user(s)?';
$messages['norights'] = 'No rights has been specified!';
$messages['nouser'] = 'No username has been specified!';
?>

@ -0,0 +1,83 @@
<?php
$labels['sharing'] = 'Udostępnianie';
$labels['myrights'] = 'Prawa dostępu';
$labels['username'] = 'Użytkownik:';
$labels['advanced'] = 'tryb zaawansowany';
$labels['newuser'] = 'Dodaj rekord';
$labels['actions'] = 'Akcje na prawach...';
$labels['anyone'] = 'Wszyscy (anyone)';
$labels['anonymous'] = 'Goście (anonymous)';
$labels['identifier'] = 'Identyfikator';
$labels['acll'] = 'Podgląd (Lookup)';
$labels['aclr'] = 'Odczyt (Read)';
$labels['acls'] = 'Zmiana stanu wiadomości (Keep)';
$labels['aclw'] = 'Zmiana flag wiadomości (Write)';
$labels['acli'] = 'Dodawanie/Kopiowanie do (Insert)';
$labels['aclp'] = 'Wysyłanie (Post)';
$labels['aclc'] = 'Tworzenie podfolderów (Create)';
$labels['aclk'] = 'Tworzenie podfolderów (Create)';
$labels['acld'] = 'Usuwanie wiadomości (Delete)';
$labels['aclt'] = 'Usuwanie wiadomości (Delete)';
$labels['acle'] = 'Porządkowanie folderu (Expunge)';
$labels['aclx'] = 'Usuwanie folderu (Delete)';
$labels['acla'] = 'Administracja (Administer)';
$labels['aclfull'] = 'Wszystkie';
$labels['aclother'] = 'Inne';
$labels['aclread'] = 'Odczyt';
$labels['aclwrite'] = 'Zapis';
$labels['acldelete'] = 'Usuwanie';
$labels['shortacll'] = 'Podgląd';
$labels['shortaclr'] = 'Odczyt';
$labels['shortacls'] = 'Zmiana';
$labels['shortaclw'] = 'Zmiana flag';
$labels['shortacli'] = 'Dodawanie';
$labels['shortaclp'] = 'Wysyłanie';
$labels['shortaclc'] = 'Tworzenie';
$labels['shortaclk'] = 'Tworzenie';
$labels['shortacld'] = 'Usuwanie';
$labels['shortaclt'] = 'Usuwanie';
$labels['shortacle'] = 'Porządkowanie';
$labels['shortaclx'] = 'Usuwanie folderu';
$labels['shortacla'] = 'Administracja';
$labels['shortaclother'] = 'Pozostałe';
$labels['shortaclread'] = 'Odczyt';
$labels['shortaclwrite'] = 'Zapis';
$labels['shortacldelete'] = 'Usuwanie';
$labels['longacll'] = 'Pozwala na subskrybowanie folderu i powoduje, że jest on widoczny na liście';
$labels['longaclr'] = 'Pozwala na otwarcie folderu w trybie do odczytu';
$labels['longacls'] = 'Pozwala na zmienę stanu wiadomości';
$labels['longaclw'] = 'Pozwala zmieniać wszystkie flagi wiadomości, oprócz "Przeczytano" i "Usunięto"';
$labels['longacli'] = 'Pozwala zapisywać wiadomości i kopiować do folderu';
$labels['longaclp'] = 'Pozwala wysyłać wiadomości do folderu';
$labels['longaclc'] = 'Pozwala tworzyć (lub zmieniać nazwę) podfoldery';
$labels['longaclk'] = 'Pozwala tworzyć (lub zmieniać nazwę) podfoldery';
$labels['longacld'] = 'Pozwala zmianiać flagę "Usunięto" wiadomości';
$labels['longaclt'] = 'Pozwala zmianiać flagę "Usunięto" wiadomości';
$labels['longacle'] = 'Pozwala na usuwanie wiadomości oznaczonych do usunięcia';
$labels['longaclx'] = 'Pozwala na zmianę nazwy lub usunięcie folderu';
$labels['longacla'] = 'Pozwala na zmiane praw dostępu do folderu';
$labels['longaclfull'] = 'Pełna kontrola włącznie z administrowaniem folderem';
$labels['longaclread'] = 'Folder może być otwarty w trybie do odczytu';
$labels['longaclwrite'] = 'Wiadomości mogą być oznaczane, zapisywane i kopiowane do folderu';
$labels['longacldelete'] = 'Wiadomości mogą być usuwane';
$messages['deleting'] = 'Usuwanie praw dostępu...';
$messages['saving'] = 'Zapisywanie praw dostępu...';
$messages['updatesuccess'] = 'Pomyślnie zmieniono prawa dostępu';
$messages['deletesuccess'] = 'Pomyślnie usunięto prawa dostępu';
$messages['createsuccess'] = 'Pomyślnie dodano prawa dostępu';
$messages['updateerror'] = 'Nie udało się zmienić praw dostępu';
$messages['deleteerror'] = 'Nie udało się usunąć praw dostępu';
$messages['createerror'] = 'Nie udało się dodać praw dostępu';
$messages['deleteconfirm'] = 'Czy na pewno chcesz usunąć prawa wybranym użytkownikom?';
$messages['norights'] = 'Nie wybrano praw dostępu!';
$messages['nouser'] = 'Nie podano nazwy użytkownika!';
?>

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>acl</name>
<channel>pear.roundcube.net</channel>
<summary>Folders Access Control Lists</summary>
<description>IMAP Folders Access Control Lists Management (RFC4314, RFC2086).</description>
<lead>
<name>Aleksander Machniak</name>
<user>alec</user>
<email>alec@alec.pl</email>
<active>yes</active>
</lead>
<date>2012-06-28</date>
<version>
<release>0.9</release>
<api>0.7</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://www.gnu.org/licenses/gpl-2.0.html">GNU GPLv2</license>
<notes>-</notes>
<contents>
<dir baseinstalldir="/" name="/">
<file name="acl.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="acl.js" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="config.inc.php.dist" role="data"></file>
<file name="localization/de_DE.inc" role="data"></file>
<file name="localization/en_US.inc" role="data"></file>
<file name="localization/pl_PL.inc" role="data"></file>
<file name="skins/default/acl.css" role="data"></file>
<file name="skins/default/images/enabled.png" role="data"></file>
<file name="skins/default/images/partial.png" role="data"></file>
<file name="skins/default/templates/table.html" role="data"></file>
<file name="skins/larry/acl.css" role="data"></file>
<file name="skins/larry/images/enabled.png" role="data"></file>
<file name="skins/larry/images/partial.png" role="data"></file>
<file name="skins/larry/templates/table.html" role="data"></file>
</dir>
<!-- / -->
</contents>
<dependencies>
<required>
<php>
<min>5.2.1</min>
</php>
<pearinstaller>
<min>1.7.0</min>
</pearinstaller>
</required>
</dependencies>
<phprelease/>
</package>

@ -0,0 +1,100 @@
#aclmanager
{
position: relative;
border: 1px solid #999;
min-height: 302px;
}
#aclcontainer
{
overflow-x: auto;
}
#acltable
{
width: 100%;
border-collapse: collapse;
background-color: #F9F9F9;
}
#acltable td
{
width: 1%;
white-space: nowrap;
}
#acltable thead td
{
padding: 0 4px 0 2px;
}
#acltable tbody td
{
text-align: center;
padding: 2px;
border-bottom: 1px solid #999999;
cursor: default;
}
#acltable tbody td.user
{
width: 96%;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
#acltable tbody td.partial
{
background: url(images/partial.png) center no-repeat;
}
#acltable tbody td.enabled
{
background: url(images/enabled.png) center no-repeat;
}
#acltable tr.selected td
{
color: #FFFFFF;
background-color: #CC3333;
}
#acltable tr.unfocused td
{
color: #FFFFFF;
background-color: #929292;
}
#acladvswitch
{
position: absolute;
right: 4px;
text-align: right;
line-height: 22px;
}
#acladvswitch input
{
vertical-align: middle;
}
#acladvswitch span
{
display: block;
}
#aclform
{
top: 80px;
width: 480px;
padding: 10px;
}
#aclform div
{
padding: 0;
text-align: center;
clear: both;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

@ -0,0 +1,54 @@
<!--[if lte IE 6]>
<style type="text/css">
#aclmanager { height: expression(Math.min(302, parseInt(document.documentElement.clientHeight))+'px'); }
</style>
<![endif]-->
<div id="aclmanager">
<div id="aclcontainer" class="boxlistcontent" style="top:0">
<roundcube:object name="acltable" id="acltable" class="records-table" />
</div>
<div class="boxfooter">
<roundcube:button command="acl-create" id="aclcreatelink" type="link" title="acl.newuser" class="buttonPas addgroup" classAct="button addgroup" content=" " />
<roundcube:button name="aclmenulink" id="aclmenulink" type="link" title="acl.actions" class="button groupactions" onclick="show_aclmenu(); return false" content=" " />
<roundcube:if condition="!in_array('acl_advanced_mode', (array)config:dont_override)" />
<div id="acladvswitch" class="pagenav">
<span><label for="acl-switch"><roundcube:label name="acl.advanced" /></label>
<input type="checkbox" id="acl-switch" onclick="rcmail.command('acl-mode-switch')"<roundcube:exp expression="config:acl_advanced_mode == true ? ' checked=checked' : ''" /> />
</span>
</div>
<roundcube:endif />
</div>
</div>
<div id="aclmenu" class="popupmenu">
<ul>
<li><roundcube:button command="acl-edit" label="edit" classAct="active" /></li>
<li><roundcube:button command="acl-delete" label="delete" classAct="active" /></li>
</ul>
</div>
<div id="aclform" class="popupmenu">
<fieldset class="thinbordered"><legend><roundcube:label name="acl.identifier" /></legend>
<roundcube:object name="acluser" class="toolbarmenu" id="acluser" size="35" />
</fieldset>
<fieldset class="thinbordered"><legend><roundcube:label name="acl.myrights" /></legend>
<roundcube:object name="aclrights" class="toolbarmenu" />
</fieldset>
<div>
<roundcube:button command="acl-cancel" type="input" class="button" label="cancel" />
<roundcube:button command="acl-save" type="input" class="button mainaction" label="save" />
</div>
</div>
<script type="text/javascript">
function show_aclmenu()
{
if (!rcmail_ui) {
rcube_init_mail_ui();
rcmail_ui.popups.aclmenu = {id:'aclmenu', above:1, obj: $('#aclmenu')};
}
rcmail_ui.show_popup('aclmenu');
}
</script>

@ -0,0 +1,43 @@
<?php
/**
* Additional Message Headers
*
* Very simple plugin which will add additional headers
* to or remove them from outgoing messages.
*
* Enable the plugin in config/main.inc.php and add your desired headers:
* $rcmail_config['additional_message_headers'] = array('User-Agent');
*
* @version @package_version@
* @author Ziba Scott
* @website http://roundcube.net
*/
class additional_message_headers extends rcube_plugin
{
public $task = 'mail';
function init()
{
$this->add_hook('message_outgoing_headers', array($this, 'message_headers'));
}
function message_headers($args)
{
$this->load_config();
// additional email headers
$additional_headers = rcmail::get_instance()->config->get('additional_message_headers',array());
foreach($additional_headers as $header=>$value){
if (null === $value) {
unset($args['headers'][$header]);
} else {
$args['headers'][$header] = $value;
}
}
return $args;
}
}
?>

@ -0,0 +1,14 @@
<?php
// $rcmail_config['additional_message_headers']['X-Remote-Browser'] = $_SERVER['HTTP_USER_AGENT'];
// $rcmail_config['additional_message_headers']['X-Originating-IP'] = $_SERVER['REMOTE_ADDR'];
// $rcmail_config['additional_message_headers']['X-RoundCube-Server'] = $_SERVER['SERVER_ADDR'];
// if( isset( $_SERVER['MACHINE_NAME'] )) {
// $rcmail_config['additional_message_headers']['X-RoundCube-Server'] .= ' (' . $_SERVER['MACHINE_NAME'] . ')';
// }
// To remove (e.g. X-Sender) message header use null value
// $rcmail_config['additional_message_headers']['X-Sender'] = null;
?>

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.9.0" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>additional_message_headers</name>
<channel>pear.roundcube.net</channel>
<summary>Additional message headers for Roundcube</summary>
<description>Very simple plugin which will add additional headers to or remove them from outgoing messages.</description>
<lead>
<name>Ziba Scott</name>
<user>ziba</user>
<email>email@example.org</email>
<active>yes</active>
</lead>
<date>2010-01-16</date>
<time>18:19:33</time>
<version>
<release>1.1.0</release>
<api>1.1.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://www.gnu.org/licenses/gpl-2.0.html">GNU GPL v2</license>
<notes>-</notes>
<contents>
<dir baseinstalldir="/" name="/">
<file name="additional_message_headers.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info" />
<tasks:replace from="@package_version@" to="version" type="package-info" />
</file>
</dir> <!-- / -->
</contents>
<dependencies>
<required>
<php>
<min>5.2.1</min>
</php>
<pearinstaller>
<min>1.7.0</min>
</pearinstaller>
</required>
</dependencies>
<phprelease />
</package>

@ -0,0 +1,36 @@
/*
* Archive plugin script
* @version @package_version@
*/
function rcmail_archive(prop)
{
if (!rcmail.env.uid && (!rcmail.message_list || !rcmail.message_list.get_selection().length))
return;
if (rcmail.env.mailbox != rcmail.env.archive_folder)
rcmail.command('moveto', rcmail.env.archive_folder);
}
// callback for app-onload event
if (window.rcmail) {
rcmail.addEventListener('init', function(evt) {
// register command (directly enable in message view mode)
rcmail.register_command('plugin.archive', rcmail_archive, (rcmail.env.uid && rcmail.env.mailbox != rcmail.env.archive_folder));
// add event-listener to message list
if (rcmail.message_list)
rcmail.message_list.addEventListener('select', function(list){
rcmail.enable_command('plugin.archive', (list.get_selection().length > 0 && rcmail.env.mailbox != rcmail.env.archive_folder));
});
// set css style for archive folder
var li;
if (rcmail.env.archive_folder && rcmail.env.archive_folder_icon
&& (li = rcmail.get_folder_li(rcmail.env.archive_folder, '', true))
)
$(li).css('background-image', 'url(' + rcmail.env.archive_folder_icon + ')');
})
}

@ -0,0 +1,124 @@
<?php
/**
* Archive
*
* Plugin that adds a new button to the mailbox toolbar
* to move messages to a (user selectable) archive folder.
*
* @version @package_version@
* @author Andre Rodier, Thomas Bruederli
*/
class archive extends rcube_plugin
{
public $task = 'mail|settings';
function init()
{
$rcmail = rcmail::get_instance();
// There is no "Archived flags"
// $GLOBALS['IMAP_FLAGS']['ARCHIVED'] = 'Archive';
if ($rcmail->task == 'mail' && ($rcmail->action == '' || $rcmail->action == 'show')
&& ($archive_folder = $rcmail->config->get('archive_mbox'))) {
$skin_path = $this->local_skin_path();
$this->include_script('archive.js');
$this->add_texts('localization', true);
$this->add_button(
array(
'command' => 'plugin.archive',
'imagepas' => $skin_path.'/archive_pas.png',
'imageact' => $skin_path.'/archive_act.png',
'width' => 32,
'height' => 32,
'title' => 'buttontitle',
'domain' => $this->ID,
),
'toolbar');
// register hook to localize the archive folder
$this->add_hook('render_mailboxlist', array($this, 'render_mailboxlist'));
// set env variable for client
$rcmail->output->set_env('archive_folder', $archive_folder);
$rcmail->output->set_env('archive_folder_icon', $this->url($skin_path.'/foldericon.png'));
// add archive folder to the list of default mailboxes
if (($default_folders = $rcmail->config->get('default_imap_folders')) && !in_array($archive_folder, $default_folders)) {
$default_folders[] = $archive_folder;
$rcmail->config->set('default_imap_folders', $default_folders);
}
}
else if ($rcmail->task == 'settings') {
$dont_override = $rcmail->config->get('dont_override', array());
if (!in_array('archive_mbox', $dont_override)) {
$this->add_hook('preferences_list', array($this, 'prefs_table'));
$this->add_hook('preferences_save', array($this, 'save_prefs'));
}
}
}
function render_mailboxlist($p)
{
$rcmail = rcmail::get_instance();
$archive_folder = $rcmail->config->get('archive_mbox');
// set localized name for the configured archive folder
if ($archive_folder) {
if (isset($p['list'][$archive_folder]))
$p['list'][$archive_folder]['name'] = $this->gettext('archivefolder');
else // search in subfolders
$this->_mod_folder_name($p['list'], $archive_folder, $this->gettext('archivefolder'));
}
return $p;
}
function _mod_folder_name(&$list, $folder, $new_name)
{
foreach ($list as $idx => $item) {
if ($item['id'] == $folder) {
$list[$idx]['name'] = $new_name;
return true;
} else if (!empty($item['folders']))
if ($this->_mod_folder_name($list[$idx]['folders'], $folder, $new_name))
return true;
}
return false;
}
function prefs_table($args)
{
global $CURR_SECTION;
if ($args['section'] == 'folders') {
$this->add_texts('localization');
$rcmail = rcmail::get_instance();
// load folders list when needed
if ($CURR_SECTION)
$select = rcmail_mailbox_select(array('noselection' => '---', 'realnames' => true,
'maxlength' => 30, 'exceptions' => array('INBOX'), 'folder_filter' => 'mail', 'folder_rights' => 'w'));
else
$select = new html_select();
$args['blocks']['main']['options']['archive_mbox'] = array(
'title' => $this->gettext('archivefolder'),
'content' => $select->show($rcmail->config->get('archive_mbox'), array('name' => "_archive_mbox"))
);
}
return $args;
}
function save_prefs($args)
{
if ($args['section'] == 'folders') {
$args['prefs']['archive_mbox'] = get_input_value('_archive_mbox', RCUBE_INPUT_POST);
return $args;
}
}
}

@ -0,0 +1,25 @@
<?php
/*
+-----------------------------------------------------------------------+
| language/cs_CZ/labels.inc |
| |
| Language file of the Roundcube archive plugin |
| Copyright (C) 2005-2009, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
+-----------------------------------------------------------------------+
| Author: Milan Kozak <hodza@hodza.net> |
+-----------------------------------------------------------------------+
@version $Id: labels.inc 2993 2009-09-26 18:32:07Z alec $
*/
$labels = array();
$labels['buttontitle'] = 'Archivovat zprávu';
$labels['archived'] = 'Úspěšně vloženo do archivu';
$labels['archivefolder'] = 'Archiv';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Nachricht archivieren';
$labels['archived'] = 'Nachricht erfolgreich archiviert';
$labels['archivefolder'] = 'Archiv';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Nachricht archivieren';
$labels['archived'] = 'Nachricht erfolgreich archiviert';
$labels['archivefolder'] = 'Archiv';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Archive this message';
$labels['archived'] = 'Successfully archived';
$labels['archivefolder'] = 'Archive';
?>

@ -0,0 +1,10 @@
<?php
// MPBAUPGRADE
$labels = array();
$labels['buttontitle'] = 'Archivar este mensaje';
$labels['archived'] = 'Mensaje Archivado';
$labels['archivefolder'] = 'Archivo';
?>

@ -0,0 +1,10 @@
<?php
// MPBAUPGRADE
$labels = array();
$labels['buttontitle'] = 'Archivar este mensaje';
$labels['archived'] = 'Mensaje Archivado';
$labels['archivefolder'] = 'Archivo';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Arhiveeri see kiri';
$labels['archived'] = 'Edukalt arhiveeritud';
$labels['archivefolder'] = 'Arhiveeri';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Archiver ce message';
$labels['archived'] = 'Message archivé avec success';
$labels['archivefolder'] = 'Archive';
?>

@ -0,0 +1,10 @@
<?php
// MPBAUPGRADE
$labels = array();
$labels['buttontitle'] = 'Arquivar esta mensaxe';
$labels['archived'] = 'Aquivouse a mensaxe';
$labels['archivefolder'] = 'Arquivo';
?>

@ -0,0 +1,10 @@
<?php
// EN-Revision: 3891
$labels = array();
$labels['buttontitle'] = 'このメッセージのアーカイブ';
$labels['archived'] = 'アーカイブに成功しました。';
$labels['archivefolder'] = 'アーカイブ';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Archiveer dit bericht';
$labels['archived'] = 'Succesvol gearchiveerd';
$labels['archivefolder'] = 'Archief';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Przenieś do archiwum';
$labels['archived'] = 'Pomyślnie zarchiwizowano';
$labels['archivefolder'] = 'Archiwum';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Arquivar esta mensagem';
$labels['archived'] = 'Arquivada com sucesso';
$labels['archivefolder'] = 'Arquivo';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Переместить выбранное в архив';
$labels['archived'] = 'Перенесено в Архив';
$labels['archivefolder'] = 'Архив';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Arkivera meddelande';
$labels['archived'] = 'Meddelandet är arkiverat';
$labels['archivefolder'] = 'Arkiv';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = '封存此信件';
$labels['archived'] = '已成功封存';
$labels['archivefolder'] = '封存';
?>

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>archive</name>
<channel>pear.roundcube.net</channel>
<summary>Archive feature for Roundcube</summary>
<description>This adds a button to move the selected messages to an archive folder. The folder can be selected in the settings panel.</description>
<lead>
<name>Thomas Bruederli</name>
<user>thomasb</user>
<email>roundcube@gmail.com</email>
<active>yes</active>
</lead>
<date>2011-11-23</date>
<version>
<release>1.5</release>
<api>1.4</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://www.gnu.org/licenses/gpl-2.0.html">GNU GPLv2</license>
<notes>-</notes>
<contents>
<dir baseinstalldir="/" name="/">
<file name="archive.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="archive.js" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="localization/cs_CZ.inc" role="data"></file>
<file name="localization/de_CH.inc" role="data"></file>
<file name="localization/de_DE.inc" role="data"></file>
<file name="localization/en_US.inc" role="data"></file>
<file name="localization/es_AR.inc" role="data"></file>
<file name="localization/es_ES.inc" role="data"></file>
<file name="localization/et_EE.inc" role="data"></file>
<file name="localization/fr_FR.inc" role="data"></file>
<file name="localization/gl_ES.inc" role="data"></file>
<file name="localization/ja_JP.inc" role="data"></file>
<file name="localization/nl_NL.inc" role="data"></file>
<file name="localization/pl_PL.inc" role="data"></file>
<file name="localization/pt_BR.inc" role="data"></file>
<file name="localization/ru_RU.inc" role="data"></file>
<file name="localization/sv_SE.inc" role="data"></file>
<file name="localization/zh_TW.inc" role="data"></file>
<file name="skins/default/archive_act.png" role="data"></file>
<file name="skins/default/archive_pas.png" role="data"></file>
<file name="skins/default/foldericon.png" role="data"></file>
</dir>
<!-- / -->
</contents>
<dependencies>
<required>
<php>
<min>5.2.1</min>
</php>
<pearinstaller>
<min>1.7.0</min>
</pearinstaller>
</required>
</dependencies>
<phprelease/>
</package>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -0,0 +1,47 @@
<?php
/**
* Sample plugin to try out some hooks.
* This performs an automatic login if accessed from localhost
*/
class autologon extends rcube_plugin
{
public $task = 'login';
function init()
{
$this->add_hook('startup', array($this, 'startup'));
$this->add_hook('authenticate', array($this, 'authenticate'));
}
function startup($args)
{
$rcmail = rcmail::get_instance();
// change action to login
if (empty($_SESSION['user_id']) && !empty($_GET['_autologin']) && $this->is_localhost())
$args['action'] = 'login';
return $args;
}
function authenticate($args)
{
if (!empty($_GET['_autologin']) && $this->is_localhost()) {
$args['user'] = 'me';
$args['pass'] = '******';
$args['host'] = 'localhost';
$args['cookiecheck'] = false;
$args['valid'] = true;
}
return $args;
}
function is_localhost()
{
return $_SERVER['REMOTE_ADDR'] == '::1' || $_SERVER['REMOTE_ADDR'] == '127.0.0.1';
}
}

@ -0,0 +1,168 @@
<?php
/**
* Filesystem Attachments
*
* This plugin which provides database backed storage for temporary
* attachment file handling. The primary advantage of this plugin
* is its compatibility with round-robin dns multi-server roundcube
* installations.
*
* This plugin relies on the core filesystem_attachments plugin
*
* @author Ziba Scott <ziba@umich.edu>
*
*/
require_once('plugins/filesystem_attachments/filesystem_attachments.php');
class database_attachments extends filesystem_attachments
{
// A prefix for the cache key used in the session and in the key field of the cache table
private $cache_prefix = "db_attach";
/**
* Helper method to generate a unique key for the given attachment file
*/
private function _key($args)
{
$uname = $args['path'] ? $args['path'] : $args['name'];
return $this->cache_prefix . $args['group'] . md5(mktime() . $uname . $_SESSION['user_id']);
}
/**
* Save a newly uploaded attachment
*/
function upload($args)
{
$args['status'] = false;
$rcmail = rcmail::get_instance();
$key = $this->_key($args);
$data = file_get_contents($args['path']);
if ($data === false)
return $args;
$data = base64_encode($data);
$status = $rcmail->db->query(
"INSERT INTO ".get_table_name('cache')."
(created, user_id, cache_key, data)
VALUES (".$rcmail->db->now().", ?, ?, ?)",
$_SESSION['user_id'],
$key,
$data);
if ($status) {
$args['id'] = $key;
$args['status'] = true;
unset($args['path']);
}
return $args;
}
/**
* Save an attachment from a non-upload source (draft or forward)
*/
function save($args)
{
$args['status'] = false;
$rcmail = rcmail::get_instance();
$key = $this->_key($args);
if ($args['path']) {
$args['data'] = file_get_contents($args['path']);
if ($args['data'] === false)
return $args;
}
$data = base64_encode($args['data']);
$status = $rcmail->db->query(
"INSERT INTO ".get_table_name('cache')."
(created, user_id, cache_key, data)
VALUES (".$rcmail->db->now().", ?, ?, ?)",
$_SESSION['user_id'],
$key,
$data);
if ($status) {
$args['id'] = $key;
$args['status'] = true;
}
return $args;
}
/**
* Remove an attachment from storage
* This is triggered by the remove attachment button on the compose screen
*/
function remove($args)
{
$args['status'] = false;
$rcmail = rcmail::get_instance();
$status = $rcmail->db->query(
"DELETE FROM ".get_table_name('cache')."
WHERE user_id=?
AND cache_key=?",
$_SESSION['user_id'],
$args['id']);
if ($status) {
$args['status'] = true;
}
return $args;
}
/**
* When composing an html message, image attachments may be shown
* For this plugin, $this->get() will check the file and
* return it's contents
*/
function display($args)
{
return $this->get($args);
}
/**
* When displaying or sending the attachment the file contents are fetched
* using this method. This is also called by the attachment_display hook.
*/
function get($args)
{
$rcmail = rcmail::get_instance();
$sql_result = $rcmail->db->query(
"SELECT cache_id, data
FROM ".get_table_name('cache')."
WHERE user_id=?
AND cache_key=?",
$_SESSION['user_id'],
$args['id']);
if ($sql_arr = $rcmail->db->fetch_assoc($sql_result)) {
$args['data'] = base64_decode($sql_arr['data']);
$args['status'] = true;
}
return $args;
}
/**
* Delete all temp files associated with this user
*/
function cleanup($args)
{
$prefix = $this->cache_prefix . $args['group'];
$rcmail = rcmail::get_instance();
$rcmail->db->query(
"DELETE FROM ".get_table_name('cache')."
WHERE user_id=?
AND cache_key like '{$prefix}%'",
$_SESSION['user_id']);
}
}

@ -0,0 +1,146 @@
<?php
/**
* Debug Logger
*
* Enhanced logging for debugging purposes. It is not recommened
* to be enabled on production systems without testing because of
* the somewhat increased memory, cpu and disk i/o overhead.
*
* Debug Logger listens for existing console("message") calls and
* introduces start and end tags as well as free form tagging
* which can redirect messages to files. The resulting log files
* provide timing and tag quantity results.
*
* Enable the plugin in config/main.inc.php and add your desired
* log types and files.
*
* @version 1.0
* @author Ziba Scott
* @website http://roundcube.net
*
* Example:
*
* config/main.inc.php:
*
* // $rcmail_config['debug_logger'][type of logging] = name of file in log_dir
* // The 'master' log includes timing information
* $rcmail_config['debug_logger']['master'] = 'master';
* // If you want sql messages to also go into a separate file
* $rcmail_config['debug_logger']['sql'] = 'sql';
*
* index.php (just after $RCMAIL->plugins->init()):
*
* console("my test","start");
* console("my message");
* console("my sql calls","start");
* console("cp -r * /dev/null","shell exec");
* console("select * from example","sql");
* console("select * from example","sql");
* console("select * from example","sql");
* console("end");
* console("end");
*
*
* logs/master (after reloading the main page):
*
* [17-Feb-2009 16:51:37 -0500] start: Task: mail.
* [17-Feb-2009 16:51:37 -0500] start: my test
* [17-Feb-2009 16:51:37 -0500] my message
* [17-Feb-2009 16:51:37 -0500] shell exec: cp -r * /dev/null
* [17-Feb-2009 16:51:37 -0500] start: my sql calls
* [17-Feb-2009 16:51:37 -0500] sql: select * from example
* [17-Feb-2009 16:51:37 -0500] sql: select * from example
* [17-Feb-2009 16:51:37 -0500] sql: select * from example
* [17-Feb-2009 16:51:37 -0500] end: my sql calls - 0.0018 seconds shell exec: 1, sql: 3,
* [17-Feb-2009 16:51:37 -0500] end: my test - 0.0055 seconds shell exec: 1, sql: 3,
* [17-Feb-2009 16:51:38 -0500] end: Task: mail. - 0.8854 seconds shell exec: 1, sql: 3,
*
* logs/sql (after reloading the main page):
*
* [17-Feb-2009 16:51:37 -0500] sql: select * from example
* [17-Feb-2009 16:51:37 -0500] sql: select * from example
* [17-Feb-2009 16:51:37 -0500] sql: select * from example
*/
class debug_logger extends rcube_plugin
{
function init()
{
require_once(dirname(__FILE__).'/runlog/runlog.php');
$this->runlog = new runlog();
if(!rcmail::get_instance()->config->get('log_dir')){
rcmail::get_instance()->config->set('log_dir',INSTALL_PATH.'logs');
}
$log_config = rcmail::get_instance()->config->get('debug_logger',array());
foreach($log_config as $type=>$file){
$this->runlog->set_file(rcmail::get_instance()->config->get('log_dir').'/'.$file, $type);
}
$start_string = "";
$action = rcmail::get_instance()->action;
$task = rcmail::get_instance()->task;
if($action){
$start_string .= "Action: ".$action.". ";
}
if($task){
$start_string .= "Task: ".$task.". ";
}
$this->runlog->start($start_string);
$this->add_hook('console', array($this, 'console'));
$this->add_hook('authenticate', array($this, 'authenticate'));
}
function authenticate($args){
$this->runlog->note('Authenticating '.$args['user'].'@'.$args['host']);
return $args;
}
function console($args){
$note = $args[0];
$type = $args[1];
if(!isset($args[1])){
// This could be extended to detect types based on the
// file which called console. For now only rcube_imap.inc is supported
$bt = debug_backtrace();
$file = $bt[3]['file'];
switch(basename($file)){
case 'rcube_imap.php':
$type = 'imap';
break;
default:
$type = FALSE;
break;
}
}
switch($note){
case 'end':
$type = 'end';
break;
}
switch($type){
case 'start':
$this->runlog->start($note);
break;
case 'end':
$this->runlog->end();
break;
default:
$this->runlog->note($note, $type);
break;
}
return $args;
}
function __destruct(){
$this->runlog->end();
}
}
?>

@ -0,0 +1,227 @@
<?php
/**
* runlog
*
* @author Ziba Scott <ziba@umich.edu>
*/
class runlog {
private $start_time = FALSE;
private $parent_stack = array();
public $print_to_console = FALSE;
private $file_handles = array();
private $indent = 0;
public $threshold = 0;
public $tag_count = array();
public $timestamp = "d-M-Y H:i:s O";
public $max_line_size = 150;
private $run_log = array();
function runlog()
{
$this->start_time = microtime( TRUE );
}
public function start( $name, $tag = FALSE )
{
$this->run_log[] = array( 'type' => 'start',
'tag' => $tag,
'index' => count($this->run_log),
'value' => $name,
'time' => microtime( TRUE ),
'parents' => $this->parent_stack,
'ended' => false,
);
$this->parent_stack[] = $name;
$this->print_to_console("start: ".$name, $tag, 'start');
$this->print_to_file("start: ".$name, $tag, 'start');
$this->indent++;
}
public function end()
{
$name = array_pop( $this->parent_stack );
foreach ( $this->run_log as $k => $entry ) {
if ( $entry['value'] == $name && $entry['type'] == 'start' && $entry['ended'] == false) {
$lastk = $k;
}
}
$start = $this->run_log[$lastk]['time'];
$this->run_log[$lastk]['duration'] = microtime( TRUE ) - $start;
$this->run_log[$lastk]['ended'] = true;
$this->run_log[] = array( 'type' => 'end',
'tag' => $this->run_log[$lastk]['tag'],
'index' => $lastk,
'value' => $name,
'time' => microtime( TRUE ),
'duration' => microtime( TRUE ) - $start,
'parents' => $this->parent_stack,
);
$this->indent--;
if($this->run_log[$lastk]['duration'] >= $this->threshold){
$tag_report = "";
foreach($this->tag_count as $tag=>$count){
$tag_report .= "$tag: $count, ";
}
if(!empty($tag_report)){
// $tag_report = "\n$tag_report\n";
}
$end_txt = sprintf("end: $name - %0.4f seconds $tag_report", $this->run_log[$lastk]['duration'] );
$this->print_to_console($end_txt, $this->run_log[$lastk]['tag'] , 'end');
$this->print_to_file($end_txt, $this->run_log[$lastk]['tag'], 'end');
}
}
public function increase_tag_count($tag){
if(!isset($this->tag_count[$tag])){
$this->tag_count[$tag] = 0;
}
$this->tag_count[$tag]++;
}
public function get_text(){
$text = "";
foreach($this->run_log as $entry){
$text .= str_repeat(" ",count($entry['parents']));
if($entry['tag'] != 'text'){
$text .= $entry['tag'].': ';
}
$text .= $entry['value'];
if($entry['tag'] == 'end'){
$text .= sprintf(" - %0.4f seconds", $entry['duration'] );
}
$text .= "\n";
}
return $text;
}
public function set_file($filename, $tag = 'master'){
if(!isset($this->file_handle[$tag])){
$this->file_handles[$tag] = fopen($filename, 'a');
if(!$this->file_handles[$tag]){
trigger_error('Could not open file for writing: '.$filename);
}
}
}
public function note( $msg, $tag = FALSE )
{
if($tag){
$this->increase_tag_count($tag);
}
if ( is_array( $msg )) {
$msg = '<pre>' . print_r( $msg, TRUE ) . '</pre>';
}
$this->debug_messages[] = $msg;
$this->run_log[] = array( 'type' => 'note',
'tag' => $tag ? $tag:"text",
'value' => htmlentities($msg),
'time' => microtime( TRUE ),
'parents' => $this->parent_stack,
);
$this->print_to_file($msg, $tag);
$this->print_to_console($msg, $tag);
}
public function print_to_file($msg, $tag = FALSE, $type = FALSE){
if(!$tag){
$file_handle_tag = 'master';
}
else{
$file_handle_tag = $tag;
}
if($file_handle_tag != 'master' && isset($this->file_handles[$file_handle_tag])){
$buffer = $this->get_indent();
$buffer .= "$msg\n";
if(!empty($this->timestamp)){
$buffer = sprintf("[%s] %s",date($this->timestamp, mktime()), $buffer);
}
fwrite($this->file_handles[$file_handle_tag], wordwrap($buffer, $this->max_line_size, "\n "));
}
if(isset($this->file_handles['master']) && $this->file_handles['master']){
$buffer = $this->get_indent();
if($tag){
$buffer .= "$tag: ";
}
$msg = str_replace("\n","",$msg);
$buffer .= "$msg";
if(!empty($this->timestamp)){
$buffer = sprintf("[%s] %s",date($this->timestamp, mktime()), $buffer);
}
if(strlen($buffer) > $this->max_line_size){
$buffer = substr($buffer,0,$this->max_line_size - 3)."...";
}
fwrite($this->file_handles['master'], $buffer."\n");
}
}
public function print_to_console($msg, $tag=FALSE){
if($this->print_to_console){
if(is_array($this->print_to_console)){
if(in_array($tag, $this->print_to_console)){
echo $this->get_indent();
if($tag){
echo "$tag: ";
}
echo "$msg\n";
}
}
else{
echo $this->get_indent();
if($tag){
echo "$tag: ";
}
echo "$msg\n";
}
}
}
public function print_totals(){
$totals = array();
foreach ( $this->run_log as $k => $entry ) {
if ( $entry['type'] == 'start' && $entry['ended'] == true) {
$totals[$entry['value']]['duration'] += $entry['duration'];
$totals[$entry['value']]['count'] += 1;
}
}
if($this->file_handle){
foreach($totals as $name=>$details){
fwrite($this->file_handle,$name.": ".number_format($details['duration'],4)."sec, ".$details['count']." calls \n");
}
}
}
private function get_indent(){
$buf = "";
for($i = 0; $i < $this->indent; $i++){
$buf .= " ";
}
return $buf;
}
function __destruct(){
foreach($this->file_handles as $handle){
fclose($handle);
}
}
}
?>

@ -0,0 +1,77 @@
<?php
/**
* Display Emoticons
*
* Sample plugin to replace emoticons in plain text message body with real icons
*
* @version 1.3
* @author Thomas Bruederli
* @author Aleksander Machniak
* @website http://roundcube.net
*/
class emoticons extends rcube_plugin
{
public $task = 'mail';
function init()
{
$this->add_hook('message_part_after', array($this, 'replace'));
}
function replace($args)
{
// This is a lookbehind assertion which will exclude html entities
// E.g. situation when ";)" in "&quot;)" shouldn't be replaced by the icon
// It's so long because of assertion format restrictions
$entity = '(?<!&'
. '[a-zA-Z0-9]{2}' . '|' . '#[0-9]{2}' . '|'
. '[a-zA-Z0-9]{3}' . '|' . '#[0-9]{3}' . '|'
. '[a-zA-Z0-9]{4}' . '|' . '#[0-9]{4}' . '|'
. '[a-zA-Z0-9]{5}' . '|'
. '[a-zA-Z0-9]{6}' . '|'
. '[a-zA-Z0-9]{7}'
. ')';
// map of emoticon replacements
$map = array(
'/:\)/' => $this->img_tag('smiley-smile.gif', ':)' ),
'/:-\)/' => $this->img_tag('smiley-smile.gif', ':-)' ),
'/(?<!mailto):D/' => $this->img_tag('smiley-laughing.gif', ':D' ),
'/:-D/' => $this->img_tag('smiley-laughing.gif', ':-D' ),
'/:\(/' => $this->img_tag('smiley-frown.gif', ':(' ),
'/:-\(/' => $this->img_tag('smiley-frown.gif', ':-(' ),
'/'.$entity.';\)/' => $this->img_tag('smiley-wink.gif', ';)' ),
'/'.$entity.';-\)/' => $this->img_tag('smiley-wink.gif', ';-)' ),
'/8\)/' => $this->img_tag('smiley-cool.gif', '8)' ),
'/8-\)/' => $this->img_tag('smiley-cool.gif', '8-)' ),
'/(?<!mailto):O/i' => $this->img_tag('smiley-surprised.gif', ':O' ),
'/(?<!mailto):-O/i' => $this->img_tag('smiley-surprised.gif', ':-O' ),
'/(?<!mailto):P/i' => $this->img_tag('smiley-tongue-out.gif', ':P' ),
'/(?<!mailto):-P/i' => $this->img_tag('smiley-tongue-out.gif', ':-P' ),
'/(?<!mailto):@/i' => $this->img_tag('smiley-yell.gif', ':@' ),
'/(?<!mailto):-@/i' => $this->img_tag('smiley-yell.gif', ':-@' ),
'/O:\)/i' => $this->img_tag('smiley-innocent.gif', 'O:)' ),
'/O:-\)/i' => $this->img_tag('smiley-innocent.gif', 'O:-)' ),
'/(?<!mailto):$/' => $this->img_tag('smiley-embarassed.gif', ':$' ),
'/(?<!mailto):-$/' => $this->img_tag('smiley-embarassed.gif', ':-$' ),
'/(?<!mailto):\*/i' => $this->img_tag('smiley-kiss.gif', ':*' ),
'/(?<!mailto):-\*/i' => $this->img_tag('smiley-kiss.gif', ':-*' ),
'/(?<!mailto):S/i' => $this->img_tag('smiley-undecided.gif', ':S' ),
'/(?<!mailto):-S/i' => $this->img_tag('smiley-undecided.gif', ':-S' ),
);
if ($args['type'] == 'plain') {
$args['body'] = preg_replace(
array_keys($map), array_values($map), $args['body']);
}
return $args;
}
private function img_tag($ico, $title)
{
$path = './program/js/tiny_mce/plugins/emotions/img/';
return html::img(array('src' => $path.$ico, 'title' => $title));
}
}

@ -0,0 +1,35 @@
------------------------------------------------------------------
THIS IS NOT EVEN AN "ALPHA" STATE. USE ONLY FOR DEVELOPMENT!!!!!!!
------------------------------------------------------------------
WARNING: Don't use with gnupg-2.x!
Enigma Plugin Status:
* DONE:
- PGP signed messages verification
- Handling of PGP keys files attached to incoming messages
- PGP encrypted messages decryption (started)
- PGP keys management UI (started)
* TODO (must have):
- Parsing of decrypted messages into array (see rcube_mime_struct) and then into rcube_message_part structure
(create core class rcube_mime_parser or take over PEAR::Mail_mimeDecode package and improve it)
- Sending encrypted/signed messages (probably some changes in core will be needed)
- Per-Identity settings (including keys/certs)
- Handling big messages with temp files (including changes in Roundcube core)
- Performance improvements (some caching, code review)
- better (and more) icons
* TODO (later):
- Keys generation
- Certs generation
- Keys/Certs info in Contacts details page (+ split Contact details page into tabs)
- Key server support
- S/MIME signed messages verification
- S/MIME encrypted messages decryption
- Handling of S/MIME certs files attached to incoming messages
- SSL (S/MIME) Certs management

@ -0,0 +1,14 @@
<?php
// Enigma Plugin options
// --------------------
// A driver to use for PGP. Default: "gnupg".
$rcmail_config['enigma_pgp_driver'] = 'gnupg';
// A driver to use for S/MIME. Default: "phpssl".
$rcmail_config['enigma_smime_driver'] = 'phpssl';
// Keys directory for all users. Default 'enigma/home'.
// Must be writeable by PHP process
$rcmail_config['enigma_pgp_homedir'] = null;

@ -0,0 +1,206 @@
/* Enigma Plugin */
if (window.rcmail)
{
rcmail.addEventListener('init', function(evt)
{
if (rcmail.env.task == 'settings') {
rcmail.register_command('plugin.enigma', function() { rcmail.goto_url('plugin.enigma') }, true);
rcmail.register_command('plugin.enigma-key-import', function() { rcmail.enigma_key_import() }, true);
rcmail.register_command('plugin.enigma-key-export', function() { rcmail.enigma_key_export() }, true);
if (rcmail.gui_objects.keyslist)
{
var p = rcmail;
rcmail.keys_list = new rcube_list_widget(rcmail.gui_objects.keyslist,
{multiselect:false, draggable:false, keyboard:false});
rcmail.keys_list.addEventListener('select', function(o){ p.enigma_key_select(o); });
rcmail.keys_list.init();
rcmail.keys_list.focus();
rcmail.enigma_list();
rcmail.register_command('firstpage', function(props) {return rcmail.enigma_list_page('first'); });
rcmail.register_command('previouspage', function(props) {return rcmail.enigma_list_page('previous'); });
rcmail.register_command('nextpage', function(props) {return rcmail.enigma_list_page('next'); });
rcmail.register_command('lastpage', function(props) {return rcmail.enigma_list_page('last'); });
}
if (rcmail.env.action == 'edit-prefs') {
rcmail.register_command('search', function(props) {return rcmail.enigma_search(props); }, true);
rcmail.register_command('reset-search', function(props) {return rcmail.enigma_search_reset(props); }, true);
}
else if (rcmail.env.action == 'plugin.enigma') {
rcmail.register_command('plugin.enigma-import', function() { rcmail.enigma_import() }, true);
rcmail.register_command('plugin.enigma-export', function() { rcmail.enigma_export() }, true);
}
}
});
}
/*********************************************************/
/********* Enigma Settings/Keys/Certs UI *********/
/*********************************************************/
// Display key(s) import form
rcube_webmail.prototype.enigma_key_import = function()
{
this.enigma_loadframe(null, '&_a=keyimport');
};
// Submit key(s) form
rcube_webmail.prototype.enigma_import = function()
{
var form, file;
if (form = this.gui_objects.importform) {
file = document.getElementById('rcmimportfile');
if (file && !file.value) {
alert(this.get_label('selectimportfile'));
return;
}
form.submit();
this.set_busy(true, 'importwait');
this.lock_form(form, true);
}
};
// list row selection handler
rcube_webmail.prototype.enigma_key_select = function(list)
{
var id;
if (id = list.get_single_selection())
this.enigma_loadframe(id);
};
// load key frame
rcube_webmail.prototype.enigma_loadframe = function(id, url)
{
var frm, win;
if (this.env.contentframe && window.frames && (frm = window.frames[this.env.contentframe])) {
if (!id && !url && (win = window.frames[this.env.contentframe])) {
if (win.location && win.location.href.indexOf(this.env.blankpage)<0)
win.location.href = this.env.blankpage;
return;
}
this.set_busy(true);
if (!url)
url = '&_a=keyinfo&_id='+id;
frm.location.href = this.env.comm_path+'&_action=plugin.enigma&_framed=1' + url;
}
};
// Search keys/certs
rcube_webmail.prototype.enigma_search = function(props)
{
if (!props && this.gui_objects.qsearchbox)
props = this.gui_objects.qsearchbox.value;
if (props || this.env.search_request) {
var params = {'_a': 'keysearch', '_q': urlencode(props)},
lock = this.set_busy(true, 'searching');
// if (this.gui_objects.search_filter)
// addurl += '&_filter=' + this.gui_objects.search_filter.value;
this.env.current_page = 1;
this.enigma_loadframe();
this.enigma_clear_list();
this.http_post('plugin.enigma', params, lock);
}
return false;
}
// Reset search filter and the list
rcube_webmail.prototype.enigma_search_reset = function(props)
{
var s = this.env.search_request;
this.reset_qsearch();
if (s) {
this.enigma_loadframe();
this.enigma_clear_list();
// refresh the list
this.enigma_list();
}
return false;
}
// Keys/certs listing
rcube_webmail.prototype.enigma_list = function(page)
{
var params = {'_a': 'keylist'},
lock = this.set_busy(true, 'loading');
this.env.current_page = page ? page : 1;
if (this.env.search_request)
params._q = this.env.search_request;
if (page)
params._p = page;
this.enigma_clear_list();
this.http_post('plugin.enigma', params, lock);
}
// Change list page
rcube_webmail.prototype.enigma_list_page = function(page)
{
if (page == 'next')
page = this.env.current_page + 1;
else if (page == 'last')
page = this.env.pagecount;
else if (page == 'prev' && this.env.current_page > 1)
page = this.env.current_page - 1;
else if (page == 'first' && this.env.current_page > 1)
page = 1;
this.enigma_list(page);
}
// Remove list rows
rcube_webmail.prototype.enigma_clear_list = function()
{
this.enigma_loadframe();
if (this.keys_list)
this.keys_list.clear(true);
}
// Adds a row to the list
rcube_webmail.prototype.enigma_add_list_row = function(r)
{
if (!this.gui_objects.keyslist || !this.keys_list)
return false;
var list = this.keys_list,
tbody = this.gui_objects.keyslist.tBodies[0],
rowcount = tbody.rows.length,
even = rowcount%2,
css_class = 'message'
+ (even ? ' even' : ' odd'),
// for performance use DOM instead of jQuery here
row = document.createElement('tr'),
col = document.createElement('td');
row.id = 'rcmrow' + r.id;
row.className = css_class;
col.innerHTML = r.name;
row.appendChild(col);
list.insert_row(row);
}
/*********************************************************/
/********* Enigma Message methods *********/
/*********************************************************/
// Import attached keys/certs file
rcube_webmail.prototype.enigma_import_attachment = function(mime_id)
{
var lock = this.set_busy(true, 'loading');
this.http_post('plugin.enigmaimport', '_uid='+this.env.uid+'&_mbox='
+urlencode(this.env.mailbox)+'&_part='+urlencode(mime_id), lock);
return false;
};

@ -0,0 +1,475 @@
<?php
/*
+-------------------------------------------------------------------------+
| Enigma Plugin for Roundcube |
| Version 0.1 |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program 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; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
/*
This class contains only hooks and action handlers.
Most plugin logic is placed in enigma_engine and enigma_ui classes.
*/
class enigma extends rcube_plugin
{
public $task = 'mail|settings';
public $rc;
public $engine;
private $env_loaded;
private $message;
private $keys_parts = array();
private $keys_bodies = array();
/**
* Plugin initialization.
*/
function init()
{
$rcmail = rcmail::get_instance();
$this->rc = $rcmail;
if ($this->rc->task == 'mail') {
// message parse/display hooks
$this->add_hook('message_part_structure', array($this, 'parse_structure'));
$this->add_hook('message_body_prefix', array($this, 'status_message'));
// message displaying
if ($rcmail->action == 'show' || $rcmail->action == 'preview') {
$this->add_hook('message_load', array($this, 'message_load'));
$this->add_hook('template_object_messagebody', array($this, 'message_output'));
$this->register_action('plugin.enigmaimport', array($this, 'import_file'));
}
// message composing
else if ($rcmail->action == 'compose') {
$this->load_ui();
$this->ui->init($section);
}
// message sending (and draft storing)
else if ($rcmail->action == 'sendmail') {
//$this->add_hook('outgoing_message_body', array($this, 'msg_encode'));
//$this->add_hook('outgoing_message_body', array($this, 'msg_sign'));
}
}
else if ($this->rc->task == 'settings') {
// add hooks for Enigma settings
$this->add_hook('preferences_sections_list', array($this, 'preferences_section'));
$this->add_hook('preferences_list', array($this, 'preferences_list'));
$this->add_hook('preferences_save', array($this, 'preferences_save'));
// register handler for keys/certs management
$this->register_action('plugin.enigma', array($this, 'preferences_ui'));
// grab keys/certs management iframe requests
$section = get_input_value('_section', RCUBE_INPUT_GET);
if ($this->rc->action == 'edit-prefs' && preg_match('/^enigma(certs|keys)/', $section)) {
$this->load_ui();
$this->ui->init($section);
}
}
}
/**
* Plugin environment initialization.
*/
function load_env()
{
if ($this->env_loaded)
return;
$this->env_loaded = true;
// Add include path for Enigma classes and drivers
$include_path = $this->home . '/lib' . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
set_include_path($include_path);
// load the Enigma plugin configuration
$this->load_config();
// include localization (if wasn't included before)
$this->add_texts('localization/');
}
/**
* Plugin UI initialization.
*/
function load_ui()
{
if ($this->ui)
return;
// load config/localization
$this->load_env();
// Load UI
$this->ui = new enigma_ui($this, $this->home);
}
/**
* Plugin engine initialization.
*/
function load_engine()
{
if ($this->engine)
return;
// load config/localization
$this->load_env();
$this->engine = new enigma_engine($this);
}
/**
* Handler for message_part_structure hook.
* Called for every part of the message.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function parse_structure($p)
{
$struct = $p['structure'];
if ($p['mimetype'] == 'text/plain' || $p['mimetype'] == 'application/pgp') {
$this->parse_plain($p);
}
else if ($p['mimetype'] == 'multipart/signed') {
$this->parse_signed($p);
}
else if ($p['mimetype'] == 'multipart/encrypted') {
$this->parse_encrypted($p);
}
else if ($p['mimetype'] == 'application/pkcs7-mime') {
$this->parse_encrypted($p);
}
return $p;
}
/**
* Handler for preferences_sections_list hook.
* Adds Enigma settings sections into preferences sections list.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function preferences_section($p)
{
// add labels
$this->add_texts('localization/');
$p['list']['enigmasettings'] = array(
'id' => 'enigmasettings', 'section' => $this->gettext('enigmasettings'),
);
$p['list']['enigmacerts'] = array(
'id' => 'enigmacerts', 'section' => $this->gettext('enigmacerts'),
);
$p['list']['enigmakeys'] = array(
'id' => 'enigmakeys', 'section' => $this->gettext('enigmakeys'),
);
return $p;
}
/**
* Handler for preferences_list hook.
* Adds options blocks into Enigma settings sections in Preferences.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function preferences_list($p)
{
if ($p['section'] == 'enigmasettings') {
// This makes that section is not removed from the list
$p['blocks']['dummy']['options']['dummy'] = array();
}
else if ($p['section'] == 'enigmacerts') {
// This makes that section is not removed from the list
$p['blocks']['dummy']['options']['dummy'] = array();
}
else if ($p['section'] == 'enigmakeys') {
// This makes that section is not removed from the list
$p['blocks']['dummy']['options']['dummy'] = array();
}
return $p;
}
/**
* Handler for preferences_save hook.
* Executed on Enigma settings form submit.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function preferences_save($p)
{
if ($p['section'] == 'enigmasettings') {
$a['prefs'] = array(
// 'dummy' => get_input_value('_dummy', RCUBE_INPUT_POST),
);
}
return $p;
}
/**
* Handler for keys/certs management UI template.
*/
function preferences_ui()
{
$this->load_ui();
$this->ui->init();
}
/**
* Handler for message_body_prefix hook.
* Called for every displayed (content) part of the message.
* Adds infobox about signature verification and/or decryption
* status above the body.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function status_message($p)
{
$part_id = $p['part']->mime_id;
// skip: not a message part
if ($p['part'] instanceof rcube_message)
return $p;
// skip: message has no signed/encoded content
if (!$this->engine)
return $p;
// Decryption status
if (isset($this->engine->decryptions[$part_id])) {
// get decryption status
$status = $this->engine->decryptions[$part_id];
// Load UI and add css script
$this->load_ui();
$this->ui->add_css();
// display status info
$attrib['id'] = 'enigma-message';
if ($status instanceof enigma_error) {
$attrib['class'] = 'enigmaerror';
$code = $status->getCode();
if ($code == enigma_error::E_KEYNOTFOUND)
$msg = Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')),
$this->gettext('decryptnokey')));
else if ($code == enigma_error::E_BADPASS)
$msg = Q($this->gettext('decryptbadpass'));
else
$msg = Q($this->gettext('decrypterror'));
}
else {
$attrib['class'] = 'enigmanotice';
$msg = Q($this->gettext('decryptok'));
}
$p['prefix'] .= html::div($attrib, $msg);
}
// Signature verification status
if (isset($this->engine->signed_parts[$part_id])
&& ($sig = $this->engine->signatures[$this->engine->signed_parts[$part_id]])
) {
// add css script
$this->load_ui();
$this->ui->add_css();
// display status info
$attrib['id'] = 'enigma-message';
if ($sig instanceof enigma_signature) {
if ($sig->valid) {
$attrib['class'] = 'enigmanotice';
$sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
$msg = Q(str_replace('$sender', $sender, $this->gettext('sigvalid')));
}
else {
$attrib['class'] = 'enigmawarning';
$sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
$msg = Q(str_replace('$sender', $sender, $this->gettext('siginvalid')));
}
}
else if ($sig->getCode() == enigma_error::E_KEYNOTFOUND) {
$attrib['class'] = 'enigmawarning';
$msg = Q(str_replace('$keyid', enigma_key::format_id($sig->getData('id')),
$this->gettext('signokey')));
}
else {
$attrib['class'] = 'enigmaerror';
$msg = Q($this->gettext('sigerror'));
}
/*
$msg .= '&nbsp;' . html::a(array('href' => "#sigdetails",
'onclick' => JS_OBJECT_NAME.".command('enigma-sig-details')"),
Q($this->gettext('showdetails')));
*/
// test
// $msg .= '<br /><pre>'.$sig->body.'</pre>';
$p['prefix'] .= html::div($attrib, $msg);
// Display each signature message only once
unset($this->engine->signatures[$this->engine->signed_parts[$part_id]]);
}
return $p;
}
/**
* Handler for plain/text message.
*
* @param array Reference to hook's parameters (see enigma::parse_structure())
*/
private function parse_plain(&$p)
{
$this->load_engine();
$this->engine->parse_plain($p);
}
/**
* Handler for multipart/signed message.
* Verifies signature.
*
* @param array Reference to hook's parameters (see enigma::parse_structure())
*/
private function parse_signed(&$p)
{
$this->load_engine();
$this->engine->parse_signed($p);
}
/**
* Handler for multipart/encrypted and application/pkcs7-mime message.
*
* @param array Reference to hook's parameters (see enigma::parse_structure())
*/
private function parse_encrypted(&$p)
{
$this->load_engine();
$this->engine->parse_encrypted($p);
}
/**
* Handler for message_load hook.
* Check message bodies and attachments for keys/certs.
*/
function message_load($p)
{
$this->message = $p['object'];
// handle attachments vcard attachments
foreach ((array)$this->message->attachments as $attachment) {
if ($this->is_keys_part($attachment)) {
$this->keys_parts[] = $attachment->mime_id;
}
}
// the same with message bodies
foreach ((array)$this->message->parts as $idx => $part) {
if ($this->is_keys_part($part)) {
$this->keys_parts[] = $part->mime_id;
$this->keys_bodies[] = $part->mime_id;
}
}
// @TODO: inline PGP keys
if ($this->keys_parts) {
$this->add_texts('localization');
}
}
/**
* Handler for template_object_messagebody hook.
* This callback function adds a box below the message content
* if there is a key/cert attachment available
*/
function message_output($p)
{
$attach_script = false;
foreach ($this->keys_parts as $part) {
// remove part's body
if (in_array($part, $this->keys_bodies))
$p['content'] = '';
$style = "margin:0 1em; padding:0.2em 0.5em; border:1px solid #999; width: auto"
." border-radius:4px; -moz-border-radius:4px; -webkit-border-radius:4px";
// add box below messsage body
$p['content'] .= html::p(array('style' => $style),
html::a(array(
'href' => "#",
'onclick' => "return ".JS_OBJECT_NAME.".enigma_import_attachment('".JQ($part)."')",
'title' => $this->gettext('keyattimport')),
html::img(array('src' => $this->url('skins/default/key_add.png'), 'style' => "vertical-align:middle")))
. ' ' . html::span(null, $this->gettext('keyattfound')));
$attach_script = true;
}
if ($attach_script) {
$this->include_script('enigma.js');
}
return $p;
}
/**
* Handler for attached keys/certs import
*/
function import_file()
{
$this->load_engine();
$this->engine->import_file();
}
/**
* Checks if specified message part is a PGP-key or S/MIME cert data
*
* @param rcube_message_part Part object
*
* @return boolean True if part is a key/cert
*/
private function is_keys_part($part)
{
// @TODO: S/MIME
return (
// Content-Type: application/pgp-keys
$part->mimetype == 'application/pgp-keys'
);
}
}

@ -0,0 +1,2 @@
Order allow,deny
Deny from all

File diff suppressed because it is too large Load Diff

@ -0,0 +1,336 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Crypt_GPG is a package to use GPG from PHP
*
* This file contains an object that handles GPG's status output for the
* decrypt operation.
*
* PHP version 5
*
* LICENSE:
*
* This library 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.1 of the
* License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2009 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: DecryptStatusHandler.php 302814 2010-08-26 15:43:07Z gauthierm $
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
/**
* Crypt_GPG base class
*/
require_once 'Crypt/GPG.php';
/**
* GPG exception classes
*/
require_once 'Crypt/GPG/Exceptions.php';
/**
* Status line handler for the decrypt operation
*
* This class is used internally by Crypt_GPG and does not need be used
* directly. See the {@link Crypt_GPG} class for end-user API.
*
* This class is responsible for sending the passphrase commands when required
* by the {@link Crypt_GPG::decrypt()} method. See <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG distribution} for detailed
* information on GPG's status output for the decrypt operation.
*
* This class is also responsible for parsing error status and throwing a
* meaningful exception in the event that decryption fails.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
class Crypt_GPG_DecryptStatusHandler
{
// {{{ protected properties
/**
* Keys used to decrypt
*
* The array is of the form:
* <code>
* array(
* $key_id => array(
* 'fingerprint' => $fingerprint,
* 'passphrase' => $passphrase
* )
* );
* </code>
*
* @var array
*/
protected $keys = array();
/**
* Engine used to which passphrases are passed
*
* @var Crypt_GPG_Engine
*/
protected $engine = null;
/**
* The id of the current sub-key used for decryption
*
* @var string
*/
protected $currentSubKey = '';
/**
* Whether or not decryption succeeded
*
* If the message is only signed (compressed) and not encrypted, this is
* always true. If the message is encrypted, this flag is set to false
* until we know the decryption succeeded.
*
* @var boolean
*/
protected $decryptionOkay = true;
/**
* Whether or not there was no data for decryption
*
* @var boolean
*/
protected $noData = false;
/**
* Keys for which the passhprase is missing
*
* This contains primary user ids indexed by sub-key id and is used to
* create helpful exception messages.
*
* @var array
*/
protected $missingPassphrases = array();
/**
* Keys for which the passhprase is incorrect
*
* This contains primary user ids indexed by sub-key id and is used to
* create helpful exception messages.
*
* @var array
*/
protected $badPassphrases = array();
/**
* Keys that can be used to decrypt the data but are missing from the
* keychain
*
* This is an array with both the key and value being the sub-key id of
* the missing keys.
*
* @var array
*/
protected $missingKeys = array();
// }}}
// {{{ __construct()
/**
* Creates a new decryption status handler
*
* @param Crypt_GPG_Engine $engine the GPG engine to which passphrases are
* passed.
* @param array $keys the decryption keys to use.
*/
public function __construct(Crypt_GPG_Engine $engine, array $keys)
{
$this->engine = $engine;
$this->keys = $keys;
}
// }}}
// {{{ handle()
/**
* Handles a status line
*
* @param string $line the status line to handle.
*
* @return void
*/
public function handle($line)
{
$tokens = explode(' ', $line);
switch ($tokens[0]) {
case 'ENC_TO':
// Now we know the message is encrypted. Set flag to check if
// decryption succeeded.
$this->decryptionOkay = false;
// this is the new key message
$this->currentSubKeyId = $tokens[1];
break;
case 'NEED_PASSPHRASE':
// send passphrase to the GPG engine
$subKeyId = $tokens[1];
if (array_key_exists($subKeyId, $this->keys)) {
$passphrase = $this->keys[$subKeyId]['passphrase'];
$this->engine->sendCommand($passphrase);
} else {
$this->engine->sendCommand('');
}
break;
case 'USERID_HINT':
// remember the user id for pretty exception messages
$this->badPassphrases[$tokens[1]]
= implode(' ', array_splice($tokens, 2));
break;
case 'GOOD_PASSPHRASE':
// if we got a good passphrase, remove the key from the list of
// bad passphrases.
unset($this->badPassphrases[$this->currentSubKeyId]);
break;
case 'MISSING_PASSPHRASE':
$this->missingPassphrases[$this->currentSubKeyId]
= $this->currentSubKeyId;
break;
case 'NO_SECKEY':
// note: this message is also received if there are multiple
// recipients and a previous key had a correct passphrase.
$this->missingKeys[$tokens[1]] = $tokens[1];
break;
case 'NODATA':
$this->noData = true;
break;
case 'DECRYPTION_OKAY':
// If the message is encrypted, this is the all-clear signal.
$this->decryptionOkay = true;
break;
}
}
// }}}
// {{{ throwException()
/**
* Takes the final status of the decrypt operation and throws an
* appropriate exception
*
* If decryption was successful, no exception is thrown.
*
* @return void
*
* @throws Crypt_GPG_KeyNotFoundException if the private key needed to
* decrypt the data is not in the user's keyring.
*
* @throws Crypt_GPG_NoDataException if specified data does not contain
* GPG encrypted data.
*
* @throws Crypt_GPG_BadPassphraseException if a required passphrase is
* incorrect or if a required passphrase is not specified. See
* {@link Crypt_GPG::addDecryptKey()}.
*
* @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
* Use the <i>debug</i> option and file a bug report if these
* exceptions occur.
*/
public function throwException()
{
$code = Crypt_GPG::ERROR_NONE;
if (!$this->decryptionOkay) {
if (count($this->badPassphrases) > 0) {
$code = Crypt_GPG::ERROR_BAD_PASSPHRASE;
} elseif (count($this->missingKeys) > 0) {
$code = Crypt_GPG::ERROR_KEY_NOT_FOUND;
} else {
$code = Crypt_GPG::ERROR_UNKNOWN;
}
} elseif ($this->noData) {
$code = Crypt_GPG::ERROR_NO_DATA;
}
switch ($code) {
case Crypt_GPG::ERROR_NONE:
break;
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
if (count($this->missingKeys) > 0) {
$keyId = reset($this->missingKeys);
} else {
$keyId = '';
}
throw new Crypt_GPG_KeyNotFoundException(
'Cannot decrypt data. No suitable private key is in the ' .
'keyring. Import a suitable private key before trying to ' .
'decrypt this data.', $code, $keyId);
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
$badPassphrases = array_diff_key(
$this->badPassphrases,
$this->missingPassphrases
);
$missingPassphrases = array_intersect_key(
$this->badPassphrases,
$this->missingPassphrases
);
$message = 'Cannot decrypt data.';
if (count($badPassphrases) > 0) {
$message = ' Incorrect passphrase provided for keys: "' .
implode('", "', $badPassphrases) . '".';
}
if (count($missingPassphrases) > 0) {
$message = ' No passphrase provided for keys: "' .
implode('", "', $badPassphrases) . '".';
}
throw new Crypt_GPG_BadPassphraseException($message, $code,
$badPassphrases, $missingPassphrases);
case Crypt_GPG::ERROR_NO_DATA:
throw new Crypt_GPG_NoDataException(
'Cannot decrypt data. No PGP encrypted data was found in '.
'the provided data.', $code);
default:
throw new Crypt_GPG_Exception(
'Unknown error decrypting data.', $code);
}
}
// }}}
}
?>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,473 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Various exception handling classes for Crypt_GPG
*
* Crypt_GPG provides an object oriented interface to GNU Privacy
* Guard (GPG). It requires the GPG executable to be on the system.
*
* This file contains various exception classes used by the Crypt_GPG package.
*
* PHP version 5
*
* LICENSE:
*
* This library 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.1 of the
* License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Exceptions.php 273745 2009-01-18 05:24:25Z gauthierm $
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* PEAR Exception handler and base class
*/
require_once 'PEAR/Exception.php';
// {{{ class Crypt_GPG_Exception
/**
* An exception thrown by the Crypt_GPG package
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_Exception extends PEAR_Exception
{
}
// }}}
// {{{ class Crypt_GPG_FileException
/**
* An exception thrown when a file is used in ways it cannot be used
*
* For example, if an output file is specified and the file is not writeable, or
* if an input file is specified and the file is not readable, this exception
* is thrown.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2007-2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_FileException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* The name of the file that caused this exception
*
* @var string
*/
private $_filename = '';
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_FileException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $filename the name of the file that caused this exception.
*/
public function __construct($message, $code = 0, $filename = '')
{
$this->_filename = $filename;
parent::__construct($message, $code);
}
// }}}
// {{{ getFilename()
/**
* Returns the filename of the file that caused this exception
*
* @return string the filename of the file that caused this exception.
*
* @see Crypt_GPG_FileException::$_filename
*/
public function getFilename()
{
return $this->_filename;
}
// }}}
}
// }}}
// {{{ class Crypt_GPG_OpenSubprocessException
/**
* An exception thrown when the GPG subprocess cannot be opened
*
* This exception is thrown when the {@link Crypt_GPG_Engine} tries to open a
* new subprocess and fails.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_OpenSubprocessException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* The command used to try to open the subprocess
*
* @var string
*/
private $_command = '';
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_OpenSubprocessException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $command the command that was called to open the
* new subprocess.
*
* @see Crypt_GPG::_openSubprocess()
*/
public function __construct($message, $code = 0, $command = '')
{
$this->_command = $command;
parent::__construct($message, $code);
}
// }}}
// {{{ getCommand()
/**
* Returns the contents of the internal _command property
*
* @return string the command used to open the subprocess.
*
* @see Crypt_GPG_OpenSubprocessException::$_command
*/
public function getCommand()
{
return $this->_command;
}
// }}}
}
// }}}
// {{{ class Crypt_GPG_InvalidOperationException
/**
* An exception thrown when an invalid GPG operation is attempted
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_InvalidOperationException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* The attempted operation
*
* @var string
*/
private $_operation = '';
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_OpenSubprocessException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $operation the operation.
*/
public function __construct($message, $code = 0, $operation = '')
{
$this->_operation = $operation;
parent::__construct($message, $code);
}
// }}}
// {{{ getOperation()
/**
* Returns the contents of the internal _operation property
*
* @return string the attempted operation.
*
* @see Crypt_GPG_InvalidOperationException::$_operation
*/
public function getOperation()
{
return $this->_operation;
}
// }}}
}
// }}}
// {{{ class Crypt_GPG_KeyNotFoundException
/**
* An exception thrown when Crypt_GPG fails to find the key for various
* operations
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_KeyNotFoundException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* The key identifier that was searched for
*
* @var string
*/
private $_keyId = '';
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_KeyNotFoundException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $keyId the key identifier of the key.
*/
public function __construct($message, $code = 0, $keyId= '')
{
$this->_keyId = $keyId;
parent::__construct($message, $code);
}
// }}}
// {{{ getKeyId()
/**
* Gets the key identifier of the key that was not found
*
* @return string the key identifier of the key that was not found.
*/
public function getKeyId()
{
return $this->_keyId;
}
// }}}
}
// }}}
// {{{ class Crypt_GPG_NoDataException
/**
* An exception thrown when Crypt_GPG cannot find valid data for various
* operations
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2006 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_NoDataException extends Crypt_GPG_Exception
{
}
// }}}
// {{{ class Crypt_GPG_BadPassphraseException
/**
* An exception thrown when a required passphrase is incorrect or missing
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2006-2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_BadPassphraseException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* Keys for which the passhprase is missing
*
* This contains primary user ids indexed by sub-key id.
*
* @var array
*/
private $_missingPassphrases = array();
/**
* Keys for which the passhprase is incorrect
*
* This contains primary user ids indexed by sub-key id.
*
* @var array
*/
private $_badPassphrases = array();
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_BadPassphraseException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $badPassphrases an array containing user ids of keys
* for which the passphrase is incorrect.
* @param string $missingPassphrases an array containing user ids of keys
* for which the passphrase is missing.
*/
public function __construct($message, $code = 0,
array $badPassphrases = array(), array $missingPassphrases = array()
) {
$this->_badPassphrases = $badPassphrases;
$this->_missingPassphrases = $missingPassphrases;
parent::__construct($message, $code);
}
// }}}
// {{{ getBadPassphrases()
/**
* Gets keys for which the passhprase is incorrect
*
* @return array an array of keys for which the passphrase is incorrect.
* The array contains primary user ids indexed by the sub-key
* id.
*/
public function getBadPassphrases()
{
return $this->_badPassphrases;
}
// }}}
// {{{ getMissingPassphrases()
/**
* Gets keys for which the passhprase is missing
*
* @return array an array of keys for which the passphrase is missing.
* The array contains primary user ids indexed by the sub-key
* id.
*/
public function getMissingPassphrases()
{
return $this->_missingPassphrases;
}
// }}}
}
// }}}
// {{{ class Crypt_GPG_DeletePrivateKeyException
/**
* An exception thrown when an attempt is made to delete public key that has an
* associated private key on the keyring
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_DeletePrivateKeyException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* The key identifier the deletion attempt was made upon
*
* @var string
*/
private $_keyId = '';
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_DeletePrivateKeyException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $keyId the key identifier of the public key that was
* attempted to delete.
*
* @see Crypt_GPG::deletePublicKey()
*/
public function __construct($message, $code = 0, $keyId = '')
{
$this->_keyId = $keyId;
parent::__construct($message, $code);
}
// }}}
// {{{ getKeyId()
/**
* Gets the key identifier of the key that was not found
*
* @return string the key identifier of the key that was not found.
*/
public function getKeyId()
{
return $this->_keyId;
}
// }}}
}
// }}}
?>

@ -0,0 +1,223 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Contains a class representing GPG keys
*
* PHP version 5
*
* LICENSE:
*
* This library 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.1 of the
* License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Key.php 295621 2010-03-01 04:18:54Z gauthierm $
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* Sub-key class definition
*/
require_once 'Crypt/GPG/SubKey.php';
/**
* User id class definition
*/
require_once 'Crypt/GPG/UserId.php';
// {{{ class Crypt_GPG_Key
/**
* A data class for GPG key information
*
* This class is used to store the results of the {@link Crypt_GPG::getKeys()}
* method.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::getKeys()
*/
class Crypt_GPG_Key
{
// {{{ class properties
/**
* The user ids associated with this key
*
* This is an array of {@link Crypt_GPG_UserId} objects.
*
* @var array
*
* @see Crypt_GPG_Key::addUserId()
* @see Crypt_GPG_Key::getUserIds()
*/
private $_userIds = array();
/**
* The subkeys of this key
*
* This is an array of {@link Crypt_GPG_SubKey} objects.
*
* @var array
*
* @see Crypt_GPG_Key::addSubKey()
* @see Crypt_GPG_Key::getSubKeys()
*/
private $_subKeys = array();
// }}}
// {{{ getSubKeys()
/**
* Gets the sub-keys of this key
*
* @return array the sub-keys of this key.
*
* @see Crypt_GPG_Key::addSubKey()
*/
public function getSubKeys()
{
return $this->_subKeys;
}
// }}}
// {{{ getUserIds()
/**
* Gets the user ids of this key
*
* @return array the user ids of this key.
*
* @see Crypt_GPG_Key::addUserId()
*/
public function getUserIds()
{
return $this->_userIds;
}
// }}}
// {{{ getPrimaryKey()
/**
* Gets the primary sub-key of this key
*
* The primary key is the first added sub-key.
*
* @return Crypt_GPG_SubKey the primary sub-key of this key.
*/
public function getPrimaryKey()
{
$primary_key = null;
if (count($this->_subKeys) > 0) {
$primary_key = $this->_subKeys[0];
}
return $primary_key;
}
// }}}
// {{{ canSign()
/**
* Gets whether or not this key can sign data
*
* This key can sign data if any sub-key of this key can sign data.
*
* @return boolean true if this key can sign data and false if this key
* cannot sign data.
*/
public function canSign()
{
$canSign = false;
foreach ($this->_subKeys as $subKey) {
if ($subKey->canSign()) {
$canSign = true;
break;
}
}
return $canSign;
}
// }}}
// {{{ canEncrypt()
/**
* Gets whether or not this key can encrypt data
*
* This key can encrypt data if any sub-key of this key can encrypt data.
*
* @return boolean true if this key can encrypt data and false if this
* key cannot encrypt data.
*/
public function canEncrypt()
{
$canEncrypt = false;
foreach ($this->_subKeys as $subKey) {
if ($subKey->canEncrypt()) {
$canEncrypt = true;
break;
}
}
return $canEncrypt;
}
// }}}
// {{{ addSubKey()
/**
* Adds a sub-key to this key
*
* The first added sub-key will be the primary key of this key.
*
* @param Crypt_GPG_SubKey $subKey the sub-key to add.
*
* @return Crypt_GPG_Key the current object, for fluent interface.
*/
public function addSubKey(Crypt_GPG_SubKey $subKey)
{
$this->_subKeys[] = $subKey;
return $this;
}
// }}}
// {{{ addUserId()
/**
* Adds a user id to this key
*
* @param Crypt_GPG_UserId $userId the user id to add.
*
* @return Crypt_GPG_Key the current object, for fluent interface.
*/
public function addUserId(Crypt_GPG_UserId $userId)
{
$this->_userIds[] = $userId;
return $this;
}
// }}}
}
// }}}
?>

@ -0,0 +1,428 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* A class representing GPG signatures
*
* This file contains a data class representing a GPG signature.
*
* PHP version 5
*
* LICENSE:
*
* This library 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.1 of the
* License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @copyright 2005-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Signature.php 302773 2010-08-25 14:16:28Z gauthierm $
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* User id class definition
*/
require_once 'Crypt/GPG/UserId.php';
// {{{ class Crypt_GPG_Signature
/**
* A class for GPG signature information
*
* This class is used to store the results of the Crypt_GPG::verify() method.
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::verify()
*/
class Crypt_GPG_Signature
{
// {{{ class properties
/**
* A base64-encoded string containing a unique id for this signature if
* this signature has been verified as ok
*
* This id is used to prevent replay attacks and is not present for all
* types of signatures.
*
* @var string
*/
private $_id = '';
/**
* The fingerprint of the key used to create the signature
*
* @var string
*/
private $_keyFingerprint = '';
/**
* The id of the key used to create the signature
*
* @var string
*/
private $_keyId = '';
/**
* The creation date of this signature
*
* This is a Unix timestamp.
*
* @var integer
*/
private $_creationDate = 0;
/**
* The expiration date of the signature
*
* This is a Unix timestamp. If this signature does not expire, this will
* be zero.
*
* @var integer
*/
private $_expirationDate = 0;
/**
* The user id associated with this signature
*
* @var Crypt_GPG_UserId
*/
private $_userId = null;
/**
* Whether or not this signature is valid
*
* @var boolean
*/
private $_isValid = false;
// }}}
// {{{ __construct()
/**
* Creates a new signature
*
* Signatures can be initialized from an array of named values. Available
* names are:
*
* - <kbd>string id</kbd> - the unique id of this signature.
* - <kbd>string fingerprint</kbd> - the fingerprint of the key used to
* create the signature. The fingerprint
* should not contain formatting
* characters.
* - <kbd>string keyId</kbd> - the id of the key used to create the
* the signature.
* - <kbd>integer creation</kbd> - the date the signature was created.
* This is a UNIX timestamp.
* - <kbd>integer expiration</kbd> - the date the signature expired. This
* is a UNIX timestamp. If the signature
* does not expire, use 0.
* - <kbd>boolean valid</kbd> - whether or not the signature is valid.
* - <kbd>string userId</kbd> - the user id associated with the
* signature. This may also be a
* {@link Crypt_GPG_UserId} object.
*
* @param Crypt_GPG_Signature|array $signature optional. Either an existing
* signature object, which is copied; or an array of initial values.
*/
public function __construct($signature = null)
{
// copy from object
if ($signature instanceof Crypt_GPG_Signature) {
$this->_id = $signature->_id;
$this->_keyFingerprint = $signature->_keyFingerprint;
$this->_keyId = $signature->_keyId;
$this->_creationDate = $signature->_creationDate;
$this->_expirationDate = $signature->_expirationDate;
$this->_isValid = $signature->_isValid;
if ($signature->_userId instanceof Crypt_GPG_UserId) {
$this->_userId = clone $signature->_userId;
} else {
$this->_userId = $signature->_userId;
}
}
// initialize from array
if (is_array($signature)) {
if (array_key_exists('id', $signature)) {
$this->setId($signature['id']);
}
if (array_key_exists('fingerprint', $signature)) {
$this->setKeyFingerprint($signature['fingerprint']);
}
if (array_key_exists('keyId', $signature)) {
$this->setKeyId($signature['keyId']);
}
if (array_key_exists('creation', $signature)) {
$this->setCreationDate($signature['creation']);
}
if (array_key_exists('expiration', $signature)) {
$this->setExpirationDate($signature['expiration']);
}
if (array_key_exists('valid', $signature)) {
$this->setValid($signature['valid']);
}
if (array_key_exists('userId', $signature)) {
$userId = new Crypt_GPG_UserId($signature['userId']);
$this->setUserId($userId);
}
}
}
// }}}
// {{{ getId()
/**
* Gets the id of this signature
*
* @return string a base64-encoded string containing a unique id for this
* signature. This id is used to prevent replay attacks and
* is not present for all types of signatures.
*/
public function getId()
{
return $this->_id;
}
// }}}
// {{{ getKeyFingerprint()
/**
* Gets the fingerprint of the key used to create this signature
*
* @return string the fingerprint of the key used to create this signature.
*/
public function getKeyFingerprint()
{
return $this->_keyFingerprint;
}
// }}}
// {{{ getKeyId()
/**
* Gets the id of the key used to create this signature
*
* Whereas the fingerprint of the signing key may not always be available
* (for example if the signature is bad), the id should always be
* available.
*
* @return string the id of the key used to create this signature.
*/
public function getKeyId()
{
return $this->_keyId;
}
// }}}
// {{{ getCreationDate()
/**
* Gets the creation date of this signature
*
* @return integer the creation date of this signature. This is a Unix
* timestamp.
*/
public function getCreationDate()
{
return $this->_creationDate;
}
// }}}
// {{{ getExpirationDate()
/**
* Gets the expiration date of the signature
*
* @return integer the expiration date of this signature. This is a Unix
* timestamp. If this signature does not expire, this will
* be zero.
*/
public function getExpirationDate()
{
return $this->_expirationDate;
}
// }}}
// {{{ getUserId()
/**
* Gets the user id associated with this signature
*
* @return Crypt_GPG_UserId the user id associated with this signature.
*/
public function getUserId()
{
return $this->_userId;
}
// }}}
// {{{ isValid()
/**
* Gets whether or no this signature is valid
*
* @return boolean true if this signature is valid and false if it is not.
*/
public function isValid()
{
return $this->_isValid;
}
// }}}
// {{{ setId()
/**
* Sets the id of this signature
*
* @param string $id a base64-encoded string containing a unique id for
* this signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*
* @see Crypt_GPG_Signature::getId()
*/
public function setId($id)
{
$this->_id = strval($id);
return $this;
}
// }}}
// {{{ setKeyFingerprint()
/**
* Sets the key fingerprint of this signature
*
* @param string $fingerprint the key fingerprint of this signature. This
* is the fingerprint of the primary key used to
* create this signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setKeyFingerprint($fingerprint)
{
$this->_keyFingerprint = strval($fingerprint);
return $this;
}
// }}}
// {{{ setKeyId()
/**
* Sets the key id of this signature
*
* @param string $id the key id of this signature. This is the id of the
* primary key used to create this signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setKeyId($id)
{
$this->_keyId = strval($id);
return $this;
}
// }}}
// {{{ setCreationDate()
/**
* Sets the creation date of this signature
*
* @param integer $creationDate the creation date of this signature. This
* is a Unix timestamp.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setCreationDate($creationDate)
{
$this->_creationDate = intval($creationDate);
return $this;
}
// }}}
// {{{ setExpirationDate()
/**
* Sets the expiration date of this signature
*
* @param integer $expirationDate the expiration date of this signature.
* This is a Unix timestamp. Specify zero if
* this signature does not expire.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setExpirationDate($expirationDate)
{
$this->_expirationDate = intval($expirationDate);
return $this;
}
// }}}
// {{{ setUserId()
/**
* Sets the user id associated with this signature
*
* @param Crypt_GPG_UserId $userId the user id associated with this
* signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setUserId(Crypt_GPG_UserId $userId)
{
$this->_userId = $userId;
return $this;
}
// }}}
// {{{ setValid()
/**
* Sets whether or not this signature is valid
*
* @param boolean $isValid true if this signature is valid and false if it
* is not.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setValid($isValid)
{
$this->_isValid = ($isValid) ? true : false;
return $this;
}
// }}}
}
// }}}
?>

@ -0,0 +1,649 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Contains a class representing GPG sub-keys and constants for GPG algorithms
*
* PHP version 5
*
* LICENSE:
*
* This library 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.1 of the
* License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @author Nathan Fredrickson <nathan@silverorange.com>
* @copyright 2005-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: SubKey.php 302768 2010-08-25 13:45:52Z gauthierm $
* @link http://pear.php.net/package/Crypt_GPG
*/
// {{{ class Crypt_GPG_SubKey
/**
* A class for GPG sub-key information
*
* This class is used to store the results of the {@link Crypt_GPG::getKeys()}
* method. Sub-key objects are members of a {@link Crypt_GPG_Key} object.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @author Nathan Fredrickson <nathan@silverorange.com>
* @copyright 2005-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::getKeys()
* @see Crypt_GPG_Key::getSubKeys()
*/
class Crypt_GPG_SubKey
{
// {{{ class constants
/**
* RSA encryption algorithm.
*/
const ALGORITHM_RSA = 1;
/**
* Elgamal encryption algorithm (encryption only).
*/
const ALGORITHM_ELGAMAL_ENC = 16;
/**
* DSA encryption algorithm (sometimes called DH, sign only).
*/
const ALGORITHM_DSA = 17;
/**
* Elgamal encryption algorithm (signage and encryption - should not be
* used).
*/
const ALGORITHM_ELGAMAL_ENC_SGN = 20;
// }}}
// {{{ class properties
/**
* The id of this sub-key
*
* @var string
*/
private $_id = '';
/**
* The algorithm used to create this sub-key
*
* The value is one of the Crypt_GPG_SubKey::ALGORITHM_* constants.
*
* @var integer
*/
private $_algorithm = 0;
/**
* The fingerprint of this sub-key
*
* @var string
*/
private $_fingerprint = '';
/**
* Length of this sub-key in bits
*
* @var integer
*/
private $_length = 0;
/**
* Date this sub-key was created
*
* This is a Unix timestamp.
*
* @var integer
*/
private $_creationDate = 0;
/**
* Date this sub-key expires
*
* This is a Unix timestamp. If this sub-key does not expire, this will be
* zero.
*
* @var integer
*/
private $_expirationDate = 0;
/**
* Whether or not this sub-key can sign data
*
* @var boolean
*/
private $_canSign = false;
/**
* Whether or not this sub-key can encrypt data
*
* @var boolean
*/
private $_canEncrypt = false;
/**
* Whether or not the private key for this sub-key exists in the keyring
*
* @var boolean
*/
private $_hasPrivate = false;
/**
* Whether or not this sub-key is revoked
*
* @var boolean
*/
private $_isRevoked = false;
// }}}
// {{{ __construct()
/**
* Creates a new sub-key object
*
* Sub-keys can be initialized from an array of named values. Available
* names are:
*
* - <kbd>string id</kbd> - the key id of the sub-key.
* - <kbd>integer algorithm</kbd> - the encryption algorithm of the
* sub-key.
* - <kbd>string fingerprint</kbd> - the fingerprint of the sub-key. The
* fingerprint should not contain
* formatting characters.
* - <kbd>integer length</kbd> - the length of the sub-key in bits.
* - <kbd>integer creation</kbd> - the date the sub-key was created.
* This is a UNIX timestamp.
* - <kbd>integer expiration</kbd> - the date the sub-key expires. This
* is a UNIX timestamp. If the sub-key
* does not expire, use 0.
* - <kbd>boolean canSign</kbd> - whether or not the sub-key can be
* used to sign data.
* - <kbd>boolean canEncrypt</kbd> - whether or not the sub-key can be
* used to encrypt data.
* - <kbd>boolean hasPrivate</kbd> - whether or not the private key for
* the sub-key exists in the keyring.
* - <kbd>boolean isRevoked</kbd> - whether or not this sub-key is
* revoked.
*
* @param Crypt_GPG_SubKey|string|array $key optional. Either an existing
* sub-key object, which is copied; a sub-key string, which is
* parsed; or an array of initial values.
*/
public function __construct($key = null)
{
// parse from string
if (is_string($key)) {
$key = self::parse($key);
}
// copy from object
if ($key instanceof Crypt_GPG_SubKey) {
$this->_id = $key->_id;
$this->_algorithm = $key->_algorithm;
$this->_fingerprint = $key->_fingerprint;
$this->_length = $key->_length;
$this->_creationDate = $key->_creationDate;
$this->_expirationDate = $key->_expirationDate;
$this->_canSign = $key->_canSign;
$this->_canEncrypt = $key->_canEncrypt;
$this->_hasPrivate = $key->_hasPrivate;
$this->_isRevoked = $key->_isRevoked;
}
// initialize from array
if (is_array($key)) {
if (array_key_exists('id', $key)) {
$this->setId($key['id']);
}
if (array_key_exists('algorithm', $key)) {
$this->setAlgorithm($key['algorithm']);
}
if (array_key_exists('fingerprint', $key)) {
$this->setFingerprint($key['fingerprint']);
}
if (array_key_exists('length', $key)) {
$this->setLength($key['length']);
}
if (array_key_exists('creation', $key)) {
$this->setCreationDate($key['creation']);
}
if (array_key_exists('expiration', $key)) {
$this->setExpirationDate($key['expiration']);
}
if (array_key_exists('canSign', $key)) {
$this->setCanSign($key['canSign']);
}
if (array_key_exists('canEncrypt', $key)) {
$this->setCanEncrypt($key['canEncrypt']);
}
if (array_key_exists('hasPrivate', $key)) {
$this->setHasPrivate($key['hasPrivate']);
}
if (array_key_exists('isRevoked', $key)) {
$this->setRevoked($key['isRevoked']);
}
}
}
// }}}
// {{{ getId()
/**
* Gets the id of this sub-key
*
* @return string the id of this sub-key.
*/
public function getId()
{
return $this->_id;
}
// }}}
// {{{ getAlgorithm()
/**
* Gets the algorithm used by this sub-key
*
* The algorithm should be one of the Crypt_GPG_SubKey::ALGORITHM_*
* constants.
*
* @return integer the algorithm used by this sub-key.
*/
public function getAlgorithm()
{
return $this->_algorithm;
}
// }}}
// {{{ getCreationDate()
/**
* Gets the creation date of this sub-key
*
* This is a Unix timestamp.
*
* @return integer the creation date of this sub-key.
*/
public function getCreationDate()
{
return $this->_creationDate;
}
// }}}
// {{{ getExpirationDate()
/**
* Gets the date this sub-key expires
*
* This is a Unix timestamp. If this sub-key does not expire, this will be
* zero.
*
* @return integer the date this sub-key expires.
*/
public function getExpirationDate()
{
return $this->_expirationDate;
}
// }}}
// {{{ getFingerprint()
/**
* Gets the fingerprint of this sub-key
*
* @return string the fingerprint of this sub-key.
*/
public function getFingerprint()
{
return $this->_fingerprint;
}
// }}}
// {{{ getLength()
/**
* Gets the length of this sub-key in bits
*
* @return integer the length of this sub-key in bits.
*/
public function getLength()
{
return $this->_length;
}
// }}}
// {{{ canSign()
/**
* Gets whether or not this sub-key can sign data
*
* @return boolean true if this sub-key can sign data and false if this
* sub-key can not sign data.
*/
public function canSign()
{
return $this->_canSign;
}
// }}}
// {{{ canEncrypt()
/**
* Gets whether or not this sub-key can encrypt data
*
* @return boolean true if this sub-key can encrypt data and false if this
* sub-key can not encrypt data.
*/
public function canEncrypt()
{
return $this->_canEncrypt;
}
// }}}
// {{{ hasPrivate()
/**
* Gets whether or not the private key for this sub-key exists in the
* keyring
*
* @return boolean true the private key for this sub-key exists in the
* keyring and false if it does not.
*/
public function hasPrivate()
{
return $this->_hasPrivate;
}
// }}}
// {{{ isRevoked()
/**
* Gets whether or not this sub-key is revoked
*
* @return boolean true if this sub-key is revoked and false if it is not.
*/
public function isRevoked()
{
return $this->_isRevoked;
}
// }}}
// {{{ setCreationDate()
/**
* Sets the creation date of this sub-key
*
* The creation date is a Unix timestamp.
*
* @param integer $creationDate the creation date of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setCreationDate($creationDate)
{
$this->_creationDate = intval($creationDate);
return $this;
}
// }}}
// {{{ setExpirationDate()
/**
* Sets the expiration date of this sub-key
*
* The expiration date is a Unix timestamp. Specify zero if this sub-key
* does not expire.
*
* @param integer $expirationDate the expiration date of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setExpirationDate($expirationDate)
{
$this->_expirationDate = intval($expirationDate);
return $this;
}
// }}}
// {{{ setId()
/**
* Sets the id of this sub-key
*
* @param string $id the id of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setId($id)
{
$this->_id = strval($id);
return $this;
}
// }}}
// {{{ setAlgorithm()
/**
* Sets the algorithm used by this sub-key
*
* @param integer $algorithm the algorithm used by this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setAlgorithm($algorithm)
{
$this->_algorithm = intval($algorithm);
return $this;
}
// }}}
// {{{ setFingerprint()
/**
* Sets the fingerprint of this sub-key
*
* @param string $fingerprint the fingerprint of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setFingerprint($fingerprint)
{
$this->_fingerprint = strval($fingerprint);
return $this;
}
// }}}
// {{{ setLength()
/**
* Sets the length of this sub-key in bits
*
* @param integer $length the length of this sub-key in bits.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setLength($length)
{
$this->_length = intval($length);
return $this;
}
// }}}
// {{{ setCanSign()
/**
* Sets whether of not this sub-key can sign data
*
* @param boolean $canSign true if this sub-key can sign data and false if
* it can not.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setCanSign($canSign)
{
$this->_canSign = ($canSign) ? true : false;
return $this;
}
// }}}
// {{{ setCanEncrypt()
/**
* Sets whether of not this sub-key can encrypt data
*
* @param boolean $canEncrypt true if this sub-key can encrypt data and
* false if it can not.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setCanEncrypt($canEncrypt)
{
$this->_canEncrypt = ($canEncrypt) ? true : false;
return $this;
}
// }}}
// {{{ setHasPrivate()
/**
* Sets whether of not the private key for this sub-key exists in the
* keyring
*
* @param boolean $hasPrivate true if the private key for this sub-key
* exists in the keyring and false if it does
* not.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setHasPrivate($hasPrivate)
{
$this->_hasPrivate = ($hasPrivate) ? true : false;
return $this;
}
// }}}
// {{{ setRevoked()
/**
* Sets whether or not this sub-key is revoked
*
* @param boolean $isRevoked whether or not this sub-key is revoked.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setRevoked($isRevoked)
{
$this->_isRevoked = ($isRevoked) ? true : false;
return $this;
}
// }}}
// {{{ parse()
/**
* Parses a sub-key object from a sub-key string
*
* See <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG distribution} for information
* on how the sub-key string is parsed.
*
* @param string $string the string containing the sub-key.
*
* @return Crypt_GPG_SubKey the sub-key object parsed from the string.
*/
public static function parse($string)
{
$tokens = explode(':', $string);
$subKey = new Crypt_GPG_SubKey();
$subKey->setId($tokens[4]);
$subKey->setLength($tokens[2]);
$subKey->setAlgorithm($tokens[3]);
$subKey->setCreationDate(self::_parseDate($tokens[5]));
$subKey->setExpirationDate(self::_parseDate($tokens[6]));
if ($tokens[1] == 'r') {
$subKey->setRevoked(true);
}
if (strpos($tokens[11], 's') !== false) {
$subKey->setCanSign(true);
}
if (strpos($tokens[11], 'e') !== false) {
$subKey->setCanEncrypt(true);
}
return $subKey;
}
// }}}
// {{{ _parseDate()
/**
* Parses a date string as provided by GPG into a UNIX timestamp
*
* @param string $string the date string.
*
* @return integer the UNIX timestamp corresponding to the provided date
* string.
*/
private static function _parseDate($string)
{
if ($string == '') {
$timestamp = 0;
} else {
// all times are in UTC according to GPG documentation
$timeZone = new DateTimeZone('UTC');
if (strpos($string, 'T') === false) {
// interpret as UNIX timestamp
$string = '@' . $string;
}
$date = new DateTime($string, $timeZone);
// convert to UNIX timestamp
$timestamp = intval($date->format('U'));
}
return $timestamp;
}
// }}}
}
// }}}
?>

@ -0,0 +1,373 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Contains a data class representing a GPG user id
*
* PHP version 5
*
* LICENSE:
*
* This library 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.1 of the
* License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: UserId.php 295621 2010-03-01 04:18:54Z gauthierm $
* @link http://pear.php.net/package/Crypt_GPG
*/
// {{{ class Crypt_GPG_UserId
/**
* A class for GPG user id information
*
* This class is used to store the results of the {@link Crypt_GPG::getKeys()}
* method. User id objects are members of a {@link Crypt_GPG_Key} object.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::getKeys()
* @see Crypt_GPG_Key::getUserIds()
*/
class Crypt_GPG_UserId
{
// {{{ class properties
/**
* The name field of this user id
*
* @var string
*/
private $_name = '';
/**
* The comment field of this user id
*
* @var string
*/
private $_comment = '';
/**
* The email field of this user id
*
* @var string
*/
private $_email = '';
/**
* Whether or not this user id is revoked
*
* @var boolean
*/
private $_isRevoked = false;
/**
* Whether or not this user id is valid
*
* @var boolean
*/
private $_isValid = true;
// }}}
// {{{ __construct()
/**
* Creates a new user id
*
* User ids can be initialized from an array of named values. Available
* names are:
*
* - <kbd>string name</kbd> - the name field of the user id.
* - <kbd>string comment</kbd> - the comment field of the user id.
* - <kbd>string email</kbd> - the email field of the user id.
* - <kbd>boolean valid</kbd> - whether or not the user id is valid.
* - <kbd>boolean revoked</kbd> - whether or not the user id is revoked.
*
* @param Crypt_GPG_UserId|string|array $userId optional. Either an
* existing user id object, which is copied; a user id string, which
* is parsed; or an array of initial values.
*/
public function __construct($userId = null)
{
// parse from string
if (is_string($userId)) {
$userId = self::parse($userId);
}
// copy from object
if ($userId instanceof Crypt_GPG_UserId) {
$this->_name = $userId->_name;
$this->_comment = $userId->_comment;
$this->_email = $userId->_email;
$this->_isRevoked = $userId->_isRevoked;
$this->_isValid = $userId->_isValid;
}
// initialize from array
if (is_array($userId)) {
if (array_key_exists('name', $userId)) {
$this->setName($userId['name']);
}
if (array_key_exists('comment', $userId)) {
$this->setComment($userId['comment']);
}
if (array_key_exists('email', $userId)) {
$this->setEmail($userId['email']);
}
if (array_key_exists('revoked', $userId)) {
$this->setRevoked($userId['revoked']);
}
if (array_key_exists('valid', $userId)) {
$this->setValid($userId['valid']);
}
}
}
// }}}
// {{{ getName()
/**
* Gets the name field of this user id
*
* @return string the name field of this user id.
*/
public function getName()
{
return $this->_name;
}
// }}}
// {{{ getComment()
/**
* Gets the comments field of this user id
*
* @return string the comments field of this user id.
*/
public function getComment()
{
return $this->_comment;
}
// }}}
// {{{ getEmail()
/**
* Gets the email field of this user id
*
* @return string the email field of this user id.
*/
public function getEmail()
{
return $this->_email;
}
// }}}
// {{{ isRevoked()
/**
* Gets whether or not this user id is revoked
*
* @return boolean true if this user id is revoked and false if it is not.
*/
public function isRevoked()
{
return $this->_isRevoked;
}
// }}}
// {{{ isValid()
/**
* Gets whether or not this user id is valid
*
* @return boolean true if this user id is valid and false if it is not.
*/
public function isValid()
{
return $this->_isValid;
}
// }}}
// {{{ __toString()
/**
* Gets a string representation of this user id
*
* The string is formatted as:
* <b><kbd>name (comment) <email-address></kbd></b>.
*
* @return string a string representation of this user id.
*/
public function __toString()
{
$components = array();
if (strlen($this->_name) > 0) {
$components[] = $this->_name;
}
if (strlen($this->_comment) > 0) {
$components[] = '(' . $this->_comment . ')';
}
if (strlen($this->_email) > 0) {
$components[] = '<' . $this->_email. '>';
}
return implode(' ', $components);
}
// }}}
// {{{ setName()
/**
* Sets the name field of this user id
*
* @param string $name the name field of this user id.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setName($name)
{
$this->_name = strval($name);
return $this;
}
// }}}
// {{{ setComment()
/**
* Sets the comment field of this user id
*
* @param string $comment the comment field of this user id.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setComment($comment)
{
$this->_comment = strval($comment);
return $this;
}
// }}}
// {{{ setEmail()
/**
* Sets the email field of this user id
*
* @param string $email the email field of this user id.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setEmail($email)
{
$this->_email = strval($email);
return $this;
}
// }}}
// {{{ setRevoked()
/**
* Sets whether or not this user id is revoked
*
* @param boolean $isRevoked whether or not this user id is revoked.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setRevoked($isRevoked)
{
$this->_isRevoked = ($isRevoked) ? true : false;
return $this;
}
// }}}
// {{{ setValid()
/**
* Sets whether or not this user id is valid
*
* @param boolean $isValid whether or not this user id is valid.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setValid($isValid)
{
$this->_isValid = ($isValid) ? true : false;
return $this;
}
// }}}
// {{{ parse()
/**
* Parses a user id object from a user id string
*
* A user id string is of the form:
* <b><kbd>name (comment) <email-address></kbd></b> with the <i>comment</i>
* and <i>email-address</i> fields being optional.
*
* @param string $string the user id string to parse.
*
* @return Crypt_GPG_UserId the user id object parsed from the string.
*/
public static function parse($string)
{
$userId = new Crypt_GPG_UserId();
$email = '';
$comment = '';
// get email address from end of string if it exists
$matches = array();
if (preg_match('/^(.+?) <([^>]+)>$/', $string, $matches) === 1) {
$string = $matches[1];
$email = $matches[2];
}
// get comment from end of string if it exists
$matches = array();
if (preg_match('/^(.+?) \(([^\)]+)\)$/', $string, $matches) === 1) {
$string = $matches[1];
$comment = $matches[2];
}
$name = $string;
$userId->setName($name);
$userId->setComment($comment);
$userId->setEmail($email);
return $userId;
}
// }}}
}
// }}}
?>

@ -0,0 +1,216 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Crypt_GPG is a package to use GPG from PHP
*
* This file contains an object that handles GPG's status output for the verify
* operation.
*
* PHP version 5
*
* LICENSE:
*
* This library 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.1 of the
* License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: VerifyStatusHandler.php 302908 2010-08-31 03:56:54Z gauthierm $
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
/**
* Signature object class definition
*/
require_once 'Crypt/GPG/Signature.php';
/**
* Status line handler for the verify operation
*
* This class is used internally by Crypt_GPG and does not need be used
* directly. See the {@link Crypt_GPG} class for end-user API.
*
* This class is responsible for building signature objects that are returned
* by the {@link Crypt_GPG::verify()} method. See <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG distribution} for detailed
* information on GPG's status output for the verify operation.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
class Crypt_GPG_VerifyStatusHandler
{
// {{{ protected properties
/**
* The current signature id
*
* Ths signature id is emitted by GPG before the new signature line so we
* must remember it temporarily.
*
* @var string
*/
protected $signatureId = '';
/**
* List of parsed {@link Crypt_GPG_Signature} objects
*
* @var array
*/
protected $signatures = array();
/**
* Array index of the current signature
*
* @var integer
*/
protected $index = -1;
// }}}
// {{{ handle()
/**
* Handles a status line
*
* @param string $line the status line to handle.
*
* @return void
*/
public function handle($line)
{
$tokens = explode(' ', $line);
switch ($tokens[0]) {
case 'GOODSIG':
case 'EXPSIG':
case 'EXPKEYSIG':
case 'REVKEYSIG':
case 'BADSIG':
$signature = new Crypt_GPG_Signature();
// if there was a signature id, set it on the new signature
if ($this->signatureId != '') {
$signature->setId($this->signatureId);
$this->signatureId = '';
}
// Detect whether fingerprint or key id was returned and set
// signature values appropriately. Key ids are strings of either
// 16 or 8 hexadecimal characters. Fingerprints are strings of 40
// hexadecimal characters. The key id is the last 16 characters of
// the key fingerprint.
if (strlen($tokens[1]) > 16) {
$signature->setKeyFingerprint($tokens[1]);
$signature->setKeyId(substr($tokens[1], -16));
} else {
$signature->setKeyId($tokens[1]);
}
// get user id string
$string = implode(' ', array_splice($tokens, 2));
$string = rawurldecode($string);
$signature->setUserId(Crypt_GPG_UserId::parse($string));
$this->index++;
$this->signatures[$this->index] = $signature;
break;
case 'ERRSIG':
$signature = new Crypt_GPG_Signature();
// if there was a signature id, set it on the new signature
if ($this->signatureId != '') {
$signature->setId($this->signatureId);
$this->signatureId = '';
}
// Detect whether fingerprint or key id was returned and set
// signature values appropriately. Key ids are strings of either
// 16 or 8 hexadecimal characters. Fingerprints are strings of 40
// hexadecimal characters. The key id is the last 16 characters of
// the key fingerprint.
if (strlen($tokens[1]) > 16) {
$signature->setKeyFingerprint($tokens[1]);
$signature->setKeyId(substr($tokens[1], -16));
} else {
$signature->setKeyId($tokens[1]);
}
$this->index++;
$this->signatures[$this->index] = $signature;
break;
case 'VALIDSIG':
if (!array_key_exists($this->index, $this->signatures)) {
break;
}
$signature = $this->signatures[$this->index];
$signature->setValid(true);
$signature->setKeyFingerprint($tokens[1]);
if (strpos($tokens[3], 'T') === false) {
$signature->setCreationDate($tokens[3]);
} else {
$signature->setCreationDate(strtotime($tokens[3]));
}
if (array_key_exists(4, $tokens)) {
if (strpos($tokens[4], 'T') === false) {
$signature->setExpirationDate($tokens[4]);
} else {
$signature->setExpirationDate(strtotime($tokens[4]));
}
}
break;
case 'SIG_ID':
// note: signature id comes before new signature line and may not
// exist for some signature types
$this->signatureId = $tokens[1];
break;
}
}
// }}}
// {{{ getSignatures()
/**
* Gets the {@link Crypt_GPG_Signature} objects parsed by this handler
*
* @return array the signature objects parsed by this handler.
*/
public function getSignatures()
{
return $this->signatures;
}
// }}}
}
?>

@ -0,0 +1,106 @@
<?php
/*
+-------------------------------------------------------------------------+
| Abstract driver for the Enigma Plugin |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program 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; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
abstract class enigma_driver
{
/**
* Class constructor.
*
* @param string User name (email address)
*/
abstract function __construct($user);
/**
* Driver initialization.
*
* @return mixed NULL on success, enigma_error on failure
*/
abstract function init();
/**
* Encryption.
*/
abstract function encrypt($text, $keys);
/**
* Decryption..
*/
abstract function decrypt($text, $key, $passwd);
/**
* Signing.
*/
abstract function sign($text, $key, $passwd);
/**
* Signature verification.
*
* @param string Message body
* @param string Signature, if message is of type PGP/MIME and body doesn't contain it
*
* @return mixed Signature information (enigma_signature) or enigma_error
*/
abstract function verify($text, $signature);
/**
* Key/Cert file import.
*
* @param string File name or file content
* @param bollean True if first argument is a filename
*
* @return mixed Import status array or enigma_error
*/
abstract function import($content, $isfile=false);
/**
* Keys listing.
*
* @param string Optional pattern for key ID, user ID or fingerprint
*
* @return mixed Array of enigma_key objects or enigma_error
*/
abstract function list_keys($pattern='');
/**
* Single key information.
*
* @param string Key ID, user ID or fingerprint
*
* @return mixed Key (enigma_key) object or enigma_error
*/
abstract function get_key($keyid);
/**
* Key pair generation.
*
* @param array Key/User data
*
* @return mixed Key (enigma_key) object or enigma_error
*/
abstract function gen_key($data);
/**
* Key deletion.
*/
abstract function del_key($keyid);
}

@ -0,0 +1,305 @@
<?php
/*
+-------------------------------------------------------------------------+
| GnuPG (PGP) driver for the Enigma Plugin |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program 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; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
require_once 'Crypt/GPG.php';
class enigma_driver_gnupg extends enigma_driver
{
private $rc;
private $gpg;
private $homedir;
private $user;
function __construct($user)
{
$rcmail = rcmail::get_instance();
$this->rc = $rcmail;
$this->user = $user;
}
/**
* Driver initialization and environment checking.
* Should only return critical errors.
*
* @return mixed NULL on success, enigma_error on failure
*/
function init()
{
$homedir = $this->rc->config->get('enigma_pgp_homedir', INSTALL_PATH . '/plugins/enigma/home');
if (!$homedir)
return new enigma_error(enigma_error::E_INTERNAL,
"Option 'enigma_pgp_homedir' not specified");
// check if homedir exists (create it if not) and is readable
if (!file_exists($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Keys directory doesn't exists: $homedir");
if (!is_writable($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Keys directory isn't writeable: $homedir");
$homedir = $homedir . '/' . $this->user;
// check if user's homedir exists (create it if not) and is readable
if (!file_exists($homedir))
mkdir($homedir, 0700);
if (!file_exists($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Unable to create keys directory: $homedir");
if (!is_writable($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Unable to write to keys directory: $homedir");
$this->homedir = $homedir;
// Create Crypt_GPG object
try {
$this->gpg = new Crypt_GPG(array(
'homedir' => $this->homedir,
// 'debug' => true,
));
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
function encrypt($text, $keys)
{
/*
foreach ($keys as $key) {
$this->gpg->addEncryptKey($key);
}
$enc = $this->gpg->encrypt($text);
return $enc;
*/
}
function decrypt($text, $key, $passwd)
{
// $this->gpg->addDecryptKey($key, $passwd);
try {
$dec = $this->gpg->decrypt($text);
return $dec;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
function sign($text, $key, $passwd)
{
/*
$this->gpg->addSignKey($key, $passwd);
$signed = $this->gpg->sign($text, Crypt_GPG::SIGN_MODE_DETACHED);
return $signed;
*/
}
function verify($text, $signature)
{
try {
$verified = $this->gpg->verify($text, $signature);
return $this->parse_signature($verified[0]);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
public function import($content, $isfile=false)
{
try {
if ($isfile)
return $this->gpg->importKeyFile($content);
else
return $this->gpg->importKey($content);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
public function list_keys($pattern='')
{
try {
$keys = $this->gpg->getKeys($pattern);
$result = array();
//print_r($keys);
foreach ($keys as $idx => $key) {
$result[] = $this->parse_key($key);
unset($keys[$idx]);
}
//print_r($result);
return $result;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
public function get_key($keyid)
{
$list = $this->list_keys($keyid);
if (is_array($list))
return array_shift($list);
// error
return $list;
}
public function gen_key($data)
{
}
public function del_key($keyid)
{
// $this->get_key($keyid);
}
public function del_privkey($keyid)
{
try {
$this->gpg->deletePrivateKey($keyid);
return true;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
public function del_pubkey($keyid)
{
try {
$this->gpg->deletePublicKey($keyid);
return true;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Converts Crypt_GPG exception into Enigma's error object
*
* @param mixed Exception object
*
* @return enigma_error Error object
*/
private function get_error_from_exception($e)
{
$data = array();
if ($e instanceof Crypt_GPG_KeyNotFoundException) {
$error = enigma_error::E_KEYNOTFOUND;
$data['id'] = $e->getKeyId();
}
else if ($e instanceof Crypt_GPG_BadPassphraseException) {
$error = enigma_error::E_BADPASS;
$data['bad'] = $e->getBadPassphrases();
$data['missing'] = $e->getMissingPassphrases();
}
else if ($e instanceof Crypt_GPG_NoDataException)
$error = enigma_error::E_NODATA;
else if ($e instanceof Crypt_GPG_DeletePrivateKeyException)
$error = enigma_error::E_DELKEY;
else
$error = enigma_error::E_INTERNAL;
$msg = $e->getMessage();
return new enigma_error($error, $msg, $data);
}
/**
* Converts Crypt_GPG_Signature object into Enigma's signature object
*
* @param Crypt_GPG_Signature Signature object
*
* @return enigma_signature Signature object
*/
private function parse_signature($sig)
{
$user = $sig->getUserId();
$data = new enigma_signature();
$data->id = $sig->getId();
$data->valid = $sig->isValid();
$data->fingerprint = $sig->getKeyFingerprint();
$data->created = $sig->getCreationDate();
$data->expires = $sig->getExpirationDate();
$data->name = $user->getName();
$data->comment = $user->getComment();
$data->email = $user->getEmail();
return $data;
}
/**
* Converts Crypt_GPG_Key object into Enigma's key object
*
* @param Crypt_GPG_Key Key object
*
* @return enigma_key Key object
*/
private function parse_key($key)
{
$ekey = new enigma_key();
foreach ($key->getUserIds() as $idx => $user) {
$id = new enigma_userid();
$id->name = $user->getName();
$id->comment = $user->getComment();
$id->email = $user->getEmail();
$id->valid = $user->isValid();
$id->revoked = $user->isRevoked();
$ekey->users[$idx] = $id;
}
$ekey->name = trim($ekey->users[0]->name . ' <' . $ekey->users[0]->email . '>');
foreach ($key->getSubKeys() as $idx => $subkey) {
$skey = new enigma_subkey();
$skey->id = $subkey->getId();
$skey->revoked = $subkey->isRevoked();
$skey->created = $subkey->getCreationDate();
$skey->expires = $subkey->getExpirationDate();
$skey->fingerprint = $subkey->getFingerprint();
$skey->has_private = $subkey->hasPrivate();
$skey->can_sign = $subkey->canSign();
$skey->can_encrypt = $subkey->canEncrypt();
$ekey->subkeys[$idx] = $skey;
};
$ekey->id = $ekey->subkeys[0]->id;
return $ekey;
}
}

@ -0,0 +1,547 @@
<?php
/*
+-------------------------------------------------------------------------+
| Engine of the Enigma Plugin |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program 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; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
/*
RFC2440: OpenPGP Message Format
RFC3156: MIME Security with OpenPGP
RFC3851: S/MIME
*/
class enigma_engine
{
private $rc;
private $enigma;
private $pgp_driver;
private $smime_driver;
public $decryptions = array();
public $signatures = array();
public $signed_parts = array();
/**
* Plugin initialization.
*/
function __construct($enigma)
{
$rcmail = rcmail::get_instance();
$this->rc = $rcmail;
$this->enigma = $enigma;
}
/**
* PGP driver initialization.
*/
function load_pgp_driver()
{
if ($this->pgp_driver)
return;
$driver = 'enigma_driver_' . $this->rc->config->get('enigma_pgp_driver', 'gnupg');
$username = $this->rc->user->get_username();
// Load driver
$this->pgp_driver = new $driver($username);
if (!$this->pgp_driver) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: Unable to load PGP driver: $driver"
), true, true);
}
// Initialise driver
$result = $this->pgp_driver->init();
if ($result instanceof enigma_error) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: ".$result->getMessage()
), true, true);
}
}
/**
* S/MIME driver initialization.
*/
function load_smime_driver()
{
if ($this->smime_driver)
return;
// NOT IMPLEMENTED!
return;
$driver = 'enigma_driver_' . $this->rc->config->get('enigma_smime_driver', 'phpssl');
$username = $this->rc->user->get_username();
// Load driver
$this->smime_driver = new $driver($username);
if (!$this->smime_driver) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: Unable to load S/MIME driver: $driver"
), true, true);
}
// Initialise driver
$result = $this->smime_driver->init();
if ($result instanceof enigma_error) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: ".$result->getMessage()
), true, true);
}
}
/**
* Handler for plain/text message.
*
* @param array Reference to hook's parameters
*/
function parse_plain(&$p)
{
$part = $p['structure'];
// Get message body from IMAP server
$this->set_part_body($part, $p['object']->uid);
// @TODO: big message body can be a file resource
// PGP signed message
if (preg_match('/^-----BEGIN PGP SIGNED MESSAGE-----/', $part->body)) {
$this->parse_plain_signed($p);
}
// PGP encrypted message
else if (preg_match('/^-----BEGIN PGP MESSAGE-----/', $part->body)) {
$this->parse_plain_encrypted($p);
}
}
/**
* Handler for multipart/signed message.
*
* @param array Reference to hook's parameters
*/
function parse_signed(&$p)
{
$struct = $p['structure'];
// S/MIME
if ($struct->parts[1] && $struct->parts[1]->mimetype == 'application/pkcs7-signature') {
$this->parse_smime_signed($p);
}
// PGP/MIME:
// The multipart/signed body MUST consist of exactly two parts.
// The first part contains the signed data in MIME canonical format,
// including a set of appropriate content headers describing the data.
// The second body MUST contain the PGP digital signature. It MUST be
// labeled with a content type of "application/pgp-signature".
else if ($struct->parts[1] && $struct->parts[1]->mimetype == 'application/pgp-signature') {
$this->parse_pgp_signed($p);
}
}
/**
* Handler for multipart/encrypted message.
*
* @param array Reference to hook's parameters
*/
function parse_encrypted(&$p)
{
$struct = $p['structure'];
// S/MIME
if ($struct->mimetype == 'application/pkcs7-mime') {
$this->parse_smime_encrypted($p);
}
// PGP/MIME:
// The multipart/encrypted MUST consist of exactly two parts. The first
// MIME body part must have a content type of "application/pgp-encrypted".
// This body contains the control information.
// The second MIME body part MUST contain the actual encrypted data. It
// must be labeled with a content type of "application/octet-stream".
else if ($struct->parts[0] && $struct->parts[0]->mimetype == 'application/pgp-encrypted' &&
$struct->parts[1] && $struct->parts[1]->mimetype == 'application/octet-stream'
) {
$this->parse_pgp_encrypted($p);
}
}
/**
* Handler for plain signed message.
* Excludes message and signature bodies and verifies signature.
*
* @param array Reference to hook's parameters
*/
private function parse_plain_signed(&$p)
{
$this->load_pgp_driver();
$part = $p['structure'];
// Verify signature
if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
$sig = $this->pgp_verify($part->body);
}
// @TODO: Handle big bodies using (temp) files
// In this way we can use fgets on string as on file handle
$fh = fopen('php://memory', 'br+');
// @TODO: fopen/fwrite errors handling
if ($fh) {
fwrite($fh, $part->body);
rewind($fh);
}
$part->body = null;
// Extract body (and signature?)
while (!feof($fh)) {
$line = fgets($fh, 1024);
if ($part->body === null)
$part->body = '';
else if (preg_match('/^-----BEGIN PGP SIGNATURE-----/', $line))
break;
else
$part->body .= $line;
}
// Remove "Hash" Armor Headers
$part->body = preg_replace('/^.*\r*\n\r*\n/', '', $part->body);
// de-Dash-Escape (RFC2440)
$part->body = preg_replace('/(^|\n)- -/', '\\1-', $part->body);
// Store signature data for display
if (!empty($sig)) {
$this->signed_parts[$part->mime_id] = $part->mime_id;
$this->signatures[$part->mime_id] = $sig;
}
fclose($fh);
}
/**
* Handler for PGP/MIME signed message.
* Verifies signature.
*
* @param array Reference to hook's parameters
*/
private function parse_pgp_signed(&$p)
{
$this->load_pgp_driver();
$struct = $p['structure'];
// Verify signature
if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
$msg_part = $struct->parts[0];
$sig_part = $struct->parts[1];
// Get bodies
$this->set_part_body($msg_part, $p['object']->uid);
$this->set_part_body($sig_part, $p['object']->uid);
// Verify
$sig = $this->pgp_verify($msg_part->body, $sig_part->body);
// Store signature data for display
$this->signatures[$struct->mime_id] = $sig;
// Message can be multipart (assign signature to each subpart)
if (!empty($msg_part->parts)) {
foreach ($msg_part->parts as $part)
$this->signed_parts[$part->mime_id] = $struct->mime_id;
}
else
$this->signed_parts[$msg_part->mime_id] = $struct->mime_id;
// Remove signature file from attachments list
unset($struct->parts[1]);
}
}
/**
* Handler for S/MIME signed message.
* Verifies signature.
*
* @param array Reference to hook's parameters
*/
private function parse_smime_signed(&$p)
{
$this->load_smime_driver();
}
/**
* Handler for plain encrypted message.
*
* @param array Reference to hook's parameters
*/
private function parse_plain_encrypted(&$p)
{
$this->load_pgp_driver();
$part = $p['structure'];
// Get body
$this->set_part_body($part, $p['object']->uid);
// Decrypt
$result = $this->pgp_decrypt($part->body);
// Store decryption status
$this->decryptions[$part->mime_id] = $result;
// Parse decrypted message
if ($result === true) {
// @TODO
}
}
/**
* Handler for PGP/MIME encrypted message.
*
* @param array Reference to hook's parameters
*/
private function parse_pgp_encrypted(&$p)
{
$this->load_pgp_driver();
$struct = $p['structure'];
$part = $struct->parts[1];
// Get body
$this->set_part_body($part, $p['object']->uid);
// Decrypt
$result = $this->pgp_decrypt($part->body);
$this->decryptions[$part->mime_id] = $result;
//print_r($part);
// Parse decrypted message
if ($result === true) {
// @TODO
}
else {
// Make sure decryption status message will be displayed
$part->type = 'content';
$p['object']->parts[] = $part;
}
}
/**
* Handler for S/MIME encrypted message.
*
* @param array Reference to hook's parameters
*/
private function parse_smime_encrypted(&$p)
{
$this->load_smime_driver();
}
/**
* PGP signature verification.
*
* @param mixed Message body
* @param mixed Signature body (for MIME messages)
*
* @return mixed enigma_signature or enigma_error
*/
private function pgp_verify(&$msg_body, $sig_body=null)
{
// @TODO: Handle big bodies using (temp) files
// @TODO: caching of verification result
$sig = $this->pgp_driver->verify($msg_body, $sig_body);
if (($sig instanceof enigma_error) && $sig->getCode() != enigma_error::E_KEYNOTFOUND)
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $error->getMessage()
), true, false);
//print_r($sig);
return $sig;
}
/**
* PGP message decryption.
*
* @param mixed Message body
*
* @return mixed True or enigma_error
*/
private function pgp_decrypt(&$msg_body)
{
// @TODO: Handle big bodies using (temp) files
// @TODO: caching of verification result
$result = $this->pgp_driver->decrypt($msg_body, $key, $pass);
//print_r($result);
if ($result instanceof enigma_error) {
$err_code = $result->getCode();
if (!in_array($err_code, array(enigma_error::E_KEYNOTFOUND, enigma_error::E_BADPASS)))
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
return $result;
}
// $msg_body = $result;
return true;
}
/**
* PGP keys listing.
*
* @param mixed Key ID/Name pattern
*
* @return mixed Array of keys or enigma_error
*/
function list_keys($pattern='')
{
$this->load_pgp_driver();
$result = $this->pgp_driver->list_keys($pattern);
if ($result instanceof enigma_error) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
}
return $result;
}
/**
* PGP key details.
*
* @param mixed Key ID
*
* @return mixed enigma_key or enigma_error
*/
function get_key($keyid)
{
$this->load_pgp_driver();
$result = $this->pgp_driver->get_key($keyid);
if ($result instanceof enigma_error) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
}
return $result;
}
/**
* PGP keys/certs importing.
*
* @param mixed Import file name or content
* @param boolean True if first argument is a filename
*
* @return mixed Import status data array or enigma_error
*/
function import_key($content, $isfile=false)
{
$this->load_pgp_driver();
$result = $this->pgp_driver->import($content, $isfile);
if ($result instanceof enigma_error) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
}
else {
$result['imported'] = $result['public_imported'] + $result['private_imported'];
$result['unchanged'] = $result['public_unchanged'] + $result['private_unchanged'];
}
return $result;
}
/**
* Handler for keys/certs import request action
*/
function import_file()
{
$uid = get_input_value('_uid', RCUBE_INPUT_POST);
$mbox = get_input_value('_mbox', RCUBE_INPUT_POST);
$mime_id = get_input_value('_part', RCUBE_INPUT_POST);
if ($uid && $mime_id) {
$part = $this->rc->imap->get_message_part($uid, $mime_id);
}
if ($part && is_array($result = $this->import_key($part))) {
$this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
array('new' => $result['imported'], 'old' => $result['unchanged']));
}
else
$this->rc->output->show_message('enigma.keysimportfailed', 'error');
$this->rc->output->send();
}
/**
* Checks if specified message part contains body data.
* If body is not set it will be fetched from IMAP server.
*
* @param rcube_message_part Message part object
* @param integer Message UID
*/
private function set_part_body($part, $uid)
{
// @TODO: Create such function in core
// @TODO: Handle big bodies using file handles
if (!isset($part->body)) {
$part->body = $this->rc->imap->get_message_part(
$uid, $part->mime_id, $part);
}
}
/**
* Adds CSS style file to the page header.
*/
private function add_css()
{
$skin = $this->rc->config->get('skin');
if (!file_exists($this->home . "/skins/$skin/enigma.css"))
$skin = 'default';
$this->include_stylesheet("skins/$skin/enigma.css");
}
}

@ -0,0 +1,62 @@
<?php
/*
+-------------------------------------------------------------------------+
| Error class for the Enigma Plugin |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program 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; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_error
{
private $code;
private $message;
private $data = array();
// error codes
const E_OK = 0;
const E_INTERNAL = 1;
const E_NODATA = 2;
const E_KEYNOTFOUND = 3;
const E_DELKEY = 4;
const E_BADPASS = 5;
function __construct($code = null, $message = '', $data = array())
{
$this->code = $code;
$this->message = $message;
$this->data = $data;
}
function getCode()
{
return $this->code;
}
function getMessage()
{
return $this->message;
}
function getData($name)
{
if ($name)
return $this->data[$name];
else
return $this->data;
}
}

@ -0,0 +1,129 @@
<?php
/*
+-------------------------------------------------------------------------+
| Key class for the Enigma Plugin |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program 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; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_key
{
public $id;
public $name;
public $users = array();
public $subkeys = array();
const TYPE_UNKNOWN = 0;
const TYPE_KEYPAIR = 1;
const TYPE_PUBLIC = 2;
/**
* Keys list sorting callback for usort()
*/
static function cmp($a, $b)
{
return strcmp($a->name, $b->name);
}
/**
* Returns key type
*/
function get_type()
{
if ($this->subkeys[0]->has_private)
return enigma_key::TYPE_KEYPAIR;
else if (!empty($this->subkeys[0]))
return enigma_key::TYPE_PUBLIC;
return enigma_key::TYPE_UNKNOWN;
}
/**
* Returns true if all user IDs are revoked
*/
function is_revoked()
{
foreach ($this->subkeys as $subkey)
if (!$subkey->revoked)
return false;
return true;
}
/**
* Returns true if any user ID is valid
*/
function is_valid()
{
foreach ($this->users as $user)
if ($user->valid)
return true;
return false;
}
/**
* Returns true if any of subkeys is not expired
*/
function is_expired()
{
$now = time();
foreach ($this->subkeys as $subkey)
if (!$subkey->expires || $subkey->expires > $now)
return true;
return false;
}
/**
* Converts long ID or Fingerprint to short ID
* Crypt_GPG uses internal, but e.g. Thunderbird's Enigmail displays short ID
*
* @param string Key ID or fingerprint
* @return string Key short ID
*/
static function format_id($id)
{
// E.g. 04622F2089E037A5 => 89E037A5
return substr($id, -8);
}
/**
* Formats fingerprint string
*
* @param string Key fingerprint
*
* @return string Formatted fingerprint (with spaces)
*/
static function format_fingerprint($fingerprint)
{
if (!$fingerprint)
return '';
$result = '';
for ($i=0; $i<40; $i++) {
if ($i % 4 == 0)
$result .= ' ';
$result .= $fingerprint[$i];
}
return $result;
}
}

@ -0,0 +1,34 @@
<?php
/*
+-------------------------------------------------------------------------+
| Signature class for the Enigma Plugin |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program 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; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_signature
{
public $id;
public $valid;
public $fingerprint;
public $created;
public $expires;
public $name;
public $comment;
public $email;
}

@ -0,0 +1,57 @@
<?php
/*
+-------------------------------------------------------------------------+
| SubKey class for the Enigma Plugin |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program 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; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_subkey
{
public $id;
public $fingerprint;
public $expires;
public $created;
public $revoked;
public $has_private;
public $can_sign;
public $can_encrypt;
/**
* Converts internal ID to short ID
* Crypt_GPG uses internal, but e.g. Thunderbird's Enigmail displays short ID
*
* @return string Key ID
*/
function get_short_id()
{
// E.g. 04622F2089E037A5 => 89E037A5
return enigma_key::format_id($this->id);
}
/**
* Getter for formatted fingerprint
*
* @return string Formatted fingerprint
*/
function get_fingerprint()
{
return enigma_key::format_fingerprint($this->fingerprint);
}
}

@ -0,0 +1,456 @@
<?php
/*
+-------------------------------------------------------------------------+
| User Interface for the Enigma Plugin |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program 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; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_ui
{
private $rc;
private $enigma;
private $home;
private $css_added;
private $data;
function __construct($enigma_plugin, $home='')
{
$this->enigma = $enigma_plugin;
$this->rc = $enigma_plugin->rc;
// we cannot use $enigma_plugin->home here
$this->home = $home;
}
/**
* UI initialization and requests handlers.
*
* @param string Preferences section
*/
function init($section='')
{
$this->enigma->include_script('enigma.js');
// Enigma actions
if ($this->rc->action == 'plugin.enigma') {
$action = get_input_value('_a', RCUBE_INPUT_GPC);
switch ($action) {
case 'keyedit':
$this->key_edit();
break;
case 'keyimport':
$this->key_import();
break;
case 'keysearch':
case 'keylist':
$this->key_list();
break;
case 'keyinfo':
default:
$this->key_info();
}
}
// Message composing UI
else if ($this->rc->action == 'compose') {
$this->compose_ui();
}
// Preferences UI
else { // if ($this->rc->action == 'edit-prefs') {
if ($section == 'enigmacerts') {
$this->rc->output->add_handlers(array(
'keyslist' => array($this, 'tpl_certs_list'),
'keyframe' => array($this, 'tpl_cert_frame'),
'countdisplay' => array($this, 'tpl_certs_rowcount'),
'searchform' => array($this->rc->output, 'search_form'),
));
$this->rc->output->set_pagetitle($this->enigma->gettext('enigmacerts'));
$this->rc->output->send('enigma.certs');
}
else {
$this->rc->output->add_handlers(array(
'keyslist' => array($this, 'tpl_keys_list'),
'keyframe' => array($this, 'tpl_key_frame'),
'countdisplay' => array($this, 'tpl_keys_rowcount'),
'searchform' => array($this->rc->output, 'search_form'),
));
$this->rc->output->set_pagetitle($this->enigma->gettext('enigmakeys'));
$this->rc->output->send('enigma.keys');
}
}
}
/**
* Adds CSS style file to the page header.
*/
function add_css()
{
if ($this->css_loaded)
return;
$skin = $this->rc->config->get('skin');
if (!file_exists($this->home . "/skins/$skin/enigma.css"))
$skin = 'default';
$this->enigma->include_stylesheet("skins/$skin/enigma.css");
$this->css_added = true;
}
/**
* Template object for key info/edit frame.
*
* @param array Object attributes
*
* @return string HTML output
*/
function tpl_key_frame($attrib)
{
if (!$attrib['id']) {
$attrib['id'] = 'rcmkeysframe';
}
$attrib['name'] = $attrib['id'];
$this->rc->output->set_env('contentframe', $attrib['name']);
$this->rc->output->set_env('blankpage', $attrib['src'] ?
$this->rc->output->abs_url($attrib['src']) : 'program/blank.gif');
return html::tag('iframe', $attrib);
}
/**
* Template object for list of keys.
*
* @param array Object attributes
*
* @return string HTML content
*/
function tpl_keys_list($attrib)
{
// add id to message list table if not specified
if (!strlen($attrib['id'])) {
$attrib['id'] = 'rcmenigmakeyslist';
}
// define list of cols to be displayed
$a_show_cols = array('name');
// create XHTML table
$out = rcube_table_output($attrib, array(), $a_show_cols, 'id');
// set client env
$this->rc->output->add_gui_object('keyslist', $attrib['id']);
$this->rc->output->include_script('list.js');
// add some labels to client
$this->rc->output->add_label('enigma.keyconfirmdelete');
return $out;
}
/**
* Key listing (and searching) request handler
*/
private function key_list()
{
$this->enigma->load_engine();
$pagesize = $this->rc->config->get('pagesize', 100);
$page = max(intval(get_input_value('_p', RCUBE_INPUT_GPC)), 1);
$search = get_input_value('_q', RCUBE_INPUT_GPC);
// define list of cols to be displayed
$a_show_cols = array('name');
$result = array();
// Get the list
$list = $this->enigma->engine->list_keys($search);
if ($list && ($list instanceof enigma_error))
$this->rc->output->show_message('enigma.keylisterror', 'error');
else if (empty($list))
$this->rc->output->show_message('enigma.nokeysfound', 'notice');
else {
if (is_array($list)) {
// Save the size
$listsize = count($list);
// Sort the list by key (user) name
usort($list, array('enigma_key', 'cmp'));
// Slice current page
$list = array_slice($list, ($page - 1) * $pagesize, $pagesize);
$size = count($list);
// Add rows
foreach($list as $idx => $key) {
$this->rc->output->command('enigma_add_list_row',
array('name' => Q($key->name), 'id' => $key->id));
}
}
}
$this->rc->output->set_env('search_request', $search);
$this->rc->output->set_env('pagecount', ceil($listsize/$pagesize));
$this->rc->output->set_env('current_page', $page);
$this->rc->output->command('set_rowcount',
$this->get_rowcount_text($listsize, $size, $page));
$this->rc->output->send();
}
/**
* Template object for list records counter.
*
* @param array Object attributes
*
* @return string HTML output
*/
function tpl_keys_rowcount($attrib)
{
if (!$attrib['id'])
$attrib['id'] = 'rcmcountdisplay';
$this->rc->output->add_gui_object('countdisplay', $attrib['id']);
return html::span($attrib, $this->get_rowcount_text());
}
/**
* Returns text representation of list records counter
*/
private function get_rowcount_text($all=0, $curr_count=0, $page=1)
{
if (!$curr_count)
$out = $this->enigma->gettext('nokeysfound');
else {
$pagesize = $this->rc->config->get('pagesize', 100);
$first = ($page - 1) * $pagesize;
$out = $this->enigma->gettext(array(
'name' => 'keysfromto',
'vars' => array(
'from' => $first + 1,
'to' => $first + $curr_count,
'count' => $all)
));
}
return $out;
}
/**
* Key information page handler
*/
private function key_info()
{
$id = get_input_value('_id', RCUBE_INPUT_GET);
$this->enigma->load_engine();
$res = $this->enigma->engine->get_key($id);
if ($res instanceof enigma_key)
$this->data = $res;
else { // error
$this->rc->output->show_message('enigma.keyopenerror', 'error');
$this->rc->output->command('parent.enigma_loadframe');
$this->rc->output->send('iframe');
}
$this->rc->output->add_handlers(array(
'keyname' => array($this, 'tpl_key_name'),
'keydata' => array($this, 'tpl_key_data'),
));
$this->rc->output->set_pagetitle($this->enigma->gettext('keyinfo'));
$this->rc->output->send('enigma.keyinfo');
}
/**
* Template object for key name
*/
function tpl_key_name($attrib)
{
return Q($this->data->name);
}
/**
* Template object for key information page content
*/
function tpl_key_data($attrib)
{
$out = '';
$table = new html_table(array('cols' => 2));
// Key user ID
$table->add('title', $this->enigma->gettext('keyuserid'));
$table->add(null, Q($this->data->name));
// Key ID
$table->add('title', $this->enigma->gettext('keyid'));
$table->add(null, $this->data->subkeys[0]->get_short_id());
// Key type
$keytype = $this->data->get_type();
if ($keytype == enigma_key::TYPE_KEYPAIR)
$type = $this->enigma->gettext('typekeypair');
else if ($keytype == enigma_key::TYPE_PUBLIC)
$type = $this->enigma->gettext('typepublickey');
$table->add('title', $this->enigma->gettext('keytype'));
$table->add(null, $type);
// Key fingerprint
$table->add('title', $this->enigma->gettext('fingerprint'));
$table->add(null, $this->data->subkeys[0]->get_fingerprint());
$out .= html::tag('fieldset', null,
html::tag('legend', null,
$this->enigma->gettext('basicinfo')) . $table->show($attrib));
// Subkeys
$table = new html_table(array('cols' => 6));
// Columns: Type, ID, Algorithm, Size, Created, Expires
$out .= html::tag('fieldset', null,
html::tag('legend', null,
$this->enigma->gettext('subkeys')) . $table->show($attrib));
// Additional user IDs
$table = new html_table(array('cols' => 2));
// Columns: User ID, Validity
$out .= html::tag('fieldset', null,
html::tag('legend', null,
$this->enigma->gettext('userids')) . $table->show($attrib));
return $out;
}
/**
* Key import page handler
*/
private function key_import()
{
// Import process
if ($_FILES['_file']['tmp_name'] && is_uploaded_file($_FILES['_file']['tmp_name'])) {
$this->enigma->load_engine();
$result = $this->enigma->engine->import_key($_FILES['_file']['tmp_name'], true);
if (is_array($result)) {
// reload list if any keys has been added
if ($result['imported']) {
$this->rc->output->command('parent.enigma_list', 1);
}
else
$this->rc->output->command('parent.enigma_loadframe');
$this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
array('new' => $result['imported'], 'old' => $result['unchanged']));
$this->rc->output->send('iframe');
}
else
$this->rc->output->show_message('enigma.keysimportfailed', 'error');
}
else if ($err = $_FILES['_file']['error']) {
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
$this->rc->output->show_message('filesizeerror', 'error',
array('size' => show_bytes(parse_bytes(ini_get('upload_max_filesize')))));
} else {
$this->rc->output->show_message('fileuploaderror', 'error');
}
}
$this->rc->output->add_handlers(array(
'importform' => array($this, 'tpl_key_import_form'),
));
$this->rc->output->set_pagetitle($this->enigma->gettext('keyimport'));
$this->rc->output->send('enigma.keyimport');
}
/**
* Template object for key import (upload) form
*/
function tpl_key_import_form($attrib)
{
$attrib += array('id' => 'rcmKeyImportForm');
$upload = new html_inputfield(array('type' => 'file', 'name' => '_file',
'id' => 'rcmimportfile', 'size' => 30));
$form = html::p(null,
Q($this->enigma->gettext('keyimporttext'), 'show')
. html::br() . html::br() . $upload->show()
);
$this->rc->output->add_label('selectimportfile', 'importwait');
$this->rc->output->add_gui_object('importform', $attrib['id']);
$out = $this->rc->output->form_tag(array(
'action' => $this->rc->url(array('action' => 'plugin.enigma', 'a' => 'keyimport')),
'method' => 'post',
'enctype' => 'multipart/form-data') + $attrib,
$form);
return $out;
}
private function compose_ui()
{
// Options menu button
// @TODO: make this work with non-default skins
$this->enigma->add_button(array(
'name' => 'enigmamenu',
'imagepas' => 'skins/default/enigma.png',
'imageact' => 'skins/default/enigma.png',
'onclick' => "rcmail_ui.show_popup('enigmamenu', true); return false",
'title' => 'securityoptions',
'domain' => 'enigma',
), 'toolbar');
// Options menu contents
$this->enigma->add_hook('render_page', array($this, 'compose_menu'));
}
function compose_menu($p)
{
$menu = new html_table(array('cols' => 2));
$chbox = new html_checkbox(array('value' => 1));
$menu->add(null, html::label(array('for' => 'enigmadefaultopt'),
Q($this->enigma->gettext('identdefault'))));
$menu->add(null, $chbox->show(1, array('name' => '_enigma_default', 'id' => 'enigmadefaultopt')));
$menu->add(null, html::label(array('for' => 'enigmasignopt'),
Q($this->enigma->gettext('signmsg'))));
$menu->add(null, $chbox->show(1, array('name' => '_enigma_sign', 'id' => 'enigmasignopt')));
$menu->add(null, html::label(array('for' => 'enigmacryptopt'),
Q($this->enigma->gettext('encryptmsg'))));
$menu->add(null, $chbox->show(1, array('name' => '_enigma_crypt', 'id' => 'enigmacryptopt')));
$menu = html::div(array('id' => 'enigmamenu', 'class' => 'popupmenu'),
$menu->show());
$p['content'] = preg_replace('/(<form name="form"[^>]+>)/i', '\\1'."\n$menu", $p['content']);
return $p;
}
}

@ -0,0 +1,31 @@
<?php
/*
+-------------------------------------------------------------------------+
| User ID class for the Enigma Plugin |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program 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; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_userid
{
public $revoked;
public $valid;
public $name;
public $comment;
public $email;
}

@ -0,0 +1,53 @@
<?php
$labels = array();
$labels['enigmasettings'] = 'Enigma: Settings';
$labels['enigmacerts'] = 'Enigma: Certificates (S/MIME)';
$labels['enigmakeys'] = 'Enigma: Keys (PGP)';
$labels['keysfromto'] = 'Keys $from to $to of $count';
$labels['keyname'] = 'Name';
$labels['keyid'] = 'Key ID';
$labels['keyuserid'] = 'User ID';
$labels['keytype'] = 'Key type';
$labels['fingerprint'] = 'Fingerprint';
$labels['subkeys'] = 'Subkeys';
$labels['basicinfo'] = 'Basic Information';
$labels['userids'] = 'Additional User IDs';
$labels['typepublickey'] = 'public key';
$labels['typekeypair'] = 'key pair';
$labels['keyattfound'] = 'This message contains attached PGP key(s).';
$labels['keyattimport'] = 'Import key(s)';
$labels['createkeys'] = 'Create a new key pair';
$labels['importkeys'] = 'Import key(s)';
$labels['exportkeys'] = 'Export key(s)';
$labels['deletekeys'] = 'Delete key(s)';
$labels['keyactions'] = 'Key actions...';
$labels['keydisable'] = 'Disable key';
$labels['keyrevoke'] = 'Revoke key';
$labels['keysend'] = 'Send public key in a message';
$labels['keychpass'] = 'Change password';
$labels['securityoptions'] = 'Message security options...';
$labels['identdefault'] = 'Use settings of selected identity';
$labels['encryptmsg'] = 'Encrypt this message';
$labels['signmsg'] = 'Digitally sign this message';
$messages = array();
$messages['sigvalid'] = 'Verified signature from $sender.';
$messages['siginvalid'] = 'Invalid signature from $sender.';
$messages['signokey'] = 'Unverified signature. Public key not found. Key ID: $keyid.';
$messages['sigerror'] = 'Unverified signature. Internal error.';
$messages['decryptok'] = 'Message decrypted.';
$messages['decrypterror'] = 'Decryption failed.';
$messages['decryptnokey'] = 'Decryption failed. Private key not found. Key ID: $keyid.';
$messages['decryptbadpass'] = 'Decryption failed. Bad password.';
$messages['nokeysfound'] = 'No keys found';
$messages['keyopenerror'] = 'Unable to get key information! Internal error.';
$messages['keylisterror'] = 'Unable to list keys! Internal error.';
$messages['keysimportfailed'] = 'Unable to import key(s)! Internal error.';
$messages['keysimportsuccess'] = 'Key(s) imported successfully. Imported: $new, unchanged: $old.';
$messages['keyconfirmdelete'] = 'Are you sure, you want to delete selected key(s)?';
$messages['keyimporttext'] = 'You can import private and public key(s) or revocation signatures in ASCII-Armor format.';
?>

@ -0,0 +1,55 @@
<?php
// EN-Revision: 4203
$labels = array();
$labels['enigmasettings'] = 'Enigma: 設定';
$labels['enigmacerts'] = 'Enigma: 証明書 (S/MIME)';
$labels['enigmakeys'] = 'Enigma: 鍵 (PGP)';
$labels['keysfromto'] = '鍵の一覧 $from $to (合計: $count )';
$labels['keyname'] = '名前';
$labels['keyid'] = '鍵 ID';
$labels['keyuserid'] = 'ユーザー ID';
$labels['keytype'] = '鍵の種類';
$labels['fingerprint'] = '指紋';
$labels['subkeys'] = 'Subkeys';
$labels['basicinfo'] = '基本情報';
$labels['userids'] = '追加のユーザー ID';
$labels['typepublickey'] = '公開鍵';
$labels['typekeypair'] = '鍵のペア';
$labels['keyattfound'] = 'このメールは PGP 鍵の添付があります。';
$labels['keyattimport'] = '鍵のインポート';
$labels['createkeys'] = '新しい鍵のペアを作成する';
$labels['importkeys'] = '鍵のインポート';
$labels['exportkeys'] = '鍵のエクスポート';
$labels['deletekeys'] = '鍵の削除';
$labels['keyactions'] = '鍵の操作...';
$labels['keydisable'] = '鍵を無効にする';
$labels['keyrevoke'] = '鍵を取り消す';
$labels['keysend'] = 'メッセージに公開鍵を含んで送信する';
$labels['keychpass'] = 'パスワードの変更';
$labels['securityoptions'] = 'メールのセキュリティ オプション...';
$labels['identdefault'] = '選択した識別子の設定を使う';
$labels['encryptmsg'] = 'このメールの暗号化';
$labels['signmsg'] = 'このメールのデジタル署名';
$messages = array();
$messages['sigvalid'] = '$sender からの署名を検証しました。';
$messages['siginvalid'] = '$sender からの署名が正しくありません。';
$messages['signokey'] = '署名は未検証です。公開鍵が見つかりません。鍵 ID: $keyid';
$messages['sigerror'] = '署名は未検証です。内部エラーです。';
$messages['decryptok'] = 'メールを復号しました。';
$messages['decrypterror'] = '復号に失敗しました。';
$messages['decryptnokey'] = '復号に失敗しました。秘密鍵が見つかりません。鍵 ID: $keyid.';
$messages['decryptbadpass'] = '復号に失敗しました。パスワードが正しくありません。';
$messages['nokeysfound'] = '鍵が見つかりません。';
$messages['keyopenerror'] = '鍵情報の取得に失敗しました! 内部エラーです。';
$messages['keylisterror'] = '鍵情報のリストに失敗しました! 内部エラーです。';
$messages['keysimportfailed'] = '鍵のインポートに失敗しました! 内部エラーです。';
$messages['keysimportsuccess'] = '鍵をインポートしました。インポート: $new, 未変更: $old';
$messages['keyconfirmdelete'] = '選択した鍵を本当に削除しますか?';
$messages['keyimporttext'] = '秘密鍵と公開鍵のインポート、または ASCII 形式の署名を無効にできます。';
?>

@ -0,0 +1,65 @@
<?php
/*
+-----------------------------------------------------------------------+
| plugins/enigma/localization/ru_RU.inc |
| |
| Russian translation for roundcube/enigma plugin |
| Copyright (C) 2010 |
| Licensed under the GNU GPL |
| |
+-----------------------------------------------------------------------+
| Author: Sergey Dukachev <iam@dukess.ru> |
| Updates: |
+-----------------------------------------------------------------------+
@version 2010-12-23
*/
$labels = array();
$labels['enigmasettings'] = 'Enigma: Настройки';
$labels['enigmacerts'] = 'Enigma: Сертификаты (S/MIME)';
$labels['enigmakeys'] = 'Enigma: Ключи (PGP)';
$labels['keysfromto'] = 'Ключи от $from к $to в количестве $count';
$labels['keyname'] = 'Имя';
$labels['keyid'] = 'Идентификатор ключа';
$labels['keyuserid'] = 'Идентификатор пользователя';
$labels['keytype'] = 'Тип ключа';
$labels['fingerprint'] = 'Отпечаток (хэш) ключа';
$labels['subkeys'] = 'Подразделы';
$labels['basicinfo'] = 'Основные сведения';
$labels['userids'] = 'Дополнительные идентификаторы пользователя';
$labels['typepublickey'] = 'Открытый ключ';
$labels['typekeypair'] = 'пара ключей';
$labels['keyattfound'] = 'Это сообщение содержит один или несколько ключей PGP.';
$labels['keyattimport'] = 'Импортировать ключи';
$labels['createkeys'] = 'Создать новую пару ключей';
$labels['importkeys'] = 'Импортировать ключ(и)';
$labels['exportkeys'] = 'Экспортировать ключ(и)';
$labels['deletekeys'] = 'Удалить ключ(и)';
$labels['keyactions'] = 'Действия с ключами...';
$labels['keydisable'] = 'Отключить ключ';
$labels['keyrevoke'] = 'Отозвать ключ';
$labels['keysend'] = 'Отправить публичный ключ в собщении';
$labels['keychpass'] = 'Изменить пароль';
$messages = array();
$messages['sigvalid'] = 'Проверенная подпись у $sender.';
$messages['siginvalid'] = 'Неверная подпись у $sender.';
$messages['signokey'] = 'Непроверяемая подпись. Открытый ключ не найден. Идентификатор ключа: $keyid.';
$messages['sigerror'] = 'Непроверяемая подпись. Внутренняя ошибка.';
$messages['decryptok'] = 'Сообщение расшифровано.';
$messages['decrypterror'] = 'Расшифровка не удалась.';
$messages['decryptnokey'] = 'Расшифровка не удалась. Секретный ключ не найден. Идентификатор ключа: $keyid.';
$messages['decryptbadpass'] = 'Расшифровка не удалась. Неправильный пароль.';
$messages['nokeysfound'] = 'Ключи не найдены';
$messages['keyopenerror'] = 'Невозможно получить информацию о ключе! Внутренняя ошибка.';
$messages['keylisterror'] = 'Невозможно сделать список ключей! Внутренняя ошибка.';
$messages['keysimportfailed'] = 'Невозможно импортировать ключ(и)! Внутренняя ошибка.';
$messages['keysimportsuccess'] = 'Ключи успешно импортированы. Импортировано: $new, без изменений: $old.';
$messages['keyconfirmdelete'] = 'Вы точно хотите удалить выбранные ключи?';
$messages['keyimporttext'] = 'Вы можете импортировать открытые и секретные ключи или сообщения об отзыве ключей в формате ASCII-Armor.';
?>

@ -0,0 +1,182 @@
/*** Style for Enigma plugin ***/
/***** Messages displaying *****/
#enigma-message,
/* fixes border-top */
#messagebody div #enigma-message
{
margin: 0;
margin-bottom: 5px;
min-height: 20px;
padding: 10px 10px 6px 46px;
}
div.enigmaerror,
/* fixes border-top */
#messagebody div.enigmaerror
{
background: url(enigma_error.png) 6px 1px no-repeat;
background-color: #EF9398;
border: 1px solid #DC5757;
}
div.enigmanotice,
/* fixes border-top */
#messagebody div.enigmanotice
{
background: url(enigma.png) 6px 1px no-repeat;
background-color: #A6EF7B;
border: 1px solid #76C83F;
}
div.enigmawarning,
/* fixes border-top */
#messagebody div.enigmawarning
{
background: url(enigma.png) 6px 1px no-repeat;
background-color: #F7FDCB;
border: 1px solid #C2D071;
}
#enigma-message a
{
color: #666666;
padding-left: 10px;
}
#enigma-message a:hover
{
color: #333333;
}
/***** Keys/Certs Management *****/
div.enigmascreen
{
position: absolute;
top: 65px;
right: 10px;
bottom: 10px;
left: 10px;
}
#enigmacontent-box
{
position: absolute;
top: 0px;
left: 290px;
right: 0px;
bottom: 0px;
border: 1px solid #999999;
overflow: hidden;
}
#enigmakeyslist
{
position: absolute;
top: 0;
bottom: 0;
left: 0;
border: 1px solid #999999;
background-color: #F9F9F9;
overflow: hidden;
}
#keylistcountbar
{
margin-top: 4px;
margin-left: 4px;
}
#keys-table
{
width: 100%;
table-layout: fixed;
}
#keys-table td
{
cursor: default;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
#key-details table td.title
{
font-weight: bold;
text-align: right;
}
#keystoolbar
{
position: absolute;
top: 30px;
left: 10px;
height: 35px;
}
#keystoolbar a
{
padding-right: 10px;
}
#keystoolbar a.button,
#keystoolbar a.buttonPas,
#keystoolbar span.separator {
display: block;
float: left;
width: 32px;
height: 32px;
padding: 0;
margin-right: 10px;
overflow: hidden;
background: url(keys_toolbar.png) 0 0 no-repeat transparent;
opacity: 0.99; /* this is needed to make buttons appear correctly in Chrome */
}
#keystoolbar a.buttonPas {
opacity: 0.35;
}
#keystoolbar a.createSel {
background-position: 0 -32px;
}
#keystoolbar a.create {
background-position: 0 0;
}
#keystoolbar a.deleteSel {
background-position: -32px -32px;
}
#keystoolbar a.delete {
background-position: -32px 0;
}
#keystoolbar a.importSel {
background-position: -64px -32px;
}
#keystoolbar a.import {
background-position: -64px 0;
}
#keystoolbar a.exportSel {
background-position: -96px -32px;
}
#keystoolbar a.export {
background-position: -96px 0;
}
#keystoolbar a.keymenu {
background-position: -128px 0;
width: 36px;
}
#keystoolbar span.separator {
width: 5px;
background-position: -166px 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,20 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/enigma.css" />
</head>
<body class="iframe">
<div id="keyimport-title" class="boxtitle"><roundcube:label name="enigma.importkeys" /></div>
<div id="import-form" class="boxcontent">
<roundcube:object name="importform" />
<p>
<br /><roundcube:button command="plugin.enigma-import" type="input" class="button mainaction" label="import" />
</p>
</div>
</body>
</html>

@ -0,0 +1,17 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/enigma.css" />
</head>
<body class="iframe">
<div id="keyinfo-title" class="boxtitle"><roundcube:object name="keyname" part="name" /></div>
<div id="key-details" class="boxcontent">
<roundcube:object name="keydata" />
</div>
</body>
</html>

@ -0,0 +1,76 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/enigma.css" />
<script type="text/javascript" src="/functions.js"></script>
<script type="text/javascript" src="/splitter.js"></script>
<style type="text/css">
#enigmakeyslist { width: <roundcube:exp expression="!empty(cookie:enigmaviewsplitter) ? cookie:enigmaviewsplitter-5 : 210" />px; }
#enigmacontent-box { left: <roundcube:exp expression="!empty(cookie:enigmaviewsplitter) ? cookie:enigmaviewsplitter+5 : 220" />px;
<roundcube:exp expression="browser:ie ? ('width:expression((parseInt(this.parentNode.offsetWidth)-'.(!empty(cookie:enigmaeviewsplitter) ? cookie:enigmaviewsplitter+5 : 220).')+\\'px\\');') : ''" />
}
</style>
</head>
<body class="iframe" onload="rcube_init_mail_ui()">
<div id="prefs-title" class="boxtitle"><roundcube:label name="enigma.enigmakeys" /></div>
<div id="prefs-details" class="boxcontent">
<div id="keystoolbar">
<roundcube:button command="plugin.enigma-key-create" type="link" class="buttonPas create" classAct="button create" classSel="button createSel" title="enigma.createkeys" content=" " />
<roundcube:button command="plugin.enigma-key-delete" type="link" class="buttonPas delete" classAct="button delete" classSel="button deleteSel" title="enigma.deletekeys" content=" " />
<span class="separator">&nbsp;</span>
<roundcube:button command="plugin.enigma-key-import" type="link" class="buttonPas import" classAct="button import" classSel="button importSel" title="enigma.importkeys" content=" " />
<roundcube:button command="plugin.enigma-key-export" type="link" class="buttonPas export" classAct="button export" classSel="button exportSel" title="enigma.exportkeys" content=" " />
<roundcube:button name="messagemenulink" id="messagemenulink" type="link" class="button keymenu" title="enigma.keyactions" onclick="rcmail_ui.show_popup('messagemenu');return false" content=" " />
</div>
<div id="quicksearchbar" style="top: 35px; right: 10px;">
<roundcube:button name="searchmenulink" id="searchmenulink" image="/images/icons/glass.png" />
<roundcube:object name="searchform" id="quicksearchbox" />
<roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" />
</div>
<div class="enigmascreen">
<div id="enigmakeyslist">
<div class="boxtitle"><roundcube:label name="enigma.keyname" /></div>
<div class="boxlistcontent">
<roundcube:object name="keyslist" id="keys-table" class="records-table" cellspacing="0" noheader="true" />
</div>
<div class="boxfooter">
<div id="keylistcountbar" class="pagenav">
<roundcube:button command="firstpage" type="link" class="buttonPas firstpage" classAct="button firstpage" classSel="button firstpageSel" title="firstpage" content=" " />
<roundcube:button command="previouspage" type="link" class="buttonPas prevpage" classAct="button prevpage" classSel="button prevpageSel" title="previouspage" content=" " />
<roundcube:object name="countdisplay" style="padding:0 .5em; float:left" />
<roundcube:button command="nextpage" type="link" class="buttonPas nextpage" classAct="button nextpage" classSel="button nextpageSel" title="nextpage" content=" " />
<roundcube:button command="lastpage" type="link" class="buttonPas lastpage" classAct="button lastpage" classSel="button lastpageSel" title="lastpage" content=" " />
</div>
</div>
</div>
<script type="text/javascript">
var enigmaviewsplit = new rcube_splitter({id:'enigmaviewsplitter', p1: 'enigmakeyslist', p2: 'enigmacontent-box', orientation: 'v', relative: true, start: 215});
rcmail.add_onload('enigmaviewsplit.init()');
</script>
<div id="enigmacontent-box">
<roundcube:object name="keyframe" id="keyframe" width="100%" height="100%" frameborder="0" src="/watermark.html" />
</div>
</div>
</div>
<div id="messagemenu" class="popupmenu">
<ul class="toolbarmenu">
<li><roundcube:button class="disablelink" command="enigma.key-disable" label="enigma.keydisable" target="_blank" classAct="disablelink active" /></li>
<li><roundcube:button class="revokelink" command="enigma.key-revoke" label="enigma.keyrevoke" classAct="revokelink active" /></li>
<li class="separator_below"><roundcube:button class="sendlink" command="enigma.key-send" label="enigma.keysend" classAct="sendlink active" /></li>
<li><roundcube:button class="chpasslink" command="enigma.key-chpass" label="enigma.keychpass" classAct="chpasslink active" /></li>
</ul>
</div>
</body>
</html>

@ -0,0 +1,50 @@
<?php
require_once(dirname(__FILE__) . '/example_addressbook_backend.php');
/**
* Sample plugin to add a new address book
* with just a static list of contacts
*/
class example_addressbook extends rcube_plugin
{
private $abook_id = 'static';
private $abook_name = 'Static List';
public function init()
{
$this->add_hook('addressbooks_list', array($this, 'address_sources'));
$this->add_hook('addressbook_get', array($this, 'get_address_book'));
// use this address book for autocompletion queries
// (maybe this should be configurable by the user?)
$config = rcmail::get_instance()->config;
$sources = (array) $config->get('autocomplete_addressbooks', array('sql'));
if (!in_array($this->abook_id, $sources)) {
$sources[] = $this->abook_id;
$config->set('autocomplete_addressbooks', $sources);
}
}
public function address_sources($p)
{
$abook = new example_addressbook_backend($this->abook_name);
$p['sources'][$this->abook_id] = array(
'id' => $this->abook_id,
'name' => $this->abook_name,
'readonly' => $abook->readonly,
'groups' => $abook->groups,
);
return $p;
}
public function get_address_book($p)
{
if ($p['id'] === $this->abook_id) {
$p['instance'] = new example_addressbook_backend($this->abook_name);
}
return $p;
}
}

@ -0,0 +1,116 @@
<?php
/**
* Example backend class for a custom address book
*
* This one just holds a static list of address records
*
* @author Thomas Bruederli
*/
class example_addressbook_backend extends rcube_addressbook
{
public $primary_key = 'ID';
public $readonly = true;
public $groups = true;
private $filter;
private $result;
private $name;
public function __construct($name)
{
$this->ready = true;
$this->name = $name;
}
public function get_name()
{
return $this->name;
}
public function set_search_set($filter)
{
$this->filter = $filter;
}
public function get_search_set()
{
return $this->filter;
}
public function reset()
{
$this->result = null;
$this->filter = null;
}
function list_groups($search = null)
{
return array(
array('ID' => 'testgroup1', 'name' => "Testgroup"),
array('ID' => 'testgroup2', 'name' => "Sample Group"),
);
}
public function list_records($cols=null, $subset=0)
{
$this->result = $this->count();
$this->result->add(array('ID' => '111', 'name' => "Example Contact", 'firstname' => "Example", 'surname' => "Contact", 'email' => "example@roundcube.net"));
return $this->result;
}
public function search($fields, $value, $strict=false, $select=true, $nocount=false, $required=array())
{
// no search implemented, just list all records
return $this->list_records();
}
public function count()
{
return new rcube_result_set(1, ($this->list_page-1) * $this->page_size);
}
public function get_result()
{
return $this->result;
}
public function get_record($id, $assoc=false)
{
$this->list_records();
$first = $this->result->first();
$sql_arr = $first['ID'] == $id ? $first : null;
return $assoc && $sql_arr ? $sql_arr : $this->result;
}
function create_group($name)
{
$result = false;
return $result;
}
function delete_group($gid)
{
return false;
}
function rename_group($gid, $newname)
{
return $newname;
}
function add_to_group($group_id, $ids)
{
return false;
}
function remove_from_group($group_id, $ids)
{
return false;
}
}

@ -0,0 +1,161 @@
<?php
/**
* Filesystem Attachments
*
* This is a core plugin which provides basic, filesystem based
* attachment temporary file handling. This includes storing
* attachments of messages currently being composed, writing attachments
* to disk when drafts with attachments are re-opened and writing
* attachments to disk for inline display in current html compositions.
*
* Developers may wish to extend this class when creating attachment
* handler plugins:
* require_once('plugins/filesystem_attachments/filesystem_attachments.php');
* class myCustom_attachments extends filesystem_attachments
*
* @author Ziba Scott <ziba@umich.edu>
* @author Thomas Bruederli <roundcube@gmail.com>
*
*/
class filesystem_attachments extends rcube_plugin
{
public $task = '?(?!login).*';
function init()
{
// Save a newly uploaded attachment
$this->add_hook('attachment_upload', array($this, 'upload'));
// Save an attachment from a non-upload source (draft or forward)
$this->add_hook('attachment_save', array($this, 'save'));
// Remove an attachment from storage
$this->add_hook('attachment_delete', array($this, 'remove'));
// When composing an html message, image attachments may be shown
$this->add_hook('attachment_display', array($this, 'display'));
// Get the attachment from storage and place it on disk to be sent
$this->add_hook('attachment_get', array($this, 'get'));
// Delete all temp files associated with this user
$this->add_hook('attachments_cleanup', array($this, 'cleanup'));
$this->add_hook('session_destroy', array($this, 'cleanup'));
}
/**
* Save a newly uploaded attachment
*/
function upload($args)
{
$args['status'] = false;
$group = $args['group'];
$rcmail = rcmail::get_instance();
// use common temp dir for file uploads
$temp_dir = $rcmail->config->get('temp_dir');
$tmpfname = tempnam($temp_dir, 'rcmAttmnt');
if (move_uploaded_file($args['path'], $tmpfname) && file_exists($tmpfname)) {
$args['id'] = $this->file_id();
$args['path'] = $tmpfname;
$args['status'] = true;
// Note the file for later cleanup
$_SESSION['plugins']['filesystem_attachments'][$group][] = $tmpfname;
}
return $args;
}
/**
* Save an attachment from a non-upload source (draft or forward)
*/
function save($args)
{
$group = $args['group'];
$args['status'] = false;
if (!$args['path']) {
$rcmail = rcmail::get_instance();
$temp_dir = $rcmail->config->get('temp_dir');
$tmp_path = tempnam($temp_dir, 'rcmAttmnt');
if ($fp = fopen($tmp_path, 'w')) {
fwrite($fp, $args['data']);
fclose($fp);
$args['path'] = $tmp_path;
} else
return $args;
}
$args['id'] = $this->file_id();
$args['status'] = true;
// Note the file for later cleanup
$_SESSION['plugins']['filesystem_attachments'][$group][] = $args['path'];
return $args;
}
/**
* Remove an attachment from storage
* This is triggered by the remove attachment button on the compose screen
*/
function remove($args)
{
$args['status'] = @unlink($args['path']);
return $args;
}
/**
* When composing an html message, image attachments may be shown
* For this plugin, the file is already in place, just check for
* the existance of the proper metadata
*/
function display($args)
{
$args['status'] = file_exists($args['path']);
return $args;
}
/**
* This attachment plugin doesn't require any steps to put the file
* on disk for use. This stub function is kept here to make this
* class handy as a parent class for other plugins which may need it.
*/
function get($args)
{
return $args;
}
/**
* Delete all temp files associated with this user
*/
function cleanup($args)
{
// $_SESSION['compose']['attachments'] is not a complete record of
// temporary files because loading a draft or starting a forward copies
// the file to disk, but does not make an entry in that array
if (is_array($_SESSION['plugins']['filesystem_attachments'])){
foreach ($_SESSION['plugins']['filesystem_attachments'] as $group => $files) {
if ($args['group'] && $args['group'] != $group)
continue;
foreach ((array)$files as $filename){
if(file_exists($filename)){
unlink($filename);
}
}
unset($_SESSION['plugins']['filesystem_attachments'][$group]);
}
}
return $args;
}
function file_id()
{
$userid = rcmail::get_instance()->user->ID;
list($usec, $sec) = explode(' ', microtime());
return preg_replace('/[^0-9]/', '', $userid . $sec . $usec);
}
}

@ -0,0 +1,5 @@
<?php
// Help content iframe source
// $rcmail_config['help_source'] = 'http://trac.roundcube.net/wiki';
$rcmail_config['help_source'] = '';

@ -0,0 +1,39 @@
<div id="helpabout">
<h3 align="center">Copyright &copy; 2005-2010, The Roundcube Dev Team</h3>
<p>This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
</p>
<p>
This program 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.
</p>
<p>
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
</p>
<div align="center">
<h3>Project management and administration</h3>
<b>Thomas Bruederli (thomasb)</b> - Project leader and head developer<br />
<b>Till Klampäckel (till)</b> - Co-leader<br />
<b>Brett Patterson</b> - Forum administrator<br />
<b>Adam Grelck</b> - Trac administrator<br />
<b>Jason Fesler</b> - Mailing list administrator<br />
<b>Brennan Stehling</b> - Mentor, Coordinator
<h3>Developers</h3>
<b>Eric Stadtherr (estadtherr)</b><br />
<b>Robin Elfrink (robin, wobin)</b><br />
<b>Rich Sandberg (richs)</b><br />
<b>Tomasz Pajor (tomekp)</b><br />
<b>Fourat Zouari (fourat.zouari)</b><br />
<b>Aleksander Machniak (alec)</b>
<p><br/>Website: <a href="http://roundcube.net">roundcube.net</a></p>
</div>
</div>

@ -0,0 +1,387 @@
<div id="helplicense">
<h3>GNU GENERAL PUBLIC LICENSE</h3>
<p>
Version 2, June 1991
</p>
<pre>
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
</pre>
<h3>Preamble</h3>
<p>
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
</p>
<p>
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
</p>
<p>
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
</p>
<p>
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
</p>
<p>
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
</p>
<p>
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
</p>
<p>
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
</p>
<p>
The precise terms and conditions for copying, distribution and
modification follow.
</p>
<h3>TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION</h3>
<p>
<strong>0.</strong>
This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
</p>
<p>
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
</p>
<p>
<strong>1.</strong>
You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
</p>
<p>
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
</p>
<p>
<strong>2.</strong>
You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
</p>
<dl>
<dt></dt>
<dd>
<strong>a)</strong>
You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
</dd>
<dt></dt>
<dd>
<strong>b)</strong>
You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
</dd>
<dt></dt>
<dd>
<strong>c)</strong>
If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
</dd>
</dl>
<p>
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
</p>
<p>
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
</p>
<p>
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
</p>
<p>
<strong>3.</strong>
You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
</p>
<dl>
<dt></dt>
<dd>
<strong>a)</strong>
Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
</dd>
<dt></dt>
<dd>
<strong>b)</strong>
Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
</dd>
<dt></dt>
<dd>
<strong>c)</strong>
Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
</dd>
</dl>
<p>
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
</p>
<p>
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
</p>
<p>
<strong>4.</strong>
You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
</p>
<p>
<strong>5.</strong>
You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
</p>
<p>
<strong>6.</strong>
Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
</p>
<p>
<strong>7.</strong>
If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
</p>
<p>
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
</p>
<p>
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
</p>
<p>
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
</p>
<p>
<strong>8.</strong>
If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
</p>
<p>
<strong>9.</strong>
The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
</p>
<p>
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
</p>
<p>
<strong>10.</strong>
If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
</p>
<p><strong>NO WARRANTY</strong></p>
<p>
<strong>11.</strong>
BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
</p>
<p>
<strong>12.</strong>
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
</p>
</div>

@ -0,0 +1,107 @@
<?php
/**
* Help Plugin
*
* @author Aleksander 'A.L.E.C' Machniak
* @licence GNU GPL
*
* Configuration (see config.inc.php.dist)
*
**/
class help extends rcube_plugin
{
// all task excluding 'login' and 'logout'
public $task = '?(?!login|logout).*';
// we've got no ajax handlers
public $noajax = true;
// skip frames
public $noframe = true;
function init()
{
$rcmail = rcmail::get_instance();
$this->add_texts('localization/', false);
// register task
$this->register_task('help');
// register actions
$this->register_action('index', array($this, 'action'));
$this->register_action('about', array($this, 'action'));
$this->register_action('license', array($this, 'action'));
// add taskbar button
$this->add_button(array(
'name' => 'helptask',
'class' => 'button-help',
'label' => 'help.help',
'href' => './?_task=help',
'onclick' => sprintf("return %s.command('help')", JS_OBJECT_NAME)
), 'taskbar');
$rcmail->output->add_script(
JS_OBJECT_NAME . ".enable_command('help', true);\n" .
JS_OBJECT_NAME . ".help = function () { location.href = './?_task=help'; }",
'head');
$skin = $rcmail->config->get('skin');
if (!file_exists($this->home."/skins/$skin/help.css"))
$skin = 'default';
// add style for taskbar button (must be here) and Help UI
$this->include_stylesheet("skins/$skin/help.css");
}
function action()
{
$rcmail = rcmail::get_instance();
$this->load_config();
// register UI objects
$rcmail->output->add_handlers(array(
'helpcontent' => array($this, 'content'),
));
if ($rcmail->action == 'about')
$rcmail->output->set_pagetitle($this->gettext('about'));
else if ($rcmail->action == 'license')
$rcmail->output->set_pagetitle($this->gettext('license'));
else
$rcmail->output->set_pagetitle($this->gettext('help'));
$rcmail->output->send('help.help');
}
function content($attrib)
{
$rcmail = rcmail::get_instance();
if ($rcmail->action == 'about') {
return @file_get_contents($this->home.'/content/about.html');
}
else if ($rcmail->action == 'license') {
return @file_get_contents($this->home.'/content/license.html');
}
// default content: iframe
if ($src = $rcmail->config->get('help_source'))
$attrib['src'] = $src;
if (empty($attrib['id']))
$attrib['id'] = 'rcmailhelpcontent';
// allow the following attributes to be added to the <iframe> tag
$attrib_str = create_attrib_string($attrib, array(
'id', 'class', 'style', 'src', 'width', 'height', 'frameborder'));
$out = sprintf('<iframe name="%s"%s></iframe>'."\n", $attrib['id'], $attrib_str);
return $out;
}
}

@ -0,0 +1,25 @@
<?php
/*
+-----------------------------------------------------------------------+
| language/cs_CZ/labels.inc |
| |
| Language file of the Roundcube help plugin |
| Copyright (C) 2005-2009, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
+-----------------------------------------------------------------------+
| Author: Milan Kozak <hodza@hodza.net> |
+-----------------------------------------------------------------------+
@version $Id: labels.inc 2993 2009-09-26 18:32:07Z alec $
*/
$labels = array();
$labels['help'] = 'Nápověda';
$labels['about'] = 'O aplikaci';
$labels['license'] = 'Licence';
?>

@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['help'] = 'Hjælp';
$labels['about'] = 'Om';
$labels['license'] = 'Licens';
?>

@ -0,0 +1,8 @@
<?php
// translation done by Ulli Heist - http://heist.hobby-site.org/
$labels = array();
$labels['help'] = 'Hilfe';
$labels['about'] = '&Uuml;ber';
$labels['license'] = 'Lizenz';
?>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save