diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index 2441dc5..8eb1ab4 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -1,3 +1,18 @@
+1.3.0.:
+------
+
+- Removed PDF support
+
+- GUI fixes
+
+- Added HTML purifier support
+
+- Rewritten pilerpurge in Python
+
+- sphinx.conf fix
+
+- minor other fixes as well
+
1.2.0:
------
diff --git a/VERSION b/VERSION
index 26aaba0..f0bb29e 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.2.0
+1.3.0
diff --git a/contrib/README b/contrib/README
index 6116c75..755d826 100644
--- a/contrib/README
+++ b/contrib/README
@@ -1 +1,3 @@
mime/mime.types: MIME types from the apache 2.2.x distribution
+import/rewrite.pl: https://bitbucket.org/jsuto/piler/issues/815/pst-mailer-daemon-to-cc-issues-fix
+import/import.pl: https://bitbucket.org/jsuto/piler/issues/818/import-ldap-rename-script
diff --git a/contrib/import/import.pl b/contrib/import/import.pl
new file mode 100644
index 0000000..eed9660
--- /dev/null
+++ b/contrib/import/import.pl
@@ -0,0 +1,230 @@
+#!/usr/bin/perl
+
+# Written by Rory McInerney, rorymcinerney@gmail.com
+# feel free to use this for whatever, I make it public domain
+use strict;
+use warnings;
+use Net::LDAP;
+use File::Find;
+
+# LDAP user name
+my $uid = "cn=piler,cn=users,dc=yourdomain";
+# LDAP user password
+my $bindPass = "youpass";
+# LDAP password
+my $ldapServer = "ldap://yourdc.yourdomain";
+# dummy not found email address to use
+my $notfound = "noexist\@domain.com";
+# ldap base
+my $base = "ou=Users,dc=domain";
+# define some dirs we're going to use
+my $inputdir = '/var/pst-import/in';
+my $workdir = '/var/pst-import/working';
+
+
+# the script runs as piler user and needs the correct permissions
+sub takeownership {
+ system("sudo chown piler $workdir");
+ system("sudo chown piler $inputdir");
+ system("sudo chmod 755 /var/pst-import");
+}
+
+# quick function to trim whitespace
+sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
+
+
+# this rewrites To: and Cc: headers
+sub rewrite_to_headers {
+ #initiate variables
+ my @files;
+ my $start_dir = "$_[0]"; # top level dir to search
+
+ # find all the files and return the variable
+ find( sub { push @files, $File::Find::name unless -d; }, $start_dir );
+
+ # This iterates through all the email files found
+ foreach my $mailpath (@files) {
+
+ # REWRITE To: Headers
+ # this matches to see if it's a sent item, based on the file structure of the PST
+ if($mailpath =~ /Sent Items/i) { # if it's a sent item
+ # this extracts the line in the header to manipulate
+ my $line = `grep \"To:\" \"$mailpath\" -m 1`;
+ # lose the newline
+ chomp($line);
+ my $origline = $line;
+ # lose the To: bit off the start of the header
+ $line =~ s/To: //g;
+ # split the mush of addresses into an array of parts on the semicolons
+ my @adds = split(/\;/, $line);
+ my $newline = "To:";
+ # cycle through all the address fragments
+ foreach my $add (@adds) {
+ $add = trim($add);
+ # if it matches the format of an email address
+ if($add =~ /\'.+\@.+\'/) {
+ $newline = $newline . " ".$add.";";
+ } else {
+ my $email = find_ldap_mail($add);
+ $email =~ s/\@/\\\@/gi;
+ $newline = $newline . " ".$add." <$email>;";
+ }
+ }
+ chop($newline);
+ my $command = "perl -i -p -e \"s/$origline/$newline/g;\" \"$mailpath\"\n";
+ `$command`;
+
+
+ #### REWRITES CC: HEADERS
+ my $result = `head -n 5 \"$mailpath\" | grep "Cc:" -i -m 1`;
+ if($result) {
+ my $line = `grep \"Cc:\" \"$mailpath\" -m 1`;
+ # lose the newline
+ chomp($line);
+ my $origline = $line;
+ # lose the To: bit off the start of the header
+ $line =~ s/Cc: //g;
+ # split the mush of addresses into an array of parts on the semicolons
+ my @adds = split(/\;/, $line);
+ my $newline = "Cc:";
+ # cycle through all the address fragments
+ foreach my $add (@adds) {
+ $add = trim($add);
+ # if it matches the format of an email address
+ if($add =~ /\'.+\@.+\'/) {
+ $newline = $newline . " ".$add.";";
+ } else {
+ my $email = find_ldap_mail($add);
+ $email =~ s/\@/\\\@/gi;
+ $newline = $newline . " ".$add." <$email>;";
+ }
+ }
+ chop($newline);
+ my $command = "perl -i -p -e \"s/$origline/$newline/g;\" \"$mailpath\"\n";
+ `$command`;
+ }
+ }
+ }
+}
+
+sub rewrite_send_headers {
+ # argument is the folder containing the unzipped (from pst) email files
+ # get a list of the files in the directory (see subs)
+ my @mails = getfiles(@_);
+ # get the displayname from the emails (see subs)
+ my $dN = get_displayname(@mails);
+ # query active directory to get email to write from the dN
+ my $result = find_ldap_mail($dN);
+ # escape the special @ symbol to make system call work properly
+ $result =~ s/\@/\\\@/;
+
+
+ # for each email in the list of emails...
+ foreach my $mailpath (@mails) {
+ if($mailpath =~ /Sent Items/i) { # if it's a sent item
+ system("perl -i -p -e \'s/MAILER-DAEMON/$result/g;\' \"$mailpath\"\n"); # rewrite the mailer daemon with the correct stuff
+ }
+ }
+}
+
+# this sub gets the DN from the first email in the sent items folder in the pst we are working with
+sub get_displayname {
+ my @fragments; # initialise this
+ foreach my $mailpath (@_) { # the argument is all the files in the pst broken open
+ if($mailpath =~ /Sent Items/i) { # if it's a sent item
+ my $line = `grep \"From:\" \"$mailpath\" -m 1`; # find the display name of the first email sent
+ @fragments = split(/\"/, $line); # isolate it
+ last; # once we've found it, we've found it
+ }
+ }
+ return $fragments[1]; #return it
+}
+
+# sub for getting the filenames, takes directory as argument
+sub getfiles {
+ #initiate variables
+ my @files;
+ my $start_dir = "$_[0]"; # top level dir to search
+ # find all the files and return the variable
+ find( sub { push @files, $File::Find::name unless -d; }, $start_dir );
+ return @files;
+}
+
+#sub for finding the ldap mail attribute from a displayName
+sub find_ldap_mail {
+ # escape ( and )
+ my $filter = $_[0];
+ $filter =~ s/\(/\\\(/gi;
+ $filter =~ s/\)/\\\)/gi;
+
+ # variables we using
+ my $uid = "cn=piler,cn=users,dc=multigroupplc";
+ my $bindPass = "M1ssion1977";
+ my $ldapServer = "ldap://svcadc03.multigroupplc";
+ # connect to ldap server
+ my $ldap = Net::LDAP -> new ($ldapServer) || die "Could not connect to server\n";
+
+ # bind to ldap server
+ my $result = $ldap -> bind($uid, password => $bindPass);
+
+ #we're looking for the mail attribute so tell it that
+ my $attrs = [ 'cn','mail' ];
+
+ # ask the ldap server
+ $result = $ldap->search( base => "ou=Servoca Plc,dc=multigroupplc",
+ filter => "(&(objectClass=Person)(displayName=$filter))",
+ attrs => $attrs
+ );
+
+ # not really sure what this does but it makes it work
+ $result->code && die $result->error;
+
+ # initialise this as you're not allowed inside the foreach loop
+ my @foundemails;
+
+ # look through the entries and put each result into an array
+ foreach my $entry ($result->all_entries) {
+ push @foundemails,$entry->get_value('mail');
+ }
+ if(@foundemails) {
+ #return what we've found as a scalar value
+ return $foundemails[0];
+ } else {
+ return "noexist\@servoca.com";
+ }
+ #unbind from the AD server
+ $result = $ldap->unbind;
+}
+
+
+# this processes it
+sub processpst {
+ my @a = split(/\./, $_[0]); # chop off the first name of the pst
+ my $newname = lc $_[0]; # rename it in lowercase
+ system("mv $inputdir/$_[0] $workdir/$newname"); # move it to the working directory
+ system("mkdir $workdir/$a[0]"); # make output directory for readpst
+ system("readpst -D -M -b -q -o $workdir/$a[0] $workdir/$newname\n"); # crack the pst open into the output folder
+ rewrite_send_headers("$workdir/$a[0]"); # rewrite the from: header from mailer-daemon
+ rewrite_to_headers("$workdir/$a[0]"); # rewrite the to/cc headers for piler to read
+ system("pilerimport -D -d $workdir/$a[0] -r"); # import emails into piler archive
+ system("rm $workdir/$newname"); # tidying up
+ system("rm -rf $workdir/$a[0]"); # more tidying up
+}
+
+# SCRIPT OPERATES FROM HERE
+
+# get the right permissions
+takeownership();
+
+# do we have any files?
+opendir DIR, $inputdir or die;
+my @rawfiles= readdir DIR;
+closedir DIR;
+
+# more importantly, do we have any psts?
+foreach my $file (@rawfiles) {
+ if($file =~ /.*\.pst$/i) {
+ processpst($file);
+ }
+}
+
diff --git a/contrib/import/rewrite.pl b/contrib/import/rewrite.pl
new file mode 100644
index 0000000..83dd55b
--- /dev/null
+++ b/contrib/import/rewrite.pl
@@ -0,0 +1,188 @@
+#!/usr/bin/perl
+
+# Written by Rory McInerney, rorymcinerney@gmail.com
+# feel free to use this for whatever, I make it public domain
+
+use strict;
+use warnings;
+use Net::LDAP;
+use File::Find;
+
+
+# LDAP user name
+my $uid = "cn=piler,cn=users,dc=yourdomain";
+# LDAP user password
+my $bindPass = "youpass";
+# LDAP password
+my $ldapServer = "ldap://yourdc.yourdomain";
+# dummy not found email address to use
+my $notfound = "noexist\@domain.com";
+# where the eml files are found (will recurse)
+my $dir = "/var/pst-import/test";
+# ldap base
+my $base = "ou=Users,dc=domain";
+
+
+
+
+# quick function to trim whitespace
+sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
+
+# this rewrites To: and Cc: headers
+sub rewrite_to_headers {
+ #initiate variables
+ my @files;
+ my $start_dir = "$_[0]"; # top level dir to search
+
+ # find all the files and return the variable
+ find( sub { push @files, $File::Find::name unless -d; }, $start_dir );
+
+ # This iterates through all the email files found
+ foreach my $mailpath (@files) {
+
+ # REWRITE To: Headers
+ # this matches to see if it's a sent item, based on the file structure of the PST
+ if($mailpath =~ /Sent Items/i) { # if it's a sent item
+ # this extracts the line in the header to manipulate
+ my $line = `grep \"To:\" \"$mailpath\" -m 1`;
+ # lose the newline
+ chomp($line);
+ my $origline = $line;
+ # lose the To: bit off the start of the header
+ $line =~ s/To: //g;
+ # split the mush of addresses into an array of parts on the semicolons
+ my @adds = split(/\;/, $line);
+ my $newline = "To:";
+ # cycle through all the address fragments
+ foreach my $add (@adds) {
+ $add = trim($add);
+ # if it matches the format of an email address
+ if($add =~ /\'.+\@.+\'/) {
+ $newline = $newline . " ".$add.";";
+ } else {
+ my $email = find_ldap_mail($add);
+ $email =~ s/\@/\\\@/gi;
+ $newline = $newline . " ".$add." <$email>;";
+ }
+ }
+ chop($newline);
+ my $command = "perl -i -p -e \"s/$origline/$newline/g;\" \"$mailpath\"\n";
+ `$command`;
+
+
+ #### REWRITES CC: HEADERS
+ my $result = `head -n 5 \"$mailpath\" | grep "Cc:" -i -m 1`;
+ if($result) {
+ my $line = `grep \"Cc:\" \"$mailpath\" -m 1`;
+ # lose the newline
+ chomp($line);
+ my $origline = $line;
+ # lose the To: bit off the start of the header
+ $line =~ s/Cc: //g;
+ # split the mush of addresses into an array of parts on the semicolons
+ my @adds = split(/\;/, $line);
+ my $newline = "Cc:";
+ # cycle through all the address fragments
+ foreach my $add (@adds) {
+ $add = trim($add);
+ # if it matches the format of an email address
+ if($add =~ /\'.+\@.+\'/) {
+ $newline = $newline . " ".$add.";";
+ } else {
+ my $email = find_ldap_mail($add);
+ $email =~ s/\@/\\\@/gi;
+ $newline = $newline . " ".$add." <$email>;";
+ }
+ }
+ chop($newline);
+ my $command = "perl -i -p -e \"s/$origline/$newline/g;\" \"$mailpath\"\n";
+ `$command`;
+ }
+ }
+ }
+}
+
+sub rewrite_send_headers {
+ # argument is the folder containing the unzipped (from pst) email files
+ # get a list of the files in the directory (see subs)
+ my @mails = getfiles(@_);
+ # get the displayname from the emails (see subs)
+ my $dN = get_displayname(@mails);
+ # query active directory to get email to write from the dN
+ my $result = find_ldap_mail($dN);
+ # escape the special @ symbol to make system call work properly
+ $result =~ s/\@/\\\@/;
+
+
+ # for each email in the list of emails...
+ foreach my $mailpath (@mails) {
+ if($mailpath =~ /Sent Items/i) { # if it's a sent item
+ system("perl -i -p -e \'s/MAILER-DAEMON/$result/g;\' \"$mailpath\"\n"); # rewrite the mailer daemon with the correct stuff
+ }
+ }
+}
+
+# this sub gets the DN from the first email in the sent items folder in the pst we are working with
+sub get_displayname {
+ my @fragments; # initialise this
+ foreach my $mailpath (@_) { # the argument is all the files in the pst broken open
+ if($mailpath =~ /Sent Items/i) { # if it's a sent item
+ my $line = `grep \"From:\" \"$mailpath\" -m 1`; # find the display name of the first email sent
+ @fragments = split(/\"/, $line); # isolate it
+ last; # once we've found it, we've found it
+ }
+ }
+ return $fragments[1]; #return it
+}
+
+# sub for getting the filenames, takes directory as argument
+sub getfiles {
+ #initiate variables
+ my @files;
+ my $start_dir = "$_[0]"; # top level dir to search
+ # find all the files and return the variable
+ find( sub { push @files, $File::Find::name unless -d; }, $start_dir );
+ return @files;
+}
+
+#sub for finding the ldap mail attribute from a displayName
+sub find_ldap_mail {
+
+ # connect to ldap server
+ my $ldap = Net::LDAP -> new ($ldapServer) || die "Could not connect to server\n";
+
+ # bind to ldap server
+ my $result = $ldap -> bind($uid, password => $bindPass);
+
+ #we're looking for the mail attribute so tell it that
+ my $attrs = [ 'cn','mail' ];
+
+ # ask the ldap server, replace the base with your base
+ $result = $ldap->search( base => $base,
+ filter => "(&(objectClass=Person)(displayName=$_[0]))",
+ attrs => $attrs
+ );
+
+ # not really sure what this does but it makes it work
+ $result->code && die $result->error;
+
+ # initialise this as you're not allowed inside the foreach loop
+ my @foundemails;
+
+ # look through the entries and put each result into an array
+ foreach my $entry ($result->all_entries) {
+ push @foundemails,$entry->get_value('mail');
+ }
+ if(@foundemails) {
+ #return what we've found as a scalar value
+ return $foundemails[0];
+ } else {
+ return $notfound;
+ }
+ #unbind from the AD server
+ $result = $ldap->unbind;
+}
+
+rewrite_send_headers($dir); # rewrite the from: header from mailer-daemon
+rewrite_to_headers($dir); # rewrite to/cc headers
+
diff --git a/contrib/webserver/piler-apache-2.x.conf b/contrib/webserver/piler-apache-2.x.conf
index dd3517a..62f061a 100644
--- a/contrib/webserver/piler-apache-2.x.conf
+++ b/contrib/webserver/piler-apache-2.x.conf
@@ -4,8 +4,7 @@
DocumentRoot "/var/piler/www"
- Order allow,deny
- Allow from all
+ Require all granted
AllowOverride all
diff --git a/contrib/webserver/piler-nginx.conf b/contrib/webserver/piler-nginx.conf
index 0c80292..a2c620e 100644
--- a/contrib/webserver/piler-nginx.conf
+++ b/contrib/webserver/piler-nginx.conf
@@ -1,11 +1,11 @@
server {
- server_name HOSTNAME;
+ server_name PILER_HOST;
- root /var/www/piler;
+ root /var/piler/www;
- access_log /var/log/nginx/HOSTNAME-access.log;
- error_log /var/log/nginx/HOSTNAME-error.log;
+ access_log /var/log/nginx/access.log;
+ error_log /var/log/nginx/error.log;
gzip on;
gzip_types text/plain application/xml text/css;
@@ -31,7 +31,8 @@
return 404;
}
- fastcgi_pass unix:/var/run/php5-fpm.sock;
+ #fastcgi_pass unix:/var/run/php5-fpm.sock;
+ fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
@@ -59,6 +60,7 @@
rewrite /customer.php /index.php?route=customer/list;
rewrite /retention.php /index.php?route=policy/retention;
rewrite /archiving.php /index.php?route=policy/archiving;
+ rewrite /legalhold.php /index.php?route=policy/legalhold;
rewrite /view/javascript/piler.js /js.php;
}
diff --git a/etc/Makefile.in b/etc/Makefile.in
index 2cacee3..c212f6d 100644
--- a/etc/Makefile.in
+++ b/etc/Makefile.in
@@ -31,9 +31,14 @@
install:
$(INSTALL) -m 0640 -g $(RUNNING_GROUP) $(srcdir)/piler.conf $(DESTDIR)$(sysconfdir)/piler/piler.conf.dist
- if [ ! -f "$(DESTDIR)$(sysconfdir)/piler/piler.conf" ]; then $(INSTALL) -m 0640 -g $(RUNNING_GROUP) $(srcdir)/piler.conf $(DESTDIR)$(sysconfdir)/piler/piler.conf; fi
sed -e 's%LOCALSTATEDIR%$(localstatedir)%g' $(srcdir)/sphinx.conf.in > sphinx.conf.dist
$(INSTALL) -m 0644 -g $(RUNNING_GROUP) $(srcdir)/sphinx.conf.dist $(DESTDIR)$(sysconfdir)/piler/sphinx.conf.dist
+ $(INSTALL) -m 0644 -g $(RUNNING_GROUP) $(srcdir)/config-site.dist.php $(DESTDIR)$(sysconfdir)/piler/config-site.dist.php
+ $(INSTALL) -m 0644 -g $(RUNNING_GROUP) $(srcdir)/cron.jobs.in $(DESTDIR)$(datarootdir)/piler/piler.cron
+ sed -i -e 's%LOCALSTATEDIR%$(localstatedir)%g' $(DESTDIR)$(datarootdir)/piler/piler.cron
+ sed -i -e 's%LIBEXECDIR%$(libexecdir)%g' $(DESTDIR)$(datarootdir)/piler/piler.cron
+ sed -i -e 's%BINDIR%$(bindir)%g' $(DESTDIR)$(datarootdir)/piler/piler.cron
+ sed -i -e 's%SYSCONFDIR%$(sysconfdir)%g' $(DESTDIR)$(datarootdir)/piler/piler.cron
clean:
rm -f piler.conf cron.jobs sphinx.conf.dist
diff --git a/etc/config-site.dist.php b/etc/config-site.dist.php
new file mode 100644
index 0000000..ac57e3e
--- /dev/null
+++ b/etc/config-site.dist.php
@@ -0,0 +1,11 @@
+/dev/null
### optional: purge aged emails
-2 0 * * * /usr/local/bin/pilerpurge
+###2 0 * * * BINDIR/pilerpurge
+### PILEREND
diff --git a/etc/sphinx.conf.in b/etc/sphinx.conf.in
index b1bb4fa..37c984e 100644
--- a/etc/sphinx.conf.in
+++ b/etc/sphinx.conf.in
@@ -18,7 +18,7 @@
source delta : base
{
sql_query_pre = SET NAMES utf8
- sql_query_pre = REPLACE INTO sph_counter SELECT 1, MAX(id) FROM sph_index
+ sql_query_pre = REPLACE INTO sph_counter SELECT 1, IFNULL(MAX(id), 0) FROM sph_index
sql_query_post_index = DELETE FROM sph_index WHERE id<=(SELECT max_doc_id FROM sph_counter WHERE counter_id=1)
sql_query = SELECT id, `from`, `to`, `fromdomain`, `todomain`, `subject`, `arrived`, `sent`, `body`, `size`, `direction`, `folder`, `attachments`, `attachment_types` FROM sph_index \
WHERE id <= (SELECT max_doc_id FROM sph_counter WHERE counter_id=1)
diff --git a/src/Makefile.in b/src/Makefile.in
index 4c5ef87..6a33b07 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -33,7 +33,7 @@
INSTALL = @INSTALL@
-all: libpiler.a piler pilerconf pilerget pileraget pilerimport pilerexport pilerpurge reindex test
+all: libpiler.a piler pilerconf pilerget pileraget pilerimport pilerexport reindex test
install: install-piler
@@ -60,9 +60,6 @@
pilerexport: pilerexport.c libpiler.a
$(CC) $(CFLAGS) $(INCDIR) $(DEFS) -o $@ $< -lpiler $(LIBS) $(LIBDIR)
-pilerpurge: pilerpurge.c libpiler.a
- $(CC) $(CFLAGS) $(INCDIR) $(DEFS) -o $@ $< -lpiler $(LIBS) $(LIBDIR)
-
pilerconf: pilerconf.c libpiler.a
$(CC) $(CFLAGS) $(INCDIR) $(DEFS) -o $@ $< -lpiler $(LIBS) $(LIBDIR)
@@ -88,12 +85,11 @@
$(INSTALL) -m 6755 -o $(RUNNING_USER) -g $(RUNNING_GROUP) pileraget $(DESTDIR)$(bindir)
$(INSTALL) -m 6755 -o $(RUNNING_USER) -g $(RUNNING_GROUP) pilerimport $(DESTDIR)$(bindir)
$(INSTALL) -m 6755 -o $(RUNNING_USER) -g $(RUNNING_GROUP) pilerexport $(DESTDIR)$(bindir)
- $(INSTALL) -m 6755 -o $(RUNNING_USER) -g $(RUNNING_GROUP) pilerpurge $(DESTDIR)$(bindir)
$(INSTALL) -m 6755 -o $(RUNNING_USER) -g $(RUNNING_GROUP) reindex $(DESTDIR)$(bindir)
$(INSTALL) -m 6755 -o $(RUNNING_USER) -g $(RUNNING_GROUP) pilertest $(DESTDIR)$(bindir)
clean:
- rm -f *.o *.a libpiler.so* piler pilerconf pilerget pileraget pilerimport pilerexport pilerpurge pilertest reindex
+ rm -f *.o *.a libpiler.so* piler pilerconf pilerget pileraget pilerimport pilerexport pilertest reindex
distclean: clean
rm -f Makefile
diff --git a/src/archive.c b/src/archive.c
index 3160b6a..17196a3 100644
--- a/src/archive.c
+++ b/src/archive.c
@@ -135,7 +135,11 @@
int rc=0, n, olen, tlen, len, fd=-1;
unsigned char *s=NULL, *addr=NULL, inbuf[REALLYBIGBUFSIZE];
struct stat st;
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_CIPHER_CTX ctx;
+#else
+ EVP_CIPHER_CTX *ctx;
+#endif
if(filename == NULL) return 1;
@@ -156,8 +160,16 @@
if(cfg->encrypt_messages == 1){
+ #if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_CIPHER_CTX_init(&ctx);
EVP_DecryptInit_ex(&ctx, EVP_bf_cbc(), NULL, cfg->key, cfg->iv);
+ #else
+ ctx = EVP_CIPHER_CTX_new();
+ if(!ctx) goto CLEANUP;
+
+ EVP_CIPHER_CTX_init(ctx);
+ EVP_DecryptInit_ex(ctx, EVP_bf_cbc(), NULL, cfg->key, cfg->iv);
+ #endif
len = st.st_size+EVP_MAX_BLOCK_LENGTH;
@@ -172,7 +184,11 @@
while((n = read(fd, inbuf, sizeof(inbuf)))){
+ #if OPENSSL_VERSION_NUMBER < 0x10100000L
if(!EVP_DecryptUpdate(&ctx, s+tlen, &olen, inbuf, n)){
+ #else
+ if(!EVP_DecryptUpdate(ctx, s+tlen, &olen, inbuf, n)){
+ #endif
syslog(LOG_PRIORITY, "%s: EVP_DecryptUpdate()", filename);
goto CLEANUP;
}
@@ -181,7 +197,11 @@
}
+ #if OPENSSL_VERSION_NUMBER < 0x10100000L
if(EVP_DecryptFinal(&ctx, s + tlen, &olen) != 1){
+ #else
+ if(EVP_DecryptFinal(ctx, s + tlen, &olen) != 1){
+ #endif
syslog(LOG_PRIORITY, "%s: EVP_DecryptFinal()", filename);
goto CLEANUP;
}
@@ -203,7 +223,12 @@
CLEANUP:
if(fd != -1) close(fd);
if(s) free(s);
- if(cfg->encrypt_messages == 1) EVP_CIPHER_CTX_cleanup(&ctx);
+ if(cfg->encrypt_messages == 1)
+ #if OPENSSL_VERSION_NUMBER < 0x10100000L
+ EVP_CIPHER_CTX_cleanup(&ctx);
+ #else
+ EVP_CIPHER_CTX_free(ctx);
+ #endif
return 0;
}
diff --git a/src/config.h b/src/config.h
index 4ac5f4c..0ff5716 100644
--- a/src/config.h
+++ b/src/config.h
@@ -11,9 +11,8 @@
#define PROGNAME "piler"
-#define VERSION "1.2.0"
-
-#define BUILD 952
+#define VERSION "1.3.1"
+#define BUILD 956
#define HOSTID "mailarchiver"
@@ -39,6 +38,7 @@
#define BUFLEN 32
#define IPLEN 16+1
#define KEYLEN 56
+#define MIN_EMAIL_ADDRESS_LEN 9
#define CRLF "\n"
diff --git a/src/defs.h b/src/defs.h
index 3f56adf..c5e3773 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -284,6 +284,7 @@
struct import {
char *extra_recipient;
char *move_folder;
+ char *failed_folder;
int status;
int total_messages;
int processed_messages;
@@ -373,6 +374,7 @@
struct session_ctx {
+ char virusinfo[SMALLBUFSIZE];
char *status;
int new_sd;
int db_conn;
diff --git a/src/imap.c b/src/imap.c
index 6668ae2..3d56b76 100644
--- a/src/imap.c
+++ b/src/imap.c
@@ -99,7 +99,10 @@
/* imap cmd: SELECT */
- snprintf(buf, sizeof(buf)-1, "A%d SELECT %s\r\n", *seq, folder);
+ if(strchr(folder, '"'))
+ snprintf(buf, sizeof(buf)-1, "A%d SELECT %s\r\n", *seq, folder);
+ else
+ snprintf(buf, sizeof(buf)-1, "A%d SELECT \"%s\"\r\n", *seq, folder);
write1(sd, buf, strlen(buf), use_ssl, data->ssl);
if(read_response(sd, buf, sizeof(buf), seq, data, use_ssl) == 0){
@@ -320,7 +323,11 @@
SSL_library_init();
SSL_load_error_strings();
+ #if OPENSSL_VERSION_NUMBER < 0x10100000L
data->ctx = SSL_CTX_new(TLSv1_client_method());
+ #else
+ data->ctx = SSL_CTX_new(TLS_client_method());
+ #endif
CHK_NULL(data->ctx, "internal SSL error");
data->ssl = SSL_new(data->ctx);
@@ -392,8 +399,9 @@
}
-int list_folders(int sd, int *seq, int use_ssl, struct __data *data){
+int list_folders(int sd, int *seq, int use_ssl, char *folder_name, struct __data *data){
char *p, *q, *r, *buf, *ruf, tag[SMALLBUFSIZE], tagok[SMALLBUFSIZE], puf[MAXBUFSIZE];
+ char attrs[SMALLBUFSIZE], folder[SMALLBUFSIZE];
int len=MAXBUFSIZE+3, pos=0, n, rc=ERR, fldrlen=0, result;
printf("List of IMAP folders:\n");
@@ -404,11 +412,15 @@
memset(buf, 0, len);
snprintf(tag, sizeof(tag)-1, "A%d", *seq); snprintf(tagok, sizeof(tagok)-1, "A%d OK", (*seq)++);
- //snprintf(puf, sizeof(puf)-1, "%s LIST \"\" %%\r\n", tag);
- snprintf(puf, sizeof(puf)-1, "%s LIST \"\" \"*\"\r\n", tag);
+ if(folder_name == NULL)
+ snprintf(puf, sizeof(puf)-1, "%s LIST \"\" \"*\"\r\n", tag);
+ else
+ snprintf(puf, sizeof(puf)-1, "%s LIST \"%s\" \"*\"\r\n", tag, folder_name);
write1(sd, puf, strlen(puf), use_ssl, data->ssl);
+ p = NULL;
+
while(1){
n = recvtimeoutssl(sd, puf, sizeof(puf), data->import->timeout, use_ssl, data->ssl);
if(n < 0) return ERR;
@@ -428,9 +440,20 @@
memcpy(buf + pos, puf, n);
pos += n;
- if(strstr(buf, tagok)) break;
+ p = strstr(buf, tagok);
+ if(p) break;
}
+ // trim the "A3 OK LIST completed" trailer off
+ if(p) *p = '\0';
+
+ // No folder list, so this must be a leaf folder
+ if(strlen(buf) == 0){
+ addnode(data->imapfolders, folder_name);
+ return OK;
+ }
+
+ memset(attrs, 0, sizeof(attrs));
p = buf;
do {
@@ -446,6 +469,9 @@
q = strstr(puf, ") \"");
if(q){
if (!fldrlen) {
+ *q = '\0';
+ snprintf(attrs, sizeof(attrs)-1, "%s", &puf[8]);
+
q += 3;
while(*q != '"') q++;
q++;
@@ -469,22 +495,27 @@
}
r++;
}
- addnode(data->imapfolders, ruf);
- printf("=> '%s'\n", ruf);
+
+ snprintf(folder, sizeof(folder)-1, "%s", ruf);
+
free(ruf);
fldrlen = 0;
} else {
- addnode(data->imapfolders, q);
- printf("=> '%s'\n", q);
+ snprintf(folder, sizeof(folder)-1, "%s", q);
}
-
+
+ if(!strstr(attrs, "\\Noselect")){
+ addnode(data->imapfolders, folder);
+ }
+ else printf("skipping ");
+
+ printf("=> '%s [%s]'\n", folder, attrs);
+
+ memset(attrs, 0, sizeof(attrs));
}
}
}
- else {
- if(strncmp(puf, tagok, strlen(tagok)) == 0) {}
- }
} while(p);
diff --git a/src/import.c b/src/import.c
index 5b82d4f..1b973f0 100644
--- a/src/import.c
+++ b/src/import.c
@@ -20,7 +20,7 @@
int import_message(char *filename, struct session_data *sdata, struct __data *data, struct __config *cfg){
int rc=ERR;
- char *rule;
+ char *p, *rule, newpath[SMALLBUFSIZE];
struct stat st;
struct parser_state state;
struct counters counters;
@@ -126,6 +126,19 @@
break;
}
+ if(rc != OK && data->import->failed_folder){
+ p = strrchr(filename, '/');
+ if(p)
+ p++;
+ else
+ p = filename;
+
+ snprintf(newpath, sizeof(newpath)-2, "%s/%s", data->import->failed_folder, p);
+
+ if(rename(filename, newpath))
+ printf("cannot move %s to %s\n", filename, newpath);
+ }
+
return rc;
}
diff --git a/src/import.h b/src/import.h
index 9db5ba1..e52bfa2 100644
--- a/src/import.h
+++ b/src/import.h
@@ -21,7 +21,7 @@
int process_pop3_emails(int sd, struct session_data *sdata, struct __data *data, int use_ssl, int dryrun, struct __config *cfg);
int connect_to_imap_server(int sd, int *seq, char *username, char *password, struct __data *data, int use_ssl);
-int list_folders(int sd, int *seq, int use_ssl, struct __data *data);
+int list_folders(int sd, int *seq, int use_ssl, char *folder_name, struct __data *data);
int process_imap_folder(int sd, int *seq, char *folder, struct session_data *sdata, struct __data *data, int use_ssl, int dryrun, struct __config *cfg);
void send_imap_close(int sd, int *seq, struct __data *data, int use_ssl);
diff --git a/src/import_imap.c b/src/import_imap.c
index 3929f73..ef3b098 100644
--- a/src/import_imap.c
+++ b/src/import_imap.c
@@ -64,18 +64,7 @@
}
- /*
- * if the user gives -f , then don't iterate through the folder list
- * rather build the folderlist based on the option, 2014.10.14, SJ
- */
-
- if(folder_imap){
- addnode(data->imapfolders, folder_imap);
- } else {
- rc = list_folders(sd, &seq, use_ssl, data);
- if(rc == ERR) goto ENDE_IMAP;
- }
-
+ if(list_folders(sd, &seq, use_ssl, folder_imap, data) == ERR) goto ENDE_IMAP;
for(i=0;iimapfolders[i];
@@ -90,10 +79,6 @@
if(strstr(skiplist, puf)) skipmatch = 1;
}
- if(folder_imap && strstr(q->str, folder_imap) == NULL){
- skipmatch = 1;
- }
-
if(skipmatch == 1){
if(data->quiet == 0) printf("SKIPPING FOLDER: %s\n", (char *)q->str);
}
diff --git a/src/misc.c b/src/misc.c
index c006a8c..013f909 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -216,13 +216,13 @@
int n=0;
char *p;
- p = strrchr(s, '\n');
+ p = strchr(s, '\n');
if(p){
*p = '\0';
n++;
}
- p = strrchr(s, '\r');
+ p = strchr(s, '\r');
if(p){
*p = '\0';
n++;
@@ -609,11 +609,14 @@
}
-int can_i_write_current_directory(){
+int can_i_write_directory(char *dir){
int fd;
char filename[SMALLBUFSIZE];
- snprintf(filename, sizeof(filename)-1, "__piler_%d", getpid());
+ if(dir)
+ snprintf(filename, sizeof(filename)-1, "%s/__piler_%d", dir, getpid());
+ else
+ snprintf(filename, sizeof(filename)-1, "__piler_%d", getpid());
fd = open(filename, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP);
if(fd == -1){
diff --git a/src/misc.h b/src/misc.h
index 89a4050..ced3ab1 100644
--- a/src/misc.h
+++ b/src/misc.h
@@ -44,7 +44,7 @@
void *get_in_addr(struct sockaddr *sa);
-int can_i_write_current_directory();
+int can_i_write_directory(char *dir);
#ifndef _GNU_SOURCE
char *strcasestr(const char *s, const char *find);
diff --git a/src/parser.c b/src/parser.c
index 703fe3b..6480c8e 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -172,10 +172,10 @@
int parse_line(char *buf, struct parser_state *state, struct session_data *sdata, int take_into_pieces, char *writebuffer, int writebuffersize, char *abuffer, int abuffersize, struct __data *data, struct __config *cfg){
- char *p, *q, puf[SMALLBUFSIZE];
+ char *p, *q, *r, puf[SMALLBUFSIZE];
unsigned char b64buffer[MAXBUFSIZE];
char tmpbuf[MAXBUFSIZE];
- int n64, writelen, boundary_line=0, result;
+ int n, n64, writelen, boundary_line=0, result;
unsigned int len;
if(cfg->debug == 1) printf("line: %s", buf);
@@ -197,7 +197,14 @@
sdata->restored_copy = 1;
}
- if(sdata->ms_journal == 0 && strncmp(buf, "X-MS-Journal-Report:", strlen("X-MS-Journal-Report:")) == 0){
+ if(sdata->ms_journal == 0 && ( strncmp(buf, "X-MS-Journal-Report:", strlen("X-MS-Journal-Report:")) == 0 || strncmp(buf, "X-WM-Journal-Report: journal", strlen("X-WM-Journal-Report: journal")) == 0) ){
+
+ memset(state->b_to, 0, MAXBUFSIZE);
+ state->tolen = 0;
+ memset(state->b_to_domain, 0, SMALLBUFSIZE);
+
+ clearhash(state->rcpt);
+
//if(sdata->import == 0){
sdata->ms_journal = 1;
memset(state->message_id, 0, SMALLBUFSIZE);
@@ -654,8 +661,6 @@
}
- /* remove all HTML tags */
- if(state->texthtml == 1 && state->message_state == MSG_BODY) markHTML(buf, state);
if(state->message_state == MSG_BODY && state->qp == 1){
fixupSoftBreakInQuotedPritableLine(buf, state); // 2011.12.07
@@ -665,6 +670,10 @@
/* I believe that we can live without this function call */
//decodeURL(buf);
+ /* remove all HTML tags */
+ if(state->texthtml == 1 && state->message_state == MSG_BODY) markHTML(buf, state);
+
+
if(state->texthtml == 1) decodeHTML(buf, state->utf8);
/* encode the body if it's not utf-8 encoded */
@@ -699,15 +708,17 @@
len = strlen(puf);
- if(state->is_header == 0 && strncmp(puf, "__URL__", 7) && (puf[0] == ' ' || (len > MAX_WORD_LEN && cfg->enable_cjk == 0) || isHexNumber(puf)) ) continue;
-
+ // skip body tokens if not an URL && (empty token || too long)
+ if(state->is_header == 0 && strncmp(puf, "__URL__", 7) && (puf[0] == ' ' || (len > MAX_WORD_LEN && cfg->enable_cjk == 0)) ){
+ continue;
+ }
if(state->message_state == MSG_FROM && state->is_1st_header == 1 && strlen(state->b_from) < SMALLBUFSIZE-len-1){
strtolower(puf);
memcpy(&(state->b_from[strlen(state->b_from)]), puf, len);
- if(does_it_seem_like_an_email_address(puf) == 1 && state->b_from_domain[0] == '\0' && len > 5){
+ if(len >= MIN_EMAIL_ADDRESS_LEN && does_it_seem_like_an_email_address(puf) == 1 && state->b_from_domain[0] == '\0'){
q = strchr(puf, '@');
if(q && strlen(q) > 5){
memcpy(&(state->b_from_domain), q+1, strlen(q+1)-1);
@@ -739,15 +750,28 @@
/* skip any address matching ...@cfg->hostid, 2013.10.29, SJ */
q = strchr(puf, '@');
- if(q && strncmp(q+1, cfg->hostid, cfg->hostid_len) == 0){
- continue;
+ if(q){
+ if(strncmp(q+1, cfg->hostid, cfg->hostid_len) == 0){
+ continue;
+ }
+
+ /* fix aaa+bbb@ccc.fu address to aaa@ccc.fu, 2017.01.31, SJ */
+
+ r = strchr(puf, '+');
+ if(r){
+ n = strlen(q);
+ memmove(r, q, n);
+ *(r+n) = '\0';
+ q = r;
+ }
}
addnode(state->rcpt, puf);
memcpy(&(state->b_to[state->tolen]), puf, len);
state->tolen += len;
- if(does_it_seem_like_an_email_address(puf) == 1){
+ if(len >= MIN_EMAIL_ADDRESS_LEN && does_it_seem_like_an_email_address(puf) == 1){
+
if(is_email_address_on_my_domains(puf, data) == 1) sdata->internal_recipient = 1;
else sdata->external_recipient = 1;
@@ -770,6 +794,11 @@
}
else if(state->message_state == MSG_BODY && len >= cfg->min_word_len && state->bodylen < BIGBUFSIZE-len-1){
+ // 99% of email addresses are longer than 8 characters
+ if(len >= MIN_EMAIL_ADDRESS_LEN && does_it_seem_like_an_email_address(puf)){
+ fix_email_address_for_sphinx(puf);
+ }
+
memcpy(&(state->b_body[state->bodylen]), puf, len);
state->bodylen += len;
}
diff --git a/src/parser.h b/src/parser.h
index fd361ee..1ad74af 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -15,7 +15,6 @@
void init_state(struct parser_state *state);
time_t parse_date_header(char *s);
-int isHexNumber(char *p);
int extract_boundary(char *p, struct parser_state *state);
void fixupEncodedHeaderLine(char *buf, int buflen);
void fixupSoftBreakInQuotedPritableLine(char *buf, struct parser_state *state);
diff --git a/src/parser_utils.c b/src/parser_utils.c
index acc8c62..27188ce 100644
--- a/src/parser_utils.c
+++ b/src/parser_utils.c
@@ -261,16 +261,6 @@
}
-int isHexNumber(char *p){
- for(; *p; p++){
- if(!(*p == '-' || (*p >= 0x30 && *p <= 0x39) || (*p >= 0x41 && *p <= 0x46) || (*p >= 0x61 && *p <= 0x66)) )
- return 0;
- }
-
- return 1;
-}
-
-
int extract_boundary(char *p, struct parser_state *state){
char *q, *q2;
@@ -604,7 +594,7 @@
if(*p == '\'' && prev == '"') { *p = ' '; }
if(*p == '\'' && *(p+1) == '"'){ *p = ' '; }
- if(*p == '_' || *p == '\'' || *p == '&'){ continue; }
+ if(*p == '_' || *p == '\'' || *p == '&' || *p == '+'){ continue; }
prev = *p;
}
@@ -623,8 +613,6 @@
if(url == 1) url = 0;
}
- if(*p == '@') *p = 'X';
-
if(delimiter_characters[(unsigned int)*p] != ' ') *p = ' ';
/* we MUSTN'T convert it to lowercase in the 'else' case, because it breaks utf-8 encoding! */
diff --git a/src/piler.c b/src/piler.c
index e59d84f..5e3e289 100644
--- a/src/piler.c
+++ b/src/piler.c
@@ -265,7 +265,11 @@
SSL_library_init();
SSL_load_error_strings();
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
data.ctx = SSL_CTX_new(TLSv1_server_method());
+#else
+ data.ctx = SSL_CTX_new(TLS_server_method());
+#endif
if(data.ctx == NULL){ syslog(LOG_PRIORITY, "SSL_CTX_new() failed"); return ERR; }
diff --git a/src/pilerexport.c b/src/pilerexport.c
index 5595f4f..3983ff1 100644
--- a/src/pilerexport.c
+++ b/src/pilerexport.c
@@ -560,7 +560,7 @@
regfree(®exp);
- if(!can_i_write_current_directory()) __fatal("cannot write current directory!");
+ if(!can_i_write_directory(NULL)) __fatal("cannot write current directory!");
(void) openlog("pilerexport", LOG_PID, LOG_MAIL);
diff --git a/src/pilerimport.c b/src/pilerimport.c
index 4ee4df5..4a1333f 100644
--- a/src/pilerimport.c
+++ b/src/pilerimport.c
@@ -53,6 +53,7 @@
printf(" -R Assign IMAP folder names as Piler folder names\n");
printf(" -b Import only this many emails\n");
printf(" -s Start importing POP3 emails from this position\n");
+ printf(" -j Move failed to import emails to this folder\n");
printf(" -a Add recipient to the To:/Cc: list\n");
printf(" -D Dry-run, do not import anything\n");
printf(" -o Only download emails for POP3/IMAP import\n");
@@ -83,7 +84,7 @@
import.import_job_id = import.total_messages = import.total_size = import.processed_messages = import.batch_processing_limit = 0;
import.started = import.updated = import.finished = import.remove_after_import = 0;
- import.extra_recipient = import.move_folder = NULL;
+ import.extra_recipient = import.move_folder = import.failed_folder = NULL;
import.start_position = 1;
import.download_only = 0;
import.timeout = 30;
@@ -120,6 +121,7 @@
{"quiet", no_argument, 0, 'q' },
{"recursive", required_argument, 0, 'R' },
{"remove-after-import",no_argument, 0, 'r' },
+ {"failed-folder", required_argument, 0, 'j' },
{"move-folder", required_argument, 0, 'g' },
{"only-download",no_argument, 0, 'o' },
{"gui-import", no_argument, 0, 'G' },
@@ -130,9 +132,9 @@
int option_index = 0;
- c = getopt_long(argc, argv, "c:m:M:e:d:i:K:u:p:P:x:F:f:a:b:t:s:g:GDRroqh?", long_options, &option_index);
+ c = getopt_long(argc, argv, "c:m:M:e:d:i:K:u:p:P:x:F:f:a:b:t:s:g:j:GDRroqh?", long_options, &option_index);
#else
- c = getopt(argc, argv, "c:m:M:e:d:i:K:u:p:P:x:F:f:a:b:t:s:g:GDRroqh?");
+ c = getopt(argc, argv, "c:m:M:e:d:i:K:u:p:P:x:F:f:a:b:t:s:g:j:GDRroqh?");
#endif
if(c == -1) break;
@@ -209,6 +211,10 @@
data.import->move_folder = optarg;
break;
+ case 'j' :
+ data.import->failed_folder = optarg;
+ break;
+
case 'o' :
data.import->download_only = 1;
dryrun = 1;
@@ -262,7 +268,12 @@
if(!mbox[0] && !mboxdir && !emlfile && !directory && !imapserver && !pop3server && import_from_gui == 0) usage();
- if(!can_i_write_current_directory()) __fatal("cannot write current directory!");
+ if(data.import->failed_folder && !can_i_write_directory(data.import->failed_folder)){
+ printf("cannot write failed directory '%s'\n", data.import->failed_folder);
+ return ERR;
+ }
+
+ if(!can_i_write_directory(NULL)) __fatal("cannot write current directory!");
cfg = read_config(configfile);
diff --git a/src/pop3.c b/src/pop3.c
index 3e7eb37..7daf342 100644
--- a/src/pop3.c
+++ b/src/pop3.c
@@ -48,7 +48,11 @@
SSL_library_init();
SSL_load_error_strings();
+ #if OPENSSL_VERSION_NUMBER < 0x10100000L
data->ctx = SSL_CTX_new(TLSv1_client_method());
+ #else
+ data->ctx = SSL_CTX_new(TLS_client_method());
+ #endif
CHK_NULL(data->ctx, "internal SSL error");
data->ssl = SSL_new(data->ctx);
diff --git a/src/reindex.c b/src/reindex.c
index dbf1cc6..e4d966e 100644
--- a/src/reindex.c
+++ b/src/reindex.c
@@ -211,7 +211,7 @@
if(all == 0 && (from_id <= 0 || to_id <= 0) ) usage();
- if(!can_i_write_current_directory()) __fatal("cannot write current directory!");
+ if(!can_i_write_directory(NULL)) __fatal("cannot write current directory!");
(void) openlog("reindex", LOG_PID, LOG_MAIL);
diff --git a/src/session.c b/src/session.c
index 2b7a649..d050fee 100644
--- a/src/session.c
+++ b/src/session.c
@@ -428,7 +428,7 @@
#ifdef HAVE_ANTIVIRUS
if(sctx->cfg->use_antivirus == 1){
- sctx->sdata->rav = do_av_check(sctx->sdata, &virusinfo[0], sctx->data, sctx->cfg);
+ sctx->sdata->rav = do_av_check(sctx->sdata, sctx->virusinfo, sctx->data, sctx->cfg);
}
#endif
@@ -467,7 +467,6 @@
void process_data(struct session_ctx *sctx){
char *arule = NULL;
- char virusinfo[SMALLBUFSIZE];
sctx->inj = ERR;
sctx->status = S_STATUS_UNDEF;
@@ -483,9 +482,10 @@
sctx->inj = OK;
}
else if(AVIR_VIRUS == sctx->sdata->rav){
- syslog(LOG_PRIORITY, "%s: found virus: %s", sctx->sdata->ttmpfile, virusinfo);
+ syslog(LOG_PRIORITY, "%s: found virus: %s", sctx->sdata->ttmpfile, sctx->virusinfo);
sctx->counters->c_virus++;
sctx->inj = OK;
+ sctx->status = S_STATUS_DISCARDED;
} else if(strlen(sctx->sdata->bodydigest) < 10) {
syslog(LOG_PRIORITY, "%s: invalid digest", sctx->sdata->ttmpfile);
sctx->inj = ERR;
diff --git a/src/store.c b/src/store.c
index 1230a45..0e1d821 100644
--- a/src/store.c
+++ b/src/store.c
@@ -46,7 +46,11 @@
Bytef *z=NULL;
uLongf dstlen;
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_CIPHER_CTX ctx;
+#else
+ EVP_CIPHER_CTX *ctx;
+#endif
unsigned char *outbuf=NULL;
int outlen=0, writelen, tmplen;
@@ -101,16 +105,34 @@
if(cfg->encrypt_messages == 1){
gettimeofday(&tv1, &tz);
+ #if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_CIPHER_CTX_init(&ctx);
EVP_EncryptInit_ex(&ctx, EVP_bf_cbc(), NULL, cfg->key, cfg->iv);
+ #else
+ ctx = EVP_CIPHER_CTX_new();
+ if(!ctx) goto ENDE;
+
+ EVP_CIPHER_CTX_init(ctx);
+ EVP_EncryptInit_ex(ctx, EVP_bf_cbc(), NULL, cfg->key, cfg->iv);
+ #endif
outbuf = malloc(dstlen + EVP_MAX_BLOCK_LENGTH);
if(outbuf == NULL) goto ENDE;
+ #if OPENSSL_VERSION_NUMBER < 0x10100000L
if(!EVP_EncryptUpdate(&ctx, outbuf, &outlen, z, dstlen)) goto ENDE;
if(!EVP_EncryptFinal_ex(&ctx, outbuf + outlen, &tmplen)) goto ENDE;
+ #else
+ if(!EVP_EncryptUpdate(ctx, outbuf, &outlen, z, dstlen)) goto ENDE;
+ if(!EVP_EncryptFinal_ex(ctx, outbuf + outlen, &tmplen)) goto ENDE;
+ #endif
+
outlen += tmplen;
+ #if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_CIPHER_CTX_cleanup(&ctx);
+ #else
+ EVP_CIPHER_CTX_free(ctx);
+ #endif
gettimeofday(&tv2, &tz);
sdata->__encrypt += tvdiff(tv2, tv1);
diff --git a/src/test.c b/src/test.c
index 3c2cf1a..f85e53c 100644
--- a/src/test.c
+++ b/src/test.c
@@ -17,6 +17,7 @@
int main(int argc, char **argv){
int i;
+ time_t retention_seconds=0;
struct stat st;
struct session_data sdata;
struct parser_state state;
@@ -32,7 +33,7 @@
exit(1);
}
- if(!can_i_write_current_directory()) __fatal("cannot write current directory!");
+ if(!can_i_write_directory(NULL)) __fatal("cannot write current directory!");
if(stat(argv[1], &st) != 0){
fprintf(stderr, "%s is not found\n", argv[1]);
@@ -107,11 +108,12 @@
printf("rules check: %s\n", rule);
- sdata.retained = sdata.now + query_retain_period(&data, &state, st.st_size, sdata.spam_message, &cfg);
+ retention_seconds = query_retain_period(&data, &state, st.st_size, sdata.spam_message, &cfg);
+ sdata.retained = sdata.now + retention_seconds;
printf("folder: %d\n", get_folder_id_by_rule(&data, &state, st.st_size, sdata.spam_message, &cfg));
- printf("retention period: %lu\n", sdata.retained);
+ printf("retention period: %lu (%ld days)\n", sdata.retained, retention_seconds/86400);
clearrules(data.archiving_rules);
clearrules(data.retention_rules);
diff --git a/src/trans.h b/src/trans.h
index 562853f..f5dee4b 100644
--- a/src/trans.h
+++ b/src/trans.h
@@ -9,7 +9,7 @@
'x',' ','"','#', ' ','%','&','x', '(',')','*','+', ',',' ',' ','/',
' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',':',';', '<','=','>','?',
- '@',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ',
+ ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ',
' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ','[', '\\',']','^','_',
'`',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ',
' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ','{', '|','}','~','x',
diff --git a/unit_tests/check_mydomains.c b/unit_tests/check_mydomains.c
index 3cdefa2..2ef1eed 100644
--- a/unit_tests/check_mydomains.c
+++ b/unit_tests/check_mydomains.c
@@ -115,7 +115,7 @@
int main(){
struct __config cfg;
- if(!can_i_write_current_directory()) __fatal("cannot write current directory!");
+ if(!can_i_write_directory(NULL)) __fatal("cannot write current directory!");
(void) openlog("mydomains_test", LOG_PID, LOG_MAIL);
diff --git a/unit_tests/check_parser.c b/unit_tests/check_parser.c
index 8f0c0ce..3835240 100644
--- a/unit_tests/check_parser.c
+++ b/unit_tests/check_parser.c
@@ -104,7 +104,7 @@
struct __config cfg;
- if(!can_i_write_current_directory()) __fatal("cannot write current directory!");
+ if(!can_i_write_directory(NULL)) __fatal("cannot write current directory!");
(void) openlog("mydomains_test", LOG_PID, LOG_MAIL);
diff --git a/unit_tests/check_rules.c b/unit_tests/check_rules.c
index 457baf1..cfaaa77 100644
--- a/unit_tests/check_rules.c
+++ b/unit_tests/check_rules.c
@@ -158,7 +158,7 @@
int main(){
struct __config cfg;
- if(!can_i_write_current_directory()) __fatal("cannot write current directory!");
+ if(!can_i_write_directory(NULL)) __fatal("cannot write current directory!");
(void) openlog("rule_test", LOG_PID, LOG_MAIL);
diff --git a/util/Makefile.in b/util/Makefile.in
index f4b7e5c..a0be994 100644
--- a/util/Makefile.in
+++ b/util/Makefile.in
@@ -42,6 +42,7 @@
$(INSTALL) -m 0755 $(srcdir)/indexer.attachment.sh $(DESTDIR)$(libexecdir)/piler
$(INSTALL) -m 0755 $(srcdir)/import.sh $(DESTDIR)$(libexecdir)/piler
$(INSTALL) -m 0755 $(srcdir)/purge.sh $(DESTDIR)$(libexecdir)/piler
+ $(INSTALL) -m 0755 $(srcdir)/pilerpurge.py $(DESTDIR)$(libexecdir)/piler
$(INSTALL) -m 0755 $(srcdir)/postinstall.sh $(DESTDIR)$(libexecdir)/piler
$(INSTALL) -m 0755 $(srcdir)/db-mysql.sql $(DESTDIR)$(datarootdir)/piler
diff --git a/util/automated-search.php b/util/automated-search.php
old mode 100644
new mode 100755
index 022408d..67702b4
--- a/util/automated-search.php
+++ b/util/automated-search.php
@@ -112,7 +112,7 @@
-if(ENABLE_SYSLOG == 1) { openlog("piler-automated-search", LOG_PID, LOG_MAIL); }
+openlog("piler-automated-search", LOG_PID, LOG_MAIL);
/* check if user has authenticated himself. If not, we send him to login */
@@ -197,6 +197,8 @@
global $search_expression;
global $page_len;
+ $EOL = "\r\n";
+
$lang = Registry::get('language');
extract($lang->data);
@@ -215,22 +217,32 @@
$a['date1'] = $a['date2'] = date("Y.m.d", time() - 86400);
}
+ $boundary = "--=_NextPart_000_ABCDEFGHI";
+
list ($n, $total_found, $all_ids, $messages) = $search->search_messages($a, 0);
if($dry_run == 0)
{
- $msg = "From: " . SMTP_FROMADDR . EOL;
- $msg .= "To: " . ADMIN_EMAIL . EOL;
- $msg .= "Date: " . date(DATE_RFC2822) . EOL;
- $msg .= "Subject: =?UTF-8?Q?" . preg_replace("/\n/", "", my_qp_encode($title)) . "?=" . EOL;
- $msg .= "Message-ID: <" . generate_random_string(25) . '@' . SITE_NAME . ">" . EOL;
- $msg .= "MIME-Version: 1.0" . EOL;
- $msg .= "Content-Type: text/html; charset=\"utf-8\"" . EOL;
- $msg .= EOL . EOL;
+ $msg = "From: " . SMTP_FROMADDR . $EOL;
+ $msg .= "To: " . ADMIN_EMAIL . $EOL;
+ $msg .= "Date: " . date(DATE_RFC2822) . $EOL;
+ $msg .= "Subject: =?UTF-8?Q?" . preg_replace("/\n/", "", my_qp_encode($title)) . "?=" . $EOL;
+ $msg .= "Message-ID: <" . generate_random_string(25) . '@' . SITE_NAME . ">" . $EOL;
+ $msg .= "MIME-Version: 1.0" . $EOL;
+ $msg .= "Content-Type: multipart/alternative;" . $EOL;
+ $msg .= "\tboundary=\"$boundary\"" . $EOL;
+ $msg .= $EOL . $EOL;
+
+ $msg .= "--$boundary" . $EOL;
+ $msg .= "Content-Type: text/html; charset=\"utf-8\"" . $EOL;
+ $msg .= "Content-Transfer-Encoding: base64" . $EOL . $EOL;
ob_start();
include($webuidir . "/view/theme/default/templates/search/auto.tpl");
- $msg .= ob_get_contents();
+
+ $msg .= chunk_split(base64_encode(ob_get_contents()), 76, $EOL);
+
+ $msg .= "--" . $boundary . $EOL . $EOL;
ob_end_clean();
@@ -239,7 +251,7 @@
else {
print "search = " . $data['search'] . "\n";
print_r($all_ids);
- print EOL . EOL;
+ print $EOL . $EOL;
}
}
diff --git a/util/generate_stats.php b/util/generate_stats.php
index 2f4ff1a..5b80e5b 100644
--- a/util/generate_stats.php
+++ b/util/generate_stats.php
@@ -1,5 +1,5 @@
model('accounting/accounting');
+$loader->model('domain/domain');
$messagestats = new ModelAccountingAccounting();
$_SESSION['username'] = 'cli-admin';
@@ -76,7 +74,7 @@
extract($language->data);
-$records = $messagestats->run_counters($start,$stop);
+$records = $messagestats->run_counters($start, $stop, 'sent');
$timeend = microtime(true);
$timegone = $timeend - $timestart;
@@ -89,15 +87,16 @@
echo("Added ".$records['addedstats']." records\n");
echo("Completed Run in ".$timegone." seconds\n\n");
-# Functions
+
function display_help() {
- $phpself = basename(__FILE__);
- echo("\nUsage: $phpself --webui [PATH] [OPTIONS...]\n\n");
- echo("\t--webui=\"[REQUIRED: path to the Piler WebUI Directory]\"\n\n");
- echo("options:\n");
- echo("\t-a Reruns statistics for all records in the message view\n");
- echo("\t-h Prints this help screen and exits\n");
- echo("\t--start=\"Beginning of date range to process, ok values are today, yesterday or DDMMMYYYY...anything php's strtotime can process. Optional, will default to beginning of current day.\"\n");
- echo("\t--stop=\"End of date range, same parameters as above. Optional (will default to end of current day)\"\n\n");
+ $phpself = basename(__FILE__);
+ echo("\nUsage: $phpself --webui [PATH] [OPTIONS...]\n\n");
+ echo("\t--webui=\"[REQUIRED: path to the Piler WebUI Directory]\"\n\n");
+ echo("options:\n");
+ echo("\t-a Reruns statistics for all records in the message view\n");
+ echo("\t-h Prints this help screen and exits\n");
+ echo("\t--start=\"Beginning of date range to process, ok values are today, yesterday or MYYYY/MM/DD...anything php's strtotime can process. Optional, will default to beginning of current day.\"\n");
+ echo("\t--stop=\"End of date range, same parameters as above. Optional (will default to end of current day)\"\n\n");
+
+ exit;
}
-?>
diff --git a/util/pilerpurge.py b/util/pilerpurge.py
new file mode 100755
index 0000000..fa8809b
--- /dev/null
+++ b/util/pilerpurge.py
@@ -0,0 +1,254 @@
+#!/usr/bin/python
+
+import ConfigParser
+import MySQLdb as dbapi
+import StringIO
+import argparse
+import getpass
+import os
+import sys
+import syslog
+import time
+
+SQL_PURGE_SELECT_QUERY = "SELECT piler_id, size FROM " +\
+ "metadata WHERE deleted=0 AND retained < UNIX_TIMESTAMP(NOW()) " +\
+ "AND id NOT IN (SELECT id FROM rcpt WHERE `to` IN " +\
+ "(SELECT email FROM legal_hold)) AND id NOT IN (SELECT " +\
+ "id FROM metadata WHERE `from` IN (SELECT email FROM " +\
+ "legal_hold))"
+
+opts = {}
+default_conf = "/usr/local/etc/piler/piler.conf"
+
+
+def read_options(filename="", opts={}):
+ s = "[piler]\n" + open(filename, 'r').read()
+ fp = StringIO.StringIO(s)
+ config = ConfigParser.RawConfigParser()
+ config.readfp(fp)
+
+ opts['username'] = config.get('piler', 'mysqluser')
+ opts['password'] = config.get('piler', 'mysqlpwd')
+ opts['database'] = config.get('piler', 'mysqldb')
+ opts['storedir'] = config.get('piler', 'queuedir')
+
+ opts['server_id'] = "%02x" % config.getint('piler', 'server_id')
+
+
+def is_purge_enabled(opts={}):
+ cursor = opts['db'].cursor()
+
+ cursor.execute("SELECT `value` FROM `option` WHERE `key`='enable_purge'")
+
+ row = cursor.fetchone()
+ if row and row[0] == '1':
+ return True
+
+ return False
+
+
+def purge_m_files(ids=[], opts={}):
+ if len(ids) > 0:
+ remove_m_files(ids, opts)
+
+ # Set deleted=1 for aged metadata entries
+
+ if opts['dry_run'] is False:
+ cursor = opts['db'].cursor()
+ format = ", ".join(['%s'] * len(ids))
+ cursor.execute("UPDATE metadata SET deleted=1 WHERE piler_id IN " +
+ "(%s)" % (format), ids)
+ opts['db'].commit()
+
+
+def purge_attachments_by_piler_id(ids=[], opts={}):
+ format = ", ".join(['%s'] * len(ids))
+
+ cursor = opts['db'].cursor()
+
+ cursor.execute("SELECT i, piler_id, attachment_id, refcount FROM " +
+ "v_attachment WHERE piler_id IN (%s)" % (format), ids)
+
+ while True:
+ rows = cursor.fetchall()
+ if rows == ():
+ break
+ else:
+ remove_attachment_files(rows, opts)
+
+
+def purge_attachments_by_attachment_id(opts={}):
+ format = ", ".join(['%s'] * len(opts['referenced_attachments']))
+
+ cursor = opts['db'].cursor()
+
+ cursor.execute("SELECT i, piler_id, attachment_id, refcount FROM " +
+ "v_attachment WHERE i IN (%s)" %
+ (format), opts['referenced_attachments'])
+
+ while True:
+ rows = cursor.fetchall()
+ if rows == ():
+ break
+ else:
+ remove_attachment_files(rows, opts)
+
+
+def remove_attachment_files(rows=(), opts={}):
+ remove_ids = []
+ referenced_ids = []
+
+ if rows == ():
+ return
+
+ # If refcount > 0, then save attachment.id, and handle later,
+ # otherwise delete the attachment from the filesystem, and
+ # attachment table
+
+ for (id, piler_id, attachment_id, refcount) in rows:
+ if refcount == 0:
+ remove_ids.append(id)
+
+ if opts['dry_run'] is False:
+ unlink(get_attachment_file_path(piler_id, attachment_id,
+ opts), opts)
+ else:
+ print get_attachment_file_path(piler_id, attachment_id, opts)
+ else:
+ referenced_ids.append(id)
+
+ if remove_ids:
+ if opts['dry_run'] is False:
+ format = ", ".join(['%s'] * len(remove_ids))
+ cursor = opts['db'].cursor()
+ cursor.execute("DELETE FROM attachment WHERE id IN (%s)" %
+ (format), remove_ids)
+ opts['db'].commit()
+ else:
+ print remove_ids
+
+ opts['referenced_attachments'] = referenced_ids
+
+
+def remove_m_files(ids=[], opts={}):
+ for i in range(0, len(ids)):
+ if opts['dry_run'] is False:
+ unlink(get_m_file_path(ids[i], opts), opts)
+ opts['messages'] = opts['messages'] + 1
+ else:
+ print get_m_file_path(ids[i], opts)
+
+
+def unlink(filename="", opts={}):
+ if opts['verbose']:
+ print "removing", filename
+
+ try:
+ st = os.stat(filename)
+ opts['purged_stored_size'] = opts['purged_stored_size'] + st.st_size
+ opts['files'] = opts['files'] + 1
+ os.unlink(filename)
+ except:
+ pass
+
+
+def get_m_file_path(id='', opts={}):
+ return "/".join([opts['storedir'], opts['server_id'], id[8:11], id[32:34],
+ id[34:36], id + ".m"])
+
+
+def get_attachment_file_path(piler_id='', attachment_id=0, opts={}):
+ return "/".join([opts['storedir'], opts['server_id'], piler_id[8:11],
+ piler_id[32:34], piler_id[34:36], piler_id + ".a" +
+ str(attachment_id)])
+
+
+def main():
+ if "/usr/libexec" in __file__:
+ default_conf = "/etc/piler/piler.conf"
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-c", "--config", type=str, help="piler.conf path",
+ default="/etc/piler/piler.conf")
+ parser.add_argument("-b", "--batch-size", type=int, help="batch size " +
+ "to delete", default=1000)
+ parser.add_argument("-d", "--dry-run", help="dry run", action='store_true')
+ parser.add_argument("-v", "--verbose", help="verbose mode",
+ action='store_true')
+
+ args = parser.parse_args()
+
+ if getpass.getuser() not in ['root', 'piler']:
+ print "Please run me as user 'piler'"
+ sys.exit(1)
+
+ opts['dry_run'] = args.dry_run
+ opts['verbose'] = args.verbose
+ opts['db'] = None
+ opts['messages'] = 0
+ opts['files'] = 0
+ opts['size'] = 0
+ opts['purged_size'] = 0
+ opts['purged_stored_size'] = 0
+ opts['referenced_attachments'] = []
+
+ syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_MAIL)
+
+ read_options(args.config, opts)
+ try:
+ opts['db'] = dbapi.connect("localhost", opts['username'],
+ opts['password'], opts['database'])
+
+ if is_purge_enabled(opts) is False:
+ syslog.syslog("Purging emails is disabled")
+ sys.exit(1)
+
+ cursor = opts['db'].cursor()
+ cursor.execute(SQL_PURGE_SELECT_QUERY)
+
+ while True:
+ rows = cursor.fetchmany(args.batch_size)
+ if rows == ():
+ break
+
+ piler_id = [x[0] for x in rows]
+ size = [x[1] for x in rows]
+
+ opts['purged_size'] = opts['purged_size'] + sum(size)
+
+ purge_m_files(piler_id, opts)
+ purge_attachments_by_piler_id(piler_id, opts)
+
+ # It's possible that there's attachment duplication, thus
+ # refcount > 0, even though after deleting the duplicates
+ # (references) refcount becomes zero.
+ if len(opts['referenced_attachments']) > 0:
+ purge_attachments_by_attachment_id(opts)
+
+ # Update the counter table
+ if opts['dry_run'] is False:
+ cursor.execute("UPDATE counter SET rcvd=rcvd-%s, size=size-%s, " +
+ "stored_size=stored_size-%s",
+ (str(opts['messages']), str(opts['purged_size']),
+ str(opts['purged_stored_size'])))
+ opts['db'].commit()
+
+ except dbapi.DatabaseError, e:
+ print "Error %s" % e
+
+ if opts['db']:
+ opts['db'].close()
+
+ summary = "Purged " + str(opts['messages']) + " messages, " +\
+ str(opts['files']) + " files, " +\
+ str(opts['purged_size']) + "/" +\
+ str(opts['purged_stored_size']) + " bytes"
+
+ if opts['verbose']:
+ print summary
+
+ syslog.syslog(summary)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/util/postinstall.sh.in b/util/postinstall.sh.in
index c81a09e..680e039 100755
--- a/util/postinstall.sh.in
+++ b/util/postinstall.sh.in
@@ -20,7 +20,7 @@
KEYTMPFILE="piler.key"
KEYFILE="$SYSCONFDIR/piler/piler.key"
- HOSTNAME=`hostname --fqdn`
+ HOSTNAME=$(hostname --fqdn)
MYSQL_HOSTNAME="localhost"
MYSQL_DATABASE="piler"
@@ -44,9 +44,10 @@
make_certificate() {
if [ ! -f $SYSCONFDIR/piler/piler.pem ]; then
echo -n "Making an ssl certificate ... "
- openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 -subj "$SSL_CERT_DATA" -keyout $SYSCONFDIR/piler/piler.pem -out 1.cert -sha1
- cat 1.cert >> $SYSCONFDIR/piler/piler.pem
- chmod 600 $SYSCONFDIR/piler/piler.pem
+ openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 -subj "$SSL_CERT_DATA" -keyout "$SYSCONFDIR/piler/piler.pem" -out 1.cert -sha1
+ cat 1.cert >> "$SYSCONFDIR/piler/piler.pem"
+ chmod 640 "$SYSCONFDIR/piler/piler.pem"
+ chgrp "$PILERUSER" "$SYSCONFDIR/piler/piler.pem"
rm 1.cert
fi
}
@@ -60,7 +61,7 @@
echo ""
askYN "Continue? [Y/N]" "N"
- if [ $response != "yes" ]; then
+ if [ "$response" != "yes" ]; then
echo "Aborted."
exit
fi
@@ -72,7 +73,7 @@
check_user() {
user=$1
- if [ x`whoami` != x$user ]; then echo "ERROR: postinstaller must be run as $user user"; exit 1; fi
+ if [ x`whoami` != x$user ]; then echo "ERROR: postinstaller must be run as ${user} user"; exit 1; fi
}
@@ -279,7 +280,7 @@
echo "15,45 * * * * $LIBEXECDIR/piler/indexer.attachment.sh" >> $CRON_TMP
echo "*/15 * * * * $INDEXER --quiet tag1 --rotate --config $SYSCONFDIR/piler/sphinx.conf" >> $CRON_TMP
echo "*/15 * * * * $INDEXER --quiet note1 --rotate --config $SYSCONFDIR/piler/sphinx.conf" >> $CRON_TMP
- echo "30 6 * * * /usr/bin/php $LIBEXECDIR/piler/generate_stats.php --webui $DOCROOT" >> $CRON_TMP
+ echo "30 6 * * * /usr/bin/php $LIBEXECDIR/piler/generate_stats.php --webui $DOCROOT >/dev/null" >> $CRON_TMP
echo "*/5 * * * * /usr/bin/find $DOCROOT/tmp -type f -name i.\* -exec rm -f {} \;" >> $CRON_TMP
echo "### PILEREND" >> $CRON_TMP
}
@@ -379,7 +380,7 @@
MYSQL_SOCKET=`perl $SOCKET_HELPER_SCRIPT $MYSQL_SOCKET`
- sed -e "s/mysqlpwd=verystrongpassword/mysqlpwd=$MYSQL_PASSWORD/" -e "s/tls_enable=0/tls_enable=1/" -e "s/mysqlsocket=\/var\/run\/mysqld\/mysqld.sock/mysqlsocket=$MYSQL_SOCKET/" $SYSCONFDIR/piler/piler.conf > $PILERCONF_TMP
+ sed -e "s/mysqlpwd=verystrongpassword/mysqlpwd=$MYSQL_PASSWORD/" -e "s/tls_enable=0/tls_enable=1/" -e "s/mysqlsocket=\/var\/run\/mysqld\/mysqld.sock/mysqlsocket=$MYSQL_SOCKET/" $SYSCONFDIR/piler/piler.conf.dist > $PILERCONF_TMP
cat $PILERCONF_TMP > $SYSCONFDIR/piler/piler.conf
rm -f $PILERCONF_TMP
@@ -417,10 +418,6 @@
echo >> $DOCROOT/config-site.php
- echo "\$config['ENABLE_SYSLOG'] = 1;" >> $DOCROOT/config-site.php
-
- echo >> $DOCROOT/config-site.php
-
echo "\$config['SMTP_DOMAIN'] = '$HOSTNAME';" >> $DOCROOT/config-site.php
echo "\$config['SMTP_FROMADDR'] = 'no-reply@$HOSTNAME';" >> $DOCROOT/config-site.php
echo "\$config['ADMIN_EMAIL'] = 'admin@$HOSTNAME';" >> $DOCROOT/config-site.php
diff --git a/util/purge.sh b/util/purge.sh
index b7167f9..29ed8ba 100644
--- a/util/purge.sh
+++ b/util/purge.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
+export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/libexec/piler:/usr/local/libexec/piler
PRIORITY=mail.error
TMPFILE=/var/run/piler/purge.tmp
PURGE_BEACON=/var/piler/stat/purge
@@ -17,6 +17,4 @@
touch $PURGE_BEACON
-pilerpurge
-
-
+pilerpurge.py
diff --git a/util/sign.php b/util/sign.php
index 10a2e0a..5fd4af0 100644
--- a/util/sign.php
+++ b/util/sign.php
@@ -47,7 +47,7 @@
require_once($webuidir . "/config.php");
-if(ENABLE_SYSLOG == 1) { openlog("piler-timestamp", LOG_PID, LOG_MAIL); }
+openlog("piler-timestamp", LOG_PID, LOG_MAIL);
require(DIR_SYSTEM . "/startup.php");
diff --git a/webui/config.php b/webui/config.php
index e81d7fc..c2742f5 100644
--- a/webui/config.php
+++ b/webui/config.php
@@ -107,7 +107,8 @@
$config['ENABLE_IMAP_AUTH'] = 0;
$config['RESTORE_OVER_IMAP'] = 0;
-$config['IMAP_RESTORE_FOLDER'] = 'INBOX';
+$config['IMAP_RESTORE_FOLDER_INBOX'] = 'INBOX';
+$config['IMAP_RESTORE_FOLDER_SENT'] = 'Sent';
$config['IMAP_HOST'] = 'mail.yourdomain.com';
$config['IMAP_PORT'] = 993;
$config['IMAP_SSL'] = true;
@@ -178,7 +179,7 @@
$config['DEFAULT_POLICY'] = 'default_policy';
-$config['DIR_BASE'] = '/var/www/piler.yourdomain.com/';
+$config['DIR_BASE'] = '/var/piler/www/';
$config['DIR_SPHINX'] = '/var/piler/sphinx/';
$config['DIR_STAT'] = '/var/piler/stat';
@@ -400,8 +401,6 @@
define('ICON_MINUS', '/view/theme/' . THEME . '/images/minus.gif');
define('ICON_EMPTY', '/view/theme/' . THEME . '/images/1x1.gif');
-define('LOG_FILE', DIR_LOG . 'webui.log');
-
define('QSHAPE_ACTIVE_INCOMING', DIR_STAT . '/active+incoming');
define('QSHAPE_ACTIVE_INCOMING_SENDER', DIR_STAT . '/active+incoming-sender');
define('QSHAPE_DEFERRED', DIR_STAT . '/deferred');
diff --git a/webui/controller/login/login.php b/webui/controller/login/login.php
index b4142a4..04625da 100644
--- a/webui/controller/login/login.php
+++ b/webui/controller/login/login.php
@@ -83,6 +83,8 @@
}
}
else {
+ LOGGER('login failed', $this->request->post['username']);
+
$this->model_user_auth->increment_failed_login_count($this->data['failed_login_count']);
$this->data['failed_login_count']++;
}
diff --git a/webui/controller/message/bulkrestore.php b/webui/controller/message/bulkrestore.php
index 6a24540..0cd51ce 100644
--- a/webui/controller/message/bulkrestore.php
+++ b/webui/controller/message/bulkrestore.php
@@ -50,6 +50,8 @@
require_once 'Zend/Mail/Protocol/Imap.php';
require_once 'Zend/Mail/Storage/Imap.php';
+ $emails = $session->get("emails");
+
$imap_ok = $this->model_mail_mail->connect_imap();
if(!$imap_ok) {
@@ -86,8 +88,15 @@
if(RESTORE_OVER_IMAP == 1) {
if($imap_ok) {
- $x = $this->imap->append(IMAP_RESTORE_FOLDER, $msg);
- syslog(LOG_INFO, "imap append $id/$piler_id, rc=$x");
+ $imap_folder = IMAP_RESTORE_FOLDER_INBOX;
+
+ $meta = $this->model_search_message->get_metadata_by_id($id);
+ if(in_array($meta['from'], $emails)) {
+ $imap_folder = IMAP_RESTORE_FOLDER_SENT;
+ }
+
+ $x = $this->imap->append($imap_folder, $msg);
+ syslog(LOG_INFO, "imap append $id/$piler_id, to " . $imap_folder . ", rc=$x");
}
else { $x = 0; }
}
diff --git a/webui/controller/message/restore.php b/webui/controller/message/restore.php
index d33e2db..4bcdc97 100644
--- a/webui/controller/message/restore.php
+++ b/webui/controller/message/restore.php
@@ -67,8 +67,8 @@
$this->data['data'] = $this->data['text_failed_to_restore'];
if(count($rcpt) > 0) {
-
- $this->data['piler_id'] = $this->model_search_message->get_piler_id_by_id($this->data['id']);
+ $this->data['meta'] = $this->model_search_message->get_metadata_by_id($this->data['id']);
+ $this->data['piler_id'] = $this->data['meta']['piler_id'];
$msg = $this->model_search_message->get_raw_message($this->data['piler_id']);
@@ -76,8 +76,16 @@
if(RESTORE_OVER_IMAP == 1) {
if($this->model_mail_mail->connect_imap()) {
- $x = $this->imap->append(IMAP_RESTORE_FOLDER, $msg);
- syslog(LOG_INFO, "imap append " . $this->data['id'] . "/" . $this->data['piler_id'] . ", rc=$x");
+
+ $imap_folder = IMAP_RESTORE_FOLDER_INBOX;
+
+ $emails = $session->get("emails");
+ if(in_array($this->data['meta']['from'], $emails)) {
+ $imap_folder = IMAP_RESTORE_FOLDER_SENT;
+ }
+
+ $x = $this->imap->append($imap_folder, $msg);
+ syslog(LOG_INFO, "imap append " . $this->data['id'] . "/" . $this->data['piler_id'] . " to " . $imap_folder . ", rc=$x");
$this->model_mail_mail->disconnect_imap();
}
else {
diff --git a/webui/controller/search/helper.php b/webui/controller/search/helper.php
index 352b986..ea162c2 100644
--- a/webui/controller/search/helper.php
+++ b/webui/controller/search/helper.php
@@ -148,20 +148,10 @@
$from .= " $v";
}
else {
- if($prev_token_is_email == 1) {
- $prev_token_is_email = 0;
- $from .= " $v";
- }
- else {
- $match .= ' ' . $v;
- }
+ $match .= ' ' . $v;
}
}
- if($match && $match != ' ' . $this->data['text_enter_search_terms']) {
- $match = "@(subject,body) $match";
- }
-
if($from) { $match = $from . ' ' . $match; }
diff --git a/webui/language/de/messages.php b/webui/language/de/messages.php
index d466cfa..be7bf50 100644
--- a/webui/language/de/messages.php
+++ b/webui/language/de/messages.php
@@ -478,14 +478,14 @@
$_['text_existing'] = "existiert";
$_['text_new'] = "neu";
-$_['text_cumulative_counts'] = "Cumulative counts";
-$_['text_message_disposition'] = "Message Disposition";
-$_['text_assigned_email_addresses'] = "Assigned email addresses";
-$_['text_storage'] = "Storage";
-$_['text_legal_hold'] = "Legal hold";
+$_['text_cumulative_counts'] = "Nachrichtenzähler";
+$_['text_message_disposition'] = "Nachrichtenzustellung";
+$_['text_assigned_email_addresses'] = "Zugeordnete Mailadresse";
+$_['text_storage'] = "Speicher";
+$_['text_legal_hold'] = "Aufbewahrungsfrist";
-$_['text_compliance_warning'] = 'The delete feature is enabled, therefore the archive is NOT compliant!';
-$_['text_folder_rules'] = "Folder rules";
-$_['text_private'] = "Private";
+$_['text_compliance_warning'] = 'Die Löschfunktion ist aktiviert, aus diesem Grund ist das Archiv NICHT gesetzeskonform!';
+$_['text_folder_rules'] = "Verzeichnisregeln";
+$_['text_private'] = "Privat";
?>
diff --git a/webui/model/accounting/accounting.php b/webui/model/accounting/accounting.php
index 7ca2309..ae453c2 100644
--- a/webui/model/accounting/accounting.php
+++ b/webui/model/accounting/accounting.php
@@ -1,267 +1,228 @@
__getAcceptedDomains() );
- $return = array(
- 'starttimestamp' => 0,
- 'stoptimestamp' => 0,
- 'addedstats' => 0,
- 'deletedstats' => 0,
- );
-
- if ( !is_null($start) )
- {
- $start = $this->__decodeDate( $start );
- } elseif ( is_null($start) )
- {
- //if we are passed nothing, operate on today
- $start = $this->__decodeDate( "00:00:00" );
- }
+ public function run_counters($start=NULL, $stop = NULL, $column = 'arrived') {
+ $now = time();
+ $counter = array();
+ $data = array(
+ 'starttimestamp' => 0,
+ 'stoptimestamp' => 0,
+ 'addedstats' => 0,
+ 'deletedstats' => 0
+ );
- if ( !is_null($stop) )
- {
- $stop = $this->__decodeDate( $stop );
- $stop = $stop + 86400;
- } elseif ( is_null($stop) )
- {
- //if we are passed nothing, operate on today
- $stop = $this->__decodeDate( "00:00:00" );
- $stop = $stop + 86400;
- }
-
- $return['starttimestamp'] = $start;
- $return['stoptimestamp'] = $stop;
-
- // run query to return all messages
- $tousers = $this->db->query('SELECT `sent`-(`sent`%86400) as `day`,`to`,count(*) as `count`,sum(`size`) as `size` FROM ' . VIEW_MESSAGES . ' WHERE `sent` >= '.$start.' AND `sent` < '.$stop.' GROUP BY FROM_UNIXTIME(`day`, "%Y.%m.%d."), `to`;');
- $fromusers = $this->db->query('SELECT `sent`-(`sent`%86400) as `day`,`from`,count(*) as `count`,sum(`size`) as `size` FROM ' . VIEW_MESSAGES . ' WHERE `sent` >= '.$start.' AND `sent` < '.$stop.' GROUP BY FROM_UNIXTIME(`day`, "%Y.%m.%d."), `from`;');
-
- // process results from above four queries
- if($tousers->num_rows > 0)
- {
- foreach($tousers->rows as $row)
- {
- $counter[$row['day']][$row['to']]['recd'] = $row['count'];
- $counter[$row['day']][$row['to']]['sizerecd'] = $row['size'];
+ if(!in_array($column, array('sent', 'arrived'))) { return $data; }
+
+ if(!is_null($start)) {
+ $start = $this->__decodeDate($start);
+ } else {
+ //if we got nothing, then operate on today
+ $start = $this->__decodeDate("00:00:00");
+ }
+
+ if(!is_null($stop)) {
+ $stop = $this->__decodeDate($stop);
+ } else {
+ //if we got nothing, then operate on today
+ $stop = $this->__decodeDate("00:00:00");
+ }
+ $stop += 86400;
+
+
+ $data['starttimestamp'] = $start;
+ $data['stoptimestamp'] = $stop;
+
+
+ // emails sent to users
+
+ $tousers = $this->db->query("SELECT `$column`-(`$column` % 86400) AS `day`, `to`, COUNT(*) AS `count`, SUM(`size`) AS `size` FROM " . VIEW_MESSAGES . " WHERE `$column` >= ? AND `$column` < ? GROUP BY FROM_UNIXTIME(`day`, '%Y.%m.%d.'), `to`", array($start, $stop));
+
+ foreach($tousers->rows as $row) {
+ $counter[$row['day']][$row['to']]['recd'] = $row['count'];
+ $counter[$row['day']][$row['to']]['sizerecd'] = $row['size'];
+ }
+
+
+ // emails sent from users
+
+ $fromusers = $this->db->query("SELECT `$column`-(`$column` % 86400) AS `day`, `from`, COUNT(*) AS `count`, SUM(`size`) AS `size` FROM " . VIEW_MESSAGES . " WHERE `$column` >= ? AND `$column` < ? GROUP BY FROM_UNIXTIME(`day`, '%Y.%m.%d.'), `from`", array($start, $stop));
+
+ foreach($fromusers->rows as $row) {
+ $counter[$row['day']][$row['from']]['sent'] = $row['count'];
+ $counter[$row['day']][$row['from']]['sizesent'] = $row['size'];
+ }
+
+
+ $accepteddomains = array_flip($this->model_domain_domain->get_mapped_domains());
+
+ foreach($counter as $date => $users) {
+
+ // execute queries to update the users and domains counter table
+ $deletestats = $this->db->query("DELETE FROM " . TABLE_STAT_COUNTER . " WHERE `date` = ?", array($date));
+ $data['deletedstats'] += $this->db->countAffected();
+
+ foreach($users as $username => $userdata) {
+
+ $sent = isset($userdata['sent']) ? $userdata['sent'] : 0;
+ $recd = isset($userdata['recd']) ? $userdata['recd'] : 0;
+ $sizesent = isset($userdata['sizesent']) ? $userdata['sizesent'] : 0;
+ $sizerecd = isset($userdata['sizerecd']) ? $userdata['sizerecd'] : 0;
+
+ $parts = explode('@', $username);
+
+ if(isset($parts[1]) && isset($accepteddomains[ $parts[1] ])) {
+ $addusers = $this->db->query("INSERT INTO " . TABLE_STAT_COUNTER . " (`date`,`email`,`domain`,`sent`,`recd`,`sentsize`,`recdsize`) VALUES(?,?,?,?,?,?,?)", array($date, $username, $parts[1], $sent, $recd, $sizesent, $sizerecd));
+ $data['addedstats'] += $this->db->countAffected();
}
- }
- if($fromusers->num_rows > 0)
- {
- foreach($fromusers->rows as $row)
- {
- $counter[$row['day']][$row['from']]['sent'] = $row['count'];
- $counter[$row['day']][$row['from']]['sizesent'] = $row['size'];
- }
- }
-
- foreach( $counter as $date=>$users )
- {
-
- // execute queries to update the users and domains counter table
- $deletestats = $this->db->query("DELETE FROM " . TABLE_STAT_COUNTER . " WHERE `date` = $date;");
- $return['deletedstats'] = $return['deletedstats'] + $this->db->countAffected();
-
- foreach( $users as $username => $userdata)
- {
- //todo: consolidate
- $sent = isset($userdata['sent']) ? $userdata['sent'] : 0;
- $recd = isset($userdata['recd']) ? $userdata['recd'] : 0;
- $sizesent = isset($userdata['sizesent']) ? $userdata['sizesent'] : 0;
- $sizerecd = isset($userdata['sizerecd']) ? $userdata['sizerecd'] : 0;
-
- $parts = explode('@',$username);
-
- if ( isset($parts[1]) && isset($accepteddomains[ $parts[1] ]) ) {
- $addusers = $this->db->query("INSERT INTO " . TABLE_STAT_COUNTER . " (`date`,`email`,`domain`,`sent`,`recd`,`sentsize`,`recdsize`) VALUES($date,'$username','".$parts[1]."',$sent,$recd,$sizesent,$sizerecd);");
- $return['addedstats'] = $return['addedstats'] + $this->db->countAffected();
- }
- }
-
- }
-
- if(LOG_LEVEL >= NORMAL) { syslog(LOG_INFO, sprintf("processed %s to %s: %d records deleted, %d records added",date(DATE_TEMPLATE, $return['starttimestamp']),date(DATE_TEMPLATE, $return['stoptimestamp']),$return['deletedstats'],$return['addedstats'])); }
-
- return $return;
- }
-
- public function get_accounting($item = 'email',$search='',$page=0,$pagelen=0,$sort='item',$order=0 ) {
-
- // item can be either email or domain, maybe folder in the future??
-
- $_order = 0;
- $_order = "";
- $limit = "";
- $return = array();
-
- /*
- if(MEMCACHED_ENABLED) {
- $memcache = Registry::get('memcache');
-
- $statscounter = $memcache->get(Registry::get('statscounters'));
-
- if(isset($counter[MEMCACHED_PREFIX . 'counters_last_update'])) {
- if(isset($counter[MEMCACHED_PREFIX . 'size'])) { $asize = nice_size($counter[MEMCACHED_PREFIX . 'size'], ' '); }
- unset($counter[MEMCACHED_PREFIX . 'size']);
-
- return array ($asize, $counter);
}
- } */
-
- $account_for_emails = $this->__getEmails();
- $account_for_domains = $this->__getDomains();
-
- $search = preg_replace("/\s{1,}/", "", $search);
-
- if ($item == 'email') {
- $account_for_emails = $this->__getEmails();
- $account_for_domains = $this->__getDomains();
- $query = "SELECT `email` AS `item`,MIN(`date`) as `oldest`,MAX(`date`) as `newest`,sum(`sent`) as `sent`,sum(`recd`) as `recd`,SUM(`sentsize`) as `sentsize`,AVG(`sentsize`) as `sentavg`,SUM(`recdsize`) as `recdsize`,AVG(`recdsize`) as `recdavg` FROM " . TABLE_STAT_COUNTER;
- $where = "WHERE ( `email` IN ('".implode("','",$account_for_emails)."') OR `domain` IN ('".implode("','",$account_for_domains)."') )";
- if($search){
- $where .= " AND ( `email` like '%".$search."%' OR `domain` like '%".$search."%' )";
- }
- $group = "GROUP BY `email`";
- } elseif ($item == 'domain') {
- $account_for_domains = $this->__getDomains();
- $query = "SELECT `domain` AS `item`,MIN(`date`) as `oldest`,MAX(`date`) as `newest`,sum(`sent`) as `sent`,sum(`recd`) as `recd`,SUM(`sentsize`) as `sentsize`,AVG(`sentsize`) as `sentavg`,SUM(`recdsize`) as `recdsize`,AVG(`recdsize`) as `recdavg` FROM " . TABLE_STAT_COUNTER;
- $where = "WHERE ( `domain` IN ('".implode("','",$account_for_domains)."') )";
- if($search){
- $where .= " AND `domain` like '%".$search."%'";
- }
- $group = "GROUP BY `domain`";
- } else {
- return false;
+ }
+
+ if(LOG_LEVEL >= NORMAL) { syslog(LOG_INFO, sprintf("processed %s to %s: %d records deleted, %d records added", date(DATE_TEMPLATE, $data['starttimestamp']), date(DATE_TEMPLATE, $data['stoptimestamp']), $data['deletedstats'], $data['addedstats'])); }
+
+ return $data;
+ }
+
+
+ public function get_accounting($item='email', $search='', $page=0, $pagelen=0, $sort='item', $order=0) {
+
+ // item can be either email or domain, maybe folder in the future??
+
+ $_order = 0;
+ $_order = "";
+ $limit = "";
+
+ $account_for_emails = $this->__getEmails();
+ $account_for_domains = $this->__getDomains();
+
+ $search = preg_replace("/\s{1,}/", "", $search);
+
+ if($item == 'email') {
+ $account_for_emails = $this->__getEmails();
+ $account_for_domains = $this->__getDomains();
+ $query = "SELECT `email` AS `item`,MIN(`date`) as `oldest`,MAX(`date`) as `newest`,sum(`sent`) as `sent`,sum(`recd`) as `recd`,SUM(`sentsize`) as `sentsize`,AVG(`sentsize`) as `sentavg`,SUM(`recdsize`) as `recdsize`,AVG(`recdsize`) as `recdavg` FROM " . TABLE_STAT_COUNTER;
+ $where = "WHERE ( `email` IN ('".implode("','",$account_for_emails)."') OR `domain` IN ('".implode("','",$account_for_domains)."') )";
+ if($search) {
+ $where .= " AND ( `email` like '%".$search."%' OR `domain` like '%".$search."%' )";
+ }
+ $group = "GROUP BY `email`";
+ } elseif ($item == 'domain') {
+ $account_for_domains = $this->__getDomains();
+ $query = "SELECT `domain` AS `item`,MIN(`date`) as `oldest`,MAX(`date`) as `newest`,sum(`sent`) as `sent`,sum(`recd`) as `recd`,SUM(`sentsize`) as `sentsize`,AVG(`sentsize`) as `sentavg`,SUM(`recdsize`) as `recdsize`,AVG(`recdsize`) as `recdavg` FROM " . TABLE_STAT_COUNTER;
+ $where = "WHERE ( `domain` IN ('".implode("','",$account_for_domains)."') )";
+ if($search) {
+ $where .= " AND `domain` like '%".$search."%'";
+ }
+ $group = "GROUP BY `domain`";
+ } else {
+ return false;
+ }
+
+ if($order == 0) { $order = "ASC"; }
+ else { $order = "DESC"; }
+
+ $_order = "ORDER BY `$sort` $order";
+
+ $from = (int)$page * (int)$pagelen;
+
+ if($pagelen > 0) { $limit = " LIMIT " . (int)$from . ", " . (int)$pagelen; }
+
+ $query = $this->db->query($query.' '.$where.' '.$group.' '.$_order.' '.$limit.';');
+
+ if($query->num_rows >= 1) {
+ return $query->rows;
+ } else {
+ // no results found
+ return false;
+ }
+ }
+
+
+ public function count_accounting($item='email', $search='') {
+ $account_for_emails = $this->__getEmails();
+ $account_for_domains = $this->__getDomains();
+
+ $search = preg_replace("/\s{1,}/", "", $search);
+
+ if($search) {
+ $search_cond .= " AND ( `email` LIKE '%".$search."%' OR `domain` LIKE '%".$search."%' )";
+ }
+
+ $query = "SELECT `email` AS `item`, MIN(`date`) AS `oldest`, MAX(`date`) AS `newest`, SUM(`sent`) AS `sent`, SUM(`recd`) AS `recd`, SUM(`sentsize`) AS `sentsize`, SUM(`recdsize`) AS `recdsize` FROM " . TABLE_STAT_COUNTER;
+
+ if($item == 'email') {
+ $where = "WHERE `email` IN ('".implode("','",$account_for_emails)."') OR `domain` IN ('".implode("','",$account_for_domains)."')";
+ if($search) {
+ $where .= " AND ( `email` LIKE '%".$search."%' OR `domain` LIKE '%".$search."%' )";
+ }
+ $group = "GROUP BY `email`";
+ } elseif ($item == 'domain') {
+ $where = "WHERE `domain` IN ('".implode("','",$account_for_domains)."')";
+ if($search) {
+ $where .= " AND `domain` LIKE '%".$search."%'";
}
+ $group = "GROUP BY `domain`";
+ } else {
+ return false;
+ }
- if($order == 0) { $order = "ASC"; }
- else { $order = "DESC"; }
-
- $_order = "ORDER BY `$sort` $order";
-
- $from = (int)$page * (int)$pagelen;
-
- if($pagelen > 0) { $limit = " LIMIT " . (int)$from . ", " . (int)$pagelen; }
-
- $query = $this->db->query($query.' '.$where.' '.$group.' '.$_order.' '.$limit.';');
+ $query = $this->db->query($query.' '.$where.' '.$group);
- if($query->num_rows >= 1)
- {
- return $query->rows;
- } else {
- // no results found
- return false;
- }
- }
+ return $query->num_rows;
+ }
-
- public function count_accounting($item = 'email',$search='') {
-
- $account_for_emails = $this->__getEmails();
- $account_for_domains = $this->__getDomains();
-
- $search = preg_replace("/\s{1,}/", "", $search);
-
- if($search){
- $search_cond .= " AND ( `email` like '%".$search."%' OR `domain` like '%".$search."%' )";
- }
-
- $query = "SELECT `email` AS `item`,MIN(`date`) as `oldest`,MAX(`date`) as `newest`,sum(`sent`) as `sent`,sum(`recd`) as `recd`,sum(`sentsize`) as `sentsize`,sum(`recdsize`) as `recdsize` FROM " . TABLE_STAT_COUNTER;
-
- if ($item == 'email') {
- $where = "WHERE `email` IN ('".implode("','",$account_for_emails)."') OR `domain` IN ('".implode("','",$account_for_domains)."')";
- if($search){
- $where .= " AND ( `email` like '%".$search."%' OR `domain` like '%".$search."%' )";
- }
- $group = "GROUP BY `email`";
- } elseif ($item == 'domain') {
- $where = "WHERE `domain` IN ('".implode("','",$account_for_domains)."')";
- if($search){
- $where .= " AND `domain` like '%".$search."%'";
- }
- $group = "GROUP BY `domain`";
- } else {
- return false;
- }
- $query = $this->db->query($query.' '.$where.' '.$group.';');
+ private function __getEmails() {
+ $emails = array();
+ $session = Registry::get('session');
- return $query->num_rows;
- }
-
- private function __getEmails() {
- $return = array();
- $session = Registry::get('session');
+ array_push($emails, $session->get("email"));
+ $__emails = $session->get("emails");
- array_push($return, $session->get("email"));
- $emails = $session->get("emails");
+ foreach ($__emails as $e) {
+ array_push($emails, $e);
+ }
- foreach ($emails as $e) {
- array_push($return,$e);
- }
-
- return $return;
- }
-
- private function __getDomains() {
- $return = array();
- $session = Registry::get('session');
+ return $emails;
+ }
- if(Registry::get('admin_user') >= 1) {
- $return = $this->__getAcceptedDomains();
- }elseif(Registry::get('auditor_user') == 1) {
- array_push($return, $session->get("domain"));
- $auditdomains = $session->get("auditdomains");
- foreach ($auditdomains as $d) {
- array_push($return,$d);
- }
- }
-
- return $return;
- }
-
-
- private function __getAcceptedDomains() {
- // todo: move to domains model?
- $return = array();
-
- $query = $this->db->query("SELECT domain, mapped FROM " . TABLE_DOMAIN . " ORDER BY domain ASC");
-
- foreach($query->rows as $domain)
- {
- if ($domain['domain'] == $domain['mapped']) {
- array_push($return,$domain['domain']);
- } else {
- array_push($return,$domain['mapped']);
- }
- }
-
- return $return;
- }
-
- private function __decodeDate( $input ) {
-
- if ( !is_numeric($input) )
- {
- // if we are passed words (yesterday, today), convert to unix timestamps representing the start of the day
- $input = strtotime($input);
- $return = $input - ($input%86400);
-
- } elseif ( is_numeric($input) )
- {
- // if we are passed unix timestamps, ensure they represent the start of the day
- $return = $input - ($input%86400);
- }
-
- return $return;
-
- }
+ private function __getDomains() {
+ $domains = array();
+ $session = Registry::get('session');
+
+ if(Registry::get('admin_user') >= 1) {
+ $domains = $this->model_domain_domain->get_mapped_domains();
+ }
+ elseif(Registry::get('auditor_user') == 1) {
+ array_push($domains, $session->get("domain"));
+ $auditdomains = $session->get("auditdomains");
+
+ foreach ($auditdomains as $d) {
+ array_push($domains, $d);
+ }
+ }
+
+ return $domains;
+ }
+
+
+ private function __decodeDate($input) {
+ $timestamp = 0;
+
+ if(!is_numeric($input)) {
+ // if we got anything, but a timestamp, eg. words (yesterday, today)
+ // then convert to unix timestamp representing the start of the day
+ $input = strtotime($input);
+ $timestamp = $input - ($input % 86400);
+ }
+ else {
+ // round the timestamp to the start of the day
+ $timestamp = $input - ($input % 86400);
+ }
+
+ return $timestamp;
+ }
+
}
-?>
diff --git a/webui/model/mail/mail.php b/webui/model/mail/mail.php
index ef265a5..6bd564c 100644
--- a/webui/model/mail/mail.php
+++ b/webui/model/mail/mail.php
@@ -12,6 +12,8 @@
$msg = preg_replace("/Message-ID:([^\n]+)\n/i", "Message-ID: <" . generate_random_string(25) . '@' . SITE_NAME . ">\n", $msg);
}
+ $msg = preg_replace("/^\..*$/m", ".$0", $msg);
+
$r = fsockopen($smtphost, $smtpport);
if(!$r){ return -1; }
@@ -34,9 +36,7 @@
fputs($r, $msg);
- if(!preg_match("/\r\n\.\r\n$/", $msg)){
- fputs($r, "\r\n.\r\n");
- }
+ fputs($r, "\r\n.\r\n");
$l = fgets($r, 4096);
diff --git a/webui/model/policy/archiving.php b/webui/model/policy/archiving.php
index 2081f0e..7bf2a0d 100644
--- a/webui/model/policy/archiving.php
+++ b/webui/model/policy/archiving.php
@@ -26,7 +26,7 @@
public function add_new_rule($data = array()) {
- $query = $this->db->query("INSERT INTO " . TABLE_ARCHIVING_RULE . " (`from`,`to`,`subject`,`body`,`_size`,`size`,`attachment_name`,`attachment_type`,`_attachment_size`,`attachment_size`,`spam`) VALUES(?,?,?,?,?,?,?,?,?,?,?)", array($data['from'], $data['to'], $data['subject'], $data['body'], $data['_size'], $data['size'], $data['attachment_name'], $data['attachment_type'], $data['_attachment_size'], $data['attachment_size'], $data['spam']));
+ $query = $this->db->query("INSERT INTO " . TABLE_ARCHIVING_RULE . " (`from`,`to`,`subject`,`body`,`_size`,`size`,`attachment_name`,`attachment_type`,`_attachment_size`,`attachment_size`,`spam`) VALUES(?,?,?,?,?,?,?,?,?,?,?)", array($data['from'], $data['to'], $data['subject'], $data['body'], $data['_size'], (int)$data['size'], $data['attachment_name'], $data['attachment_type'], $data['_attachment_size'], (int)$data['attachment_size'], $data['spam']));
return $this->db->countAffected();
}
diff --git a/webui/model/policy/retention.php b/webui/model/policy/retention.php
index 7ff99b3..b8a68d4 100644
--- a/webui/model/policy/retention.php
+++ b/webui/model/policy/retention.php
@@ -30,7 +30,7 @@
if(isset($data['domain'])) { $domain = $data['domain']; }
- $query = $this->db->query("INSERT INTO " . TABLE_RETENTION_RULE . " (`domain`,`from`,`to`,`subject`,`body`,`_size`,`size`,`attachment_name`,`attachment_type`,`_attachment_size`,`attachment_size`,`spam`,`days`) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)", array($domain, $data['from'], $data['to'], $data['subject'], $data['body'], $data['_size'], $data['size'], $data['attachment_name'], $data['attachment_type'], $data['_attachment_size'], $data['attachment_size'], $data['spam'], $data['days']) );
+ $query = $this->db->query("INSERT INTO " . TABLE_RETENTION_RULE . " (`domain`,`from`,`to`,`subject`,`body`,`_size`,`size`,`attachment_name`,`attachment_type`,`_attachment_size`,`attachment_size`,`spam`,`days`) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)", array($domain, $data['from'], $data['to'], $data['subject'], $data['body'], $data['_size'], (int)$data['size'], $data['attachment_name'], $data['attachment_type'], $data['_attachment_size'], (int)$data['attachment_size'], $data['spam'], (int)$data['days']) );
return $this->db->countAffected();
}
diff --git a/webui/model/search/message.php b/webui/model/search/message.php
index fa61566..33dee30 100644
--- a/webui/model/search/message.php
+++ b/webui/model/search/message.php
@@ -1,6 +1,7 @@
'GBK'
);
public $message;
-
+ private $verification = 0;
public function get_boundary($line='') {
$parts = explode(";", $line);
@@ -78,6 +79,10 @@
pclose($handle);
}
+ if(ENABLE_ON_THE_FLY_VERIFICATION == 0) {
+ $this->verification = $this->verify_message($id, $s);
+ }
+
if(Registry::get('auditor_user') == 0 && HEADER_LINE_TO_HIDE) {
$s = preg_replace("/" . HEADER_LINE_TO_HIDE . ".{1,}(\n(\ |\t){1,}.{1,}){0,}" . "\n/i", "", $s);
}
@@ -121,10 +126,6 @@
$has_journal = $this->remove_journal($headers);
- if(Registry::get('auditor_user') == 0 && HEADER_LINE_TO_HIDE) {
- $headers = preg_replace("/" . HEADER_LINE_TO_HIDE . ".{1,}(\n(\ |\t){1,}.{1,}){0,}" . "\n/i", "", $headers);
- }
-
$headers = $this->escape_lt_gt_symbols($headers);
return array('headers' => $headers, 'has_journal' => $has_journal);
@@ -219,24 +220,26 @@
public function extract_message($id = '', $terms = '') {
$from = "From: ";
$to = "To: ";
+ $cc = "Cc: ";
$subject = "Subject: ";
$date = "Date: ";
$msg = $this->get_raw_message($id);
- if(ENABLE_ON_THE_FLY_VERIFICATION == 0) {
- $verification = $this->verify_message($id, $msg);
- }
-
$has_journal = $this->remove_journal($msg);
Zend_Mime_Decode::splitMessage($msg, $headers, $body);
$boundary = $this->get_boundary($headers['content-type']);
+ if(is_array($headers['from'])) { $headers['from'] = $headers['from'][0]; }
+ if(is_array($headers['to'])) { $headers['to'] = $headers['to'][0]; }
+ if(is_array($headers['cc'])) { $headers['cc'] = $headers['cc'][0]; }
+ if(is_array($headers['subject'])) { $headers['subject'] = $headers['subject'][0]; }
if(is_array($headers['date'])) { $headers['date'] = $headers['date'][0]; }
if(isset($headers['from'])) $from .= $this->escape_lt_gt_symbols($headers['from']);
if(isset($headers['to'])) $to .= $this->escape_lt_gt_symbols($headers['to']);
+ if(isset($headers['cc'])) $cc .= $this->escape_lt_gt_symbols($headers['cc']);
if(isset($headers['subject'])) $subject .= $this->escape_lt_gt_symbols($headers['subject']);
if(isset($headers['date'])) $date .= $headers['date'];
@@ -249,11 +252,12 @@
return array('from' => $from,
'to' => $to,
+ 'cc' => $cc,
'subject' => $this->highlight_search_terms($subject, $terms),
'date' => $date,
'message' => $this->message['text/html'] ? $this->message['text/html'] : $this->message['text/plain'],
'has_journal' => $has_journal,
- 'verification' => $verification
+ 'verification' => $this->verification
);
}
@@ -262,19 +266,41 @@
$mime_parts = array();
if($boundary) {
- $mime_parts = Zend_Mime_Decode::splitMessageStruct($body, $boundary);
+ try {
+ $mime_parts = Zend_Mime_Decode::splitMessageStruct($body, $boundary);
+ }
+ catch (Exception $e) {
+ syslog(LOG_INFO, "Caught exception: " . $e->getMessage());
+ }
} else {
$mime_parts[] = array('header' => $headers, 'body' => $body);
}
+ require_once DIR_SYSTEM . 'helper/HTMLPurifier.standalone.php';
+
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('URI', 'DisableExternal', 'true');
+ $config->set('URI', 'DisableExternalResources', 'true');
+ $config->set('Cache.SerializerPath', DIR_BASE . 'tmp');
+
+ $purifier = new HTMLPurifier($config);
+
for($i=0; $i '',
'encoding' => ''
);
- if(isset($mime_parts[$i]['header']['content-type']))
+ if(isset($mime_parts[$i]['header']['content-type'])) {
$mime['content-type'] = Zend_Mime_Decode::splitContentType($mime_parts[$i]['header']['content-type']);
+ }
+ /*
+ Fix the mime type for some emails having a single textual body part
+ without the Content-type header.
+ */
+ else if (count($mime_parts) == 1) {
+ $mime['content-type']['type'] = 'text/plain';
+ }
if(in_array($mime['content-type']['type'], array('multipart/mixed', 'multipart/related', 'multipart/alternative')))
$this->extract_textuals_from_mime_parts($mime_parts[$i]['header'], $mime_parts[$i]['body'], $mime['content-type']['boundary']);
@@ -283,12 +309,12 @@
$mime['encoding'] = $mime_parts[$i]['header']['content-transfer-encoding'];
if(in_array($mime['content-type']['type'], array('text/plain', 'text/html')))
- $this->message[$mime['content-type']['type']] .= $this->fix_mime_body_part($mime, $mime_parts[$i]['body']);
+ $this->message[$mime['content-type']['type']] .= $this->fix_mime_body_part($purifier, $mime, $mime_parts[$i]['body']);
}
}
- private function fix_mime_body_part($mime = array(), $body = '') {
+ private function fix_mime_body_part($purifier, $mime = array(), $body = '') {
if($mime['encoding'] == 'quoted-printable')
$body = Zend_Mime_Decode::decodeQuotedPrintable($body);
@@ -308,23 +334,7 @@
}
if(strtolower($mime['content-type']['type']) == 'text/html') {
-
- $body = preg_replace("/\ Some text';
+
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('Filter', 'ExtractStyleBlocks', true);
+ $purifier = new HTMLPurifier($config);
+
+ $html = $purifier->purify($dirty);
+
+ // This implementation writes the stylesheets to the styles/ directory.
+ // You can also echo the styles inside the document, but it's a bit
+ // more difficult to make sure they get interpreted properly by
+ // browsers; try the usual CSS armoring techniques.
+ $styles = $purifier->context->get('StyleBlocks');
+ $dir = 'styles/';
+ if (!is_dir($dir)) mkdir($dir);
+ $hash = sha1($_GET['html']);
+ foreach ($styles as $i => $style) {
+ file_put_contents($name = $dir . $hash . "_$i");
+ echo '';
+ }
+?>
+
+
+
+
+
+
+
+]]>
+
+ Warning: It is possible for a user to mount an
+ imagecrash attack using this CSS. Counter-measures are difficult;
+ it is not simply enough to limit the range of CSS lengths (using
+ relative lengths with many nesting levels allows for large values
+ to be attained without actually specifying them in the stylesheet),
+ and the flexible nature of selectors makes it difficult to selectively
+ disable lengths on image tags (HTML Purifier, however, does disable
+ CSS width and height in inline styling). There are probably two effective
+ counter measures: an explicit width and height set to auto in all
+ images in your document (unlikely) or the disabling of width and
+ height (somewhat reasonable). Whether or not these measures should be
+ used is left to the reader.
+
+ Warning: Deprecated in favor of %HTML.SafeObject and
+ %Output.FlashCompat (turn both on to allow YouTube videos and other
+ Flash content).
+
+
+ This directive enables YouTube video embedding in HTML Purifier. Check
+ this document
+ on embedding videos for more information on what this filter does.
+
+ This is a preferred convenience directive that combines
+ %HTML.AllowedElements and %HTML.AllowedAttributes.
+ Specify elements and attributes that are allowed using:
+ element1[attr1|attr2],element2.... For example,
+ if you would like to only allow paragraphs and links, specify
+ a[href],p. You can specify attributes that apply
+ to all elements using an asterisk, e.g. *[lang].
+ You can also use newlines instead of commas to separate elements.
+
+
+ Warning:
+ All of the constraints on the component directives are still enforced.
+ The syntax is a subset of TinyMCE's valid_elements
+ whitelist: directly copy-pasting it here will probably result in
+ broken whitelists. If %HTML.AllowedElements or %HTML.AllowedAttributes
+ are set, this directive has no effect.
+
+ If HTML Purifier's attribute set is unsatisfactory, overload it!
+ The syntax is "tag.attr" or "*.attr" for the global attributes
+ (style, id, class, dir, lang, xml:lang).
+
+
+ Warning: If another directive conflicts with the
+ elements here, that directive will win and override. For
+ example, %HTML.EnableAttrID will take precedence over *.id in this
+ directive. You must set that directive to true before you can use
+ IDs at all.
+
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt
new file mode 100644
index 0000000..140e214
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt
@@ -0,0 +1,10 @@
+HTML.AllowedComments
+TYPE: lookup
+VERSION: 4.4.0
+DEFAULT: array()
+--DESCRIPTION--
+A whitelist which indicates what explicit comment bodies should be
+allowed, modulo leading and trailing whitespace. See also %HTML.AllowedCommentsRegexp
+(these directives are union'ed together, so a comment is considered
+valid if any directive deems it valid.)
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt
new file mode 100644
index 0000000..f22e977
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt
@@ -0,0 +1,15 @@
+HTML.AllowedCommentsRegexp
+TYPE: string/null
+VERSION: 4.4.0
+DEFAULT: NULL
+--DESCRIPTION--
+A regexp, which if it matches the body of a comment, indicates that
+it should be allowed. Trailing and leading spaces are removed prior
+to running this regular expression.
+Warning: Make sure you specify
+correct anchor metacharacters ^regex$, otherwise you may accept
+comments that you did not mean to! In particular, the regex /foo|bar/
+is probably not sufficiently strict, since it also allows foobar.
+See also %HTML.AllowedComments (these directives are union'ed together,
+so a comment is considered valid if any directive deems it valid.)
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt
new file mode 100644
index 0000000..1d3fa79
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt
@@ -0,0 +1,23 @@
+HTML.AllowedElements
+TYPE: lookup/null
+VERSION: 1.3.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+ If HTML Purifier's tag set is unsatisfactory for your needs, you can
+ overload it with your own list of tags to allow. If you change
+ this, you probably also want to change %HTML.AllowedAttributes; see
+ also %HTML.Allowed which lets you set allowed elements and
+ attributes at the same time.
+
+
+ If you attempt to allow an element that HTML Purifier does not know
+ about, HTML Purifier will raise an error. You will need to manually
+ tell HTML Purifier about this element by using the
+ advanced customization features.
+
+
+ Warning: If another directive conflicts with the
+ elements here, that directive will win and override.
+
+ A doctype comes with a set of usual modules to use. Without having
+ to mucking about with the doctypes, you can quickly activate or
+ disable these modules by specifying which modules you wish to allow
+ with this directive. This is most useful for unit testing specific
+ modules, although end users may find it useful for their own ends.
+
+
+ If you specify a module that does not exist, the manager will silently
+ fail to use it, so be careful! User-defined modules are not affected
+ by this directive. Modules defined in %HTML.CoreModules are not
+ affected by this directive.
+
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt
new file mode 100644
index 0000000..151fb7b
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt
@@ -0,0 +1,11 @@
+HTML.Attr.Name.UseCDATA
+TYPE: bool
+DEFAULT: false
+VERSION: 4.0.0
+--DESCRIPTION--
+The W3C specification DTD defines the name attribute to be CDATA, not ID, due
+to limitations of DTD. In certain documents, this relaxed behavior is desired,
+whether it is to specify duplicate names, or to specify names that would be
+illegal IDs (for example, names that begin with a digit.) Set this configuration
+directive to true to use the relaxed parsing rules.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt
new file mode 100644
index 0000000..45ae469
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt
@@ -0,0 +1,18 @@
+HTML.BlockWrapper
+TYPE: string
+VERSION: 1.3.0
+DEFAULT: 'p'
+--DESCRIPTION--
+
+
+ String name of element to wrap inline elements that are inside a block
+ context. This only occurs in the children of blockquote in strict mode.
+
+
+ Example: by default value,
+ <blockquote>Foo</blockquote> would become
+ <blockquote><p>Foo</p></blockquote>.
+ The <p> tags can be replaced with whatever you desire,
+ as long as it is a block level element.
+
+ Certain modularized doctypes (XHTML, namely), have certain modules
+ that must be included for the doctype to be an conforming document
+ type: put those modules here. By default, XHTML's core modules
+ are used. You can set this to a blank array to disable core module
+ protection, but this is not recommended.
+
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt
new file mode 100644
index 0000000..6ed70b5
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt
@@ -0,0 +1,9 @@
+HTML.CustomDoctype
+TYPE: string/null
+VERSION: 2.0.1
+DEFAULT: NULL
+--DESCRIPTION--
+
+A custom doctype for power-users who defined their own document
+type. This directive only applies when %HTML.Doctype is blank.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt
new file mode 100644
index 0000000..103db75
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt
@@ -0,0 +1,33 @@
+HTML.DefinitionID
+TYPE: string/null
+DEFAULT: NULL
+VERSION: 2.0.0
+--DESCRIPTION--
+
+
+ Unique identifier for a custom-built HTML definition. If you edit
+ the raw version of the HTMLDefinition, introducing changes that the
+ configuration object does not reflect, you must specify this variable.
+ If you change your custom edits, you should change this directive, or
+ clear your cache. Example:
+
+ In the above example, the configuration is still at the defaults, but
+ using the advanced API, an extra attribute has been added. The
+ configuration object normally has no way of knowing that this change
+ has taken place, so it needs an extra directive: %HTML.DefinitionID.
+ If someone else attempts to use the default configuration, these two
+ pieces of code will not clobber each other in the cache, since one has
+ an extra directive attached to it.
+
+
+ You must specify a value to this directive to use the
+ advanced API features.
+
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt
new file mode 100644
index 0000000..229ae02
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt
@@ -0,0 +1,16 @@
+HTML.DefinitionRev
+TYPE: int
+VERSION: 2.0.0
+DEFAULT: 1
+--DESCRIPTION--
+
+
+ Revision identifier for your custom definition specified in
+ %HTML.DefinitionID. This serves the same purpose: uniquely identifying
+ your custom definition, but this one does so in a chronological
+ context: revision 3 is more up-to-date then revision 2. Thus, when
+ this gets incremented, the cache handling is smart enough to clean
+ up any older revisions of your definition as well as flush the
+ cache.
+
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt
new file mode 100644
index 0000000..9dab497
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt
@@ -0,0 +1,11 @@
+HTML.Doctype
+TYPE: string/null
+DEFAULT: NULL
+--DESCRIPTION--
+Doctype to use during filtering. Technically speaking this is not actually
+a doctype (as it does not identify a corresponding DTD), but we are using
+this name for sake of simplicity. When non-blank, this will override any
+older directives like %HTML.XHTML or %HTML.Strict.
+--ALLOWED--
+'HTML 4.01 Transitional', 'HTML 4.01 Strict', 'XHTML 1.0 Transitional', 'XHTML 1.0 Strict', 'XHTML 1.1'
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt
new file mode 100644
index 0000000..7878dc0
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt
@@ -0,0 +1,11 @@
+HTML.FlashAllowFullScreen
+TYPE: bool
+VERSION: 4.2.0
+DEFAULT: false
+--DESCRIPTION--
+
+ Whether or not to permit embedded Flash content from
+ %HTML.SafeObject to expand to the full screen. Corresponds to
+ the allowFullScreen parameter.
+
+ While this directive is similar to %HTML.AllowedAttributes, for
+ forwards-compatibility with XML, this attribute has a different syntax. Instead of
+ tag.attr, use tag@attr. To disallow href
+ attributes in a tags, set this directive to
+ a@href. You can also disallow an attribute globally with
+ attr or *@attr (either syntax is fine; the latter
+ is provided for consistency with %HTML.AllowedAttributes).
+
+
+ Warning: This directive complements %HTML.ForbiddenElements,
+ accordingly, check
+ out that directive for a discussion of why you
+ should think twice before using this directive.
+
+ This was, perhaps, the most requested feature ever in HTML
+ Purifier. Please don't abuse it! This is the logical inverse of
+ %HTML.AllowedElements, and it will override that directive, or any
+ other directive.
+
+
+ If possible, %HTML.Allowed is recommended over this directive, because it
+ can sometimes be difficult to tell whether or not you've forbidden all of
+ the behavior you would like to disallow. If you forbid img
+ with the expectation of preventing images on your site, you'll be in for
+ a nasty surprise when people start using the background-image
+ CSS property.
+
+ This directive controls the maximum number of pixels in the width and
+ height attributes in img tags. This is
+ in place to prevent imagecrash attacks, disable with null at your own risk.
+ This directive is similar to %CSS.MaxImgLength, and both should be
+ concurrently edited, although there are
+ subtle differences in the input format (the HTML max is an integer).
+
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt
new file mode 100644
index 0000000..700b309
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt
@@ -0,0 +1,7 @@
+HTML.Nofollow
+TYPE: bool
+VERSION: 4.3.0
+DEFAULT: FALSE
+--DESCRIPTION--
+If enabled, nofollow rel attributes are added to all outgoing links.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt
new file mode 100644
index 0000000..62e8e16
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt
@@ -0,0 +1,12 @@
+HTML.Parent
+TYPE: string
+VERSION: 1.3.0
+DEFAULT: 'div'
+--DESCRIPTION--
+
+
+ String name of element that HTML fragment passed to library will be
+ inserted in. An interesting variation would be using span as the
+ parent element, meaning that only inline tags would be allowed.
+
+ Whether or not to allow proprietary elements and attributes in your
+ documents, as per HTMLPurifier_HTMLModule_Proprietary.
+ Warning: This can cause your documents to stop
+ validating!
+
+ Whether or not to permit embed tags in documents, with a number of extra
+ security features added to prevent script execution. This is similar to
+ what websites like MySpace do to embed tags. Embed is a proprietary
+ element and will cause your website to stop validating; you should
+ see if you can use %Output.FlashCompat with %HTML.SafeObject instead
+ first.
+ Whether or not to permit iframe tags in untrusted documents. This
+ directive must be accompanied by a whitelist of permitted iframes,
+ such as %URI.SafeIframeRegexp, otherwise it will fatally error.
+ This directive has no effect on strict doctypes, as iframes are not
+ valid.
+
+ Whether or not to permit object tags in documents, with a number of extra
+ security features added to prevent script execution. This is similar to
+ what websites like MySpace do to object tags. You should also enable
+ %Output.FlashCompat in order to generate Internet Explorer
+ compatibility code for your object tags.
+
+ Whether or not to permit script tags to external scripts in documents.
+ Inline scripting is not allowed, and the script must match an explicit whitelist.
+
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt
new file mode 100644
index 0000000..a8b1de5
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt
@@ -0,0 +1,9 @@
+HTML.Strict
+TYPE: bool
+VERSION: 1.3.0
+DEFAULT: false
+DEPRECATED-VERSION: 1.7.0
+DEPRECATED-USE: HTML.Doctype
+--DESCRIPTION--
+Determines whether or not to use Transitional (loose) or Strict rulesets.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt
new file mode 100644
index 0000000..587a167
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt
@@ -0,0 +1,8 @@
+HTML.TargetBlank
+TYPE: bool
+VERSION: 4.4.0
+DEFAULT: FALSE
+--DESCRIPTION--
+If enabled, target=blank attributes are added to all outgoing links.
+(This includes links from an HTTPS version of a page to an HTTP version.)
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt
new file mode 100644
index 0000000..cb5a0b0
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt
@@ -0,0 +1,9 @@
+HTML.TargetNoreferrer
+TYPE: bool
+VERSION: 4.8.0
+DEFAULT: TRUE
+--DESCRIPTION--
+If enabled, noreferrer rel attributes are added to links which have
+a target attribute associated with them. This prevents malicious
+destinations from overwriting the original window.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt
new file mode 100644
index 0000000..b4c271b
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt
@@ -0,0 +1,8 @@
+HTML.TidyAdd
+TYPE: lookup
+VERSION: 2.0.0
+DEFAULT: array()
+--DESCRIPTION--
+
+Fixes to add to the default set of Tidy fixes as per your level.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt
new file mode 100644
index 0000000..4186ccd
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt
@@ -0,0 +1,24 @@
+HTML.TidyLevel
+TYPE: string
+VERSION: 2.0.0
+DEFAULT: 'medium'
+--DESCRIPTION--
+
+
General level of cleanliness the Tidy module should enforce.
+There are four allowed values:
+
+
none
+
No extra tidying should be done
+
light
+
Only fix elements that would be discarded otherwise due to
+ lack of support in doctype
+
medium
+
Enforce best practices
+
heavy
+
Transform all deprecated elements and attributes to standards
+ compliant equivalents
+
+
+--ALLOWED--
+'none', 'light', 'medium', 'heavy'
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt
new file mode 100644
index 0000000..996762b
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt
@@ -0,0 +1,8 @@
+HTML.TidyRemove
+TYPE: lookup
+VERSION: 2.0.0
+DEFAULT: array()
+--DESCRIPTION--
+
+Fixes to remove from the default set of Tidy fixes as per your level.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
new file mode 100644
index 0000000..1db9237
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
@@ -0,0 +1,9 @@
+HTML.Trusted
+TYPE: bool
+VERSION: 2.0.0
+DEFAULT: false
+--DESCRIPTION--
+Indicates whether or not the user input is trusted or not. If the input is
+trusted, a more expansive set of allowed tags and attributes will be used.
+See also %CSS.Trusted.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt
new file mode 100644
index 0000000..2a47e38
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt
@@ -0,0 +1,11 @@
+HTML.XHTML
+TYPE: bool
+DEFAULT: true
+VERSION: 1.1.0
+DEPRECATED-VERSION: 1.7.0
+DEPRECATED-USE: HTML.Doctype
+--DESCRIPTION--
+Determines whether or not output is XHTML 1.0 or HTML 4.01 flavor.
+--ALIASES--
+Core.XHTML
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt
new file mode 100644
index 0000000..08921fd
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt
@@ -0,0 +1,10 @@
+Output.CommentScriptContents
+TYPE: bool
+VERSION: 2.0.0
+DEFAULT: true
+--DESCRIPTION--
+Determines whether or not HTML Purifier should attempt to fix up the
+contents of script tags for legacy browsers with comments.
+--ALIASES--
+Core.CommentScriptContents
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt
new file mode 100644
index 0000000..d6f0d9f
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt
@@ -0,0 +1,15 @@
+Output.FixInnerHTML
+TYPE: bool
+VERSION: 4.3.0
+DEFAULT: true
+--DESCRIPTION--
+
+ If true, HTML Purifier will protect against Internet Explorer's
+ mishandling of the innerHTML attribute by appending
+ a space to any attribute that does not contain angled brackets, spaces
+ or quotes, but contains a backtick. This slightly changes the
+ semantics of any given attribute, so if this is unacceptable and
+ you do not use innerHTML on any of your pages, you can
+ turn this directive off.
+
+ If true, HTML Purifier will generate Internet Explorer compatibility
+ code for all object code. This is highly recommended if you enable
+ %HTML.SafeObject.
+
+ Newline string to format final output with. If left null, HTML Purifier
+ will auto-detect the default newline type of the system and use that;
+ you can manually override it here. Remember, \r\n is Windows, \r
+ is Mac, and \n is Unix.
+
+ If true, HTML Purifier will sort attributes by name before writing them back
+ to the document, converting a tag like: <el b="" a="" c="" />
+ to <el a="" b="" c="" />. This is a workaround for
+ a bug in FCKeditor which causes it to swap attributes order, adding noise
+ to text diffs. If you're not seeing this bug, chances are, you don't need
+ this directive.
+
+ Determines whether or not to run Tidy on the final output for pretty
+ formatting reasons, such as indentation and wrap.
+
+
+ This can greatly improve readability for editors who are hand-editing
+ the HTML, but is by no means necessary as HTML Purifier has already
+ fixed all major errors the HTML may have had. Tidy is a non-default
+ extension, and this directive will silently fail if Tidy is not
+ available.
+
+
+ If you are looking to make the overall look of your page's source
+ better, I recommend running Tidy on the entire page rather than just
+ user-content (after all, the indentation relative to the containing
+ blocks will be incorrect).
+
+--ALIASES--
+Core.TidyFormat
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt
new file mode 100644
index 0000000..071bc02
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt
@@ -0,0 +1,7 @@
+Test.ForceNoIconv
+TYPE: bool
+DEFAULT: false
+--DESCRIPTION--
+When set to true, HTMLPurifier_Encoder will act as if iconv does not exist
+and use only pure PHP implementations.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
new file mode 100644
index 0000000..eb97307
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
@@ -0,0 +1,18 @@
+URI.AllowedSchemes
+TYPE: lookup
+--DEFAULT--
+array (
+ 'http' => true,
+ 'https' => true,
+ 'mailto' => true,
+ 'ftp' => true,
+ 'nntp' => true,
+ 'news' => true,
+ 'tel' => true,
+)
+--DESCRIPTION--
+Whitelist that defines the schemes that a URI is allowed to have. This
+prevents XSS attacks from using pseudo-schemes like javascript or mocha.
+There is also support for the data and file
+URI schemes, but they are not enabled by default.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.Base.txt
new file mode 100644
index 0000000..876f068
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.Base.txt
@@ -0,0 +1,17 @@
+URI.Base
+TYPE: string/null
+VERSION: 2.1.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+
+ The base URI is the URI of the document this purified HTML will be
+ inserted into. This information is important if HTML Purifier needs
+ to calculate absolute URIs from relative URIs, such as when %URI.MakeAbsolute
+ is on. You may use a non-absolute URI for this value, but behavior
+ may vary (%URI.MakeAbsolute deals nicely with both absolute and
+ relative paths, but forwards-compatibility is not guaranteed).
+ Warning: If set, the scheme on this URI
+ overrides the one specified by %URI.DefaultScheme.
+
+ Disables all URIs in all forms. Not sure why you'd want to do that
+ (after all, the Internet's founded on the notion of a hyperlink).
+
+
+--ALIASES--
+Attr.DisableURI
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt
new file mode 100644
index 0000000..13c122c
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt
@@ -0,0 +1,11 @@
+URI.DisableExternal
+TYPE: bool
+VERSION: 1.2.0
+DEFAULT: false
+--DESCRIPTION--
+Disables links to external websites. This is a highly effective anti-spam
+and anti-pagerank-leech measure, but comes at a hefty price: nolinks or
+images outside of your domain will be allowed. Non-linkified URIs will
+still be preserved. If you want to be able to link to subdomains or use
+absolute URIs, specify %URI.Host for your website.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt
new file mode 100644
index 0000000..abcc1ef
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt
@@ -0,0 +1,13 @@
+URI.DisableExternalResources
+TYPE: bool
+VERSION: 1.3.0
+DEFAULT: false
+--DESCRIPTION--
+Disables the embedding of external resources, preventing users from
+embedding things like images from other hosts. This prevents access
+tracking (good for email viewers), bandwidth leeching, cross-site request
+forging, goatse.cx posting, and other nasties, but also results in a loss
+of end-user functionality (they can't directly post a pic they posted from
+Flickr anymore). Use it if you don't have a robust user-content moderation
+team.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt
new file mode 100644
index 0000000..f891de4
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt
@@ -0,0 +1,15 @@
+URI.DisableResources
+TYPE: bool
+VERSION: 4.2.0
+DEFAULT: false
+--DESCRIPTION--
+
+ Disables embedding resources, essentially meaning no pictures. You can
+ still link to them though. See %URI.DisableExternalResources for why
+ this might be a good idea.
+
+
+ Note: While this directive has been available since 1.3.0,
+ it didn't actually start doing anything until 4.2.0.
+
+ Defines the domain name of the server, so we can determine whether or
+ an absolute URI is from your website or not. Not strictly necessary,
+ as users should be using relative URIs to reference resources on your
+ website. It will, however, let you use absolute URIs to link to
+ subdomains of the domain you post here: i.e. example.com will allow
+ sub.example.com. However, higher up domains will still be excluded:
+ if you set %URI.Host to sub.example.com, example.com will be blocked.
+ Note: This directive overrides %URI.Base because
+ a given page may be on a sub-domain, but you wish HTML Purifier to be
+ more relaxed and allow some of the parent domains too.
+
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt
new file mode 100644
index 0000000..0b6df76
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt
@@ -0,0 +1,9 @@
+URI.HostBlacklist
+TYPE: list
+VERSION: 1.3.0
+DEFAULT: array()
+--DESCRIPTION--
+List of strings that are forbidden in the host of any URI. Use it to kill
+domain names of spam, etc. Note that it will catch anything in the domain,
+so moo.com will catch moo.com.example.com.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt
new file mode 100644
index 0000000..4214900
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt
@@ -0,0 +1,13 @@
+URI.MakeAbsolute
+TYPE: bool
+VERSION: 2.1.0
+DEFAULT: false
+--DESCRIPTION--
+
+
+ Converts all URIs into absolute forms. This is useful when the HTML
+ being filtered assumes a specific base path, but will actually be
+ viewed in a different context (and setting an alternate base URI is
+ not possible). %URI.Base must be set for this directive to work.
+
+ Munges all browsable (usually http, https and ftp)
+ absolute URIs into another URI, usually a URI redirection service.
+ This directive accepts a URI, formatted with a %s where
+ the url-encoded original URI should be inserted (sample:
+ http://www.google.com/url?q=%s).
+
+
+ Uses for this directive:
+
+
+
+ Prevent PageRank leaks, while being fairly transparent
+ to users (you may also want to add some client side JavaScript to
+ override the text in the statusbar). Notice:
+ Many security experts believe that this form of protection does not deter spam-bots.
+
+
+ Redirect users to a splash page telling them they are leaving your
+ website. While this is poor usability practice, it is often mandated
+ in corporate environments.
+
+
+
+ Prior to HTML Purifier 3.1.1, this directive also enabled the munging
+ of browsable external resources, which could break things if your redirection
+ script was a splash page or used meta tags. To revert to
+ previous behavior, please use %URI.MungeResources.
+
+
+ You may want to also use %URI.MungeSecretKey along with this directive
+ in order to enforce what URIs your redirector script allows. Open
+ redirector scripts can be a security risk and negatively affect the
+ reputation of your domain name.
+
+
+ Starting with HTML Purifier 3.1.1, there is also these substitutions:
+
+
+
+
+
Key
+
Description
+
Example <a href="">
+
+
+
+
+
%r
+
1 - The URI embeds a resource (blank) - The URI is merely a link
+
+
+
+
%n
+
The name of the tag this URI came from
+
a
+
+
+
%m
+
The name of the attribute this URI came from
+
href
+
+
+
%p
+
The name of the CSS property this URI came from, or blank if irrelevant
+
+
+
+
+
+ Admittedly, these letters are somewhat arbitrary; the only stipulation
+ was that they couldn't be a through f. r is for resource (I would have preferred
+ e, but you take what you can get), n is for name, m
+ was picked because it came after n (and I couldn't use a), p is for
+ property.
+
+ If true, any URI munging directives like %URI.Munge
+ will also apply to embedded resources, such as <img src="">.
+ Be careful enabling this directive if you have a redirector script
+ that does not use the Location HTTP header; all of your images
+ and other embedded resources will break.
+
+
+ Warning: It is strongly advised you use this in conjunction
+ %URI.MungeSecretKey to mitigate the security risk of an open redirector.
+
+ This directive enables secure checksum generation along with %URI.Munge.
+ It should be set to a secure key that is not shared with anyone else.
+ The checksum can be placed in the URI using %t. Use of this checksum
+ affords an additional level of protection by allowing a redirector
+ to check if a URI has passed through HTML Purifier with this line:
+
+ If the output is TRUE, the redirector script should accept the URI.
+
+
+
+ Please note that it would still be possible for an attacker to procure
+ secure hashes en-mass by abusing your website's Preview feature or the
+ like, but this service affords an additional level of protection
+ that should be combined with website blacklisting.
+
+
+
+ Remember this has no effect if %URI.Munge is not on.
+
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt
new file mode 100644
index 0000000..23331a4
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt
@@ -0,0 +1,9 @@
+URI.OverrideAllowedSchemes
+TYPE: bool
+DEFAULT: true
+--DESCRIPTION--
+If this is set to true (which it is by default), you can override
+%URI.AllowedSchemes by simply registering a HTMLPurifier_URIScheme to the
+registry. If false, you will also have to update that directive in order
+to add more schemes.
+--# vim: et sw=4 sts=4
diff --git a/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt
new file mode 100644
index 0000000..7908483
--- /dev/null
+++ b/webui/system/helper/standalone/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt
@@ -0,0 +1,22 @@
+URI.SafeIframeRegexp
+TYPE: string/null
+VERSION: 4.4.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+ A PCRE regular expression that will be matched against an iframe URI. This is
+ a relatively inflexible scheme, but works well enough for the most common
+ use-case of iframes: embedded video. This directive only has an effect if
+ %HTML.SafeIframe is enabled. Here are some example values:
+
%^http://(www.youtube.com/embed/|player.vimeo.com/video/)% - Allow both
+
+
+ Note that this directive does not give you enough granularity to, say, disable
+ all autoplay videos. Pipe up on the HTML Purifier forums if this
+ is a capability you want.
+