#!--PERL--
# wwsympa.fcgi - This script provides the web interface to Sympa
# RCS Identication ; $Revision: 10242 $ ; $Date: 2014-02-16 03:58:47 +0100 (dim. 16 févr. 2014) $
#
# Sympa - SYsteme de Multi-Postage Automatique
#
# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014 GIP RENATER
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
## Copyright 1999 Comité Réseaux des Universités
## web interface to Sympa mailing lists manager
## Sympa: http://www.sympa.org/
## Authors :
## Serge Aumont
## Olivier Salaün
=encoding utf-8
=head1 NAME
I - Sympa web interface
=head1 DESCRIPTION
This fcgi script completely handles all aspects of the Sympa web interface
=cut
use lib '--modulesdir--';
use Getopt::Long;
use Archive::Zip;
use Data::Dumper;
use strict 'vars';
use Time::Local;
use MIME::Lite::HTML;
## Sympa API
use List;
use mail;
use Conf;
use confdef;
use Commands;
use Language;
use Log;
use Auth;
use admin;
use SharedDocument;
use report;
use SympaSession;
use Log;
use tools;
use time_utils;
use Sympa::Constants;
use wwslib;
use cookielib;
use Robot;
use tt2;
#use DateTime;
#use open ':utf8'; ## Default is to consider files utf8
use Mail::Header;
use Mail::Address;
my $crypt_openssl_x509_ok;
BEGIN {
if (eval "require Crypt::OpenSSL::X509") {
require Crypt::OpenSSL::X509;
$crypt_openssl_x509_ok = 1;
} else {
$crypt_openssl_x509_ok = 0;
}
};
## WWSympa librairies
my %options;
## Configuration
my $wwsconf = {};
## Change to your wwsympa.conf location
my $conf_file = Sympa::Constants::WWSCONFIG;
my $sympa_conf_file = Sympa::Constants::CONFIG;
my $loop = 0;
my $list;
my $param = {};
my ($robot, $robot_object);
my $ip ;
my $rss ;
my $session;
my $allow_absolute_path; #FIXME: to be removed in the future.
## Load config
unless ($wwsconf = &wwslib::load_config($conf_file)) {
&fatal_err('Unable to load config file %s', $conf_file);
}
## Load sympa config
unless (&Conf::load( $sympa_conf_file )) {
&fatal_err('Unable to load sympa config file %s', $sympa_conf_file);
}
&Log::set_log_level($Conf{'log_level'}) if ($Conf{'log_level'});
&mail::set_send_spool($Conf{'queue'});
if ($wwsconf->{'use_fast_cgi'}) {
require CGI::Fast;
}else {
require CGI;
}
my $daemon_name = &Log::set_daemon($0);
my $mime_types = &wwslib::load_mime_types();
# hash of all the description files already loaded
# format :
# $desc_files{pathfile}{'date'} : date of the last load
# $desc_files{pathfile}{'desc_hash'} : hash which describes
# the description file
#%desc_files_map; NOT USED ANYMORE
# hash of the icons linked with a type of file
my %icon_table;
# application file
$icon_table{'unknown'} = $Conf{'static_content_url'}.'/icons/unknown.png';
$icon_table{'folder'} = $Conf{'static_content_url'}.'/icons/folder.png';
$icon_table{'current_folder'} = $Conf{'static_content_url'}.'/icons/folder.open.png';
$icon_table{'application'} = $Conf{'static_content_url'}.'/icons/unknown.png';
$icon_table{'octet-stream'} = $Conf{'static_content_url'}.'/icons/binary.png';
$icon_table{'audio'} = $Conf{'static_content_url'}.'/icons/sound1.png';
$icon_table{'image'} = $Conf{'static_content_url'}.'/icons/image2.png';
$icon_table{'text'} = $Conf{'static_content_url'}.'/icons/text.png';
$icon_table{'video'} = $Conf{'static_content_url'}.'/icons/movie.png';
$icon_table{'father'} = $Conf{'static_content_url'}.'/icons/back.png';
$icon_table{'sort'} = $Conf{'static_content_url'}.'/icons/down.png';
$icon_table{'url'} = $Conf{'static_content_url'}.'/icons/link.png';
$icon_table{'left'} = $Conf{'static_content_url'}.'/icons/left.png';
$icon_table{'right'} = $Conf{'static_content_url'}.'/icons/right.png';
## Shared directory and description file
#$shared = 'shared';
#$desc = '.desc';
## subroutines
my %comm = ('home' => 'do_home',
'logout' => 'do_logout',
'loginrequest' => 'do_loginrequest',
'login' => 'do_login',
'sso_login' => 'do_sso_login',
'sso_login_succeeded' => 'do_sso_login_succeeded',
'subscribe' => 'do_subscribe',
'multiple_subscribe' => 'do_multiple_subscribe',
'subrequest' => 'do_subrequest',
'subindex' => 'do_subindex',
'suboptions' => 'do_suboptions',
'signoff' => 'do_signoff',
'auto_signoff' => 'do_auto_signoff',
'family_signoff' => 'do_family_signoff',
'family_signoff_request' => 'do_family_signoff_request',
#XXX'multiple_signoff' => 'do_multiple_signoff',
'sigrequest' => 'do_sigrequest',
'ignoresub' => 'do_ignoresub',
'which' => 'do_which',
'lists' => 'do_lists',
'latest_lists' => 'do_latest_lists',
'active_lists' => 'do_active_lists',
'info' => 'do_info',
'subscriber_count' => 'do_subscriber_count',
'review' => 'do_review',
'search' => 'do_search',
'pref', => 'do_pref',
'setpref' => 'do_setpref',
'setpasswd' => 'do_setpasswd',
'renewpasswd' => 'do_renewpasswd',
'firstpasswd' => 'do_firstpasswd',
'requestpasswd' => 'do_requestpasswd',
'choosepasswd' => 'do_choosepasswd',
'viewfile' => 'do_viewfile',
'set' => 'do_set',
'admin' => 'do_admin',
'add_request' => 'do_add_request',
'add' => 'do_add',
'del' => 'do_del',
'modindex' => 'do_modindex',
'reject' => 'do_reject',
#XXX'reject_notify' => 'do_reject_notify',
'distribute' => 'do_distribute',
'viewmod' => 'do_viewmod',
'd_reject_shared' => 'do_d_reject_shared',
#XXX'reject_notify_shared' => 'do_reject_notify_shared',
'd_install_shared' => 'do_d_install_shared',
'editfile' => 'do_editfile',
'savefile' => 'do_savefile',
'arc' => 'do_arc',
'latest_arc' => 'do_latest_arc',
'latest_d_read' => 'do_latest_d_read',
'arc_manage' => 'do_arc_manage',
'remove_arc' => 'do_remove_arc',
'send_me' => 'do_send_me',
'view_source' => 'do_view_source',
'arcsearch_form' => 'do_arcsearch_form',
'arcsearch_id' => 'do_arcsearch_id',
'arcsearch' => 'do_arcsearch',
'rebuildarc' => 'do_rebuildarc',
'rebuildallarc' => 'do_rebuildallarc',
'arc_download' => 'do_arc_download',
'arc_delete' => 'do_arc_delete',
'serveradmin' => 'do_serveradmin',
'set_loglevel' => 'do_set_loglevel',
'set_dumpvars' => 'do_set_dumpvars',
'show_sessions' => 'do_show_sessions',
'unset_dumpvars' => 'do_unset_dumpvars',
'set_session_email' => 'do_set_session_email',
'restore_email' => 'do_restore_email',
'skinsedit' => 'do_skinsedit',
'css' => 'do_css',
'help' => 'do_help',
'edit_list_request' => 'do_edit_list_request',
'edit_list' => 'do_edit_list',
'create_list_request' => 'do_create_list_request',
'create_list' => 'do_create_list',
'get_pending_lists' => 'do_get_pending_lists',
'get_closed_lists' => 'do_get_closed_lists',
'get_latest_lists' => 'do_get_latest_lists',
'get_inactive_lists' => 'do_get_inactive_lists',
'set_pending_list_request' => 'do_set_pending_list_request',
'install_pending_list' => 'do_install_pending_list',
'edit_config' => 'do_edit_config',
#XXX'submit_list' => 'do_submit_list',
'editsubscriber' => 'do_editsubscriber',
'viewbounce' => 'do_viewbounce',
'redirect' => 'do_redirect',
'rename_list_request' => 'do_rename_list_request',
'rename_list' => 'do_rename_list',
'copy_list' => 'do_copy_list',
'reviewbouncing' => 'do_reviewbouncing',
'resetbounce' => 'do_resetbounce',
'scenario_test' => 'do_scenario_test',
'search_list' => 'do_search_list',
'show_cert' => 'do_show_cert',
'close_list_request' => 'do_close_list_request',
'close_list' => 'do_close_list',
'purge_list' => 'do_purge_list',
'restore_list' => 'do_restore_list',
'upload_pictures' => 'do_upload_pictures',
'delete_pictures' => 'do_delete_pictures',
'd_read' => 'do_d_read',
'd_create_dir' => 'do_d_create_dir',
'd_upload' => 'do_d_upload',
'd_unzip' => 'do_d_unzip',
'd_editfile' => 'do_d_editfile',
'd_properties' => 'do_d_properties',
'd_overwrite' => 'do_d_overwrite',
'd_savefile' => 'do_d_savefile',
'd_describe' => 'do_d_describe',
'd_delete' => 'do_d_delete',
'd_rename' => 'do_d_rename',
'd_control' => 'do_d_control',
'd_change_access' => 'do_d_change_access',
'd_set_owner' => 'do_d_set_owner',
'd_admin' => 'do_d_admin',
'dump_scenario' => 'do_dump_scenario',
'dump' => 'do_dump',
'arc_protect' => 'do_arc_protect',
'remind' => 'do_remind',
'change_email' => 'do_change_email',
'change_email_request' => 'do_change_email_request',
'load_cert' => 'do_load_cert',
'compose_mail' => 'do_compose_mail',
'send_mail' => 'do_send_mail',
'request_topic' => 'do_request_topic',
'tag_topic_by_sender' =>'do_tag_topic_by_sender',
'search_user' => 'do_search_user',
'set_lang' => 'do_set_lang',
'attach' => 'do_attach',
'stats' => 'do_stats',
'viewlogs'=> 'do_viewlogs',
'wsdl'=> 'do_wsdl',
'sync_include' => 'do_sync_include',
'review_family' => 'do_review_family',
'ls_templates' => 'do_ls_templates',
'remove_template' => 'do_remove_template',
'copy_template' => 'do_copy_template',
'view_template' => 'do_view_template',
'edit_template' => 'do_edit_template',
#'rss' => 'do_rss', #FIXME:Currently processed in differenct way.
'rss_request' => 'do_rss_request',
'maintenance' => 'do_maintenance',
'blacklist' => 'do_blacklist',
'edit_attributes' => 'do_edit_attributes',
'ticket' => 'do_ticket',
'manage_template' => 'do_manage_template',
#XXX'send_newsletter' => 'do_send_newsletter',
'suspend_request' => 'do_suspend_request',
'suspend_request_action' => 'do_suspend_request_action',
'show_exclude' => 'do_show_exclude',
'ca' => 'do_ca', # 'ca' stands for 'custom_action'. I used a short name to make it discrete in a URL.
'lca' => 'do_lca', # 'lca' stands for 'list_custom_action'. I used a short name to make it discrete in a URL.
'automatic_lists_management_request' => 'do_automatic_lists_management_request',
'automatic_lists_management' => 'do_automatic_lists_management',
'automatic_lists_request' => 'do_automatic_lists_request',
'automatic_lists' => 'do_automatic_lists',
);
my %auth_action = ('logout' => 1,
'loginrequest' => 1,
'login' => 1,
'sso_login' => 1,
'sso_login_succeeded' => 1,
'renewpasswd' => 1,
'firstpasswd' => 1,
'choosepasswd' => 1,
'sendssopasswd' => 1, #FIXME: currently not used
'ticket' => 1,
);
## Arguments awaited in the PATH_INFO, depending on the action
my %action_args = ('default' => ['list'],
'editfile' => ['list','file'],
'requestpasswd' => ['email'],
'choosepasswd' => ['email','passwd'],
'lists' => ['topic','subtopic'],
'latest_lists' => ['topic','subtopic'],
'active_lists' => ['topic','subtopic'],
'login' => ['email','passwd','previous_action','previous_list'],
'sso_login' => ['auth_service_name','subaction','email', 'ticket'],
'sso_login_succeeded' => ['auth_service_name','previous_action','previous_list'],
'loginrequest' => ['previous_action','previous_list'],
'logout' => ['previous_action','previous_list'],
'renewpasswd' => ['previous_action','previous_list'],
'firstpasswd' => ['previous_action','previous_list'],
'css' => ['file'],
'pref' => ['previous_action','previous_list'],
'reject' => ['list','id'],
'distribute' => ['list','id'],
'dump_scenario' => ['list','pname'],
'd_reject_shared' => ['list','id'],
'd_install_shared' => ['list','id'],
'modindex' => ['list'],
'viewmod' => ['list','id','@file'],
'viewfile' => ['list','file'],
'add' => ['list','email'],
'add_request' => ['list'],
'del' => ['list','email'],
'editsubscriber' => ['list','email','previous_action','custom_attribute'],
# 'editsubscriber' => ['list','email','previous_action'],
'viewbounce' => ['list','email'],
'resetbounce' => ['list','email'],
'review' => ['list','page','size','sortby'],
'reviewbouncing' => ['list','page','size'],
'arc' => ['list','month','@arc_file'],
'latest_arc' => ['list'],
'arc_manage' => ['list'],
'arcsearch_form' => ['list','archive_name'],
'arcsearch_id' => ['list','archive_name','msgid'],
'rebuildarc' => ['list','month'],
'rebuildallarc' => [],
'arc_download' => ['list'],
'arc_delete' => ['list','zip'],
'home' => [],
'help' => ['help_topic'],
'show_cert' => [],
'subscribe' => ['list','email','passwd'],
#'subrequest' => ['list','email'],
'subrequest' => ['list'],
'subindex' => ['list'],
'ignoresub' => ['list','@email','@gecos'],
'signoff' => ['list','email','passwd'],
'auto_signoff' => ['list','email'],
'family_signoff' => ['family','email'],
'family_signoff_request' => ['family','email'],
'sigrequest' => ['list','email'],
'set' => ['list','email','reception','gecos'],
'serveradmin' => ['subaction'],
'set_session_email' => ['email'],
'skinsedit' => [],
'get_pending_lists' => [],
'get_closed_lists' => [],
'get_latest_lists' => [],
'get_inactive_lists' => [],
'search_list' => ['filter'],
'shared' => ['list','@path'], #FIXME: no such function.
'd_read' => ['list','@path'],
'latest_d_read' => ['list'],
'd_admin' => ['list','d_admin'],
'd_delete' => ['list','@path'],
'd_rename' => ['list','@path'],
'd_create_dir' => ['list','@path'],
'd_overwrite' => ['list','@path'],
'd_savefile' => ['list','@path'],
'd_describe' => ['list','@path'],
'd_editfile' => ['list','@path'],
'd_properties' => ['list','@path'],
'd_control' => ['list','@path'],
'd_change_access' => ['list','@path'],
'd_set_owner' => ['list','@path'],
'dump' => ['list','format'],
'search' => ['list','filter'],
'search_user' => ['email'],
'set_lang' => ['lang'],
'attach' => ['list','dir','file'],
'edit_list_request' => ['list','group'],
'rename_list' => ['list','new_list','new_robot'],
'copy_list' => ['list','new_list','new_robot'],
'redirect' => [],
'viewlogs' => ['list','page','size','sortby'],
'wsdl' => [],
'sync_include' => ['list'],
'review_family' => ['family_name'],
'ls_templates' => ['list'],
'view_template' => [],
'remove_template' => [],
'copy_template' => ['list'],
'edit_template' => ['list'],
'rss_request' => ['list'],
'request_topic' => ['list','authkey'],
'tag_topic_by_sender' => ['list'],
'multiple_subscribe' => ['lists'],
'multiple_signoff' => ['lists'],
'ticket' => ['ticket'],
'change_email' => ['email'],
'manage_template' => ['subaction','list','message_template'],
'send_newsletter' => [],
'compose_mail' => ['list','subaction'],
'suspend_request' => ['subaction'],
'show_exclude' => ['list'],
'ca' => ['custom_action','@cap'],
'lca' => ['custom_action','list','@cap'],
'automatic_lists_management_request' => [],
'automatic_lists_management' => [],
'automatic_lists_request' => ['family'],
'automatic_lists' => [],
);
## Define the required parameters for each action
## Parameter names refer to the %in structure of to $param if mentionned as 'param.x'
## This structure is used to determine if any parameter is missing
## The list of parameters is not ordered
## Some keywords are reserved: param.list and param.user.email
## Alternate parameters can be defined with the '|' character
## Limits of this structure: it does not define optional parameters (a or b)
## Limit: it does not allow to have a specific error message and redirect to a given page if the parameter is missing
my %required_args = ('active_lists' => ['for|count'],
'admin' => ['param.list','param.user.email'],
'add' => ['param.list','param.user.email'],
'add_request' => ['param.list','param.user.email'],
'arc' => ['param.list'],
'arc_delete' => ['param.user.email','param.list'],
'arc_download' => ['param.user.email','param.list'],
'arc_manage' => ['param.list'],
'arc_protect' => ['param.list'],
'arcsearch' => ['param.list'],
'arcsearch_form' => ['param.list'],
'arcsearch_id' => ['param.list'],
'automatic_lists_request' => ['family'],
'automatic_lists' => ['family'],
'attach' => ['param.list'],
'blacklist' => ['param.list'],
'change_email' => ['param.user.email'],
'change_email_request' => ['param.user.email','new_email'],
'close_list' => ['param.user.email','param.list'],
'close_list_request' => ['param.user.email','param.list'],
'compose_mail' => ['param.user.email','param.list'],
'copy_template' => ['webormail'],
'create_list' => ['param.user.email'], ## other required parameters are checked in the subroutine
'create_list_request' => ['param.user.email'],
'css' => [],
'd_admin' => ['param.list','param.user.email'],
'd_change_access' => ['param.list','param.user.email'],
'd_control' => ['param.list','param.user.email'],
'd_create_dir' => ['param.list','param.user.email','name_doc'],
'd_delete' => ['param.list','param.user.email'],
'd_describe' => ['param.list','param.user.email','content'],
'd_editfile' => ['param.list','param.user.email'],
'd_install_shared' => ['param.list','param.user.email','id'],
'd_overwrite' => ['param.list','param.user.email'],
'd_properties' => ['param.list','param.user.email'],
'd_read' => ['param.list'],
'd_reject_shared' => ['param.list','param.user.email','id'],
'd_rename' => ['param.list','param.user.email','new_name'],
'd_savefile' => ['param.list','param.user.email','content|url'],
'd_set_owner' => ['param.list','param.user.email'],
'd_unzip' => ['param.list','param.user.email'],
'd_upload' => ['param.list','param.user.email'],
'del' => ['param.list','param.user.email','email'],
'delete_pictures' => ['param.list','param.user.email'],
'distribute' => ['param.list','param.user.email','id|idspam'],
'dump' => ['param.list'],
'dump_scenario' => ['param.list','pname'],
'edit_list' => ['param.user.email','param.list'],
'edit_list_request' => ['param.user.email','param.list'],
'edit_template' => ['webormail'],
'editfile' => ['param.user.email'],
'editsubscriber' => ['param.list','param.user.email','email'],
'get_closed_lists' => ['param.user.email'],
'get_inactive_lists' => ['param.user.email'],
'get_latest_lists' => ['param.user.email'],
'get_pending_lists' => ['param.user.email'],
'ignoresub' => ['param.list','param.user.email'],
'info' => ['param.list'],
'install_pending_list' => ['param.user.email'],
'edit_config' => ['param.user.email'],
'latest_arc' => ['param.list','for|count'],
'latest_d_read' => ['param.list','for','count'],
'latest_lists' => ['for|count'],
'load_cert' => ['param.list'],
'logout' => ['param.user.email'],
'manage_template' => ['param.list','param.user.email'],
'modindex' => ['param.list','param.user.email'],
'multiple_subscribe' => ['param.list'],
'pref' => ['param.user.email'],
'purge_list' => ['param.user.email','selected_lists'],
'rebuildallarc' => ['param.user.email'],
'rebuildarc' => ['param.user.email','param.list'],
'reject' => ['param.list','param.user.email','id|idspam'],
'remind' => ['param.list','param.user.email'],
'remove_arc' => ['param.list'],
'remove_template' => ['webormail'],
'rename_list' => ['param.user.email','param.list','new_listname','new_robot'],
'copy_list' => ['param.user.email','param.list','new_listname','new_robot'],
'rename_list_request' => ['param.user.email','param.list'],
'request_topic' => ['param.list','authkey'],
'resetbounce' => ['param.list','param.user.email','email'],
'restore_list' => ['param.user.email','param.list'],
'review' => ['param.list'],
'review_family' => ['param.user.email','family_name'],
'reviewbouncing' => ['param.list'],
'rss_request' => [],
'savefile' => ['param.user.email','file'],
'search' => ['param.list','filter'],
'search_user' => ['param.user.email','email'],
'send_mail' => ['param.user.email'],
'send_newsletter' => ['param.list','param.user.email', 'url'],
'send_me' => ['param.list'],
'view_source' => ['param.list'],
'requestpasswd' => ['email'],
'serveradmin' => ['param.user.email'],
'set' => ['param.list','reception|visibility'],
'set_lang' => [],
'set_pending_list_request' => ['param.user.email'],
'setpasswd' => ['param.user.email','newpasswd1','newpasswd2'],
'setpref' => ['param.user.email'],
'signoff' => ['param.list'],
'sigrequest' => ['param.list'],
'skinsedit' => ['param.user.email'],
'sso_login' => ['auth_service_name'],
'stats' => ['param.user.email','param.list'],
'subindex' => ['param.list','param.user.email'],
'suboptions' => ['param.list','param.user.email'],
'subrequest' => ['param.list'],
'subscribe' => ['param.list'],
'subscriber_count' => ['param.list'],
'suspend_request' => [],
'suspend_request_action' => [],
'show_exclude' => ['param.list'],
'sync_include' => ['param.list','param.user.email'],
'tag_topic_by_sender' => ['param.list'],
'upload_pictures' => ['param.user.email','param.list'],
'view_template' => ['webormail'],
'viewbounce' => ['param.list','email'],
'viewfile' => ['file','param.list'],
'viewlogs' => ['param.list'],
'viewmod' => ['param.list','param.user.email','id|idspam'],
'wsdl' => [],
'which' => ['param.user.email'],
);
## Defines the required privileges to access privileged actions
## You can define a set ofequiivalent privileges in the ARRAYREF
my %required_privileges = ('admin' => ['owner','editor'],
'arc_delete' => ['owner'],
'arc_download' => ['owner'],
'arc_manage' => ['owner'],
'blacklist' => ['owner','editor'],
'close_list' => ['privileged_owner'],
'close_list_request' => ['privileged_owner'],
'copy_template' => ['listmaster'],
'd_install_shared' => ['editor','owner'],
'd_reject_shared' => ['editor','owner'],
'distribute' => ['editor','owner','listmaster'],
'dump_scenario' => ['listmaster'],
'edit_list' => ['owner'],
'edit_list_request' => ['owner'],
'edit_template' => ['listmaster'],
'editsubscriber' => ['owner','editor'],
'get_closed_lists' => ['listmaster'],
'get_inactive_lists' => ['listmaster'],
'get_latest_lists' => ['listmaster'],
'get_pending_lists' => ['listmaster'],
'ignoresub' => ['owner','editor'],
'install_pending_list' => ['listmaster'],
'edit_config' => ['listmaster'],
'ls_templates' => ['listmaster'],
'manage_template' => ['owner'],
'modindex' => ['editor','owner','listmaster'],
'purge_list' => ['privileged_owner','listmaster'],
'rebuildallarc' => ['listmaster'],
'rebuildarc' => ['listmaster'],
'reject' => ['editor','owner','listmaster'],
'remove_template' => ['listmaster'],
'rename_list' => ['privileged_owner'],
'copy_list' => ['owner','listmaster'],
'rename_list_request' => ['privileged_owner'],
'resetbounce' => ['owner','editor'],
'restore_list' => ['listmaster'],
'review_family' => ['listmaster'],
'reviewbouncing' => ['owner','editor'],
'search_user' => ['listmaster'],
'serveradmin' => ['listmaster'],
'set_dumpvars' => ['listmaster'],
'set_loglevel' => ['listmaster'],
'set_pending_list_request' => ['listmaster'],
'set_session_email' => ['listmaster'],
'show_sessions' => ['listmaster'],
'stats' => ['owner'],
'subindex' => ['owner','editor'],
'sync_include' => ['owner','editor'],
'skinsedit' => ['listmaster'],
'view_template' => ['listmaster'],
'viewbounce' => ['owner','editor'],
'viewlogs' => ['owner','editor'],
'viewmod' => ['editor','owner','listmaster'],
'automatic_lists_management_request' => ['listmaster'],
'automatic_lists_management' => ['listmaster'],
);
# this definition is used to choose the left side menu type (admin -> listowner admin menu | serveradmin -> server_admin menu | none list or your_list menu)
my %action_type = (
'review' => 'admin',
'search' => 'admin',
'viewfile' => 'admin',
'admin' => 'admin',
'add_request' =>'admin',
'add' =>'admin',
'del' =>'admin',
# 'modindex' =>'admin',
'reject' =>'admin',
'reject_notify' =>'admin',
'distribute' =>'admin',
'viewmod' =>'admin',
'savefile' =>'admin',
'rebuildallarc' =>'admin', #FIXME: serveradmin?
'reviewbouncing' =>'admin',
'edit_list_request' =>'admin',
'edit_list' =>'admin',
'editsubscriber' =>'admin',
'viewbounce' =>'admin',
'resetbounce' =>'admin',
'scenario_test' =>'admin',
'close_list_request' =>'admin',
'close_list' =>'admin',
'restore_list' => 'admin',
'd_admin' => 'admin',
'd_reject_shared' =>'admin',
'd_install_shared' =>'admin',
'dump_scenario' => 'admin',
'dump' => 'admin',
'remind' => 'admin',
# 'subindex' => 'admin',
'stats' => 'admin',
'ignoresub' => 'admin',
'rename_list' => 'admin',
'copy_list' => 'admin',
'rename_list_request' => 'admin',
'arc_manage' => 'admin',
'sync_include' => 'admin',
'view_template' => 'admin',
'remove_template' => 'admin',
'copy_template' => 'admin',
'edit_template' => 'admin',
'blacklist' => 'admin',
'viewlogs' => 'admin',
'serveradmin' => 'serveradmin',
'get_pending_lists' => 'serveradmin',
'get_closed_lists' => 'serveradmin',
'get_inactive_lists' => 'serveradmin',
'get_latest_lists' => 'serveradmin',
'ls_templates' => 'serveradmin',
'skinsedit' => 'serveradmin',
'review_family' => 'serveradmin',
'search_user' => 'serveradmin',
'show_sessions' => 'serveradmin',
'show_exclude' => 'admin',
'rebuildarc' => 'serveradmin',
'set_session_email' => 'serveradmin',
'set_loglevel' => 'serveradmin',
'editfile' => 'serveradmin', #FIXME: admin?
'unset_dumpvars' => 'serveradmin',
'set_dumpvars' => 'serveradmin',
'automatic_lists_management_request' => 'serveradmin',
'automatic_lists_management' => 'serveradmin',
);
## actions tthat are not used in return of login,
my %temporary_actions = ( 'logout' => 1,
'loginrequest' => 1,
'login' => 1,
'sso_login' => 1,
'sso_login_succeeded' => 1,
'ticket' => 1,
'css' => 1,
'rss' => 1, # FIXME:currently not used.
'wsdl' => 1,
'redirect' => 1,
);
## Regexp applied on incoming parameters (%in)
## The aim is not a strict definition of parameter format
## but rather a security check
my %in_regexp = (
## Default regexp
'*' => '[\w\-\.]+',
## List config parameters
'single_param' => '.+',
'multiple_param' => '.+',
## Textarea content
'template_content' => '.+',
'content' => '.+',
'body' => '.+',
'info' => '.+',
'new_scenario_content' => '.+',
'blacklist' => '.*',
## Integer
'page' => '\d+',
'size' => '\d+',
## Free data
'subject' => '.*',
'gecos' => '[^<>\\\*\$\n]+',
'additional_field' => '[^<>\\\*\$\n]+',
'dump' => '[^<>\\\*\$]+', # contents email + gecos
## Search
'filter' => '[^<>\\\[\]\(\)\$\n]+', # search list
'key_word' => '.*',
'format' => '[^<>\\\$\n]+', # dump format/filter string
## File names
'file' => '[^<>\*\$\n]+',
'template_path' => '[\w\-\.\/_]+',
'arc_file' => '[^<>\\\*\$\n]+',
'path' => '[^<>\\\*\$\n]+',
'uploaded_file' => '(.*[\/\\\\])?[^<>\*\$\n]+', # Could be precised (use of "'")
'unzipped_file' => '(.*[\/\\\\])?[^<>\*\$\n]+',
'dir' => '[^<>\\\*\$\n]+',
'name_doc' => '[^<>\\\*\$\[\]\/\n]+',
'shortname' => '[^<>\\\*\$\n]+',
'new_name' => '[^<>\\\*\$\n]+',
'id' => '[^<>\\\*\$\n]+',
'template_name' => &tools::get_regexp('template_name'),
'new_template_name' => &tools::get_regexp('template_name'),
'message_template' => &tools::get_regexp('template_name'),
'new_default' => &tools::get_regexp('template_name'),
## Archives
'month' => '\d{2}|\d{4}\-\d{2}', ## format is yyyy-mm for 'arc' and mm for 'send_me'
## URL
'referer' => '[^\\\$\*\"\'\`\^\|\<\>\n]+',
'failure_referer' => '[^\\\$\*\"\'\`\^\|\<\>\n]+',
'url' => '[^\\\$\*\"\'\`\^\|\<\>\n]+',
## Msg ID
'msgid' => '[^\\\*\"\'\`\^\|\n]+',
'in_reply_to' => '[^\\\*\"\'\`\^\|\n]+',
'message_id' => '[^\\\*\"\'\`\^\|\n]+',
## Password
'passwd' => '.+',
'password' => '.+',
'newpasswd1' => '.+',
'newpasswd2' => '.+',
'new_password' => '.+',
## Topics
'topic' => '[\-\w\/]+',
'topics' => '[\-\w\/]+',
'subtopic' => '[\-\w\/]+',
## List names
'list' => '[\w\-\.\+]*', ## &tools::get_regexp('listname') + uppercase
'previous_list' => '[\w\-\.\+]*',
'new_list' => '[\w\-\.\+]*',
'listname' => '[\w\-\.\+]*',
'new_listname' => '[\w\-\.\+]*',
'selected_lists' => '[\w\-\.\+]*',
## Family names
'family_name' => &tools::get_regexp('family_name'),
'family' => &tools::get_regexp('family_name'),
## Email addresses
'email' => &tools::get_regexp('email').'|'.&tools::get_regexp('uid'),
'init_email' => &tools::get_regexp('email'),
'old_email' => &tools::get_regexp('email'),
'new_email' => &tools::get_regexp('email'),
'pending_email' => &tools::get_regexp('email').',.*', # Email address is followed by ',' + gecos data
'sender' => &tools::get_regexp('email'),
'to' => '(([\w\-\_\.\/\+\=\']+|\".*\")\s[\w\-]+(\.[\w\-]+)+(,?))*',
'automatic_list_part_*' => '[\w\-\.\+]*',
## Host
'new_robot' => &tools::get_regexp('host'),
'remote_host' => &tools::get_regexp('host'),
'remote_addr' => &tools::get_regexp('host'),
## Scenario name
'scenario' => &tools::get_regexp('scenario'),
'read_access' => &tools::get_regexp('scenario'),
'edit_access' => &tools::get_regexp('scenario'),
## RSS URL or blank
'active_lists' => '.*',
'latest_lists' => '.*',
'latest_arc' => '.*',
'latest_d_read' => '.*',
##Logs
'target_type' => '[\w\-\.\:]*',
'target' => &tools::get_regexp('email'),
'date_from' => '[\d\/-]+',
'date_to' => '[\d\/-]+',
'ip' => &tools::get_regexp('host'),
## colors
'subaction_test' => '.*',
'subaction_reset' => '.*',
'subaction_install' => '.*',
'custom_color_value' => '\#[0-9a-fA-F]+',
'custom_color_number' => 'color_\w+',
## Custom attribute
'custom_attribute' => '.*',
## Templates
'scope' => 'distrib|robot|family|list|site',
## Custom Inputs from create_list_request.tt2
'custom_input' => '.*',
## conf parameters
'conf_new_value' => '.*',
## custom actions
'cap' => '.*',
'lcap' => '.*',
);
## Regexp applied on incoming parameters (%in)
## This regular expression defines forbidden expressions applied on all incoming parameters
## Note that you can use the ^ and $ expressions to match beginning and ending of expressions
my %in_negative_regexp = (
'arc_file' => '^(arctxt|\.)'
);
## List some required filtering of incoming parameters, depending on current action
## Paramater can be '*' or 'param*'
## Like Q-encoding
my %filtering = ('d_reject_shared' => {'id' => 'qencode'},
'd_install_shared' => {'id' => 'qencode'},
'd_read' => {'path' => 'qencode'},
'd_create_dir' => {'name_doc' => 'qencode', 'path' => 'qencode'},
'd_upload' => {'path' => 'qencode'},
'd_unzip' => {'path' => 'qencode'},
'd_editfile' => {'path' => 'qencode'},
'd_properties' => {'path' => 'qencode'},
'd_overwrite' => {'path' => 'qencode'},
'd_savefile' => {'path' => 'qencode', 'name_doc' => 'qencode'},
'd_describe' => {'path' => 'qencode'},
'd_delete' => {'path' => 'qencode'},
'd_rename' => {'path' => 'qencode','new_name' => 'qencode'},
'd_control' => {'path' => 'qencode'},
'd_change_access' => {'path' => 'qencode'},
'd_set_owner' => {'path' => 'qencode'},
'requestpasswd' => {'email' => 'fix_escape_uri'},
'viewbounce' => {'email' => 'fix_escape_uri'},
'editsubscriber' => {'email' => 'fix_escape_uri'},
'edit_list' => {'*param*' => 'unescape_html'}, ## Required because outgoing parameters have been html-escaped in edit_list_request
'change_email' => {'*email' => 'normalize'}, ## Remove leading/trailing white spaces and lowercase
);
## Open log
$wwsconf->{'log_facility'}||= $Conf{'syslog'};
&Log::do_openlog($wwsconf->{'log_facility'}, $Conf{'log_socket_type'}, 'wwsympa');
&do_log('info', 'WWSympa started');
## Set locale configuration
$Language::default_lang = $Conf{'lang'};
#XXX## Important to leave this there because it defined defaults for user_data_source
#XXX&List::check_db_connect();
my $pinfo = &List::_apply_defaults();
## Check that the data structure is uptodate
## If not, set the web interface to maintenance mode
my $maintenance_mode;
unless (&Upgrade::data_structure_uptodate()) {
$maintenance_mode = 1;
&do_log('err',"Web interface set to maintenance mode ; you should run sympa.pl --upgrade");
}
&tools::ciphersaber_installed();
%::changed_params;
my (%in, $query);
my $birthday = time ;
# Now internal encoding is same as input/output.
#XXX## Set output encoding
#XXX## All outgoing strings will be recoded transparently using this charset
#XXXbinmode STDOUT, ":utf8";
#XXX## Incoming data is utf8-encoded
#XXXbinmode STDIN, ":utf8";
## Main loop
my $loop_count;
my $start_time = time;
while ($query = &new_loop()) {
undef %::changed_params;
undef $param;
undef $list;
undef $robot;
undef $robot_object;
undef $ip;
undef $rss;
undef $session;
&Log::set_log_level($Conf{'log_level'});
&Language::SetLang($Language::default_lang);
## Empty cache of the List.pm module
&List::init_list_cache();
## Check effective ID
unless ($> eq (getpwnam(Sympa::Constants::USER))[2]) {
$maintenance_mode = 1;
&report::reject_report_web('intern_quiet','incorrect_server_config',{},'','');
&wwslog('err','Config error: wwsympa should run with UID %s (instead of %s). *** Switching to maintenance mode. ***', (getpwnam(Sympa::Constants::USER))[2], $>);
}
unless (&List::check_db_connect()) {
&report::reject_report_web('system_quiet','no_database',{},'','');
&do_log('info','WWSympa requires a RDBMS to run');
}
## If in maintenance mode, check if the data structure is now uptodate
if ($maintenance_mode && (&Upgrade::data_structure_uptodate() && ($> eq (getpwnam(Sympa::Constants::USER))[2]))) {
$maintenance_mode = undef;
&do_log('notice',"Data structure seem updated, setting OFF maintenance mode");
}
## Get params in a hash
# foreach ($query->param) {
# $in{$_} = $query->param($_);
# }
%in = $query->Vars;
foreach my $k (keys %::changed_params) {
&do_log('debug3', 'Changed Param: %s', $k);
}
## Free terminated sendmail processes
# &smtp::reaper;
## Parse CGI parameters
# &CGI::ReadParse();
if (defined $Conf{'robot_by_http_host'}{&get_header_field('SERVER_NAME')}) {
my ($selected_robot, $selected_path);
my ($k,$v);
while (($k, $v) = each %{$Conf{'robot_by_http_host'}{&get_header_field('SERVER_NAME')}}) {
if ($ENV{'REQUEST_URI'} =~ /^$k/) {
## Longer path wins
if (length($k) > length($selected_path)) {
($selected_robot, $selected_path) = ($v, $k);
}
}
}
$robot = $selected_robot;
}
$robot = $Conf{'host'} unless $robot;
## Create Robot object
$robot_object = new Robot $robot;
## Default robot
if ($robot eq $Conf{'host'}) {
$param->{'default_robot'} = 1;
}
$param->{'cookie_domain'} = $Conf{'robots'}{$robot}{'cookie_domain'} if $Conf{'robots'}{$robot};
$param->{'cookie_domain'} ||= $wwsconf->{'cookie_domain'};
$ip = $ENV{'REMOTE_HOST'};
$ip = $ENV{'REMOTE_ADDR'} unless ($ip);
$ip = 'undef' unless ($ip);
## In case HTTP_HOST does not match cookie_domain
my $http_host = &get_header_field('HTTP_HOST');
$http_host =~ s/:\d+$//; ## suppress port
unless (($http_host =~ /$param->{'cookie_domain'}$/) ||
($param->{'cookie_domain'} eq 'localhost')) {
&wwslog('notice', 'Cookie_domain(%s) does NOT match HTTP_HOST; setting cookie_domain to %s', $param->{'cookie_domain'}, $http_host);
$param->{'cookie_domain'} = $http_host;
}
&Log::set_log_level($Conf{'robots'}{$robot}{'log_level'});
## Sympa parameters in $param->{'conf'}
$param->{'conf'} = {};
foreach my $p ('email','host','sympa','request','soap_url','wwsympa_url','listmaster_email','logo_html_definition',
'main_menu_custom_button_1_url','main_menu_custom_button_1_title','main_menu_custom_button_1_target',
'main_menu_custom_button_2_url','main_menu_custom_button_2_title','main_menu_custom_button_2_target',
'main_menu_custom_button_3_url','main_menu_custom_button_3_title','main_menu_custom_button_3_target',
'dark_color','light_color','text_color','bg_color','error_color','use_blacklist','antispam_feature','custom_robot_parameter',
'selected_color','shaded_color','color_0','color_1','color_2','color_3','color_4','color_5','color_6','color_7','color_8','color_9','color_10','color_11','color_12','color_13','color_14','color_15','automatic_list_families') {
$param->{'conf'}{$p} = &Conf::get_robot_conf($robot, $p);
$param->{$p} = &Conf::get_robot_conf($robot, $p) if (($p =~ /_color$/)|| ($p =~ /color_/));
}
foreach my $auth (keys %{$Conf{'cas_id'}{$robot}}) {
&do_log('debug2', "cas authentication service $auth");
$param->{'sso'}{$auth} = $auth;
}
foreach my $auth (keys %{$Conf{'generic_sso_id'}{$robot}}) {
&do_log('debug', "Generic SSO authentication service $auth");
$param->{'sso'}{$auth} = $Conf{'auth_services'}{$robot}[$Conf{'generic_sso_id'}{$robot}{$auth}]{'service_name'};
}
$param->{'sso_number'} = $Conf{'cas_number'}{$robot} + $Conf{'generic_sso_number'}{$robot};
$param->{'use_passwd'} = $Conf{'use_passwd'}{$robot};
$param->{'use_sso'} = 1 if ($param->{'sso_number'});
$param->{'authentication_info_url'} = $Conf{'authentication_info_url'}{$robot};
$param->{'wwsconf'} = $wwsconf;
$param->{'path_cgi'} = $ENV{'SCRIPT_NAME'};
$param->{'path_cgi'} =~ s/\/\//\//g; ## Replace '//' with '/' because it would break navigation
$param->{'version'} = Sympa::Constants::VERSION;
$param->{'date'} = gettext_strftime "%d %b %Y at %H:%M:%S", localtime(time);
$param->{'time'} = gettext_strftime "%H:%M:%S", localtime(time);
## Hash defining the parameters where no control is performed (because they are supposed to contain html and/or javascript).
$param->{'htmlAllowedParam'} = {
'title' => 1,
'hidden_head' => 1,
'hidden_end' => 1,
'hidden_at' => 1,
'list_protected_email' => 1,
'selected' => 1,
'author_mailto' =>1,
'mailto' =>1,
'logo_html_definition' => 1,
'template_content' => 1,
'html_dumpvars' => 1,
};
## Hash defining the parameters where HTML must be filtered.
$param->{'htmlToFilter'} = {
'homepage_content' => 1,
'info_content' => 1,
};
## Change to list root
unless (chdir($Conf{'home'})) {
&report::reject_report_web('intern','chdir_error',{},'','','',$robot);
&wwslog('info','unable to change directory');
exit (-1);
}
## Sets the UMASK
umask(oct($Conf{'umask'}));
## Authentication
## use https client certificat information if define.
## Default auth method (for scenarios)
$param->{'auth_method'} = 'md5';
&report::init_report_web();
## Get PATH_INFO parameters
&get_parameters();
## CSS related
$param->{'css_path'} = &Conf::get_robot_conf($robot, 'css_path');
$param->{'css_url'} = &Conf::get_robot_conf($robot, 'css_url');
## If CSS file not found, let Sympa do the job...
unless (-f $param->{'css_path'}.'/style.css') {
&wwslog('err','Could not find CSS file %s, using default CSS', $param->{'css_path'}.'/style.css') if ($param->{'css_path'}); ## Notice only if path was defined
$param->{'css_url'} = $param->{'base_url'}.$param->{'path_cgi'}.'/css';
}
&wwslog('info', "parameter css_url '%s' seems strange, it must be the url of a directory not a css file", $param->{'css_url'}) if ($param->{'css_url'} =~ /\.css$/);
$session = new SympaSession ($robot,{'cookie'=>&SympaSession::get_session_cookie($ENV{'HTTP_COOKIE'}),
'action'=>$in{'action'},
'rss'=>$rss});
undef $ENV{'HTTP_COOKIE'}; # Getting rid of the environment variable to make sure it won't be affected to another anonymous session.
unless (defined $session) {
&List::send_notify_to_listmaster('failed_to_create_web_session', $robot);
&wwslog('info','Failed to create session');
$session = new SympaSession($robot, {});
}
$session->{'is_family_owner'} = undef;
my $automatic_list_families = &Conf::get_robot_conf($robot,'automatic_list_families');
if (defined $automatic_list_families) {
foreach my $key (keys %{$automatic_list_families}){
my $family;
if ($family = new Family($key,$robot)) {
if ($family->is_allowed_to_create_automatic_lists(('auth_level' => 'md5', 'sender' => $session->{'email'}, 'message' => undef, 'listname' => ''))){
$session->{'is_family_owner'}{$key} = 1;
}else{
$session->{'is_family_owner'}{$key} = undef;
}
}
}
}
$param->{'session'} = $session->as_hashref();
&Log::set_log_level($session->{'log_level'}) if ($session->{'log_level'});
$param->{'restore_email'} = $session->{'restore_email'};
$param->{'dumpvars'} = $session->{'dumpvars'};
$param->{'unauthenticated_email'} = $session->{'unauthenticated_email'};
## testing custom CSS
if ($session->{'custom_css'}) {
foreach my $i (0 .. 15) {
$param->{'color_' . $i} = $session->{'color_' . $i}
if $session->{'color_' . $i};
}
}
## RSS does not require user authentication
unless ($rss) {
if (($ENV{'SSL_CLIENT_VERIFY'} eq 'SUCCESS') &&
($in{'action'} ne 'sso_login')) { ## Do not check client certificate automatically if in sso_login
&do_log('debug2', "SSL verified, S_EMAIL = %s,"." S_DN_Email = %s", $ENV{'SSL_CLIENT_S_EMAIL'}, $ENV{'SSL_CLIENT_S_DN_Email'});
if (($ENV{'SSL_CLIENT_S_EMAIL'})) {
## this is the X509v3 SubjectAlternativeName, and requires
## a patch to mod_ssl -- cm@coretec.at
$param->{'user'}{'email'} = lc($ENV{'SSL_CLIENT_S_EMAIL'});
}elsif ($ENV{SSL_CLIENT_S_DN_Email}) {
$param->{'user'}{'email'} = lc($ENV{'SSL_CLIENT_S_DN_Email'});
}elsif ($ENV{'SSL_CLIENT_S_DN'} =~ /\+MAIL=([^\+\/]+)$/) {
## Compatibility issue with old a-sign.at certs
$param->{'user'}{'email'} = lc($1);
}elsif ($crypt_openssl_x509_ok and exists($ENV{SSL_CLIENT_CERT})) {
## this is the X509v3 SubjectAlternativeName, and does only
## require "SSLOptions +ExportCertData" without patching
## mod_ssl -- massar@unix-ag.uni-kl.de
$param->{'user'}{'email'} = lc(Crypt::OpenSSL::X509->new_from_string($ENV{SSL_CLIENT_CERT})->email());
}
if($param->{user}{email}) {
$session->{'email'}= $param->{user}{email} ;
$param->{'auth_method'} = 'smime';
$session->{'auth'} = 'x509' ;
$param->{'ssl_client_s_dn'} = $ENV{'SSL_CLIENT_S_DN'};
$param->{'ssl_client_v_end'} = $ENV{'SSL_CLIENT_V_END'};
$param->{'ssl_client_i_dn'} = $ENV{'SSL_CLIENT_I_DN'};
$param->{'ssl_cipher_usekeysize'} = $ENV{'SSL_CIPHER_USEKEYSIZE'};
}
}elsif (($session->{'email'}) && ($session->{'email'} ne 'nobody')) {
$param->{'user'}{'email'} = $session->{'email'};
}elsif($in{'ticket'}=~/(S|P)T\-/){ # the request contain a CAS named ticket that use CAS ticket format
delete $session->{'do_not_use_cas'}; #reset do_not_use_cas because this client probably use CAS
# select the cas server that redirect the user to sympa and check the ticket
do_log ('notice',"CAS ticket is detected. in{'ticket'}=$in{'ticket'} checked_cas=$session->{'checked_cas'}");
my $cas_id = '';
if ($in{'checked_cas'} =~ /^(\d+)\,?/) {
$cas_id = $1;
} elsif ($session->{'checked_cas'} =~ /^(\d+)\,?/) {
$cas_id = $1;
}
if ($cas_id ne '') {
my $ticket = $in{'ticket'};
my $cas_server = $Conf{'auth_services'}{$robot}[$cas_id]{'cas_server'};
my $service_url = &wwslib::get_my_url();
$service_url =~ s/(\?|&)ticket\=.+$//;
my $net_id = $cas_server->validateST($service_url, $ticket);
if(defined $net_id) { # the ticket is valid net-id
do_log('notice',"login CAS OK server netid=$net_id" );
$param->{'user'}{'email'} = lc(&Auth::get_email_by_net_id($robot, $cas_id, {'uid' => $net_id}));
$session->{'auth'} = 'cas';
$session->{'email'}= $param->{user}{email} ;
$session->{'cas_server'} = $cas_id;
}else{
do_log('err',"CAS ticket validation failed : %s", &AuthCAS::get_errors());
}
}else{
do_log ('notice',"Internal error while receiving a CAS ticket $session->{'checked_cas'} ");
}
}elsif(($Conf{'cas_number'}{$robot} > 0) && ($in{'action'} !~ /^(login|sso_login|wsdl)$/)) { # some cas server are defined but no CAS ticket detected
unless ($session->{'do_not_use_cas'}) {
# user not taggued as not using cas
foreach my $auth_service (@{$Conf{'auth_services'}{$robot}}){
next unless ($auth_service->{'auth_type'} eq 'cas'); # skip auth services not related to cas
next unless ($auth_service->{'non_blocking_redirection'} eq 'on');
## skip cas server where client as been already redirect to the list of cas servers already checked is stored in the session
## the check below works fine as long as we don't have more then 10 CAS servers (because we don't properly split the list of values)
&do_log ('debug',"check_cas checker_cas : $session->{'checked_cas'} current cas_id $Conf{'cas_id'}{$robot}{$auth_service->{'auth_service_name'}}");
next if ($session->{'checked_cas'} =~ /$Conf{'cas_id'}{$robot}{$auth_service->{'auth_service_name'}}/) ;
# before redirect update the list of already checked cas server to prevent loop
my $cas_server = $auth_service->{'cas_server'};
my $return_url = &wwslib::get_my_url();
## Append the current CAS server ID to the list of checked CAS servers
$session->{'checked_cas'} .= $Conf{'cas_id'}{$robot}{$auth_service->{'auth_service_name'}};
my $redirect_url = $cas_server->getServerLoginGatewayURL($return_url);
if ($redirect_url =~ /http(s)+\:\//i) {
$in{'action'} = 'redirect';
$param->{'redirect_to'} = $redirect_url;
last
}elsif($redirect_url == -1) { # CAS server auth error
do_log('notice',"CAS server auth error $auth_service->{'auth_service_name'}" );
}else{
do_log('notice',"Strange CAS ticket detected and validated check sympa code !" );
}
}
$session->{'do_not_use_cas'} = 1 unless ($param->{'redirect_to'} =~ /http(s)+\:\//i) ; #set do_not_use_cas because all cas servers have been checked without success
}
}
##Cookie extern : sympa_altemails
## !!
$param->{'alt_emails'} = &cookielib::check_cookie_extern($ENV{'HTTP_COOKIE'},$Conf{'cookie'},$param->{'user'}{'email'});
if ($param->{'user'}{'email'}) {
# $param->{'auth'} = $param->{'alt_emails'}{$param->{'user'}{'email'}} || 'classic';
if (&List::is_user_db($param->{'user'}{'email'})) {
$param->{'user'} = &List::get_user_db($param->{'user'}{'email'});
}
## For the parser to display an empty field instead of [xxx]
$param->{'user'}{'gecos'} ||= '';
unless (defined $param->{'user'}{'cookie_delay'}) {
$param->{'user'}{'cookie_delay'} = $wwsconf->{'cookie_expire'};
}
## Skip get_which if either in a list context or accessing the CSS
unless ($in{'action'} eq 'css' || defined $in{'list'}) {
@{$param->{'get_which'}} = &List::get_which($param->{'user'}{'email'},$robot,'member') ;
@{$param->{'get_which_owner'}} = &List::get_which($param->{'user'}{'email'},$robot,'owner') ;
@{$param->{'get_which_editor'}} = &List::get_which($param->{'user'}{'email'},$robot,'editor') ;
}
# }
}
} ## END if RSS
## Action
my $action = $in{'action'};
## Store current action in the session in order to redirect after a logi or other temporary actions.
## We should not memorize ULRs that are transitory actions
## POST is not handled
## A lot of other methods where used in the past (before session was introduced in Sympa). We must clean all.
unless ($temporary_actions{$action} || $ENV{'REQUEST_METHOD'} ne 'GET') {
$session->{'redirect_url'} = $param->{'base_url'}.$param->{'path_cgi'}.$ENV{'PATH_INFO'};
}
$action ||= &Conf::get_robot_conf($robot, 'default_home');
$param->{'remote_addr'} = $ENV{'REMOTE_ADDR'} ;
$param->{'remote_host'} = $ENV{'REMOTE_HOST'};
$param->{'http_user_agent'} = $ENV{'HTTP_USER_AGENT'};
$param->{'htmlarea_url'} = $wwsconf->{'htmlarea_url'} ;
# if ($wwsconf->{'export_topics'} =~ /all/i);
if ($in{'action'} eq 'css') {
&do_css();
$param->{'action'} = 'css';
}elsif ($maintenance_mode) {
&do_maintenance();
$param->{'action'} = 'maintenance';
}else {
## Session loop
while ($action) {
unless (&check_param_in()) {
&report::reject_report_web('user','wrong_param',{},$action,$list);
&wwslog('info','Wrong parameters');
last;
}
$param->{'host'} = $list->{'admin'}{'host'} if (ref($list) eq 'List');
$param->{'host'} ||= $robot;
$param->{'domain'} = $list->{'domain'} if (ref($list) eq 'List');
## language ( $ENV{'HTTP_ACCEPT_LANGUAGE'} not used !)
$param->{'list_lang'} = $list->{'admin'}{'lang'} if (ref($list) eq 'List');
$param->{'user_lang'} = $param->{'user'}{'lang'} if (defined $param->{'user'});
$param->{'lang'} = $session->{'lang'} || $param->{'user_lang'} || $param->{'list_lang'} || &Conf::get_robot_conf($robot, 'lang');
$param->{'locale'} = &Language::SetLang($param->{'lang'});
$param->{'lang_tag'} = &Language::LanguageTag($param->{'lang'});
&export_topics ($robot);
unless ($comm{$action}) {
if (new List ($action, $robot)){
&do_redirect ($param->{'base_url'}.$param->{'path_cgi'}.'/info/'.$action);
last;
}
&report::reject_report_web('user','unknown_action',{},$action,$list);
&wwslog('info','unknown action %s', $action);
unless ($action = prevent_visibility_bypass()) {
last;
}
}
$param->{'action'} = $action;
my $old_action = $action;
my $old_subaction = $in{'subaction'};
## Check required action parameters
my $check_output = &check_action_parameters($action);
if (! defined $check_output ) {
&wwslog('err', "missing required parameters for action '$action'");
delete($param->{'action'});
last;
}elsif ($check_output != 1) {
## The output of the check may indicate another action to run first
## Example : running loginrequest if user is not authenticated
$action = $param->{'action'} = $check_output;
}
## Execute the action ##
if (defined $action) {
$action = &{$comm{$action}}();
}
unless (defined $action) {
unless($action = prevent_visibility_bypass()) {
delete($param->{'action'});
last;
}else{
&report::reject_report_web('user','authorization_reject',{},$param->{'action'},'');
}
}
last if ($action =~ /redirect/) ; # after redirect do not send anything, it will crash fcgi lib
if ($action eq $old_action) {
# if a subaction is define and change, then it is not a loop
if (! defined ($in{'subaction'})||($in{'subaction'} eq $old_subaction)){
&wwslog('info','Stopping loop with %s action', $action);
#undef $action;
$action = 'home';
}
}
undef $action if ($action == 1);
}
}
## Prepare outgoing params
&check_param_out();
## Params
$param->{'refparam'} = ref($param);
$param->{'action_type'} = $action_type{$param->{'action'}};
$param->{'action_type'} = 'none' unless (($param->{'is_priv'})||($param->{'action_type'} eq 'serveradmin'));
$param->{'lang'} ||= $param->{'user'}{'lang'} if (defined $param->{'user'});
$param->{'lang'} ||= &Conf::get_robot_conf($robot, 'lang');
$param->{'lang_tag'} = Language::LanguageTag($param->{'lang'});
if ($param->{'list'}) {
$param->{'list_title'} = $list->{'admin'}{'subject'};
$param->{'list_protected_email'} = &get_protected_email_address($param->{'list'}, $list->{'admin'}{'host'});
$param->{'title'} = &get_protected_email_address($param->{'list'}, $list->{'admin'}{'host'});
$param->{'title_clear_txt'} = "$param->{'list'}";
if ($param->{'subtitle'}) {
$param->{'main_title'} = "$param->{'list'} - $param->{'subtitle'}";
}
}else {
$param->{'main_title'} = $param->{'title'} = &Conf::get_robot_conf($robot,'title');
$param->{'title_clear_txt'} = $param->{'title'};
}
$param->{'robot_title'} = &Conf::get_robot_conf($robot,'title');
## store in session table this session contexte
$session->store();
## Do not manage cookies at this level if content was already sent
unless ($param->{'bypass'} eq 'extreme' ||
$param->{'action'} eq 'css' ||
$maintenance_mode ||
$rss) {
my $delay = $param->{'user'}{'cookie_delay'};
unless (defined $delay) {
$delay = $wwsconf->{'cookie_expire'};
}
if ($delay == 0) {
$delay = 'session';
}
$session->renew() unless $param->{'use_ssl'};
unless ($session->set_cookie($param->{'cookie_domain'},$delay,$param->{'use_ssl'})) {
&wwslog('notice', 'Could not set HTTP cookie');
}
## Set cookies "your_subscribtions" unless in one list page
if ($param->{'user'}{'email'} && ref($list) ne 'List') {
## In case get_which was not set
@{$param->{'get_which'}} = &List::get_which($param->{'user'}{'email'},$robot,'member') unless (defined $param->{'get_which'});
@{$param->{'get_which_owner'}} = &List::get_which($param->{'user'}{'email'},$robot,'owner') unless (defined $param->{'get_which_owner'});
@{$param->{'get_which_editor'}} = &List::get_which($param->{'user'}{'email'},$robot,'editor') unless (defined $param->{'get_which_editor'});
## Add lists information to 'which_info'
foreach my $list (@{$param->{'get_which'}}) {
## Evaluate AuthZ scenario first
my $result = $list->check_list_authz('visibility', $param->{'auth_method'},
{'sender' =>$param->{'user'}{'email'} ,
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
next unless (ref($result) eq 'HASH' && $result->{'action'} eq 'do_it');
my $l = $list->{'name'};
$param->{'which_info'}{$l}{'subject'} = $list->{'admin'}{'subject'};
$param->{'which_info'}{$l}{'host'} = $list->{'admin'}{'host'};
$param->{'which_info'}{$l}{'info'} = 1;
}
foreach my $list (@{$param->{'get_which_owner'}}) {
my $l = $list->{'name'};
$param->{'which_info'}{$l}{'subject'} = $list->{'admin'}{'subject'};
$param->{'which_info'}{$l}{'host'} = $list->{'admin'}{'host'};
$param->{'which_info'}{$l}{'info'} = 1;
$param->{'which_info'}{$l}{'admin'} = 1;
}
foreach my $list (@{$param->{'get_which_editor'}}) {
my $l = $list->{'name'};
$param->{'which_info'}{$l}{'subject'} = $list->{'admin'}{'subject'};
$param->{'which_info'}{$l}{'host'} = $list->{'admin'}{'host'};
$param->{'which_info'}{$l}{'info'} = 1;
$param->{'which_info'}{$l}{'admin'} = 1;
}
}
## Set cookies unless client use https authentication
if ($param->{'user'}{'email'}) {
if ($param->{'user'}{'email'} ne 'x509') {
$session->{'auth'} ||= 'classic';
$param->{'cookie_set'} = 1;
###Cookie extern : sympa_altemails
my $number = 0;
foreach my $element (keys %{$param->{'alt_emails'}}){
$number ++ if ($element);
}
unless ($number == 0) {
unless(&cookielib::set_cookie_extern($Conf{'cookie'},$param->{'cookie_domain'},%{$param->{'alt_emails'}})){
&wwslog('notice', 'Could not set HTTP cookie for external_auth');
}
}
}
} #elsif ($ENV{'HTTP_COOKIE'} =~ /sympauser\=/){
# &cookielib::set_cookie('unknown', $Conf{'cookie'}, $param->{'cookie_domain'}, 'now');
#}
}
## Available languages
my $saved_lang = &Language::GetLang();
foreach my $l (@{&Language::GetSupportedLanguages($robot)}) {
&Language::SetLang($l) || next;
if (gettext("_language_")) {
$param->{'languages'}{$l}{'complete'} = gettext("_language_");
}else {
$param->{'languages'}{$l}{'complete'} = $l;
}
if ($param->{'locale'} eq $l) {
$param->{'languages'}{$l}{'selected'} = 'selected="selected"';
}else {
$param->{'languages'}{$l}{'selected'} = '';
}
$param->{'languages'}{$l}{'lang_tag'} = Language::LanguageTag($l);
}
&Language::SetLang($saved_lang);
$param->{'html_dumpvars'} = &tools::dump_html_var($param) if ($session->{'dumpvars'});
# if bypass is defined select the content-type from various vars
if ($param->{'bypass'}) {
## if bypass = 'extreme' leave the action send the content-type and the content itself
unless ($param->{'bypass'} eq 'extreme') {
## if bypass = 'asis', file content-type is in the file itself as is define by the action in $param->{'content_type'};
unless ($param->{'bypass'} eq 'asis') {
my $type = $param->{'content_type'} || $mime_types->{$param->{'file_extension'}} || 'application/octet-stream';
printf "Content-Type: %s\n\n", $type;
}
# $param->{'file'} or $param->{'error'} must be define in this case.
if (open (FILE, $param->{'file'})){
print ;
close FILE;
}elsif(&report::is_there_any_reject_report_web()){
## for compatibility : it could be better
my $intern = &report::get_intern_error_web();
my $system = &report::get_system_error_web();
my $user = &report::get_user_error_web();
my $auth = &report::get_auth_reject_web();
if (ref($intern) eq 'ARRAY'){
print "INTERNAL SERVER ERROR\n";
};
if (ref($system) eq 'ARRAY'){
print "SYSTEM ERROR\n";
};
if (ref($user) eq 'ARRAY'){
foreach my $err (@$user){
printf "ERROR : %s\n", $err;
}
};
if (ref($auth) eq 'ARRAY'){
foreach my $err (@$auth){
printf "AUTHORISATION FAILED : %s\n", $err;
}
};
}else{
print "Internal error content-type nor file defined\n";
&do_log('err', 'Internal error content-type nor file defined');
}
}
}elsif ($rss) {
## Send RSS
print "Cache-control: no-cache\n";
print "Content-Type: application/rss+xml; charset=utf-8\n\n";
## Icons
$param->{'icons_url'} = &Conf::get_robot_conf($robot, 'static_content_url').'/icons';
## Retro compatibility concerns
$param->{'active'} = 1;
if (defined $list) {
$param->{'list_conf'} = $list->{'admin'};
}
my $locale = Language::Lang2Locale($param->{'lang'});
my $tt2_include_path =
tools::make_tt2_include_path($robot, 'web_tt2', $locale, $list);
unless (&tt2::parse_tt2(
$param, 'rss.tt2', \*STDOUT, $tt2_include_path, {}
)) {
my $error = &tt2::get_error();
$param->{'tt2_error'} = $error;
unless (&List::send_notify_to_listmaster('web_tt2_error', $robot, [$error])) {
&wwslog('notice','Unable to send notify "web_tt2_error" to listmaster');
}
}
# close FILE;
}elsif ($param->{'redirect_to'}) {
do_log ('notice',"Redirecting to $param->{'redirect_to'}");
print "Location: $param->{'redirect_to'}\n\n";
}else {
&prepare_report_user();
&send_html('main.tt2');
}
# exit if wwsympa.fcgi itself has changed
if ((stat($ENV{'SCRIPT_FILENAME'}))[9] > $birthday ) {
do_log('notice',"Exiting because $ENV{'SCRIPT_FILENAME'} has changed since fastcgi server started");
exit(0);
}
}
##############################################################
#-#\#|#/#-#\#|#/#-#\#|#/#-#\#|#/#-#\#|#/#-#\#|#/#-#\#|#/#-#\#|#/
##############################################################
## Write to log
sub wwslog {
my $facility = shift;
# do not log if log level if too high regarding the log requested by user
return if ($Log::levels{$facility} > $Log::log_level);
my $msg = shift;
my $remote = $ENV{'REMOTE_HOST'} || $ENV{'REMOTE_ADDR'};
## Determine calling function and parameters
my @call = caller(1);
$msg = $call[3].'() ' . $msg if ($call[3]);
$msg = "[list $param->{'list'}] " . $msg
if $param->{'list'};
if ($param->{'alt_emails'}) {
my @alts;
foreach my $alt (keys %{$param->{'alt_emails'}}) {
push @alts, $alt
unless ($alt eq $param->{'user'}{'email'});
}
if ($#alts >= 0) {
my $alt_list = join ',', @alts;
$msg = "[alt $alt_list] " . $msg;
}
}
$msg = "[user $param->{'user'}{'email'}] " . $msg
if $param->{'user'}{'email'};
$msg = "[rss] ".$msg
if $rss;
$msg = "[client $remote] ".$msg
if $remote;
$msg = "[session $session->{'id_session'}] ".$msg
if $session;
$msg = "[robot $robot] ".$msg;
return &Log::do_log($facility, $msg, @_);
}
sub web_db_log {
my $data = shift;
$data->{'client'} = $param->{'remote_addr'};
$data->{'daemon'} = 'wwsympa';
$data->{'robot'} ||= $robot;
$data->{'list'} ||= $list->{'name'} if (defined $list);
$data->{'action'} ||= $param->{'action'};
$data->{'user_email'} ||= $param->{'user'}{'email'} if (defined $param->{'user'});
$data->{'target_email'} ||= $data->{'user_email'}; ## Default email is the user email
unless (&Log::db_log($data)) {
&wwslog('err','web_db_log: failed to log in database');
return undef;
}
return 1;
}
sub new_loop {
$loop++;
my $query;
if ($wwsconf->{'use_fast_cgi'}) {
$query = new CGI::Fast;
$loop_count++;
}else {
return undef if ($loop > 1);
$query = new CGI;
}
return $query;
}
sub get_header_field {
my $field = shift;
## HTTP_X_ header fields set when using a proxy
if ($field eq 'SERVER_NAME') {
return $ENV{'HTTP_X_FORWARDED_SERVER'} || $ENV{'SERVER_NAME'};
}elsif ($field eq 'HTTP_HOST') {
return $ENV{'HTTP_X_FORWARDED_HOST'} || $ENV{'HTTP_HOST'};
}else {
return $ENV{$field};
}
}
# _split_params is used by get_parameters to split path info in the appropriate parameters list.
# It is used also by action ticket to prepare the context stored in the one_time_ticket table in string like path_info
# input ENV{'PATH_INFO'} like string, output in the global $param hash
sub _split_params {
my $args_string = shift;
&do_log('debug', "PATH_INFO: %s",$ENV{'PATH_INFO'});
$args_string =~ s+^/++;
my $ending_slash = 0;
if ($args_string =~ /\/$/) {
$ending_slash = 1;
}
my @params = split /\//, $args_string;
if ($params[0] eq 'nomenu') {
$param->{'nomenu'} = 1;
$param->{'path_cgi'} .= '/nomenu'; ## other links should keep the nomenu attribute
shift @params;
}
## debug mode
if ($params[0] =~ /debug(\d)?/) {
shift @params;
if ($1) {
$main::options{'debug_level'} = $1 if ($1);
}else{
$main::options{'debug_level'} = 1 ;
}
}else{
$main::options{'debug_level'} = 0 ;
}
do_log ('debug2', "debug level $main::options{'debug_level'}");
## rss mode
if ($params[0] eq 'rss') {
shift @params;
$rss = 1;
}
if ($#params >= 0) {
$in{'action'} = $params[0];
my $args;
if (defined $action_args{$in{'action'}}) {
$args = $action_args{$in{'action'}};
}else {
$args = $action_args{'default'};
}
my $i = 1;
foreach my $p (@$args) {
my $pname;
## More than 1 param
if ($p =~ /^\@(\w+)$/) {
$pname = $1;
$in{$pname} = join '/', @params[$i..$#params];
$in{$pname} .= '/' if $ending_slash;
last;
}
else {
$pname = $p;
$in{$pname} = $params[$i];
}
&wwslog('debug',"Incoming parameter: $pname=$in{$pname}");
$i++;
}
}
}
sub get_parameters {
# &wwslog('debug3', 'get_parameters');
## CGI URL
if ($ENV{'HTTPS'} eq 'on') {
$param->{'base_url'} = sprintf 'https://%s', &get_header_field('HTTP_HOST');
$param->{'use_ssl'} = 1;
}else {
$param->{'base_url'} = sprintf 'http://%s', &get_header_field('HTTP_HOST');
$param->{'use_ssl'} = 0;
}
$param->{'path_info'} = $ENV{'PATH_INFO'};
$param->{'http_method'} = $ENV{'REQUEST_METHOD'}; ## Usefull to skip previous_action when using POST
$param->{'robot_domain'} = $wwsconf->{'robot_domain'}{&get_header_field('SERVER_NAME')};
if ($ENV{'REQUEST_METHOD'} eq 'GET') {
&_split_params ($ENV{'PATH_INFO'});
}elsif ($ENV{'REQUEST_METHOD'} eq 'POST') {
## POST
if ($in{'javascript_action'}) {
## because of incompatibility javascript
$in{'action'} = $in{'javascript_action'};
}
foreach my $p (keys %in) {
do_log('debug2',"POST key $p value $in{$p}") unless ($p =~ /passwd/);
if ($p =~ /^((\w*)action)_(\w+)((\.\w+)*)$/) {
$in{$1} = $3;
if ($4) {
foreach my $v (split /\./, $4) {
$v =~ s/^\.?(\w+)\.?/$1/;
$in{$v} = 1;
}
}
undef $in{$p};
}
}
$param->{'nomenu'} = $in{'nomenu'};
}
## Lowercase email addresses
$in{'email'} = lc ($in{'email'});
## Don't get multiple listnames
if ($in{'list'}) {
my @lists = split /\0/, $in{'list'};
$in{'list'} = $lists[0];
}
my $custom_attribute ;
my $custom_input;
## Check parameters format
foreach my $p (keys %in) {
## Skip empty parameters
next if ($in{$p} =~ /^$/);
## Remove DOS linefeeds (^M) that cause problems with Outlook 98, AOL, and EIMS:
$in{$p} =~ s/\r\n|\r/\n/g;
#XXX## Convert from the web encoding to unicode string
#XXX$in{$p} = Encode::decode('utf8', $in{$p});
my @tokens = split (/\./, $p);
my $pname = $tokens[0];
## Regular expressions applied on parameters
my $regexp;
if ($pname =~ /^additional_field/) {
$regexp = $in_regexp{'additional_field'};
}elsif ($pname =~ /^custom_attribute(.*)$/) {
my $key = $tokens[1] ;
$regexp = $in_regexp{'custom_attribute'};
do_log ('debug2', "get_parameters (custom_attribute) : ($p)($key) $pname $in{$p} $Conf{$key}{type}");
$custom_attribute->{$key} = {value=>$in{$p}} ;
undef $in{$p} ;
}elsif ($pname =~ /^custom_input(.*)$/) {
my $key = $tokens[1];
$regexp = $in_regexp{'custom_input'};
do_log ('debug2', "get_parameters (custom_input) : ($p)($key) $pname $in{$p} $Conf{$key}{type}");
$custom_input->{$key} = $in{$p};
undef $in{$p};
}elsif ($in_regexp{$pname}) {
$regexp = $in_regexp{$pname};
}else {
$regexp = $in_regexp{'*'};
}
my $negative_regexp;
if ($pname =~ /^additional_field/) {
$negative_regexp = $in_negative_regexp{'additional_field'};
}elsif ($in_negative_regexp{$pname}) {
$negative_regexp = $in_negative_regexp{$pname};
}
# If we are editing an HTML file in the shared, allow HTML but prevent XSS.
if ($pname eq 'content' && $in{'action'} eq 'd_savefile' && $in{'path'} =~ $list->{'dir'}.'/shared' && lc($in{'path'}) =~ /\.html?/) {
my $tmpparam = $in{$p};
$tmpparam = &tools::sanitize_html('robot' => $robot,
'string' => $in{$p});
if (defined $tmpparam) {
$in{$p} = $tmpparam;
}
else {
&do_log('err','Unable to sanitize parameter %s',$pname);
}
}
foreach my $one_p (split /\0/, $in{$p}) {
if ($one_p !~ /^$regexp$/s ||
(defined $negative_regexp && $one_p =~ /$negative_regexp/s) ) {
## Dump parameters in a tmp file for later analysis
my $dump_file = &Conf::get_robot_conf($robot, 'tmpdir').'/sympa_dump.'.time.'.'.$$;
unless (open DUMP, ">$dump_file") {
&wwslog('err','get_parameters: failed to create %s : %s', $dump_file, $!);
}
&tools::dump_var(\%in, 0, \*DUMP);
close DUMP;
&report::reject_report_web('user','syntax_errors',{'params' => $p},'','');
&wwslog('err','get_parameters: syntax error for parameter %s value \'%s\' not conform to regexp:%s ; dumped vars in %s', $pname, $one_p, $regexp, $dump_file);
$in{$p} = '';
next;
}
}
}
$in{custom_attribute} = $custom_attribute ;
$in{custom_input} = $custom_input;
## For shared-related actions, Q-encode filenames
## This required for filenames that include non ascii characters
if (defined $filtering{$in{'action'}}) {
my %apply_to; ## Build list of parameters filters apply to
foreach my $p (keys %{$filtering{$in{'action'}}}) {
if ($p =~ /\*/) { ## use of wildcar
my $p_regexp = $p; $p_regexp =~ s/\*/\.\*/g; ## Turn wildcar into a regexp
foreach my $in_key (keys %in) {
if ($in_key =~ /^$p_regexp$/) {
$apply_to{$in_key} = $filtering{$in{'action'}}{$p};
}
}
}else {
$apply_to{$p} = $filtering{$in{'action'}}{$p};
}
}
foreach my $p (keys %apply_to) {
my $filtering_action = $apply_to{$p};
if ($filtering_action eq 'qencode') {
## Q-encode file path
my @tokens = split /\//, $in{$p};
foreach my $i (0..$#tokens) {
$tokens[$i] = &tools::qencode_filename($tokens[$i]);
}
$in{$p} = join '/', @tokens;
## Sympa's URI escaping subroutine (tools::escape_chars()) replaces '/' with %A5 ('¥' character)
## This should be transformed into a '/' again
}elsif ($filtering_action eq 'unescape_html') {
$in{$p} = &tools::unescape_chars($in{$p});
}elsif ($filtering_action eq 'fix_escape_uri') {
$in{$p} =~ s/\xa5/\//g;
}elsif ($filtering_action eq 'normalize') {
$in{$p} =~ s/^\$+//; ## remove leading \s
$in{$p} =~ s/\$+$//; ## remove trailing \s
$in{$p} = lc($in{$p}); ## lowercase
}
}
}
return 1;
}
sub get_parameters_old {
# &wwslog('debug3', 'get_parameters');
## CGI URL
if ($ENV{'HTTPS'} eq 'on') {
$param->{'base_url'} = sprintf 'https://%s', &get_header_field('HTTP_HOST');
$param->{'use_ssl'} = 1;
}else {
$param->{'base_url'} = sprintf 'http://%s', &get_header_field('HTTP_HOST');
$param->{'use_ssl'} = 0;
}
$param->{'path_info'} = $ENV{'PATH_INFO'};
$param->{'robot_domain'} = $wwsconf->{'robot_domain'}{&get_header_field('SERVER_NAME')};
if ($ENV{'REQUEST_METHOD'} eq 'GET') {
my $path_info = $ENV{'PATH_INFO'};
&do_log('debug', "PATH_INFO: %s",$ENV{'PATH_INFO'});
$path_info =~ s+^/++;
my $ending_slash = 0;
if ($path_info =~ /\/$/) {
$ending_slash = 1;
}
my @params = split /\//, $path_info;
# foreach my $i(0..$#params) {
# $params[$i] = &tools::unescape_chars($params[$i]);
# }
if ($params[0] eq 'nomenu') {
$param->{'nomenu'} = 1;
$param->{'path_cgi'} .= '/nomenu'; ## other links should keep the nomenu attribute
shift @params;
}
## debug mode
if ($params[0] =~ /debug(\d)?/) {
shift @params;
if ($1) {
$main::options{'debug_level'} = $1 if ($1);
}else{
$main::options{'debug_level'} = 1 ;
}
}else{
$main::options{'debug_level'} = 0 ;
}
do_log ('debug2', "debug level $main::options{'debug_level'}");
## rss mode
########### /^rss$/ ???
if ($params[0] eq 'rss') {
shift @params;
$rss = 1;
}
if ($#params >= 0) {
$in{'action'} = $params[0];
my $args;
if (defined $action_args{$in{'action'}}) {
$args = $action_args{$in{'action'}};
}else {
$args = $action_args{'default'};
}
my $i = 1;
foreach my $p (@$args) {
my $pname;
## More than 1 param
if ($p =~ /^\@(\w+)$/) {
$pname = $1;
$in{$pname} = join '/', @params[$i..$#params];
$in{$pname} .= '/' if $ending_slash;
last;
}
else {
$pname = $p;
$in{$pname} = $params[$i];
}
$i++;
}
}
}elsif ($ENV{'REQUEST_METHOD'} eq 'POST') {
## POST
if ($in{'javascript_action'}) {
## because of incompatibility javascript
$in{'action'} = $in{'javascript_action'};
}
foreach my $p (keys %in) {
do_log('debug2',"POST key $p value $in{$p}");
if ($p =~ /^((\w*)action)_(\w+)((\.\w+)*)$/) {
$in{$1} = $3;
if ($4) {
foreach my $v (split /\./, $4) {
$v =~ s/^\.?(\w+)\.?/$1/;
$in{$v} = 1;
}
}
undef $in{$p};
}
}
$param->{'nomenu'} = $in{'nomenu'};
}
## Lowercase email addresses
$in{'email'} = lc ($in{'email'});
## Don't get multiple listnames
if ($in{'list'}) {
my @lists = split /\0/, $in{'list'};
$in{'list'} = $lists[0];
}
my $custom_attribute ;
## Check parameters format
foreach my $p (keys %in) {
## Skip empty parameters
next if ($in{$p} =~ /^$/);
## Remove DOS linefeeds (^M) that cause problems with Outlook 98, AOL, and EIMS:
$in{$p} =~ s/\r\n|\r/\n/g;
#XXX## Convert from the web encoding to unicode string
#XXX$in{$p} = Encode::decode('utf8', $in{$p});
my @tokens = split (/\./, $p);
my $pname = $tokens[0];
## Regular expressions applied on parameters
my $regexp;
if ($pname =~ /^additional_field/) {
$regexp = $in_regexp{'additional_field'};
}elsif ($pname =~ /^custom_attribute(.*)$/) {
my $key = $tokens[1] ;
$regexp = $in_regexp{'custom_attribute'};
do_log ('debug2', "get_parameters (custom_attribute) : ($p)($key) $pname $in{$p} $Conf{$key}{type}");
$custom_attribute->{$key} = {value=>$in{$p}} ;
undef $in{$p} ;
}elsif ($in_regexp{$pname}) {
$regexp = $in_regexp{$pname};
}else {
$regexp = $in_regexp{'*'};
}
my $negative_regexp;
if ($pname =~ /^additional_field/) {
$negative_regexp = $in_negative_regexp{'additional_field'};
}elsif ($in_negative_regexp{$pname}) {
$negative_regexp = $in_negative_regexp{$pname};
}
# If we are editing an HTML file in the shared, allow HTML but prevent XSS.
if ($pname eq 'content' && $in{'action'} eq 'd_savefile' && $in{'path'} =~ $list->{'dir'}.'/shared' && lc($in{'path'}) =~ /\.html?/) {
my $tmpparam = $in{$p};
$tmpparam = &tools::sanitize_html('robot' => $robot,
'string' => $in{$p});
if (defined $tmpparam) {
$in{$p} = $tmpparam;
}
else {
&do_log('err','Unable to sanitize parameter %s',$pname);
}
}
foreach my $one_p (split /\0/, $in{$p}) {
if ($one_p !~ /^$regexp$/s ||
(defined $negative_regexp && $one_p =~ /$negative_regexp/s) ) {
## Dump parameters in a tmp file for later analysis
my $dump_file = &Conf::get_robot_conf($robot, 'tmpdir').'/sympa_dump.'.time.'.'.$$;
unless (open DUMP, ">$dump_file") {
&wwslog('err','get_parameters: failed to create %s : %s', $dump_file, $!);
}
&tools::dump_var(\%in, 0, \*DUMP);
close DUMP;
&report::reject_report_web('user','syntax_errors',{'params' => $p},'','');
&wwslog('err','get_parameters: syntax error for parameter %s value \'%s\' not conform to regexp:%s ; dumped vars in %s', $pname, $one_p, $regexp, $dump_file);
$in{$p} = '';
next;
}
}
}
$in{custom_attribute} = $custom_attribute ;
## For shared-related actions, Q-encode filenames
## This required for filenames that include non ascii characters
if (defined $filtering{$in{'action'}}) {
foreach my $p (keys %{$filtering{$in{'action'}}}) {
if ($filtering{$in{'action'}}{$p} eq 'qencode') {
## Q-encode file path
my @tokens = split /\//, $in{$p};
foreach my $i (0..$#tokens) {
$tokens[$i] = &tools::qencode_filename($tokens[$i]);
}
$in{$p} = join '/', @tokens;
## Sympa's URI escaping subroutine (tools::escape_chars()) replaces '/' with %A5 ('¥' character)
## This should be transformed into a '/' again
}elsif ($filtering{$in{'action'}}{$p} eq 'fix_escape_uri') {
$in{$p} =~ s/\xa5/\//g;
}
}
}
return 1;
}
## Check required parameters for an action
## It compares incoming parameter to those declared as required in %required_args
## Also check required privileges to perform each action
sub check_action_parameters {
my $action = shift;
if (defined $required_args{$action}) {
foreach my $arg_name (@{$required_args{$action}}) {
## Missing list parameter
if ($arg_name eq 'param.list') {
unless (defined $list) {
&report::reject_report_web('user','missing_arg',{'argument' => 'list'},$action);
&wwslog('info','missing list parameter');
&web_db_log({'status' => 'error',
'error_type' => 'no_list'});
return undef;
}
## User is not authenticated
}elsif ($arg_name eq 'param.user.email') {
unless (defined $param->{'user'} && $param->{'user'}{'email'}) {
if(prevent_visibility_bypass()) {
&report::reject_report_web('user','authorization_reject',{},$param->{'action'},'');
}
&report::reject_report_web('user','no_user',{},$action);
&wwslog('err','user not logged in');
&web_db_log({'status' => 'error',
'error_type' => "not_logged_in"});
## User is redirected to the login request form
$param->{'previous_action'} = $action;
$param->{'previous_list'} = $param->{'list'} if (defined $param->{'list'});
return 'loginrequest';
}
## Other incoming parameters
}else {
## There may be alternate parameters
## Then at least one of them MUST be set
my @req_parameters = split(/\|/, $arg_name);
my $ok = 0;
foreach my $req_param (@req_parameters) {
$ok =1 if ($in{$req_param});
}
unless ($ok) {
## Replace \0 and '|' with ',' before logging
$in{$arg_name} =~ s/\0/,/g;
$in{$arg_name} =~ s/\|/,/g;
if(prevent_visibility_bypass()) {
&report::reject_report_web('user','authorization_reject',{'list' => $in{'list'}},$param->{'action'},'');
}
&report::reject_report_web('user','missing_arg',{'argument' => $arg_name},$action);
&wwslog('info',"missing parameter '$arg_name'");
&web_db_log({'status' => 'error',
'error_type' => 'missing_parameter'});
delete $param->{'list'};
return undef;
}
}
}
}
## Check required privileges
if (defined $required_privileges{$action}) {
## There may be alternate privileges
## Then at least one of them MUST verified
my $ok = 0;
my $missing_priv;
foreach my $req_priv (@{$required_privileges{$action}}) {
$ok =1 if ($param->{'is_'.$req_priv});
$missing_priv = $req_priv;
}
unless ($ok) {
&report::reject_report_web('auth','action_'.$missing_priv,{},$param->{'action'},$list);
&wwslog('info','authorization failed, insufficient privileges');
&web_db_log({'status' => 'error',
'error_type' => 'authorization'});
delete $param->{'list'};
return undef;
}
}
return 1;
}
## Send HTML output
sub send_html {
my $tt2_file = shift;
## Send HTML headers
if ($param->{'date'}) {
Language::PushLang("en");
printf "Date: %s\n", &POSIX::strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime(time));
Language::PopLang();
}
## If we set the header indicating the last time the file to send was modified, add an HTTP header (limitate web harvesting).
if ($param->{'header_date'}) {
Language::PushLang("en");
printf "Last-Modified: %s\n", &POSIX::strftime('%a, %d %b %Y %H:%M:%S GMT', gmtime($param->{'header_date'}));
Language::PopLang();
}
print "Cache-control: no-cache\n" unless ( $param->{'action'} eq 'arc') ;
print "Content-Type: text/html; charset=utf-8\n";
## Workaround for Internet Explorer 8 or later.
print "X-UA-Compatible: IE=100\n";
## Notify crash to client.
if ($param->{'action'} eq 'crash') {
print "Status: 503 Service Unavailable\n";
print "Retry-After: 300\n";
}
## Icons
$param->{'icons_url'} = &Conf::get_robot_conf($robot, 'static_content_url').'/icons';
## Retro compatibility concerns
$param->{'active'} = 1;
if (defined $list) {
$param->{'list_conf'} = $list->{'admin'};
}
## Trying to use custom_vars
if (defined $list->{'admin'}{'custom_vars'}) {
foreach my $var (@{$list->{'admin'}{'custom_vars'}}) {
$param->{'custom_vars'}{$var->{'name'}} = $var->{'value'};
}
}
# XSS escaping applied to all outgoing parameters.
my $param_copy = &tools::dup_var($param); ## Escape parameters on a copy to avoid altering usefull data.
if(defined $param_copy) {
unless(&tools::sanitize_var('var' => $param_copy,
'level' => 0,
'robot' => $robot,
'htmlAllowedParam' => $param_copy->{'htmlAllowedParam'} ,
'htmlToFilter' => $param_copy->{'htmlToFilter'} ,
)
)
{
&do_log('err','Failed to sanitize $param in host %s', $robot);
}
}
## Save special paths.
my @added_include_path = tt2::get_include_path();
tt2::clear_include_path();
## Custom CSSes. They are stored to $param_copy so that they won't be
## escaped.
# Do not include locale paths.
# The css.tt2 by each locales will override styles in main CSS.
my $tt2_include_path =
tools::make_tt2_include_path($robot, 'web_tt2', '', $list);
# testing custom CSS.
if ($session->{'custom_css'}) {
my $custom_css;
unless (tt2::parse_tt2($param, 'css.tt2', \$custom_css,
$tt2_include_path
)) {
wwslog('info', 'error while parsing custom CSS');
delete $param_copy->{'custom_css'};
} else {
$param_copy->{'custom_css'} = $custom_css;
}
}
my $locale = Language::Lang2Locale($param->{'lang'});
# per-locale CSS.
my $locale_css;
if (tools::get_filename('etc', {},
'web_tt2/' . $locale . '/css.tt2', $robot, $list
)) {
unless (tt2::parse_tt2($param, $locale . '/css.tt2',
\$locale_css, $tt2_include_path
)) {
wwslog('info', 'error while parsing locale CSS %s',
$locale . '/css.tt2');
} else {
$param_copy->{'locale_css'} = $locale_css;
}
}
## Now include locale paths.
$tt2_include_path =
tools::make_tt2_include_path($robot, 'web_tt2', $locale, $list);
## Restore special paths and enable absolute path if neccesary.
foreach my $dir (@added_include_path) {
tt2::add_include_path($dir);
}
tt2::allow_absolute_path() if $allow_absolute_path;
undef $allow_absolute_path;
## Output the content.
unless (&tt2::parse_tt2($param_copy,$tt2_file , \*STDOUT,
$tt2_include_path, { 'has_header' => 1 })) {
my $error = &tt2::get_error();
$param->{'tt2_error'} = $error;
$param_copy->{'tt2_error'} = $error;
if ($param->{'action'} eq 'help' and
ref $error and $error->type eq 'file') {
# "Not Found" response for random help page.
print "Status: 404 Not Found\n";
} else {
&List::send_notify_to_listmaster('web_tt2_error', $robot, [$error]);
}
&tt2::parse_tt2($param_copy,'tt2_error.tt2' , \*STDOUT,
$tt2_include_path, { 'has_header' => 1 });
}
}
sub prepare_report_user {
$param->{'intern_errors'} = &report::get_intern_error_web();
$param->{'system_errors'} = &report::get_system_error_web();
$param->{'user_errors'} = &report::get_user_error_web();
$param->{'auth_rejects'} = &report::get_auth_reject_web();
$param->{'notices'} = &report::get_notice_web();
$param->{'errors'} = &report::is_there_any_reject_report_web();
}
=pod
=head2 sub check_param_in
Checks parameters contained in the global variable $in. It is the process used to analyze the incoming parameters.
Use it to create a List object and initialize output parameters.
=head3 Arguments
=over
=item * I
=back
=head3 Return
=over
=item * I if the process encounters problems.
=item * I<1> if everything goes well
=back
=head3 Calls
=over
=item * d_access_control
=item * make_pictures_url
=item * wwslog
=item * Language::SetLang
=item * List::am_i
=item * List::check_list_authz
=item * List::get_mod_spool_size
=item * List::get_shared_moderated
=item * List::get_subscriber
=item * List::get_subscription_request_count
=item * List::get_total
=item * List::get_total_bouncing
=item * List::is_listmaster
=item * List::is_moderated
=item * List::is_user
=item * List::new
=item * List::request_action
=item * report::reject_report_web
=back
=cut
## Analysis of incoming parameters
sub check_param_in {
&wwslog('debug2', 'check_param_in');
## Lowercase list name
$in{'list'} =~ tr/A-Z/a-z/;
## In case the variable was multiple
if ($in{'list'} =~ /^(\S+)\0/) {
$in{'list'} = $1;
## Create a new List instance.
unless ($list = new List ($in{'list'}, $robot)) {
&report::reject_report_web('user','unknown_list',{'list' => $in{'list'}},$param->{'action'},'');
&wwslog('info','check_param_in: unknown list %s', $in{'list'});
return undef;
}
## Set lang to list lang
&Language::SetLang($list->{'admin'}{'lang'});
}
## listmaster has owner and editor privileges for the list
if (&List::is_listmaster($param->{'user'}{'email'},$robot)) {
$param->{'is_listmaster'} = 1;
}
if ($in{'list'}) {
## Create a new List instance.
unless ($list = new List ($in{'list'}, $robot, {})) {
&report::reject_report_web('user','unknown_list',{'list' => $in{'list'}},$param->{'action'},'');
&wwslog('info','check_param_in: unknown list %s', $in{'list'});
return undef;
}
## Gather list configuration informations for further output.
$param->{'list'} = $in{'list'};
$param->{'subtitle'} = $list->{'admin'}{'subject'};
$param->{'subscribe'} = $list->{'admin'}{'subscribe'}{'name'};
$param->{'send'} = $list->{'admin'}{'send'}{'title'}{$param->{'lang'}};
# Pictures are not available unless it is configured for the list and the robot
if ($list->{'admin'}{'pictures_feature'} eq 'off') {
$param->{'pictures_display'} = undef;
}
else {
$param->{'pictures_display'} = 'on';
}
## Get the total number of subscribers to the list.
if (defined $param->{'total'}) {
$param->{'total'} = $list->get_total();
}else {
$param->{'total'} = $list->get_total('nocache');
}
## Check if the current list has a public key X.509 certificate.
$param->{'list_as_x509_cert'} = $list->{'as_x509_cert'};
## Stores to output the whole list's admin configuration.
$param->{'listconf'} = $list->{'admin'};
## If an user is logged in, checks this user's privileges.
if ($param->{'user'}{'email'}) {
$param->{'is_subscriber'} = $list->is_user($param->{'user'}{'email'});
$param->{'subscriber'} = $list->get_subscriber($param->{'user'}{'email'})
if $param->{'is_subscriber'};
$param->{'is_privileged_owner'} = $param->{'is_listmaster'} || $list->am_i('privileged_owner', $param->{'user'}{'email'});
$param->{'is_owner'} = $param->{'is_privileged_owner'} || $list->am_i('owner', $param->{'user'}{'email'});
$param->{'is_editor'} = $list->am_i('editor', $param->{'user'}{'email'});
$param->{'is_priv'} = $param->{'is_owner'} || $param->{'is_editor'};
$param->{'pictures_url'} = &tools::make_pictures_url('email' => $param->{'user'}{'email'}, 'list' => $list);
## Checks if the user can post in this list.
my $result = $list->check_list_authz('send',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $r_action;
$r_action = $result->{'action'} if (ref($result) eq 'HASH');
$param->{'may_post'} = 1 if ($r_action !~ /reject/);
## If no user logged in, the output can ask for authentification.
}else {
$param->{'user'}{'email'} = undef;
$param->{'need_login'} = 1;
}
## Check if this list's messages must be moderated.
$param->{'is_moderated'} = $list->is_moderated();
## If the user logged in is a privileged user, gather informations relative to administration tasks
if ($param->{'is_priv'}) {
$param->{'mod_message'} = $list->get_mod_spool_size();
$param->{'mod_subscription'} = $list->get_subscription_request_count();
$param->{'doc_mod_list'} = $list->get_shared_moderated();
$param->{'mod_total_shared'} = $#{$param->{'doc_mod_list'}} + 1;
if ($param->{'total'} > 0) {
$param->{'bounce_total'} = $list->get_total_bouncing();
$param->{'bounce_rate'} = $param->{'bounce_total'} * 100 / $param->{'total'};
$param->{'bounce_rate'} = int ($param->{'bounce_rate'} * 10) / 10;
}else {
$param->{'bounce_rate'} = 0;
}
$param->{'mod_total'} = $param->{'mod_total_shared'}+$param->{'mod_message'}+$param->{'mod_subscription'};
}
## Check unsubscription authorization for the current user and list.
my $result = $list->check_list_authz('unsubscribe',$param->{'auth_method'},
{'sender' =>$param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
$main::action = $result->{'action'} if (ref($result) eq 'HASH');
if (! $param->{'user'}{'email'}) {
$param->{'may_signoff'} = 1 if ($main::action =~ /do_it|owner|request_auth/);
}elsif ($param->{'is_subscriber'}) {
$param->{'may_signoff'} = 1 if ($main::action =~ /do_it|owner|request_auth/);
$param->{'may_suboptions'} = 1;
}
## Check subscription authorization for the current user and list.
my $result = $list->check_list_authz('subscribe',$param->{'auth_method'},
{'sender' =>$param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
$main::action = $result->{'action'} if (ref($result) eq 'HASH');
$param->{'may_subscribe'} = 1 if ($main::action =~ /do_it|owner|request_auth/);
## Check if the current user can read the shared documents.
my %mode;
$mode{'read'} = 1;
my %access = &d_access_control(\%mode,"");
$param->{'may_d_read'} = $access{'may'}{'read'};
## Check the status (exists, deleted, doesn't exist) of the shared directory
$param->{'shared'} = $list->get_shared_status();
}
## Check if the current user can create a list.
my $result = &Scenario::request_action ('create_list',$param->{'auth_method'},$robot,
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $r_action;
my $reason;
if (ref($result) eq 'HASH') {
$r_action = $result->{'action'};
$reason = $result->{'reason'};
}
$param->{'create_list_reason'} = $reason;
if ($param->{'user'}{'email'} &&
(($param->{'create_list'} = $r_action ) =~ /do_it|listmaster/)) {
$param->{'may_create_list'} = 1;
}else{
undef ($param->{'may_create_list'});
}
return 1;
}
## Prepare outgoing params
sub check_param_out {
&wwslog('debug2', 'check_param_out');
$param->{'loop_count'} = $loop_count;
$param->{'start_time'} = gettext_strftime "%d %b %Y at %H:%M:%S", localtime($start_time);
$param->{'process_id'} = $$;
## listmaster has owner and editor privileges for the list
if (&List::is_listmaster($param->{'user'}{'email'},$robot)) {
$param->{'is_listmaster'} = 1;
}else {
undef $param->{'is_listmaster'};
}
## Reset $list variable if it is not expected for the current action
## To prevent the list panel from being printed in a non list context
## Only check if the corresponding entry exists in %action_args
if (defined $param->{'action'} && defined $action_args{$param->{'action'}}) {
unless (grep /^list$/, @{$action_args{$param->{'action'}}}) {
$param->{'list'} = undef;
$list = undef;
}
}
## Email addresses protection
if (defined $list) {
if ($list->{'admin'}{'spam_protection'} eq 'at') {
$param->{'hidden_head'} = ''; $param->{'hidden_at'} = ' AT '; $param->{'hidden_end'} = '';
}elsif($list->{'admin'}{'spam_protection'} eq 'javascript') {
$param->{'protection_type'} = 'javascript';
$param->{'hidden_head'} = '
';
}else {
$param->{'hidden_head'} = ''; $param->{'hidden_at'} = '@'; $param->{'hidden_end'} = '';
}
}else {
if (&Conf::get_robot_conf($robot,'spam_protection') eq 'at') {
$param->{'hidden_head'} = ''; $param->{'hidden_at'} = ' AT '; $param->{'hidden_end'} = '';
}elsif(&Conf::get_robot_conf($robot,'spam_protection') eq 'javascript') {
$param->{'protection_type'} = 'javascript';
$param->{'hidden_head'} = '
';
}else {
$param->{'hidden_head'} = ''; $param->{'hidden_at'} = '@'; $param->{'hidden_end'} = '';
}
}
if ($list->{'name'}) {
&wwslog('debug2', "list-name $list->{'name'}");
## Email addresses protection
if ($in{'action'} eq 'arc') {
$param->{'protection_type'} = undef;
if ($list->{'admin'}{'web_archive_spam_protection'} eq 'at') {
$param->{'hidden_head'} = ''; $param->{'hidden_at'} = ' AT '; $param->{'hidden_end'} = '';
}elsif($list->{'admin'}{'web_archive_spam_protection'} eq 'javascript') {
$param->{'protection_type'} = 'javascript';
$param->{'hidden_head'} = '
';
}else {
$param->{'hidden_head'} = ''; $param->{'hidden_at'} = '@'; $param->{'hidden_end'} = '';
}
}else {
if ($list->{'admin'}{'spam_protection'} eq 'at') {
$param->{'hidden_head'} = ''; $param->{'hidden_at'} = ' AT '; $param->{'hidden_end'} = '';
}elsif($list->{'admin'}{'spam_protection'} eq 'javascript') {
$param->{'hidden_head'} = '
';
}else {
$param->{'hidden_head'} = ''; $param->{'hidden_at'} = '@'; $param->{'hidden_end'} = '';
}
}
## Owners
my $owners = $list->get_owners();
foreach my $o (@{$owners}) {
next unless $o->{'email'};
$param->{'owner'}{$o->{'email'}}{'gecos'} = $o->{'gecos'};
$param->{'owner'}{$o->{'email'}}{'visibility'} = $o->{'visibility'};
$param->{'owner'}{$o->{'email'}}{'mailto'} = &mailto($list,$o->{'email'},$o->{'gecos'});
($param->{'owner'}{$o->{'email'}}{'local'},$param->{'owner'}{$o->{'email'}}{'domain'}) = split ('@',$o->{'email'});
my $masked_email = $o->{'email'};
$masked_email =~ s/\@/ AT /;
$param->{'owner'}{$o->{'email'}}{'masked_email'} = $masked_email;
}
## Editors
if (defined $list->{'admin'}{'editor'}) {
my $editors = $list->get_editors();
foreach my $e (@{$editors}) {
next unless $e->{'email'};
$param->{'editor'}{$e->{'email'}}{'gecos'} = $e->{'gecos'};
$param->{'editor'}{$e->{'email'}}{'visibility'} = $e->{'visibility'};
$param->{'editor'}{$e->{'email'}}{'mailto'} = &mailto($list,$e->{'email'},$e->{'gecos'});
($param->{'editor'}{$e->{'email'}}{'local'},$param->{'editor'}{$e->{'email'}}{'domain'}) = split ('@',$e->{'email'});
my $masked_email = $e->{'email'};
$masked_email =~ s/\@/ AT /;
$param->{'editor'}{$e->{'email'}}{'masked_email'} = $masked_email;
}
}
## Environment variables
foreach my $k (keys %ENV) {
$param->{'env'}{$k} = $ENV{$k};
}
## privileges
if ($param->{'user'}{'email'}) {
$param->{'is_subscriber'} = $list->is_user($param->{'user'}{'email'});
$param->{'subscriber'} = $list->get_subscriber($param->{'user'}{'email'})
if $param->{'is_subscriber'};
$param->{'is_privileged_owner'} = $param->{'is_listmaster'} || $list->am_i('privileged_owner', $param->{'user'}{'email'});
$param->{'is_owner'} = $param->{'is_privileged_owner'} || $list->am_i('owner', $param->{'user'}{'email'});
$param->{'is_editor'} = $list->am_i('editor', $param->{'user'}{'email'});
$param->{'is_priv'} = $param->{'is_owner'} || $param->{'is_editor'};
#May post:
my $result = $list->check_list_authz('send',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $r_action;
my $reason;
if (ref($result) eq 'HASH') {
$r_action = $result->{'action'};
$reason = $result->{'reason'};
}
if ($r_action =~ /do_it/) {
$param->{'may_post'} = 1 ;
}else {
$param->{'may_post_reason'} = $reason;
}
if ($list->has_include_data_sources() &&
$param->{'is_owner'}) {
$param->{'may_sync'} = 1;
}
}else {
## If user not logged in && GET method && not an authN-related action
## Keep track of the 'referer' parameter
if ($ENV{'REQUEST_METHOD'} eq 'GET' &&
! $auth_action{$in{'action'}} ) {
$param->{'referer'} = &tools::escape_chars(&wwslib::get_my_url());
}else {
## Keep the previous value of the referer
$param->{'referer'} = $in{'referer'};
}
}
## Should Not be used anymore ##
$param->{'may_subunsub'} = 1
if ($param->{'may_signoff'} || $param->{'may_subscribe'});
## May review
my $result = $list->check_list_authz('review',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $r_action;
$r_action = $result->{'action'} if (ref($result) eq 'HASH');
$param->{'may_suboptions'} = 1;
$param->{'total'} = $list->get_total();
$param->{'may_review'} = 1 if ($r_action =~ /do_it/);
$param->{'list_status'} = $list->{'admin'}{'status'};
## May signoff
my $result = $list->check_list_authz('unsubscribe',$param->{'auth_method'},
{'sender' =>$param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
$main::action = $result->{'action'} if (ref($result) eq 'HASH');
if (! $param->{'user'}{'email'}) {
$param->{'may_signoff'} = 1 if ($main::action =~ /do_it|owner|request_auth/);
}elsif ($param->{'is_subscriber'} &&
($param->{'subscriber'}{'subscribed'} == 1)) {
$param->{'may_signoff'} = 1 if ($main::action =~ /do_it|owner|request_auth/);
$param->{'may_suboptions'} = 1;
}
## May Subscribe
my $result = $list->check_list_authz('subscribe',$param->{'auth_method'},
{'sender' =>$param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
$main::action = $result->{'action'} if (ref($result) eq 'HASH');
$param->{'may_subscribe'} = 1 if ($main::action =~ /do_it|owner|request_auth/);
# SJS START
## May Add or del subscribers
my $result = $list->check_list_authz('add',$param->{'auth_method'},
{'sender' =>$param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
$main::action = $result->{'action'} if (ref($result) eq 'HASH');
$param->{'may_add'} = 1 if ($main::action =~ /do_it/);
my $result = $list->check_list_authz('del',$param->{'auth_method'},
{'sender' =>$param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
$main::action = $result->{'action'} if (ref($result) eq 'HASH');
$param->{'may_del'} = 1 if ($main::action =~ /do_it/);
# SJS END
## Archives Access control
if (defined $list->{'admin'}{'web_archive'}) {
$param->{'is_archived'} = 1;
## Check if the current user may access web archives
my $result = $list->check_list_authz('web_archive.access',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $r_action;
$r_action = $result->{'action'} if (ref($result) eq 'HASH');
if ($r_action =~ /do_it/i) {
$param->{'arc_access'} = 1;
}else{
undef ($param->{'arc_access'});
}
## Check if web archive is publically accessible (useful information for RSS)
my $result = $list->check_list_authz('web_archive.access',$param->{'auth_method'},
{'sender' => 'nobody'});
my $r_action;
$r_action = $result->{'action'} if (ref($result) eq 'HASH');
if ($r_action =~ /do_it/i) {
$param->{'arc_public_access'} = 1;
}
}
## Shared documents access control
if ($list->get_shared_status() eq 'exist') {
## Check if shared is publically accessible (useful information for RSS)
my $result = $list->check_list_authz('shared_doc.d_read',$param->{'auth_method'},
{'sender' => 'nobody'});
my $r_action;
if (ref($result) eq 'HASH') {
$r_action = $result->{'action'};
}
if ($r_action =~ /do_it/i) {
$param->{'shared_public_access'} = 1;
}
}
}
$param->{'robot'} = $robot;
## If parameter has the Unicode Perl flag, then switch to utf-8
## this switch is applied recursively
&tools::recursive_transformation($param, \&tools::unicode_to_utf8);
}
## ticket : this action is used if someone submits a one time ticket
sub do_ticket {
&wwslog('info', 'do_ticket(%s)', $in{'ticket'});
$param->{'ticket_context'} = &Auth::get_one_time_ticket($in{'ticket'}, $ip );
$param->{'ticket_context'}{'printable_date'} = gettext_strftime "%d %b %Y at %H:%M:%S", localtime($param->{'ticket_context'}{'date'});
return 1 unless ($param->{'ticket_context'}{'result'} eq 'success' or $param->{'ticket_context'}{'result'} eq 'closed');
# if the ticket is related to someone which is not logged in, the system performs the same operation as for a login
my $email_regexp = &tools::get_regexp('email');
if ($param->{'ticket_context'}{'result'} eq 'success') {
$session->{'email'} = lc($param->{'ticket_context'}{'email'});
$param->{'user'} = &List::get_user_db($session->{'email'});
$param->{'user'}{'email'} = $session->{'email'} ;
$param->{'last_login _host'} = $param->{'user'}{'last_login_host'};
$param->{'last_login_date'} = gettext_strftime "%d %b %Y at %H:%M:%S", localtime($param->{'user'}{'last_login_date'}) if ($param->{'user'}{'last_login_date'});
&List::update_user_db($param->{'user'}{'email'},{last_login_date =>time(),last_login_host=>$ip }) ;
}elsif($param->{'ticket_context'}{'result'} eq 'closed'){
&wwslog('info', 'do_ticket(%s) : Refusing to perform login because the ticket has been used before', $in{'ticket'});
return 1;
}else{
&wwslog('err', 'do_ticket(%s) : Unable to evaluate the ticket validity (status: %s)', $in{'ticket'}, $param->{'ticket_context'}{'result'});
return 1;
}
&_split_params($param->{'ticket_context'}{'data'});
return $in{'action'} ;
}
## Login WWSympa
sub do_login {
&wwslog('info', 'do_login(%s)', $in{'email'});
my $user;
my $next_action;
if ($in{'referer'}) {
$param->{'redirect_to'} = &tools::unescape_chars($in{'referer'});
}elsif ($in{'previous_action'} &&
$in{'previous_action'} !~ /^(login|logout|loginrequest)$/) {
$next_action = $in{'previous_action'};
$in{'list'} = $in{'previous_list'};
}else {
$next_action = &Conf::get_robot_conf($robot, 'default_home');
}
# never return to login or logout when login.
$next_action = &Conf::get_robot_conf($robot, 'default_home')
if $in{'next_action'} =~ /^(login|logout)$/;
if ($param->{'user'}{'email'}) {
&report::reject_report_web('user','already_login',{'email' => $param->{'user'}{'email'}},$param->{'action'},'');
&wwslog('info','do_login: user %s already logged in', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'email'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'already_login'});
if ($param->{'nomenu'}) {
$param->{'back_to_mom'} = 1;
return 1;
}else {
return $next_action;
}
}
unless ($in{'email'}) {
&report::reject_report_web('user','no_email',{},$param->{'action'},'');
&wwslog('info','do_login: no email');
&web_db_log({'parameters' => $in{'email'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => "no_email"});
return $in{'previous_action'} ||
&Conf::get_robot_conf($robot, 'default_home');
}
$session->{'unauthenticated_email'} = $param->{'unauthenticated_email'} = $in{'email'};
unless ($in{'passwd'}) {
my $url_redirect;
#Does the email belongs to an ldap directory?
if($url_redirect = &is_ldap_user($in{'email'})){
$param->{'redirect_to'} = $url_redirect
if ($url_redirect && ($url_redirect != 1));
}elsif ($in{'failure_referer'}) {
$param->{'redirect_to'} = $in{'failure_referer'};
}else{
$in{'init_email'} = $in{'email'};
$param->{'init_email'} = $in{'email'};
$param->{'escaped_init_email'} = &tools::escape_chars($in{'email'});
&report::reject_report_web('user','missing_arg',{'argument' => 'passwd'},$param->{'action'},'');
&wwslog('info','do_login: missing parameter passwd');
&web_db_log({'parameters' => $in{'email'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => "missing_parameter"});
$param->{'login_error'} = 'missing_password';
return $in{'previous_action'} || 'renewpasswd';
}
}
my $data;
unless ($ENV{'REQUEST_METHOD'} eq 'POST') {
&do_log('notice', "Authentication failed, because do not use HTTP method POST but %s",$ENV{'REQUEST_METHOD'} );
&web_db_log({'parameters' => $in{'email'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'not_using_post'});
return 'loginrequest';
}
unless($data = &Auth::check_auth($robot, $in{'email'},$in{'passwd'})){
&do_log('notice', "Authentication failed\n");
&web_db_log({'parameters' => $in{'email'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'authentication'});
my $unauthenticated_user = &List::get_user_db($in{'email'});
if ($unauthenticated_user->{'wrong_login_count'} > &Conf::get_robot_conf($robot, 'max_wrong_password')){
$param->{'login_error'} = 'password_reset';
}else{
$param->{'login_error'} = 'wrong_password';
}
if ($in{'previous_action'}) {
delete $in{'passwd'};
$in{'list'} = $in{'previous_list'};
return $in{'previous_action'};
}elsif ($in{'failure_referer'}) {
$param->{'redirect_to'} = $in{'failure_referer'};
}else {
return 'renewpasswd';
}
}
$param->{'user'} = $data->{'user'};
$param->{'last_login_host'} = $data->{'user'}{'last_login_host'};
$param->{'last_login_date'} = gettext_strftime "%d %b %Y at %H:%M:%S", localtime($data->{'user'}{'last_login_date'}) if ($data->{'user'}{'last_login_date'});
$session->{'auth'} = $data->{'auth'};
my $email = lc($param->{'user'}{'email'});
$session->{'email'} = $email;
$session->{'unauthenticated_email'} = '';
&List::update_user_db($param->{'user'}{'email'},{last_login_date =>time(),last_login_host=>$ip, wrong_login_count =>0}) ;
## Set alt_email
if ($data->{'alt_emails'}) {
foreach my $k (keys %{$data->{'alt_emails'}}) {
$param->{'alt_emails'}{$k} = $data->{'alt_emails'}{$k};
}
}
unless($param->{'alt_emails'}{$email}){
unless(&cookielib::set_cookie_extern($Conf{'cookie'},$param->{'cookie_domain'},%{$param->{'alt_emails'}})){
&wwslog('notice', 'Could not set HTTP cookie for external_auth');
web_db_log(
{
'parameters' => $param->{'cookie_domain'}.','.
join(',', map { "$_ => $param->{'alt_emails'}{$_}" }
keys %{$param->{'alt_emails'} || {}}),
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'cookie'
}
);
return undef;
}
}
## Current authentication mode
#$param->{'auth'} = $param->{'alt_emails'}{$param->{'user'}{'email'}} || 'classic';
if ($session->{'lang'}) { # user did choose a specific language before being logged. Apply it as a user pref.
&List::update_user_db($param->{'user'}{'email'},{lang=>$session->{'lang'}}) ;
$param->{'lang'} = $session->{'lang'};
}else{ # user did not choose a specific language, apply user pref for this session.
$param->{'lang'} = $user->{'lang'} || $list->{'admin'}{'lang'} || &Conf::get_robot_conf($robot, 'lang');
$session->{'lang'} = $param->{'lang'} ;
}
$param->{'lang_tag'} = Language::LanguageTag($param->{'lang'});
if ($session->{'review_page_size'}) { # user did choose a specific page size upgrade prefs
&List::update_user_db($param->{'user'}{'email'},{data=>&tools::hash_2_string($param->{'user'}{'prefs'})}) ;
}
if ($session->{'shared_mode'}) { # user did choose a shared expert/standard mode
&List::update_user_db($param->{'user'}{'email'},{data=>&tools::hash_2_string($param->{'user'}{'prefs'})}) ;
}
if ($in{'newpasswd1'} && $in{'newpasswd2'}) {
my $old_action = $param->{'action'};
$param->{'action'} = 'setpasswd';
&do_setpasswd();
$param->{'action'} = $old_action;
}
if ($param->{'nomenu'}) {
$param->{'back_to_mom'} = 1;
return 1;
}
&web_db_log({'parameters' => $in{'email'},
'target_email' => $in{'email'},
'status' => 'success'});
&do_redirect ($session->{'redirect_url'});
return ;
}
## Login WWSympa
## The sso_login action is made of 4 subactions that make a complete workflow.
## Note that this comlexe workflow is only used if the SSO server does not provide
## the user email address or if this email address is not trusted and therefore
## needs to be checked.
## The workflow:
## 1) init: determine if email address needs to be collected/checked
## 2) requestemail: collect the user email address in a web form. Note that form may be initialized with
## one email address provided by the SSO server
## 3) validateemail: a challenge is sent to the email address to validate it
## 4) confirmemail: user confirms his email address with the challenge
sub do_sso_login {
&wwslog('info', 'do_sso_login(%s)', $in{'auth_service_name'});
delete $session->{'do_not_use_cas'}; #when user require CAS login, reset do_not_use_cas cookie
my $next_action;
if ($param->{'user'}{'email'}) {
&report::reject_report_web('user','already_login',{'email' => $param->{'user'}{'email'}},$param->{'action'},'');
&wwslog('err','do_login: user %s already logged in', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'auth_service_name'},
'status' => 'error',
'error_type' => "already_login"});
return &Conf::get_robot_conf($robot, 'default_home');
}
## This is a CAS service
if (defined (my $cas_id = $Conf{'cas_id'}{$robot}{$in{'auth_service_name'}})) {
my $cas_server = $Conf{'auth_services'}{$robot}[$cas_id]{'cas_server'};
my $path = '';
if ($param->{'nomenu'}) {
$path = "/nomenu";
}
$path .= "/sso_login_succeeded/$in{'auth_service_name'}";
$session->{'checked_cas'} = $cas_id;
my $service = "$param->{'base_url'}$param->{'path_cgi'}".$path;
my $redirect_url = $cas_server->getServerLoginURL($service);
&wwslog('info', 'do_sso_login: redirect_url(%s)', $redirect_url);
if ($redirect_url =~ /http(s)+\:\//i) {
$in{'action'} = 'redirect';
$param->{'redirect_to'} = $redirect_url;
$param->{'bypass'} = 'extreme';
$session->set_cookie($param->{'cookie_domain'},'session');
#$session->set_cookie('localhost','session');
print "Location: $param->{'redirect_to'}\n\n";
}
}elsif (defined (my $sso_id = $Conf{'generic_sso_id'}{$robot}{$in{'auth_service_name'}})) {
## Generic SSO
## If contacted via POST, then redirect the user to the URL for the access control to apply
if ($ENV{'REQUEST_METHOD'} eq 'POST') {
my $path = '';
my $service;
if ($param->{'nomenu'}) {
$path = "/nomenu";
}
&wwslog('info', 'do_sso_login(): POST request processing');
if ($in{'subaction'} eq 'validateemail') {
$path .= "/validateemail/$in{'email'}";
}elsif ($in{'subaction'} eq 'confirmemail') {
$path .= "/confirmemail/$in{'email'}/$in{'ticket'}";
}else {
$path .= "/init";
}
my $service = "$param->{'base_url'}$param->{'path_cgi'}/sso_login/$in{'auth_service_name'}".$path;
&wwslog('info', 'do_sso_login: redirect user to %s', $service);
$in{'action'} = 'redirect';
$param->{'redirect_to'} = $service;
$param->{'bypass'} = 'extreme';
print "Location: $param->{'redirect_to'}\n\n";
return 1;
}
my $email;
## We need to collect/verify the user's email address
if (defined $Conf{'auth_services'}{$robot}[$sso_id]{'force_email_verify'}) {
my $email_is_trusted = 0;
## the subactions order is : init, requestemail, validateemail, sendssopasswd, confirmemail
## get email from NetiD table
if (defined $Conf{'auth_services'}{$robot}[$sso_id]{'internal_email_by_netid'}) {
&wwslog('debug', 'do_sso_login(): lookup email internal: %s', $sso_id);
if ($email = &Auth::get_email_by_net_id($robot, $sso_id, \%ENV)) {
$email_is_trusted = 1;
}
}
## get email from authN module
if (defined $Conf{'auth_services'}{$robot}[$sso_id]{'email_http_header'} && ! $email_is_trusted) {
my @email_list = split(/$Conf{'auth_services'}{$robot}[$sso_id]{'http_header_value_separator'}/,
lc($ENV{$Conf{'auth_services'}{$robot}[$sso_id]{'email_http_header'}}));
$email = $email_list[0]; ## Only get the first occurence if multi-valued
}
## Start the email validation process
if ($in{'subaction'} eq 'init' &&
($email_is_trusted == 0 || ! $email)) {
&wwslog('info', 'do_sso_login(): return request email');
$session->{'auth'} = 'generic_sso';
$param->{'server'}{'key'} = $in{'auth_service_name'};
$param->{'subaction'} = 'requestemail';
$param->{'init_email'} = $email;
return 1;
}
if (defined($in{'email'}) and !($in{'subaction'} eq 'init')) {
$email = $in{'email'};
}
## Send a confirmation email and request it on the web interface
if ($in{'subaction'} eq 'validateemail') {
$session->{'auth'} = 'generic_sso';
$param->{'server'}{'key'} = $in{'auth_service_name'};
$param->{'init_email'} = $email;
## Replace sendpassword with one time ticket
$param->{'one_time_ticket'} = &Auth::create_one_time_ticket($in{'email'},$robot,'sso_login/confirmemail?auth_service_name='.$in{'auth_service_name'},$ip);
unless (&sendssopasswd($email)) {
&report::reject_report_web('user','incorrect_email',{'email' => $email},$param->{'action'});
$param->{'subaction'} = 'requestemail';
return 1;
}
$param->{'subaction'} = 'validateemail';
return 1;
}
if ($in{'subaction'} eq 'confirmemail') {
$session->{'auth'} = 'generic_sso' ;
$param->{'server'}{'key'} = $in{'auth_service_name'};
$param->{'init_email'} = $email;
$in{'email'} = $email;
#
# Check input parameters and verify ticket for email, stolen from do_login
#
unless ($in{'email'}) {
&report::reject_report_web('user','no_email',{},$param->{'action'});
&wwslog('info','confirmemail: no email');
&web_db_log({'parameters' => $in{'auth_service_name'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'no_email'});
$param->{'subaction'} = 'validateemail';
return 1;
}
unless ($in{'ticket'}) {
$in{'init_email'} = $in{'email'};
$param->{'init_email'} = $in{'email'};
$param->{'escaped_init_email'} = &tools::escape_chars($in{'email'});
&report::reject_report_web('user','missing_arg',{'argument' => 'ticket'},$param->{'action'});
&wwslog('info','do_sso_login: confirmemail: missing parameter ticket');
&web_db_log({'parameters' => $in{'auth_service_name'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'missing_parameter'});
$param->{'subaction'} = 'validateemail';
return 1;
}
## Validate the ticket
my $ticket_output = &Auth::get_one_time_ticket($in{'ticket'}, $ip );
unless ($ticket_output->{'result'} eq 'success'){
&report::reject_report_web('user','auth_failed',{},$param->{'action'});
&web_db_log({'parameters' => $in{'auth_service_name'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'authentication'});
&wwslog('err', "Authentication failed\n");
$param->{'subaction'} = 'validateemail';
return 1;
}
&wwslog('info', 'do_sso_login: confirmemail: email validation succeeded');
# need to create netid to email map entry
$email = $in{'email'};
# everything is ok to proceed to with possible sympa account created and traddional sso login
## TODO : netidmap_table should also be used when no confirmation is performed
if (defined $Conf{'auth_services'}{$robot}[$sso_id]{'internal_email_by_netid'}) {
my $netid = $ENV{$Conf{'auth_services'}{$robot}[$sso_id]{'netid_http_header'}};
my $idpname = $Conf{'auth_services'}{$robot}[$sso_id]{'service_id'};
unless(&List::set_netidtoemail_db($robot, $netid, $idpname, $in{'email'})) {
&report::reject_report_web('intern','db_update_failed',{},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err', 'error update netid map');
&web_db_log({'parameters' => $in{'auth_service_name'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'internal'});
return &Conf::get_robot_conf($robot, 'default_home');
}
}else {
&wwslog('info', 'do_sso_login: confirmemail: validation failed');
$param->{'subaction'} = 'validateemail';
return 1;
}
}
}else {
##
if (defined $Conf{'auth_services'}{$robot}[$sso_id]{'email_http_header'}) {
my @email_list = split($Conf{'auth_services'}{$robot}[$sso_id]{'http_header_value_separator'},
lc($ENV{$Conf{'auth_services'}{$robot}[$sso_id]{'email_http_header'}}));
$email = $email_list[0]; ## Only get the first occurence if multi-valued
}else {
unless (defined $Conf{'auth_services'}{$robot}[$sso_id]{'ldap_host'} &&
defined $Conf{'auth_services'}{$robot}[$sso_id]{'ldap_get_email_by_uid_filter'}) {
&report::reject_report_web('intern','auth_conf_no_identified_user',{},$param->{'action'},'','',$robot);
&wwslog('err','do_sso_login: auth.conf error : either email_http_header or ldap_host/ldap_get_email_by_uid_filter entries should be defined');
&web_db_log({'parameters' => $in{'auth_service_name'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'internal'});
return 'home';
}
$email = &Auth::get_email_by_net_id($robot, $sso_id, \%ENV);
}
}
unless ($email) {
&report::reject_report_web('intern','no_identified_user',{},$param->{'action'},'','',$robot);
&wwslog('err','do_sso_login: user could not be identified, no %s HTTP header set', $Conf{'auth_services'}{$robot}[$sso_id]{'email_http_header'});
&web_db_log({'parameters' => $in{'auth_service_name'},
'status' => 'error',
'error_type' => 'no_email'});
return 'home';
}
$param->{'user'}{'email'} = $email;
$session->{'email'} = $email;
$session->{'auth'} = 'generic_sso' ;
&wwslog('notice', 'User identified as %s', $email);
## There are two ways to list the attributes that Sympa will cache for the user
## Either with a defined header prefix (http_header_prefix)
## Or with an explicit list of header fields (http_header_list)
my @sso_attr;
if ($Conf{'auth_services'}{$robot}[$sso_id]{'http_header_list'}) {
my $list_of_headers = $Conf{'auth_services'}{$robot}[$sso_id]{'http_header_list'};
foreach my $field (split(/,/, $list_of_headers)) {
if (defined $ENV{$field}) {
push @sso_attr, $field.'__PAIRS_SEP__'.$ENV{$field};
}
}
}elsif ($Conf{'auth_services'}{$robot}[$sso_id]{'http_header_prefix'}) {
my $prefix = $Conf{'auth_services'}{$robot}[$sso_id]{'http_header_prefix'};
foreach my $k (keys %ENV) {
if ($k =~ /^$prefix/) {
push @sso_attr, $k.'__PAIRS_SEP__'.$ENV{$k};
}
}
}
my $all_sso_attr = join '__ATT_SEP__', @sso_attr;
## Create user entry if required
unless (&List::is_user_db($email)) {
unless (&List::add_user_db({'email' => $email})) {
&report::reject_report_web('intern','add_user_db_failed',{'user'=>$email},$param->{'action'},'',$email,$robot);
&wwslog('info','do_sso_login: add failed');
&web_db_log({'parameters' => $in{'auth_service_name'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
unless (&List::update_user_db($email,
{'attributes' => $all_sso_attr })) {
&report::reject_report_web('intern','update_user_db_failed',{'user'=>$email},$param->{'action'},'',$email,$robot);
&wwslog('info','do_sso_login: update failed');
&web_db_log({'parameters' => $in{'auth_service_name'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
&report::notice_report_web('you_have_been_authenticated',{},$param->{'action'});
## Keep track of the SSO used to login
## Required to provide logout feature if available
$session->{'sso_id'} = $in{'auth_service_name'};
&do_redirect ($session->{'redirect_url'});
return ;
}else{
## Unknown SSO service
&report::reject_report_web('intern','unknown_authentication_service',{'name'=> $in{'auth_service_name'}},$param->{'action'},'','',$robot);
&wwslog('err','do_sso_login: unknown authentication service %s', $in{'auth_service_name'});
&web_db_log({'parameters' => $in{'auth_service_name'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'internal'});
return 'home';
}
&web_db_log({'parameters' => $in{'auth_service_name'},
'target_email' => $in{'email'},
'status' => 'success'});
return 1;
}
sub do_sso_login_succeeded {
&wwslog('info', 'do_sso_login_succeeded(%s)', $in{'auth_service_name'});
if (defined $param->{'user'} && $param->{'user'}{'email'}) {
&report::notice_report_web('you_have_been_authenticated',{},$param->{'action'});
&web_db_log({'parameters' => $in{'auth_service_name'},
'status' => 'success'});
}else{
&report::reject_report_web('user','auth_failed',{},$param->{'action'});
&web_db_log({'parameters' => $in{'auth_service_name'},
'status' => 'error',
'error_type' => 'authentication'});
}
## We should refresh the main window
if ($param->{'nomenu'}) {
$param->{'back_to_mom'} = 1;
return 1;
}else{
&do_redirect ($session->{'redirect_url'});
return;
}
}
sub is_ldap_user {
my $auth = shift; ## User email or UID
&wwslog('debug2',"is_ldap_user ($auth)");
unless (&tools::get_filename('etc',{}, 'auth.conf', $robot)) {
return undef;
}
## List all LDAP servers first
my @ldap_servers;
foreach my $ldap (@{$Conf{'auth_services'}{$robot}}){
next unless ($ldap->{'auth_type'} eq 'ldap');
push @ldap_servers, $ldap;
}
unless ($#ldap_servers >= 0) {
return undef;
}
unless (eval "require Net::LDAP") {
&wwslog ('err',"Unable to use LDAP library, Net::LDAP required,install perl-ldap (CPAN) first");
return undef;
}
require Net::LDAP;
my ($ldap_anonymous,$filter);
foreach my $ldap (@ldap_servers){
# skip ldap auth service if the user id or email do not match regexp auth service parameter
next unless ($auth =~ /$ldap->{'regexp'}/i);
my $param = &tools::dup_var($ldap);
my $ds = new Datasource('LDAP', $param);
unless (defined $ds && ($ldap_anonymous = $ds->connect())) {
&do_log('err',"Unable to connect to the LDAP server '%s'", $ldap->{'ldap_host'});
next;
}
my @alternative_conf = split(/,/,$ldap->{'alternative_email_attribute'});
my $attrs = $ldap->{'email_attribute'};
if (&tools::valid_email($auth)){
$filter = $ldap->{'get_dn_by_email_filter'};
}else{
$filter = $ldap->{'get_dn_by_uid_filter'};
}
$filter =~ s/\[sender\]/$auth/ig;
## !! une fonction get_dn_by_email/uid
my $mesg = $ldap_anonymous->search(base => $ldap->{'suffix'} ,
filter => "$filter",
scope => $ldap->{'scope'},
timeout => $ldap->{'timeout'} );
unless($mesg->count() != 0) {
&wwslog('notice','No entry in the Ldap Directory Tree of %s for %s',$ldap->{'host'},$auth);
$ds->disconnect();
last;
}
$ds->disconnect();
my $redirect = $ldap->{'authentication_info_url'};
return $redirect || 1;
next unless ($ldap_anonymous);
}
}
## send back login form
sub do_loginrequest {
&wwslog('info','do_loginrequest');
if ($param->{'user'}{'email'}) {
&report::reject_report_web('user','already_login',{'email' => $param->{'user'}{'email'}},$param->{'action'});
&wwslog('info','do_loginrequest: already logged in as %s', $param->{'user'}{'email'});
return undef;
}
if ($in{'init_email'}) {
$param->{'init_email'} = $in{'init_email'};
}
if ($in{'previous_action'} eq 'referer') {
$param->{'referer'} = &tools::escape_chars($ENV{'HTTP_REFERER'});
}elsif (! $param->{'previous_action'}) {
$param->{'previous_action'} = 'loginrequest';
}
$param->{'title'} = 'Login'
if ($param->{'nomenu'});
return 1;
}
## Help / about WWSympa
sub do_help {
&wwslog('info','do_help(%s)', $in{'help_topic'});
## Contextual help
if ($in{'help_topic'}) {
if ($in{'help_topic'} eq 'editlist') {
foreach my $pname (sort List::by_order keys %{$pinfo}) {
next if ($pname =~ /^(comment|defaults)$/);
if ($pinfo->{$pname}{'gettext_id'}) {
$param->{'param'}{$pname}{'title'} = gettext($pinfo->{$pname}{'gettext_id'});
} else {
$param->{'param'}{$pname}{'title'} = $pinfo->{$pname}{'title'}{$param->{'lang'}};
}
$param->{'param'}{$pname}{'comment'} = $pinfo->{$pname}{'comment'}{$param->{'lang'}};
}
}
$param->{'help_topic'} = $in{'help_topic'};
}
return 1;
}
# update session cookie and redirect the client to redirect_to parameter or glob var;
sub do_redirect {
my $redirect_to = shift;
&wwslog('info','do_redirect(%s)', $redirect_to);
$redirect_to ||= $param->{'redirect_to'};
# because of some bug Sympa did redirection to un empty URL. Next line should prevent it.
$redirect_to ||= $param->{'base_url'}.$param->{'path_cgi'};
#$session->set_cookie('localhost','session');
$session->set_cookie($param->{'cookie_domain'},'session');
print "Location: $redirect_to\n\n";
$param->{'bypass'} = 'extreme';
return 1;
}
## Logout from WWSympa
sub do_logout {
&wwslog('info','do_logout(%s)', $param->{'user'}{'email'});
delete $param->{'user'};
$session->{'email'} = 'nobody' ;
# no reason to alter the lang because user perform logout
# $param->{'lang'} = $param->{'cookie_lang'} = &cookielib::check_lang_cookie($ENV{'HTTP_COOKIE'}) || $list->{'admin'}{'lang'} || &Conf::get_robot_conf($robot, 'lang');
if (defined $session->{'cas_server'} && (defined $Conf{'auth_services'}{$robot}[$session->{'cas_server'}])) {
# this user was logged using CAS
my $cas_server = $Conf{'auth_services'}{$robot}[$session->{'cas_server'}]{'cas_server'};
$in{'action'} = 'redirect';
my $return_url = &wwslib::get_my_url();
$return_url =~ s/\/logout//;
$param->{'redirect_to'} = $cas_server->getServerLogoutURL($return_url);
delete $session->{'cas_server'};
return 'redirect';
} elsif (defined $session->{'sso_id'}) {
# this user was logged using a generic_sso
## Check if logout_url is known for this SSO
my $sso;
unless ($sso = &Conf::get_sso_by_id(robot => $robot, service_id => $session->{'sso_id'})) {
&wwslog('err',"unknown SSO service_id '%s'", $session->{'sso_id'});
return undef ;
}
## Remove sso_id
delete $session->{'sso_id'};
if ($sso->{'logout_url'}) {
$in{'action'} = 'redirect';
$param->{'redirect_to'} = $sso->{'logout_url'};
return 'redirect';
}
}
&wwslog('info','do_logout: logout performed');
&web_db_log({'parameters' => $param->{'user'}{'email'},
'target_email' => $in{'email'},
'status' => 'success'});
if ($in{'previous_action'} eq 'referer') {
$param->{'referer'} = &tools::escape_chars($in{'previous_list'});
}
return &Conf::get_robot_conf($robot, 'default_home');
}
sub sendssopasswd {
my $email = shift;
do_log('info', 'sendssopasswd(%s)', $email);
my ($passwd, $user);
unless ($email) {
&report::reject_report_web('user','no_email',{},$param->{'action'});
&wwslog('info','do_sendssopasswd: no email');
&web_db_log({'parameters' => $email,
'target_email' => $email,
'status' => 'error',
'error_type' => "no_email"});
return 'requestemail';
}
unless (&tools::valid_email($email)) {
&report::reject_report_web('user','incorrect_email',{'email' => $email},$param->{'action'});
&wwslog('info','do_sendssopasswd: incorrect email %s', $email);
&web_db_log({'parameters' => $email,
'target_email' => $email,
'status' => 'error',
'error_type' => "incorrect_email"});
return 'requestemail';
}
my $url_redirect;
if ($param->{'newuser'} = &List::get_user_db($email)) {
## Create a password if none
unless ($param->{'newuser'}{'password'}) {
unless ( &List::update_user_db($email,
{'password' => &tools::tmp_passwd($email)
})) {
&report::reject_report_web('intern','db_update_failed',{},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','sendssopasswd: update failed');
&web_db_log({'parameters' => $email,
'target_email' => $email,
'status' => 'error',
'error_type' => "internal"});
return undef;
}
$param->{'newuser'}{'password'} = &tools::tmp_passwd($email);
}
$param->{'newuser'}{'escaped_email'} = &tools::escape_chars($param->{'newuser'}{'email'});
}else {
$param->{'newuser'} = {'email' => $email,
'escaped_email' => &tools::escape_chars($email),
'password' => &tools::tmp_passwd($email)
};
}
$param->{'init_passwd'} = 1
if ($param->{'user'}{'password'} =~ /^init/);
&List::send_global_file('sendssopasswd', $email, $robot, $param);
$param->{'email'} = $email;
&web_db_log({'parameters' => $email,
'target_email' => $email,
'status' => 'success'});
return 'validateemail';
}
sub do_firstpasswd {
&wwslog('info', 'do_firstpasswd(%s)', $in{'email'});
$param->{'requestpasswd_context'} = 'firstpasswd';
return 'renewpasswd';
}
## send a ticket for choosing a new password
sub do_renewpasswd {
&wwslog('info', 'do_renewpasswd(%s)', $in{'email'});
my $url_redirect;
if($in{'email'}){
if($url_redirect = &is_ldap_user($in{'email'})){
$param->{'redirect_to'} = $url_redirect
if ($url_redirect && ($url_redirect != 1));
}elsif (! &tools::valid_email($in{'email'})) {
&report::reject_report_web('user','incorrect_email',{'email' => $in{'email'}},$param->{'action'});
&wwslog('info','do_renewpasswd: incorrect email \"%s\"', $in{'email'});
&web_db_log({'parameters' => $in{'email'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'incorrect_email'});
return undef;
}
}
$param->{'email'} = $in{'email'};
&web_db_log({'parameters' => $in{'email'},
'target_email' => $in{'email'},
'status' => 'success',
});
if ($in{'previous_action'} eq 'referer') {
$param->{'referer'} = &tools::escape_chars($in{'previous_list'});
}
return 1;
}
####################################################
# do_requestpasswd
####################################################
# Sends a message to the user containing user password.
#
# IN : -
#
# OUT : 'renewpasswd' | 1 | 'loginrequest' | undef
#
####################################################
sub do_requestpasswd {
&wwslog('info', 'do_requestpasswd(%s)', $in{'email'});
my ($passwd, $user);
$param->{'account_creation'} = 1;
my $url_redirect;
if($url_redirect = &is_ldap_user($in{'email'})){
## There might be no authentication_info_url URL defined in auth.conf
if ($url_redirect == 1) {
&report::reject_report_web('user','ldap_user',{},$param->{'action'});
&wwslog('info','do_requestpasswd: LDAP user %s, cannot remind password', $in{'email'});
&web_db_log({'parameters' => $in{'email'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'internal'});
return 'home';
}else{
$param->{'redirect_to'} = $url_redirect if ($url_redirect && ($url_redirect != 1));
return 1;
}
}
## Check auth.conf before creating/sending a password
unless (&Auth::may_use_sympa_native_auth($robot, $in{'email'})) {
## TODO: Error handling
&report::reject_report_web('user','passwd_reminder_not_allowed',{},$param->{'action'});
return undef
}
&wwslog('debug','do_requestpasswd: sending one tile ticket for %s', $in{'email'});
$param->{'one_time_ticket'} = &Auth::create_one_time_ticket($in{'email'},$robot,'choosepasswd',$ip);
$param->{'request_from_host'} = $ip;
unless ($param->{'newuser'} = &List::get_user_db($in{'email'})) {
$param->{'newuser'} =
{ 'email' => &tools::clean_email($in{'email'}) };
$param->{'newuser'}{'escaped_email'} =
&tools::escape_chars($param->{'newuser'}{'email'});
}
if ($param->{'one_time_ticket'}) {
$param->{'login_error'}='ticket_sent';
unless (&List::send_global_file('sendpasswd', $in{'email'}, $robot, $param)) {
&wwslog('notice',"Unable to send template 'sendpasswd' to $in{'email'}");
$param->{'login_error'}='unable_to_send_ticket';
}
}else{
&wwslog('notice',"Unable to create_one_time_ticket");
&report::reject_report_web('user','passwd_reminder_error',{},$param->{'action'});
$param->{'login_error'}='unable_to_create_ticket';
}
return 1 unless ($param->{'previous_action'}) ;
return $param->{'previous_action'};
}
## Which list the user is subscribed to
## TODO (pour listmaster, toutes les listes)
sub do_which {
my $which = {};
&wwslog('info', 'do_which');
$param->{'get_which'} = undef ;
$param->{'which'} = undef ;
foreach my $role ('member','owner','editor') {
foreach my $list ( &List::get_which($param->{'user'}{'email'}, $robot, $role) ){
my $l = $list->{'name'};
my $result = $list->check_list_authz('visibility', $param->{'auth_method'},
{'sender' =>$param->{'user'}{'email'} ,
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $r_action;
$r_action = $result->{'action'} if (ref($result) eq 'HASH');
next unless ($r_action =~ /do_it/);
$param->{'which'}{$l}{'subject'} = $list->{'admin'}{'subject'};
$param->{'which'}{$l}{'host'} = $list->{'admin'}{'host'};
if ($role eq 'member') {
push @{$param->{'get_which'}}, $list;
}
if ($role eq 'owner' || $role eq 'editor') {
$param->{'which'}{$l}{'admin'} = 1;
}
## For compatibility concerns (3.0)
## To be deleted one of these day
$param->{$role}{$l}{'subject'} = $list->{'admin'}{'subject'};
$param->{$role}{$l}{'host'} = $list->{'admin'}{'host'};
}
}
# &List::db_log('wwsympa',$param->{'user'}{'email'},$param->{'auth_method'},$ip,'which','',$robot,'','done');
return 1;
}
## The list of list
sub do_lists {
my @lists;
&wwslog('info', 'do_lists(%s,%s)', $in{'topic'}, $in{'subtopic'});
my %topics = &List::load_topics($robot);
if ($in{'topic'}) {
$param->{'topic'} = $in{'topic'};
if ($in{'subtopic'}) {
$param->{'subtopic'} = $in{'subtopic'};
$param->{'subtitle'} = sprintf "%s / %s", $topics{$in{'topic'}}{'current_title'}, $topics{$in{'topic'}}{'sub'}{$in{'subtopic'}}{'current_title'};
$param->{'subtitle'} ||= "$in{'topic'} / $in{'subtopic'}";
}else {
$param->{'subtitle'} = $topics{$in{'topic'}}{'current_title'} || $in{'topic'};
}
}
my $all_lists = &List::get_lists($robot);
foreach my $list ( @$all_lists ) {
my $sender = $param->{'user'}{'email'} || 'nobody';
my $result = $list->check_list_authz('visibility',$param->{'auth_method'},
{'sender' => $sender,
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'},
'options' => {'dont_reload_scenario' => 1}});
my $r_action;
$r_action = $result->{'action'} if (ref($result) eq 'HASH');
next unless ($r_action eq 'do_it');
my $list_info = {};
$list_info->{'subject'} = $list->{'admin'}{'subject'};
$list_info->{'host'} = $list->{'admin'}{'host'};
$list_info->{'date_epoch'} = $list->{'admin'}{'creation'}{'date_epoch'};
$list_info->{'date'} = $list->{'admin'}{'creation'}{'date'};
$list_info->{'topics'} = $list->{'admin'}{'topics'};
if ($param->{'user'}{'email'} &&
($list->am_i('owner',$param->{'user'}{'email'}) ||
$list->am_i('editor',$param->{'user'}{'email'})) ) {
$list_info->{'admin'} = 1;
}
if ($param->{'user'}{'email'} &&
$list->is_user($param->{'user'}{'email'})) {
$list_info->{'is_subscriber'} = 1;
}
## Filter lists by topic.
## topic argument 'topicsless' or 'other' means 'lists with topic
## "other" or without topics'.
## no topic argument; List all lists
if (! $in{'topic'}) {
$param->{'which'}{$list->{'name'}} = $list_info;
}elsif ($list->{'admin'}{'topics'}) {
foreach my $topic (@{$list->{'admin'}{'topics'}}) {
my @tree = split '/', $topic;
if (($in{'topic'} eq 'other' and ! $in{'subtopic'}) or
$in{'topic'} eq 'topicsless') {
$param->{'which'}{$list->{'name'}} = $list_info
if $tree[0] eq 'other' and ! $tree[1];
next;
}
next if (($in{'topic'}) && ($tree[0] ne $in{'topic'}));
next if (($in{'subtopic'}) && ($tree[1] ne $in{'subtopic'}));
$param->{'which'}{$list->{'name'}} = $list_info;
}
} elsif (($in{'topic'} eq 'other' and ! $in{'subtopic'}) or
$in{'topic'} eq 'topicsless') {
$param->{'which'}{$list->{'name'}} = $list_info;
}
}
foreach my $listname (sort keys %{$param->{'which'}}) {
if ($listname =~ /^([a-z])/){
push @{$param->{'orderedlist'}{$1}}, $listname ;
}else{
push @{$param->{'orderedlist'}{'others'}}, $listname ;
}
}
return 1;
}
## The list of latest created lists
sub do_latest_lists {
&wwslog('info', "do_latest_lists($in{'for'}, $in{'count'},$in{'topic'}, $in{'subtopic'})");
unless (&do_lists()) {
&wwslog('err','do_latest_lists: error while calling do_lists');
return undef;
}
my $today = time;
my $oldest_day;
if (defined $in{'for'}) {
$oldest_day = $today - (3600 * 24 * ($in{'for'}));
$param->{'for'} = $in{'for'};
unless ($oldest_day >= 0){
&report::reject_report_web('user','nb_days_to_much',{'nb_days' => $in{'for'} },$param->{'action'});
&wwslog('err','do_latest_lists: parameter "for" is too big"');
}
}
my $nb_lists = 0;
my @date_lists;
foreach my $listname (keys (%{$param->{'which'}})) {
if ($param->{'which'}{$listname}{'date_epoch'} < $oldest_day) {
delete $param->{'which'}{$listname};
next;
}
$nb_lists++;
}
if (defined $in{'count'}) {
$param->{'count'} = $in{'count'};
unless ($in{'count'}) {
$param->{'which'} = undef;
}
}
my $count_lists = 0;
foreach my $l ( sort {$param->{'which'}{$b}{'date_epoch'} <=> $param->{'which'}{$a}{'date_epoch'}} (keys (%{$param->{'which'}}))) {
$count_lists++;
if ($in{'count'}) {
if ($count_lists > $in{'count'}){
last;
}
}
$param->{'which'}{$l}{'name'} = $l;
push @{$param->{'latest_lists'}} , $param->{'which'}{$l};
}
$param->{'which'} = undef;
return 1;
}
## The list of the most active lists
sub do_active_lists {
&wwslog('info', "do_active_lists($in{'for'}, $in{'count'},$in{'topic'}, $in{'subtopic'})");
unless (&do_lists()) {
&wwslog('err','do_active_lists: error while calling do_lists');
return undef;
}
## oldest interesting day
my $oldest_day = 0;
if (defined $in{'for'}) {
$oldest_day = int(time/86400) - $in{'for'};
unless ($oldest_day >= 0){
&report::reject_report_web('user','nb_days_to_much',{'nb_days' => $in{'for'} },$param->{'action'});
&wwslog('err','do_latest_lists: parameter "for" is too big"');
return undef;
}
}
## get msg count for each list
foreach my $l (keys (%{$param->{'which'}})) {
my $list = new List ($l, $robot);
my $file = "$list->{'dir'}/msg_count";
my %count ;
if (open(MSG_COUNT, $file)) {
while (){
if ($_ =~ /^(\d+)\s(\d+)$/) {
$count{$1} = $2;
}
}
close MSG_COUNT ;
$param->{'which'}{$l}{'msg_count'} = &count_total_msg_since($oldest_day,\%count);
if ($in{'for'}) {
my $average = $param->{'which'}{$l}{'msg_count'} / $in{'for'}; ## nb msg by day
$average = int($average * 10);
$param->{'which'}{$l}{'average'} = $average /10; ## one digit
}
} else {
$param->{'which'}{$l}{'msg_count'} = 0;
}
}
my $nb_lists = 0;
## get "count" lists
foreach my $l ( sort {$param->{'which'}{$b}{'msg_count'} <=> $param->{'which'}{$a}{'msg_count'}} (keys (%{$param->{'which'}}))) {
if (defined $in{'count'}) {
$nb_lists++;
if ($nb_lists > $in{'count'}) {
last;
}
}
$param->{'which'}{$l}{'name'} = $l;
push @{$param->{'active_lists'}} , $param->{'which'}{$l};
}
if (defined $in{'count'}) {
$param->{'count'} = $in{'count'};
}
if (defined $in{'for'}) {
$param->{'for'} = $in{'for'};
}
$param->{'which'} = undef;
return 1;
}
sub count_total_msg_since {
my $oldest_day = shift;
my $count = shift;
my $total = 0;
foreach my $d (sort {$b <=> $a} (keys %$count)) {
if ($d < $oldest_day) {
last;
}
$total = $total + $count->{$d};
}
return $total;
}
## List information page
sub do_info {
&wwslog('info', 'do_info');
## Access control
unless (defined &check_authz('do_info', 'info')) {
delete $param->{'list'};
return undef;
}
## Get List Description
if (-r $list->{'dir'}.'/homepage') {
my $file_path = $list->{'dir'}.'/homepage';
unless (open FILE, "<", $file_path) {
&report::reject_report_web('intern','cannot_open_file',{'file' => $file_path},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_info: failed to open file %s: %s', $file_path,$!);
&web_db_log({'parameters' => $file_path,
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
while () {
Encode::from_to($_, $Conf{'filesystem_encoding'}, 'utf8');
$param->{'homepage_content'} .= $_;
}
close FILE;
## Used by previous templates
$param->{'homepage'} = 1;
}elsif (-r $list->{'dir'}.'/info') {
my $file_path = $list->{'dir'}.'/info';
unless (open FILE, "<", $file_path) {
&report::reject_report_web('intern','cannot_open_file',{'file' => $file_path},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_info: failed to open file %s: %s', $file_path,$!);
&web_db_log({'parameters' => $file_path,
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
while () {
Encode::from_to($_, $Conf{'filesystem_encoding'}, 'utf8');
$param->{'info_content'} .= $_;
}
close FILE;
$param->{'info_content'} =~ s/\n/\
/g;
}
&tt2::add_include_path($list->{'dir'});
return 1;
}
## List subcriber count page
sub do_subscriber_count {
&wwslog('info', 'do_subscriber_count');
unless (&do_info()) {
&wwslog('info','do_subscriber_count: error while calling do_info');
return undef;
}
print "Content-type: text/plain\n\n";
print $list->get_total()."\n";
$param->{'bypass'} = 'extreme';
return 1;
}
## Subscribers' list
sub do_review {
&wwslog('info', 'do_review(%s)', $in{'page'});
my $record;
my @users;
my $size ;
my $sortby = $in{'sortby'} || 'email';
my %sources;
## Access control
return undef unless (defined &check_authz('do_review', 'review'));
if($in{'size'}){
$size = $in{'size'};
$session->{'review_page_size'} = $in{'size'} ;
if ($param->{'user'}{'prefs'}{'review_page_size'} ne $in{'size'}) {
# update user pref as soon as connected user change page size
$param->{'user'}{'prefs'}{'review_page_size'} = $in{'size'};
&List::update_user_db($param->{'user'}{'email'},{data=>&tools::hash_2_string($param->{'user'}{'prefs'})}) ;
}
}else{
$size = $param->{'user'}{'prefs'}{'review_page_size'} || $session->{'review_page_size'} || $wwsconf->{'review_page_size'};
}
$param->{'review_page_size'} = $size;
unless ($param->{'total'}) {
&wwslog('info','do_review: no subscriber');
# &List::db_log('wwsympa',$param->{'user'}{'email'},$param->{'auth_method'},$ip,'review',$param->{'list'},$robot,'','no subscriber');
return 1;
}
## Owner
$param->{'page'} = $in{'page'} || 1;
$param->{'total_page'} = int ($param->{'total'} / $size);
$param->{'total_page'} ++
if ($param->{'total'} % $size);
if ($param->{'total_page'} > 0 and ($param->{'page'} > $param->{'total_page'})) {
&report::reject_report_web('user','no_page',{'page' => $param->{'page'}},$param->{'action'},$list);
# &List::db_log('wwsympa',$param->{'user'}{'email'},$param->{'auth_method'},$ip,'review',$param->{'list'},$robot,'','out of pages');
&wwslog('info','do_review: no page %d', $param->{'page'});
return undef;
}
my $offset;
if ($param->{'page'} > 1) {
$offset = (($param->{'page'} - 1) * $size);
}else {
$offset = 0;
}
## We might not use LIMIT clause
my ($limit_not_used, $count);
unless (($Conf{'db_type'} =~ /^(Pg|mysql$)/)) {
$limit_not_used = 1;
}
## Additional DB fields
my @additional_fields = split ',', $Conf{'db_additional_subscriber_fields'};
## Members list synchronization if list has included data sources.
if ($list->has_include_data_sources()) {
if ($list->on_the_fly_sync_include('use_ttl'=>1)) {
&report::notice_report_web('subscribers_updated',{},$param->{'action'});
}else {
&report::reject_report_web('intern','sync_include_failed',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
}
}
## Members list
$count = -1;
for (my $i = $list->get_first_user({'sortby' => $sortby,
'offset' => $offset,
'rows' => $size});
$i; $i = $list->get_next_user()) {
## some review pages may be empty while viewed by subscribers
next if (($i->{'visibility'} eq 'conceal')
and (! $param->{'is_priv'}) );
if ($limit_not_used) {
$count++;
next unless (($count >= $offset) && ($count <= $offset+$size));
}
## Add user
&_prepare_subscriber($i, \@additional_fields, \%sources);
push @{$param->{'members'}}, $i;
}
if ($param->{'page'} > 1) {
$param->{'prev_page'} = $param->{'page'} - 1;
}
unless (($offset + $size) >= $param->{'total'}) {
$param->{'next_page'} = $param->{'page'} + 1;
}
$param->{'size'} = $size;
$param->{'sortby'} = $sortby;
######################
if($in{'exclude'} eq '1'){
$param->{'exclude_opt'} = 0;
}else{
$param->{'exclude_opt'} = 1;
}
#######################
## additional DB fields
$param->{'additional_fields'} = $Conf{'db_additional_subscriber_fields'};
# &List::db_log('wwsympa',$param->{'user'}{'email'},$param->{'auth_method'},$ip,'review',$param->{'list'},$robot,'','done');
## msg_topics
if ($list->is_there_msg_topic()) {
foreach my $top (@{$list->{'admin'}{'msg_topic'}}) {
if (defined $top->{'name'}) {
push (@{$param->{'available_topics'}},$top);
}
}
}
return 1;
}
## Show the table of exclude
sub do_show_exclude {
&wwslog('info', 'do_exclude()');
# Get the emails of the exclude about a list and the date of their insertion
my $data_exclu = &List::get_exclusion($list->{'name'},$list->{'domain'});
my $excluded;
my $key = 0;
while (($data_exclu->{emails}->[$key]) && ($data_exclu->{date}->[$key])){
my $email = $data_exclu->{'emails'}->[$key];
my $date = gettext_strftime "%d %b %Y", localtime($data_exclu->{'date'}->[$key]);
$excluded = {'email' => $email,
'since' => $date};
push @{$param->{'exclude_users'}}, $excluded;
$key = $key + 1;
}
return 1;
}
## Search in subscribers and in exclude
sub do_search {
&wwslog('info', 'do_search(%s)', $in{'filter'});
my %sources;
## Additional DB fields
my @additional_fields = split ',', $Conf{'db_additional_subscriber_fields'};
## Access control
return undef unless (defined &check_authz('do_search', 'review'));
## Regexp
$param->{'filter'} = $in{'filter'};
## Decode regexp to Unicode for case-insensitive match.
my $regexp = &tools::escape_regexp(Encode::decode_utf8($param->{'filter'} || ''));
my $record = 0;
## Maximum size of selection
my $max_select = 50;
## Members list
for (my $i = $list->get_first_user({'sortby' => 'email'}); $i; $i = $list->get_next_user()) {
## Decode gecos to Unicode for case-insensitive match.
my $gecos = Encode::decode_utf8($i->{'gecos'} || '');
## Search filter
next if ($i->{'email'} !~ /$regexp/i
&& $gecos !~ /$regexp/i);
next if (($i->{'visibility'} eq 'conceal')
and (! $param->{'is_owner'}) );
## Add user
&_prepare_subscriber($i, \@additional_fields, \%sources);
$record++;
push @{$param->{'members'}}, $i;
}
my $data_exclu = &List::get_exclusion($list->{'name'},$list->{'domain'});
my $key = 0;
## Exclude users are searched too
while (($data_exclu->{emails}->[$key]) && ($data_exclu->{date}->[$key])){
my $email = $data_exclu->{'emails'}->[$key];
my $date = gettext_strftime "%d %b %Y", localtime($data_exclu->{'date'}->[$key]);
$key = $key + 1;
## Search filter
next if ($email !~ /$regexp/i);
next if (!$param->{'is_owner'});
my $excluded = {'email' => $email,
'since' => $date};
push @{$param->{'exclude_users'}}, $excluded;
$record++;
}
if ($record > $max_select) {
undef $param->{'members'};
$param->{'too_many_select'} = 1;
}
$param->{'similar_subscribers'} = &List::get_ressembling_subscribers_no_object({'name'=>$list->{'name'},'domain'=>$robot,'email'=>$in{'filter'}});
foreach my $u (@{$param->{'similar_subscribers'} || []}) {
$u->{'date'} = gettext_strftime "%d %b %Y", localtime($u->{'date'});
$u->{'update_date'} = gettext_strftime "%d %b %Y", localtime($u->{'update_date'});
}
$param->{'similar_subscribers_occurence'} =$#{$param->{'similar_subscribers'}}+1;
$param->{'occurrence'} = $record;
return 1;
}
## Access to user preferences
sub do_pref {
&wwslog('info', 'do_pref');
## Find nearest expiration period
my $selected = 0;
foreach my $p (sort {$b <=> $a} keys %wwslib::cookie_period) {
my $entry = {'value' => $p};
## Set description from NLS
$entry->{'desc'} = gettext($wwslib::cookie_period{$p}{'gettext_id'});
## Choose nearest delay
if ((! $selected) && $param->{'user'}{'cookie_delay'} >= $p) {
$entry->{'selected'} = 'selected="selected"';
$selected = 1;
}
unshift @{$param->{'cookie_periods'}}, $entry;
}
$param->{'previous_list'} = $in{'previous_list'};
$param->{'previous_action'} = $in{'previous_action'};
return 1;
}
## Set the initial password
sub do_choosepasswd {
&wwslog('info', 'do_choosepasswd');
if($session->{'auth'} eq 'ldap'){
&report::reject_report_web('auth','',{'login'=> $param->{'need_login'}},$param->{'action'});
&wwslog('notice', "do_choosepasswd : user not authorized\n");
&web_db_log({'parameters' => $in{'email'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'authorization'});
}
unless ($param->{'user'}{'email'}) {
unless ($in{'email'} && $in{'passwd'}) {
&report::reject_report_web('user','no_user',{},$param->{'action'});
&wwslog('info','do_pref: no user');
&web_db_log({'parameters' => $in{'email'},
'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'no_user'});
$param->{'previous_action'} = 'choosepasswd';
return 'loginrequest';
}
$in{'previous_action'} = 'choosepasswd';
return 'login';
}
&web_db_log({'parameters' => "$in{'email'}",
'target_email' => $in{'email'} || $param->{'user'}{'email'},
'status' => 'success',
});
$param->{'init_passwd'} = 1 if ($param->{'user'}{'password'} =~ /^INIT/i);
return 1;
}
####################################################
# do_set
####################################################
# Changes subscription parameter (reception or visibility)
#
# IN : -
#
# OUT :'loginrequest'|'info' | undef
sub do_set {
&wwslog('info', 'do_set(%s, %s)', $in{'reception'}, $in{'visibility'});
my ($reception, $visibility) = ($in{'reception'}, $in{'visibility'});
my $email;
my $xml_custom_attribute;
if ($in{custom_attribute}){
return undef if ( &check_custom_attribute() != 1) ;
my $xml = &List::createXMLCustomAttribute($in{custom_attribute});
$xml_custom_attribute = $xml ;
}
if ($in{'email'}) {
unless ($param->{'is_owner'}) {
&report::reject_report_web('auth','action_owner',{},$param->{'action'},$list);
&wwslog('info','do_set: not owner');
&web_db_log({'parameters' => "$in{'reception'},$in{'visibility'}",
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
$email = &tools::unescape_chars($in{'email'});
}else {
unless ($param->{'user'}{'email'}) {
&report::reject_report_web('user','no_user',{},$param->{'action'});
&wwslog('info','do_set: no user');
&web_db_log({'parameters' => "$in{'reception'},$in{'visibility'}",
'status' => 'error',
'error_type' => 'no_user'});
return 'loginrequest';
}
$email = $param->{'user'}{'email'};
}
unless ($list->is_user($email)) {
&report::reject_report_web('user','not_subscriber',{'list'=> $param->{'list'}},$param->{'action'},$list);
&wwslog('info','do_set: %s not subscriber of list %s', $email, $param->{'list'});
&web_db_log({'parameters' => "$in{'reception'},$in{'visibility'}",
'status' => 'error',
'error_type' => 'not_subscriber'});
return undef;
}
# Verify that the mode is allowed
if (! $list->is_available_reception_mode($reception)) {
&report::reject_report_web('user','not_available_reception_mode',{'recpetion_mode'=> $reception},$param->{'action'},$list);
return undef;
}
$reception = '' if $reception eq 'mail';
$visibility = '' if $visibility eq 'noconceal';
my $update = {'reception' => $reception,
'visibility' => $visibility,
'update_date' => time};
## Lower-case new email address
$in{'new_email'} = lc( $in{'new_email'});
if ($in{'new_email'} && ($in{'email'} ne $in{'new_email'})) {
unless ($in{'new_email'} && &tools::valid_email($in{'new_email'})) {
&wwslog('notice', "do_set:incorrect email %s",$in{'new_email'});
&report::reject_report_web('user','incorrect_email',{'email' => $in{'new_email'}},$param->{'action'});
&web_db_log({'parameters' => "$in{'reception'},$in{'visibility'}",
'status' => 'error',
'error_type' => 'incorrect_email'});
return undef;
}
## Check if new email is already subscribed
if ($list->is_user($in{'new_email'})) {
&report::reject_report_web('user','already_subscriber', {'list' => $list->{'name'}},$param->{'action'},$list);
&wwslog('info','do_set: %s already subscriber', $in{'new_email'});
&web_db_log({'parameters' => $in{'new_email'},
'status' => 'error',
'error_type' => 'already subscriber'});
return undef;
}
## Duplicate entry in user_table
unless (&List::is_user_db($in{'new_email'})) {
my $user_pref = &List::get_user_db($in{'email'});
$user_pref->{'email'} = $in{'new_email'};
&List::add_user_db($user_pref);
}
$update->{'email'} = $in{'new_email'};
}
## message topic subscription
if ($list->is_there_msg_topic()) {
my @user_topics;
if ($in{'no_topic'}) {
$update->{'topics'} = undef;
} else {
foreach my $msg_topic (@{$list->{'admin'}{'msg_topic'}}) {
my $var_name = "topic_"."$msg_topic->{'name'}";
if ($in{"$var_name"}) {
push @user_topics, $msg_topic->{'name'};
}
}
if ($in{"topic_other"}) {
push @user_topics, 'other';
}
$update->{'topics'} = join(',',@user_topics);
}
}
if ($reception =~ /^(digest|digestplain|nomail|summary)$/i) {
$update->{'topics'} = '';
}
## Get additional DB fields
foreach my $v (keys %in) {
if ($v =~ /^additional_field_(\w+)$/) {
$update->{$1} = $in{$v};
}
}
if ($in{'gecos'}) {
$update->{'gecos'} = $in{'gecos'};
}else{
$update->{'gecos'} = undef;
}
$update->{'custom_attribute'} = $xml_custom_attribute if $xml_custom_attribute;
unless ( $list->update_user($email, $update) ) {
&report::reject_report_web('intern','update_subscriber_db_failed',{'sub'=>$email},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info', 'do_set: set failed');
&web_db_log({'parameters' => "$email,$update",
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
&report::notice_report_web('performed',{},$param->{'action'});
&web_db_log({'parameters' => "$in{'reception'},$in{'visibility'}",
'status' => 'success',
});
return $in{'previous_action'} || 'info';
}
## checks if each element of the custom attribute is conform to the list's
## definition
sub check_custom_attribute {
my @custom_attributes = @{$list->{'admin'}{'custom_attribute'}} ;
my $isOK = 1 ;
foreach my $ca (@custom_attributes){
my $value = $in{custom_attribute}{$ca->{id}}{value} ;
if ($ca->{optional} eq 'required' && $value eq '') {
&report::reject_report_web('user','missing_arg',{'argument' => "\"$ca->{name}\" is required"},$param->{'action'});
&wwslog('info','do_set: missing parameter');
&web_db_log({'parameters' => "$in{'reception'},$in{'visibility'}",
'status' => 'error',
'error_type' => 'missing_parameter'});
$isOK = undef;
next ;
}
## No further checking if attribute if empty
next if ($value =~ /^$/);
my @values = split(/,/ , $ca->{'enum_values'}) if (defined $ca->{'enum_values'});
## Check that the parameter has the correct format
unless (($ca->{'type'} eq 'enum' && grep(/^$value$/, @values)) ||
($ca->{'type'} eq 'integer' && $value =~ /^\d+$/) ||
($ca->{'type'} eq 'string' && $value =~ /^.+$/) ||
($ca->{'type'} eq 'text' && $value =~ /^.+$/m)
) {
&report::reject_report_web('user','syntax_errors',{'params' => $ca->{name}},$param->{'action'});
&wwslog('info','do_set: syntax error');
&web_db_log({'parameters' => $ca->{name}, 'status' => 'error', 'error_type' => 'missing_parameter'});
$isOK = undef;
next ;
}
}
return $isOK ;
}
## Update of user preferences
sub do_setpref {
&wwslog('info', 'do_setpref');
my $changes = {};
foreach my $p ('gecos','lang','cookie_delay') {
$changes->{$p} = $in{$p} if (defined($in{$p}));
}
## Set session language and user language to new value
$session->{'lang'} = $in{'lang'} ;
$param->{'lang'} = $in{'lang'};
$param->{'lang_tag'} = Language::LanguageTag($param->{'lang'});
if (&List::is_user_db($param->{'user'}{'email'})) {
unless (&List::update_user_db($param->{'user'}{'email'}, $changes)) {
&report::reject_report_web('intern','update_user_db_failed',{'user'=>$param->{'user'}{'email'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_pref: update failed');
&web_db_log({'parameters' => "$in{'gecos'},$in{'lang'},$in{'cookie_delay'}",
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}else {
$changes->{'email'} = $param->{'user'}{'email'};
unless (&List::add_user_db($changes)) {
&report::reject_report_web('intern','add_user_db_failed',{'user'=>$param->{'user'}{'email'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_pref: add failed');
&web_db_log({'parameters' => "$in{'gecos'},$in{'lang'},$in{'cookie_delay'}",
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
foreach my $p ('gecos','lang','cookie_delay') {
$param->{'user'}{$p} = $in{$p};
}
&report::notice_report_web('performed',{},$param->{'action'});
&web_db_log({'parameters' => "$in{'gecos'},$in{'lang'},$in{'cookie_delay'}",
'status' => 'success',
});
if ($in{'previous_action'}) {
$in{'list'} = $in{'previous_list'};
return $in{'previous_action'};
}else {
return 'pref';
}
}
## Prendre en compte les défauts
sub do_viewfile {
&wwslog('info', 'do_viewfile');
unless (defined $wwslib::filenames{$in{'file'}}) {
&report::reject_report_web('user','file_not_editable',{'file' => $in{'file'}},$param->{'action'});
&wwslog('info','do_viewfile: file %s not editable', $in{'file'});
return undef;
}
$param->{'file'} = $in{'file'};
$param->{'filepath'} = $list->{'dir'}.'/'.$in{'file'};
if ((-e $param->{'filepath'}) and (! -r $param->{'filepath'})) {
&report::reject_report_web('intern','cannot_read',{'filepath' => $param->{'filepath'}},$param->{'action'},'','',$robot);
&wwslog('info','do_viewfile: cannot read %s', $param->{'filepath'});
return undef;
}
return 1;
}
####################################################
# do_subscribe
####################################################
# Subscribes a user to the list
#
# IN : -
#
# OUT :'subrequest'|'login'|'info'|$in{'previous_action'}
# | undef
####################################################
## TOTO: accepter nouveaux users
sub do_subscribe {
&wwslog('info', 'do_subscribe(%s)', $in{'email'});
return undef if (purely_closed('subscribe'));
if (defined $param->{'user'} && $param->{'user'}{'email'}) {
my $xml_custom_attribute;
if ($list->{'admin'}{'custom_attribute'} ) {
## This variable is set in the subrequest form
## If not set, it means that the user has not been prompted to provide custom_attributes
unless ($in{'via_subrequest'}) {
&wwslog('notice', 'Returning subrequest form');
return "subrequest";
}
if (&check_custom_attribute() != 1) {
&wwslog('notice', "Missing required custom attributes") ;
return 'subrequest';
}
my $xml = &List::createXMLCustomAttribute($in{custom_attribute});
$xml_custom_attribute = $xml ;
}
if ($param->{'is_subscriber'}) {
&report::reject_report_web('user','already_subscriber', {'list' => $list->{'name'}},$param->{'action'},$list);
&wwslog('info','do_subscribe: %s already subscriber', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'email'},
'status' => 'error',
'error_type' => 'already_subscriber'});
return undef;
}
my $result = $list->check_list_authz('subscribe',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $sub_is;
my $reason;
if (ref($result) eq 'HASH') {
$sub_is = $result->{'action'};
$reason = $result->{'reason'};
}
if ($sub_is =~ /reject/) {
&report::reject_report_web('auth',$reason,{},$param->{'action'},$list);
&wwslog('info', 'do_subscribe: subscribe closed');
&web_db_log({'parameters' => $in{'email'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
$param->{'may_subscribe'} = 1;
if ($sub_is =~ /owner/) {
unless ($list->send_notify_to_owner('subrequest',{'who' => $param->{'user'}{'email'},
'keyauth' => $list->compute_auth($param->{'user'}{'email'}, 'add'),
'replyto' => &Conf::get_robot_conf($robot, 'sympa'),
'custom_attribute' => $in{custom_attribute},
'gecos' => $param->{'user'}{'gecos'},
'ip'=>$ip})) {
&wwslog('notice',"Unable to send notify 'subrequest' to $list->{'name'} listowner");
}
$list->store_subscription_request($param->{'user'}{'email'}, "", $xml_custom_attribute);
&report::notice_report_web('sent_to_owner',{},$param->{'action'});
&wwslog('info', 'do_subscribe: subscribe sent to owners');
return 'info';
}elsif ($sub_is =~ /do_it/) {
if ($param->{'is_subscriber'}) {
unless ($list->update_user($param->{'user'}{'email'},
{'subscribed' => 1,
'update_date' => time})) {
&report::reject_report_web('intern','update_subscriber_db_failed',{'sub'=>$param->{'user'}{'email'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info', 'do_subscribe: update failed');
&web_db_log({'parameters' => $in{'email'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}else {
my $defaults = $list->get_default_user_options();
my $u;
%{$u} = %{$defaults};
$u->{'email'} = $param->{'user'}{'email'};
$u->{'gecos'} = $param->{'user'}{'gecos'} || $in{'gecos'};
$u->{'date'} = $u->{'update_date'} = time;
$u->{'password'} = $param->{'user'}{'password'};
$u->{'custom_attribute'} = $xml_custom_attribute if (defined $xml_custom_attribute);
$u->{'lang'} = $param->{'user'}{'lang'} || $param->{'lang'};
unless ($list->add_user($u)) {
&report::reject_report_web('intern','add_subscriber_db_failed',{'sub'=>$param->{'user'}{'email'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info', 'do_subscribe: subscribe failed');
&web_db_log({'parameters' => $in{'email'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
unless ($sub_is =~ /quiet/i ) {
unless ($list->send_file('welcome', $param->{'user'}{'email'}, $robot,{})) {
&wwslog('notice',"Unable to send template 'welcome' to $param->{'user'}{'email'}");
}
}
if ($sub_is =~ /notify/) {
unless ($list->send_notify_to_owner('notice',{'who' => $param->{'user'}{'email'},
'gecos' => $param->{'user'}{'gecos'},
'command' => 'subscribe'})) {
&wwslog('notice','Unable to send notify "notice" to listmaster');
}
}
}
}else{ # user is not autenticated
if ($in{'email'} && $in{'passwd'}) {
$in{'previous_action'} = 'subscribe';
$in{'previous_list'} = $param->{'list'};
return 'login';
}else{
return 'subrequest';
}
}
## perform which to update your_subscriptions cookie ;
@{$param->{'get_which'}} = &List::get_which($param->{'user'}{'email'},$robot,'member') ;
&report::notice_report_web('performed',{},$param->{'action'});
&web_db_log({'parameters' => $in{'email'},'status' => 'success'});
if ($in{'previous_action'}) {
return $in{'previous_action'};
}
# return 'suboptions';
return 'info';
}
####################################################
# do_multiple_subscribe
####################################################
# Subscribes a user to each lists
#
# IN : lists a array of lists
#
# OUT :'subrequest'|'login'|'info'|$in{'previous_action'}
# | undef
####################################################
sub do_multiple_subscribe {
&wwslog('info', 'do_multiple_subscribe(%s)', $in{'email'});
## Not authenticated
unless (defined $param->{'user'} && $param->{'user'}{'email'}) {
## no email
unless ($in{'email'}) {
return 'lists';
}
}
my @lists = split /\0/, $in{'lists'};
my $total;
my %results ;
foreach my $requested_list (@lists) {
my $param->{'list'} = new List ($requested_list, $robot);
$results{'requested_list'} = &do_subscribe();
}
}
## Subscription request (user not authenticated)
sub do_suboptions {
&wwslog('info', 'do_suboptions()');
unless($param->{'is_subscriber'} ) {
&report::reject_report_web('user','not_subscriber',{'list'=> $list->{'name'}},$param->{'action'},$list);
&wwslog('info','do_suboptions: %s not subscribed to %s',$param->{'user'}{'email'}, $param->{'list'} );
return undef;
}
my ($s, $m);
unless($s = $list->get_subscriber($param->{'user'}{'email'})) {
&report::reject_report_web('intern','subscriber_not_found',{'email' => $param->{'user'}{'email'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info', 'do_sub_options: subscriber %s not found', $param->{'user'}{'email'});
return undef;
}
$s->{'reception'} ||= 'mail';
$s->{'visibility'} ||= 'noconceal';
$s->{'date'} = gettext_strftime "%d %b %Y", localtime($s->{'date'});
$s->{'update_date'} = gettext_strftime "%d %b %Y", localtime($s->{'update_date'});
foreach $m ($list->available_reception_mode) {
$param->{'reception'}{$m}{'description'} =
$list->get_option_title($m, 'reception');
if ($s->{'reception'} eq $m) {
$param->{'reception'}{$m}{'selected'} = ' selected';
if ($m =~ /^(mail|notice|not_me|txt|html|urlize)$/i) {
$param->{'possible_topic'} = 1;
}
}else {
$param->{'reception'}{$m}{'selected'} = '';
}
}
foreach $m (qw(conceal noconceal)) {
$param->{'visibility'}{$m}{'description'} =
$list->get_option_title($m, 'visibility');
if ($s->{'visibility'} eq $m) {
$param->{'visibility'}{$m}{'selected'} = ' selected';
}else {
$param->{'visibility'}{$m}{'selected'} = '';
}
}
$param->{'subscriber'} = $s;
#msg_topic
$param->{'sub_user_topic'} = 0;
foreach my $user_topic (split (/,/,$s->{'topics'})) {
$param->{'topic_checked'}{$user_topic} = 1;
$param->{'sub_user_topic'}++;
}
if ($list->is_there_msg_topic()) {
foreach my $top (@{$list->{'admin'}{'msg_topic'}}) {
if (defined $top->{'name'}) {
push (@{$param->{'available_topics'}},$top);
}
}
}
return 1;
}
## Subscription request (user not authenticated)
sub do_subrequest {
&wwslog('info', 'do_subrequest(%s,%s)', $in{'email'},$in{'custom_attribute'});
return undef if (purely_closed('subscribe'));
if (defined $in{'custom_attribute'}) {
$param->{'custom_attribute'} = $in{'custom_attribute'};
}
## Auth ?
if ($param->{'user'}{'email'}) {
## Subscriber ?
if ($param->{'is_subscriber'}) {
&report::reject_report_web('user','already_subscriber', {'list' => $list->{'name'}},$param->{'action'},$list);
&wwslog('info','%s already subscriber', $param->{'user'}{'email'});
&web_db_log({'status' => 'error',
'error_type' => 'already_subscriber'});
return undef;
}
$param->{'status'} = 'auth';
}else {
## Provided email parameter ?
unless ($in{'email'}) {
$param->{'status'} = 'notauth_noemail';
return 1;
}
## valid email address?
unless (&tools::valid_email($in{'email'})) {
&report::reject_report_web('user','incorrect_email',{'email' => $in{'email'}},$param->{'action'},$list);
&wwslog('info','do_subrequest: incorrect email %s', $in{'email'});
&web_db_log({'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'incorrect_email'});
$param->{'status'} = 'notauth_noemail';
return 1;
}
## Subscriber ?
if ($list->is_user($in{'email'})) {
$param->{'status'} = 'notauth_subscriber';
return 1;
}
my $user;
$user = &List::get_user_db($in{'email'})
if &List::is_user_db($in{'email'});
## Need to send a password by email
$param->{'one_time_ticket'} = &Auth::create_one_time_ticket($in{'email'},$robot,'subscribe/'.$list->{'name'},$ip);
$param->{'login_error'}='ticket_sent';
$param->{'request_from_host'} = $ip;
unless ($param->{'newuser'} = &List::get_user_db($in{'email'})) {
$param->{'newuser'} =
{ 'email' => &tools::clean_email($in{'email'}) };
$param->{'newuser'}{'escaped_email'} =
&tools::escape_chars($param->{'newuser'}{'email'});
}
unless (&List::send_global_file('sendpasswd', $in{'email'}, $robot, $param)) {
&wwslog('notice',"Unable to send template 'sendpasswd' to $in{'email'}");
$param->{'login_error'}='unable_to_send_ticket';
}
# &do_requestpasswd();
$param->{'status'} = 'notauth_passwordsent';
return 1;
}
return 1;
}
sub do_auto_signoff {
&wwslog('info', 'do_auto_signoff');
## If the URL isn't valid, then go to home page. No need to guide the user: this function is supposed to be used by clicking on autocreated URL only.
return &Conf::get_robot_conf($robot, 'default_home') unless $in{'email'};
## If unsubscribe is forbidden, reject the request. Other
my $result = $list->check_list_authz('unsubscribe',$param->{'auth_method'},
{'sender' => $in{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $sig_is;
my $reason;
if (ref($result) eq 'HASH') {
$sig_is = $result->{'action'};
$reason = $result->{'reason'};
}
if ($sig_is =~ /reject/) {
&report::reject_report_web('auth',$reason,{},$param->{'action'},$list);
&wwslog('info', 'do_signoff: %s may not signoff from %s'
, $in{'email'}, $param->{'list'});
&web_db_log({'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
## Send the confirmation email to the user.
if ($list->is_user($in{'email'})) {
my $ticket = &Auth::create_one_time_ticket($in{'email'},$robot,'signoff/'.$list->{'name'},$ip);
my $tt2_param = {
'list' => $list,
'type' => 'ticket_to_signoff',
'one_time_ticket' => $ticket,
'email' => $in{'email'},
'context' => 'auto_signoff',
'ip' => $ip,
};
unless (&List::send_global_file('user_notification', $in{'email'}, $robot, $tt2_param)) {
&do_log('notice',"Unable to send template 'user_notification' to $in{'email'}");
return undef;
}
}else{
return &Conf::get_robot_conf($robot, 'default_home');
}
$param->{'signing_off_email'} = $in {'email'};
## If OK, return the page displaying the informations to the user.
return 1;
}
sub do_family_signoff_request {
&wwslog('info', 'do_family_signoff_request');
## If the URL isn't valid, then go to home page. No need to guide the user: this function is supposed to be used by clicking on autocreated URL only.
return &Conf::get_robot_conf($robot, 'default_home') unless $in{'email'};
my $ticket = &Auth::create_one_time_ticket($in{'email'},$robot,'family_signoff/'.$in{'family'}.'/'.$in{'email'},$ip);
my $tt2_param = {
'family' => $in{'family'},
'type' => 'ticket_to_family_signoff',
'one_time_ticket' => $ticket,
'email' => $in{'email'},
'context' => 'family_signoff',
'ip' => $ip,
};
unless (&List::send_global_file('user_notification', $in{'email'}, $robot, $tt2_param)) {
&do_log('notice',"Unable to send template 'user_notification' to $in{'email'}");
return undef;
}
$param->{'signing_off_email'} = $in {'email'};
$param->{'family'} = $in {'family'};
## If OK, return the page displaying the informations to the user.
return 1;
}
sub do_family_signoff {
&wwslog('info', 'do_family_signoff');
$param->{'signing_off_email'} = $in {'email'};
$param->{'family'} = $in {'family'};
unless ($in{'email'} eq $session->{'email'}) {
&report::reject_report_web('user','cannot_do_signoff');
&wwslog('err','do_signoff: user %s tried to unsubscribe address %s from family %s',$session->{'email'}, $in{'email'},$in {'family'});
return undef;
}
unless (List::insert_delete_exclusion($in{'email'},&SympaSession::get_random(),$robot,'insert',$in{'family'})) {
&report::reject_report_web('user','cannot_do_signoff');
&wwslog('err','do_signoff: Unsubscription of address %s from family %s failed.',$in{'email'},$in {'family'});
return undef;
}
return 1;
}
####################################################
# do_signoff
####################################################
# Unsubcribes a user from a list
#
# IN : -
#
# OUT : 'sigrequest' | 'login' | 'info'
#
####################################################
## Unsubscribe from list
sub do_signoff {
&wwslog('info', 'do_signoff');
return undef if (purely_closed('unsubscribe'));
my $authenticated_email_address = $param->{'user'}{'email'};
unless ($authenticated_email_address) {
unless ($in{'email'}) {
return 'sigrequest';
}
if ($in{'fingerprint'}) {
unless(&tools::get_fingerprint($in{'email'}, $in{'fingerprint'})){
&report::reject_report_web('user','cannot_do_signoff');
&wwslog('err','do_signoff: failed to unsubscribe user %s', $in{'email'});
return undef;
}
## We don't set $param->{'user'}{'email'} because we don't want the user to be authenticated
## to prevent the cookie from being set
$authenticated_email_address = $in{'email'};
}else {
## Perform login first
if ($in{'passwd'}) {
$in{'previous_action'} = 'signoff';
$in{'previous_list'} = $param->{'list'};
return 'login';
}
if ( &List::is_user_db($in{'email'}) ) {
&report::reject_report_web('user','no_user',{},$param->{'action'});
&wwslog('info','do_signoff: need auth for user %s', $in{'email'});
&web_db_log({'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'authentication'});
return undef;
}
## No passwd
&init_passwd($in{'email'}, {'lang' => $param->{'lang'} });
$param->{'user'}{'email'} = $in{'email'};
$authenticated_email_address = $in{'email'};
}
}
my %result = &unsubscribe($authenticated_email_address, $list);
if ($result{'success'} == 1) {
&report::notice_report_web($result{'details'},{},$param->{'action'});
$param->{'is_subscriber'} = 0;
$param->{'may_signoff'} = 0;
}else{
&report::reject_report_web($result{'category_error'},$result{'reason_error'},{%{$result{'reason_error'}},'list'=>$list->{'name'}},$param->{'action'},$list);
}
return &Conf::get_robot_conf($robot, 'default_home');
}
## Unsubscribe current user from a list.
sub unsubscribe {
my $authenticated_email_address = shift;
my $list = shift;
my %report = ('success',1,'details','');
unless ($list->is_user($authenticated_email_address)) {
&wwslog('info','do_signoff: %s not subscribed to %s',$authenticated_email_address, $param->{'list'} );
&web_db_log({'status' => 'error',
'error_type' => 'not_subscriber'});
$report{'success'} = 0;
$report{'category_error'} = 'user';
$report{'reason_error'} = 'not_subscribed';
$report{'details_error'} = {};
return %report;
}
my $result = $list->check_list_authz('unsubscribe',$param->{'auth_method'},
{'sender' => $authenticated_email_address,
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $sig_is;
my $reason;
if (ref($result) eq 'HASH') {
$sig_is = $result->{'action'};
$reason = $result->{'reason'};
}
$param->{'may_signoff'} = 1 if ($sig_is =~ /do_it|owner/);
if ($sig_is =~ /reject/) {
&wwslog('info', 'do_signoff: %s may not signoff from %s'
, $authenticated_email_address, $param->{'list'});
&web_db_log({'status' => 'error',
'error_type' => 'authorization'});
$report{'success'} = 0;
$report{'category_error'} = 'auth';
$report{'reason_error'} = $reason;
$report{'details_error'} = {};
return %report;
}elsif ($sig_is =~ /owner/) {
unless ($list->send_notify_to_owner('sigrequest',{'who' => $authenticated_email_address,
'keyauth' => $list->compute_auth($authenticated_email_address, 'del')})) {
&wwslog('notice',"Unable to send notify 'sigrequest' to $list->{'name'} list owner");
}
&wwslog('info', 'do_signoff: signoff sent to owner');
$report{'success'} = 1;
$report{'details'} = 'sent_to_owner';
return %report;
}else {
unless ($list->delete_user('users' => [$authenticated_email_address], 'exclude' =>' 1')) {
&wwslog('info', 'do_signoff: signoff failed');
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
$report{'success'} = 0;
$report{'category_error'} = 'intern';
$report{'reason_error'} = 'delete_subscriber_db_failed';
$report{'details_error'} = {'sub'=>$authenticated_email_address};
return %report;
}
if ($sig_is =~ /notify/) {
unless ($list->send_notify_to_owner('notice',{'who' => $authenticated_email_address,
'gecos' => '',
'command' => 'signoff'})) {
&wwslog('notice',"Unable to send notify 'notice' to $list->{'name'} list owner");
}
}
## perform which to update your_subscribtions cookie ;
@{$param->{'get_which'}} = &List::get_which($authenticated_email_address,$robot,'member') ;
unless ($list->send_file('bye', $authenticated_email_address, $robot, {})) {
&wwslog('notice',"Unable to send template 'bye' to $authenticated_email_address");
}
}
&web_db_log({'status' => 'success'});
$report{'success'} = 1;
$report{'details'} = 'performed';
return %report;
}
## Unsubscription request (user not authenticated)
sub do_sigrequest {
&wwslog('info', 'do_sigrequest(%s)', $in{'email'});
return undef if (purely_closed('unsubscribe'));
## If user is authenticated then redirect him to the signoff action but
## get a confirmation (via the sigrequest web page) first
if ($param->{'user'}{'email'}) {
return 1;
}
## Not auth & no email => return the sigrequest web form to get the user email
unless ($in{'email'}) {
return 1;
}
## valid email address?
unless (&tools::valid_email($in{'email'})) {
&report::reject_report_web('user','incorrect_email',{'email' => $in{'email'}},$param->{'action'},$list);
&wwslog('info','do_sigrequest: incorrect email %s', $in{'email'});
&web_db_log({'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'incorrect_email'});
return 1;
}
if ($list->is_user($in{'email'})) {
my $ticket = &Auth::create_one_time_ticket($in{'email'},$robot,'signoff/'.$list->{'name'},$ip);
my $tt2_param = {'type' => 'ticket_to_signoff',
'list' => $list,
'one_time_ticket' => $ticket,
'email' => $in{'email'}};
unless (&List::send_global_file('user_notification', $in{'email'}, $robot, $tt2_param)) {
&do_log('notice',"Unable to send template 'user_notification' to $in{'email'}");
return undef;
}
}else {
$param->{'not_subscriber'} = 1;
}
$param->{'email'} = $in{'email'};
return 1;
}
## Update of password
sub do_setpasswd {
&wwslog('info', 'do_setpasswd');
my $user;
if ($in{'newpasswd1'} =~ /^\s+$/ ) {
&report::reject_report_web('user','no_passwd',{},$param->{'action'});
&wwslog('info','do_setpasswd: no newpasswd1');
&web_db_log({'status' => 'error',
'error_type' => 'missing_parameter'});
return undef;
}
unless ($in{'newpasswd1'} eq $in{'newpasswd2'}) {
&report::reject_report_web('user','diff_passwd',{},$param->{'action'});
&wwslog('info','do_setpasswd: different newpasswds');
&web_db_log({'status' => 'error',
'error_type' => 'bad_parameter'});
return undef;
}
if (&List::is_user_db($param->{'user'}{'email'})) {
unless ( &List::update_user_db($param->{'user'}{'email'}, {'password' => $in{'newpasswd1'},'wrong_login_count' => 0} )) {
&report::reject_report_web('intern','update_user_db_failed',{'user'=>$param->{'user'}{'email'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_setpasswd: update failed');
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}else {
unless ( &List::add_user_db({'email' => $param->{'user'}{'email'},
'password' => $in{'newpasswd1'},
'wrong_login_count' => 0} )) {
&report::reject_report_web('intern','add_user_db_failed',{'user'=>$param->{'user'}{'email'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_setpasswd: update failed');
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
$param->{'user'}{'password'} = $in{'newpasswd1'};
&report::notice_report_web('performed',{},$param->{'action'});
&web_db_log({'status' => 'success'});
if ($in{'previous_action'}) {
$in{'list'} = $in{'previous_list'};
return $in{'previous_action'};
}else {
return 'pref';
}
}
## List admin page
sub do_admin {
&wwslog('info', 'do_admin');
## Messages edition
foreach my $f ('info','homepage','welcome.tt2','bye.tt2','removed.tt2','message.footer','message.header','remind.tt2','invite.tt2','reject.tt2') {
next unless ($list->may_edit($f, $param->{'user'}{'email'}) eq 'write');
if ($wwslib::filenames{$f}{'gettext_id'}) {
$param->{'files'}{$f}{'complete'} = gettext($wwslib::filenames{$f}{'gettext_id'});
}else {
$param->{'files'}{$f}{'complete'} = $f;
}
$param->{'files'}{$f}{'selected'} = '';
}
$param->{'files'}{'info'}{'selected'} = 'selected="selected"';
# my %mode;
# $mode{'edit'} = 1;
# my %access = &d_access_control(\%mode,$path);
return 1;
}
## Server admin page
sub do_serveradmin {
&wwslog('info', 'do_serveradmin');
my $f;
## Lists Default files
foreach my $f ('welcome.tt2','bye.tt2','removed.tt2','message.footer','message.header','remind.tt2','invite.tt2','reject.tt2','your_infected_msg.tt2') {
if ($wwslib::filenames{$f}{'gettext_id'}){
$param->{'lists_default_files'}{$f}{'complete'} = gettext($wwslib::filenames{$f}{'gettext_id'});
}else {
$param->{'lists_default_files'}{$f}{'complete'} = $f;
}
$param->{'lists_default_files'}{$f}{'selected'} = '';
}
## Checking families and other virtual hosts.
&get_server_details();
## Server files
foreach my $f ('helpfile.tt2','lists.tt2','global_remind.tt2','summary.tt2','create_list_request.tt2','list_created.tt2','list_aliases.tt2') {
$param->{'server_files'}{$f}{'complete'} = gettext($wwslib::filenames{$f}{'gettext_id'});
$param->{'server_files'}{$f}{'selected'} = '';
}
$param->{'server_files'}{'helpfile.tt2'}{'selected'} = 'selected="selected"';
$param->{'log_level'} = $session->{'log_level'} ;
$param->{'subaction'} = $in{'subaction'} ;
return 1;
}
sub do_edit_config {
my @editable_params = @confdef::params ;
&get_server_details;
unless ($param->{'main_robot'}) {
&report::reject_report_web('auth','super lismaster feature only','{}',$param->{'action'});
&wwslog('info','check_authz: access denied in edit_config for %s because not super listmaster', $param->{'user'}{'email'});
}
for my $i ( 0 .. $#editable_params ) {
if ($editable_params[$i]->{'name'}) {
$editable_params[$i]->{'current_value'} = &Conf::get_robot_conf($robot, $editable_params[$i]->{'name'});
$editable_params[$i]->{'current_value'} = join( ",", @{$editable_params[$i]->{'current_value'}}) if (ref($editable_params[$i]->{'current_value'}) eq 'ARRAY');
}
}
if ($in{'conf_new_value'}) {
my $editable; my $i;
for $i ( 0 .. $#editable_params ) {
# if the parameter is editable and if the is a change
next unless ($editable_params[$i]->{'name'} eq $in{'conf_parameter_name'});
if ($editable_params[$i]->{'edit'} ne '1'){
do_log ('err','Ignoring change of parameter %s (value %s) because not editable', $in{'conf_parameter_name'}, $in{'conf_new_value'});
last;
}
if ($in{'conf_new_value'} eq $editable_params[$i]->{'current_value'} ){
do_log ('notice','Ignoring change of parameter %s (value %s) because inchanged', $in{'conf_parameter_name'}, $in{'conf_new_value'});
last;
}else{
$editable_params[$i]->{'current_value'} = $in{'conf_new_value'};
&Conf::set_robot_conf($robot, $in{'conf_parameter_name'}, $in{'conf_new_value'});
do_log ('notice','setting parameter %s to value %s', $in{'conf_parameter_name'}, $in{'conf_new_value'});
last;
}
}
}
$param->{'editable_params'} = \@editable_params ;
return 1;
}
## Change log_level for the current session
sub do_set_loglevel {
&wwslog('info', 'do_set_loglevel');
$session->{'log_level'} = $in{'log_level'};
return 'serveradmin';
}
## activate dump var feature
sub do_set_dumpvars {
&wwslog('info', 'do_set_dumpvars');
$session->{'dumpvars'} = 'true' ;
$param->{'dumpavars'} = $session->{'dumpvars'} ;
$param->{'redirect_to'} = $param->{'base_url'}.$param->{'path_cgi'}.'/serveradmin';
return '1';
}
## un-activate dump var feature
sub do_unset_dumpvars {
&wwslog('info', 'do_unset_dumpvars');
$session->{'dumpvars'} = '' ;
$param->{'dumpavars'} = '';
$param->{'redirect_to'} = $param->{'base_url'}.$param->{'path_cgi'}.'/serveradmin';
return '1';
}
## un-activate dump var feature
sub do_show_sessions {
&wwslog('info', 'do_show_sessions');
$in{'session_delay'} = 10 unless ($in{'session_delay'});
my $delay = 60 * $in{'session_delay'};
$param->{'sessions'} = &SympaSession::list_sessions($delay,$robot,$in{'connected_only'});
return '1';
}
## Change user email
sub do_set_session_email {
&wwslog('info', 'do_set_session_email');
my $email_regexp = &tools::get_regexp('email');
unless ($in{'email'} =~ /^\s*$email_regexp\s*$/){
&report::reject_report_web('user','Invalid email provided.',{},$param->{'action'},$list);
return 'serveradmin';
};
if ($session){
$session->{'restore_email'} = $param->{'user'}{'email'};
$session->{'email'} = $in{'email'};
$param->{'redirect_to'} = $param->{'base_url'}.$param->{'path_cgi'};
return '1';
}else{
&report::reject_report_web('user','No active session',{},$param->{'action'},$list);
return 'serveradmin';
}
}
## Change user email
sub do_restore_email {
&wwslog('info', 'do_restore_email');
&wwslog('debug2', 'do_restore_email from %s to %s',$session->{'email'},$session->{'restore_email'} );
if ($param->{'restore_email'}){
$session->{'email'} = $session->{'restore_email'} ;
$param->{'restore_email'}= $session->{'restore_email'} = '' ;
$param->{'redirect_to'} = $param->{'base_url'}.$param->{'path_cgi'};
}else{
&wwslog('info','do_restore_email from %s no restore_email attached to current session', $param->{'user'}{'email'});
&report::reject_report_web('user','wrong_param',{},$param->{'action'},$list);
}
return 'home';
}
## list available templates
sub do_ls_templates {
&wwslog('info', 'do_ls_templates');
$in{'webormail'} ||= 'web';
if (defined $list) {
$param->{'templates'} = &tools::get_templates_list($in{'webormail'},$robot,$list);
}else{
$param->{'templates'} = &tools::get_templates_list($in{'webormail'},$robot, undef);
}
## List of lang per type
foreach my $level ('site','robot','list') {
$param->{'lang_per_level'}{$level}{'default'} = 1;
}
foreach my $file (keys %{$param->{'templates'}}) {
foreach my $level (keys %{$param->{'templates'}{$file}}) {
foreach my $lang (keys %{$param->{'templates'}{$file}{$level}}) {
$param->{'lang_per_level'}{$level}{$lang} =
{
'title' => (Language::GetLangName($lang) || $lang),
'lang_tag' => Language::LanguageTag($lang),
};
}
}
}
## Colspan per level
foreach my $level (keys %{$param->{'lang_per_level'}}) {
foreach my $lang (keys %{$param->{'lang_per_level'}{$level}}) {
$param->{'colspan_per_level'}{$level}++;
foreach my $file (keys %{$param->{'templates'}}) {
$param->{'templates'}{$file}{$level}{$lang} ||= '';
}
}
}
$param->{'webormail'} = $in{'webormail'};
return 1;
}
# show a template, used by copy_template and edit_emplate
sub do_remove_template {
&wwslog('info', 'do_remove_template');
my $template_path;
if ($in{'scope'} eq 'list' and ref $list ne 'List') {
report::reject_report_web('user', 'missing_arg',
{'argument' => 'list'}, $param->{'action'});
wwslog('err', 'do_remove_template: missing parameter list');
web_db_log({'parameters' => $in{'webormail'}, 'status' => 'error',
'error_type' => 'missing_parameter'});
return 1;
}
$template_path = tools::get_template_path($in{'webormail'}, $robot,
$in{'scope'}, $in{'template_name'}, $in{'tpl_lang'}, $list);
my $template_old_path = &tools::shift_file($template_path,10);
unless ($template_old_path) {
&report::reject_report_web('intern','remove_failed',{'path'=>$template_path},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info',"remove_template: could not remove $template_path");
&web_db_log({'parameters' => $in{'webormail'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
&report::notice_report_web('file_renamed',{'orig_file'=>$template_path,'new_file'=>$template_old_path}, $param->{'action'});
&web_db_log({'parameters' => $in{'webormail'},
'status' => 'status'});
$param->{'webormail'} = $in{'webormail'};
$param->{'scope'} = $in{'scope'};
$param->{'template_name'} = $in{'template_name'};
$param->{'tpl_lang'} = $in{'tpl_lang'};
return 'ls_templates';
}
# show a template, used by copy_template and edit_emplate
sub do_view_template {
&wwslog('info', "do_view_template(type=$in{'webormail'},template-name=$in{'template_name'},listname=$in{'list'},path=$in{'template_path'},scope=$in{'scope'},lang=$in{'tpl_lang'})");
my $template_path;
if ($in{'scope'} eq 'list' and ref $list ne 'List') {
report::reject_report_web('user', 'missing_arg',
{'argument' => 'list'}, $param->{'action'});
wwslog('err', 'do_view_template: missing parameter webormail');
web_db_log({'parameters' => $in{'webormail'}, 'status' => 'error',
'error_type' => 'missing_parameter'});
return 1;
}
$template_path = tools::get_template_path($in{'webormail'}, $robot,
$in{'scope'}, $in{'template_name'}, $in{'tpl_lang'}, $list);
unless (open (TPL,$template_path)) {
&report::reject_report_web('intern','cannot_open_file',{'path' => $in{'template_path'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err',"view_template: can't open file %s",$template_path);
return undef;
}
$param->{'rows'} = 5; # minimum size of 5 rows;
$param->{'template_content'} = ''; # init content
while() {$param->{'template_content'}.= $_; $param->{'rows'}++;}
$param->{'template_content'} = &tools::escape_html($param->{'template_content'});
close TPL;
$param->{'webormail'} = $in{'webormail'};
$param->{'template_name'} = $in{'template_name'};
$param->{'template_path'} = $template_path;
$param->{'scope'} = $in{'scope'};
my $tpl_lang = $in{'tpl_lang'} || 'default';
$param->{'tpl_lang'} = $tpl_lang;
unless ($tpl_lang eq 'default') {
$param->{'tpl_lang_title'} =
Language::GetLangName($tpl_lang) || $tpl_lang;
$param->{'tpl_lang_tag'} = Language::LanguageTag($tpl_lang);
}
return 1;
}
## template copy
sub do_copy_template {
&wwslog('info', 'do_copy_template');
## Load original template
&do_view_template();
## Return form
unless ($in{'scope_out'}) {
return 1;
}
# one of theses parameters is commint from the form submission
if ($in{'scope_out'} eq 'list') {
if ($in{'list_out'}) {
my $list_out;
unless ($list_out = new List $in{'list_out'}, $robot) {
&report::reject_report_web('user','unknown_list',{'list' => $in{'list_out'}},$param->{'action'},'');
&wwslog('info','do_copy_template: unknown list %s', $in{'list_out'});
&web_db_log({'parameters' => $in{'list_out'},
'status' => 'error',
'error_type' => 'unknown_list'});
return undef;
}
$param->{'template_path_out'} =
tools::get_template_path($in{'webormail'}, $robot,
'list', $in{'template_name_out'}, $in{'tpl_lang_out'},
$list_out);
}else{
&report::reject_report_web('user','missing_arg',{'argument' => 'list'},$param->{'action'});
&wwslog('err','do_copy_template: missing parameter webormail');
&web_db_log({'parameters' => $in{'webormail'},
'status' => 'error',
'error_type' => 'missing_parameter'});
return 1;
}
}else{
$param->{'template_path_out'} =
tools::get_template_path($in{'webormail'}, $robot,
$in{'scope_out'}, $in{'template_name_out'}, $in{'tpl_lang_out'});
}
unless (&tools::mk_parent_dir($param->{'template_path_out'})) {
&report::reject_report_web('intern','cannot_open_file',{'path' => $param->{'template_path_out'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err',"can't create parent directory for %s : %s", $param->{'template_path_out'}, $!);
&web_db_log({'parameters' => $param->{'template_name_out'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
unless (open (TPLOUT,'>'.$param->{'template_path_out'})) {
&report::reject_report_web('intern','cannot_open_file',{'path' => $param->{'template_path_out'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err',"can't open file %s : %s", $param->{'template_path_out'}, $!);
&web_db_log({'parameters' => $param->{'template_name_out'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
print TPLOUT &tools::unescape_html($param->{'template_content'});
close TPLOUT;
if ($in{'list_out'}) {$param->{'list'} = $in{'list'} = $in{'list_out'} ;}
$param->{'webormail'} = $in{'webormail'};
my $tpl_lang = $in{'tpl_lang_out'} || 'default';
$param->{'tpl_lang'} = $in{'tpl_lang'} = $tpl_lang;
unless ($tpl_lang eq 'default') {
$param->{'tpl_lang_title'} =
Language::GetLangName($tpl_lang) || $tpl_lang;
$param->{'tpl_lang_tag'} = Language::LanguageTag($tpl_lang);
}
$param->{'scope'} = $in{'scope'} = $in{'scope_out'} ;
$param->{'template_path'} = $in{'template_path'} = $param->{'template_path_out'};
$param->{'template_name'} = $in{'template_name'} = $in{'template_name_out'};
&web_db_log({'parameters' => $param->{'template_name_out'},
'status' => 'success'});
return ('edit_template');
}
## manage the rejection templates
sub do_manage_template {
&wwslog('info', '(%s,%s)', $in{'subaction'}, $in{'message_template'});
my $file;
$in{'message_template'} =~ s/^reject_//;
if ($in{'message_template'}) {
my $escaped_template_path = $in{'message_template'};
$escaped_template_path =~ s/\s/_/g ;
$param->{'template_path'} = tools::get_template_path('mail', $robot,
'list', 'reject_'.$escaped_template_path.'.tt2', '', $list);
}
my $default_file =
tools::get_filename('etc', {}, 'mail_tt2/reject.tt2', $robot, $list);
if ($in{'subaction'} eq 'save') {
## create the parent directory if it doesn't already exist
unless (&tools::mk_parent_dir($param->{'template_path'})) {
&report::reject_report_web('intern','cannot_open_file',{'path' => $param->{'template_path'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err',"can't create parent directory for %s : %s", $param->{'template_path'}, $!);
&web_db_log({'parameters' => $param->{'template_name'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
## open the template
unless (open (TPLOUT ,'>' ,$param->{'template_path'})) {
&report::reject_report_web('intern','cannot_open_file',{'path' => $param->{'template_path'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err',"can't open file %s : %s", $param->{'template_path'}, $!);
&web_db_log({'parameters' => $in{'template_name'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
## save template contents
print TPLOUT $in{'template_content'};
close TPLOUT;
&report::notice_report_web('performed',{},$in{'subaction'});
}elsif($in{'subaction'} eq 'create_new') {
$in{'template_new'} = $in{'new_template_name'} ;
unless ($in{'new_template_name'}) {
&report::reject_report_web('user','missing template name',{'path' => ''},$param->{'action'},'',$param->{'user'}{'email'},$robot);
return undef;
}
my $escaped_template_path = $in{'new_template_name'};$escaped_template_path =~ s/\s/_/g ;
my $new_template_path = tools::get_template_path('mail', $robot,
'list', 'reject_'.$escaped_template_path.'.tt2', '', $list);
if (-f $new_template_path) {
&report::reject_report_web('intern','template already exist',{'path' => $new_template_path},$param->{'action'},'',$param->{'user'}{'email'},$robot);
return undef;
}
## create the parent directory if it doesn't already exist
unless (&tools::mk_parent_dir($new_template_path)) {
&report::reject_report_web('intern','cannot_open_file',{'path' => $param->{'template_path'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err',"can't create parent directory for %s : %s", $param->{'template_path'}, $!);
&web_db_log({'parameters' => $param->{'template_name'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
my $default_file =
tools::get_filename('etc', {}, 'mail_tt2/reject.tt2', $robot, $list);
unless(open (DEFAULT, $default_file)){
&report::reject_report_web('intern','cannot_open_file',{'path' => $default_file},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err',"can't open file %s : %s", $default_file, $!);
return undef;
}
unless(open (TPL, '> '.$new_template_path)){
&report::reject_report_web('intern','cannot_open_file',{'path' => $new_template_path},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err',"can't open file %s : %s", $new_template_path, $!);
return undef;
}
while (){
print TPL $_;
}
close DEFAULT;
close TPL;
$in{'subaction'}='modify';
$in{'message_template'} = $in{'new_template_name'};
return 'manage_template';
}elsif ($in{'subaction'} eq 'modify') {
unless(open (FILE, $param->{'template_path'})){
&report::reject_report_web('intern','cannot_open_file',{'path' => $param->{'template_path'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err',"can't open file MODIFY %s : %s", $param->{'template_path'}, $!);
&web_db_log({'parameters' => $param->{'template_path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
while (){
$param->{'content'} .= $_;
}
$param->{'content'} = &tools::escape_html($param->{'content'});
close FILE;
$param->{'message_template'} = $in{'message_template'};
}elsif($in{'subaction'} eq 'setdefault') {
# replace existing reject.tt2 file by a symlink to reject_default.tt2 for compatibility with version older than 6.0
my $base = $list->{'dir'}.'/mail_tt2/';
$in{'new_default'} =~ s/\s/_/g ;
my $absolute_file = $base.'reject_'.$in{'new_default'}.'.tt2';
&do_log('info','Change default by linking %s 2 %s',$base.'reject.tt2',$absolute_file);
if (-l $base.'reject.tt2') {
unless (unlink ($base.'reject.tt2')){
&wwslog('err','Could not unlink %s',$base.'reject.tt2');
}
}
unless (symlink ($absolute_file,$base.'reject.tt2')){
&wwslog('err','Could not symlink %s,%s',$absolute_file,$base.'reject.tt2');
}
}elsif ($in{'subaction'} eq 'delete') {
unless(unlink $param->{'template_path'}) {
&report::reject_report_web('intern','cannot_delete',{'file_del' => $param->{'template_path'}},'','','',$robot);
&wwslog('err',"can't open file %s : %s", $param->{'template_path'}, $!);
&web_db_log({'parameters' => $param->{'template_path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
&report::notice_report_web('performed',{},$in{'subaction'});
}
## Build the list of available templates
my $available_files = &tools::get_templates_list('mail','',$list, {'ignore_global' => 1});
foreach $file (keys %$available_files) {
if ($file eq 'reject.tt2') {
my $base = $list->{'dir'}.'/mail_tt2/';
my $absolute_file = $base.'reject.tt2';
if (-l $absolute_file){
my $default = readlink ($absolute_file);
if ((-f $default )||( -f $base.$default )) {
$default =~ s/^.*reject_//;
$default =~ s/.tt2$//;
$default =~ s/_/ /g;
$param->{'default_reject_template'} = $default;
}else{
# link to no existing file. remove link
&wwslog('err','Link %s point to un no existing file (%s)', $base.'reject.tt2',$default);
unless (unlink ($absolute_file)){
&wwslog('err','do_modindex: could not unlink %s',$base.'reject.tt2');
}
}
}elsif(-f $absolute_file){
# replace existing reject.tt2 file by a symlink to reject_default.tt2 for compatibility with version older than 6.0
unless (rename ($absolute_file,$base.'reject_default.tt2')){
&wwslog('err','Could not rename %,%s',$base.'reject.tt2',$base.'reject_default.tt2');
}
unless (symlink ($base.'reject_default.tt2',$absolute_file)){
&wwslog('err','Could not symlink %s,%s',$base.'reject_default.tt2',$absolute_file);
}
$param->{'default_reject_template'} = 'default';
push (@{$param->{'available_files'}},'default');
}
}else{
next unless($file =~ /^reject_/);
$file =~ s/^reject_//;
$file =~ s/.tt2$//;
$file =~ s/_/ /g;
push (@{$param->{'available_files'}},$file);
}
}
return 1;
}
## online template edition
sub do_edit_template {
$in{'subdir'} ||= 'default';
&wwslog('info', "do_edit_template(type=$in{'webormail'},template-name=$in{'template_name'},listname=$in{'list'},path=$in{'template_path'},scope=$in{'scope'},lang=$in{'tpl_lang'})");
## Load original template
&do_view_template;
unless ($in{'content'}) {
return 1;
}
if ($in{'scope'} eq 'list' and ref $list ne 'List') {
report::reject_report_web('user', 'listname_needed',
{}, $param->{'action'});
wwslog('info',
"edit_template : no output lisname while output scope is list");
web_db_log({'parameters' => $in{'template_name'}, 'status' => 'error',
'error_type' => 'no_list'});
return undef;
}
$param->{'template_path'} =
tools::get_template_path($in{'webormail'}, $robot, $in{'scope'},
$in{'template_name'}, $in{'tpl_lang'}, $list);
unless (open (TPLOUT,'>'.$param->{'template_path'})) {
&report::reject_report_web('intern','cannot_open_file',{'path' => $param->{'template_path'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err',"edit_template: can't open file %s", $param->{'template_path'});
&web_db_log({'parameters' => $in{'template_name'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
print TPLOUT &tools::unescape_html($in{'content'});
close TPLOUT;
$param->{'saved'} = 1;
$param->{'template_content'} = $in{'content'};
$param->{'webormail'} = $in{'webormail'};
$param->{'template_name'} = $in{'template_name'};
$param->{'list'} = $in{'list'};
$param->{'scope'} = $in{'scope'};
$param->{'template_path'} = $in{'template_path'};
$param->{'tpl_lang'} = $in{'tpl_lang'};
&web_db_log({'parameters' => $in{'template_name'},
'status' => 'success'});
return 'ls_templates';
}
## Server show colors, and install static css in future edit colors etc
## Server show colors, and install static css in futur edit colors etc
sub do_skinsedit {
&wwslog('info', 'do_skinsedit');
my $f;
my $dir = &Conf::get_robot_conf($robot, 'css_path');
my $css_url = &Conf::get_robot_conf($robot, 'css_url');
## Checking families and other virtual hosts.
&get_server_details();
$param->{'css_warning'} = "parameter css_url seems strange, it must be the url of a directory not a css file" if ($css_url =~ /\.css$/);
if(($in{'editcolors'})&&($in{'subaction'} eq 'reset')){
delete $session->{'custom_css'};
delete $param ->{'session'}{'custom_css'};
delete $param->{'custom_css'};
foreach my $colornumber (0..15){
delete $session->{'color_'.$colornumber} ;
delete $param ->{'session'}{'color_'.$colornumber};
}
}
if ($in{'editcolors'} and $in{'subaction'} eq 'test') {
return unless $in{'custom_color_number'} =~ /color_/;
$param->{'custom_color_number'} = $in{'custom_color_number'};
$param->{'custom_color_value'} = $in{'custom_color_value'};
$param->{'custom_css'} = $css_url.'/'.$param->{'user'}{'email'}.'.style.css';
$session->{'custom_css'} = $param->{'custom_css'};
$session->{$in{'custom_color_number'}} = $in{'custom_color_value'};
$param->{$in{'custom_color_number'}} = $in{'custom_color_value'};
foreach my $colornumber (0..15){
if ($session->{'color_'.$colornumber} ) {
$param->{'color_'.$colornumber} = $session->{'color_'.$colornumber} ;
$param->{'session'}{'color_'.$colornumber} = $session->{'color_'.$colornumber} ;
}
}
}
if ($in{'subaction'} eq 'install' or $in{'installcss'}) {
## Do not include locale subdirectories.
## The css.tt2 by each locales will override styles in main CSS.
my $tt2_include_path = tools::make_tt2_include_path($robot, 'web_tt2', '', '');
my $date= time;
my $style_file;
# update config
foreach my $colornumber (0..15){
&Conf::set_robot_conf($robot, 'color_'.$colornumber, $session->{'color_'.$colornumber}) if ($session->{'color_'.$colornumber});
}
$param->{'conf'}=$Conf::Conf;
foreach my $css ('style.css','print.css','fullPage.css','print-preview.css') {
$param->{'css'} = $css;
my $css_file;
# if user use editcolor form we must generate a static CSS that used custom colors.
if($in{'subaction_test'}){
$css_file = "$dir/$param->{'user'}{'email'}.$css";
}else{
$css_file = "$dir/$css";
}
unless (-d $dir) {
unless (mkdir $dir, 0775) {
&report::reject_report_web('intern',"mkdir_failed",{'path' => $dir}, $param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err','skinsedit : failed to create directory %s : %s',$dir, $!);
return undef;
}
chmod 0775, $dir;
&wwslog('notice','skinsedit : created missing directory %s',$dir);
}
## Keep a copy of the previous CSS (only if this is not a custom css).
if ((-f "$css_file")&&!($in{'editcolors'})) {
unless (rename "$css_file", "$css_file.$date") {
&report::reject_report_web('intern','cannot_rename_file',{'path' => "$css_file.$date"},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err','skinsedit : can\'t open file %s.%s',$css_file,$date);
return undef;
}
}
if ($in{'subaction_install'}) {
foreach my $colornumber (0..15){
$param->{'color_'.$colornumber} = $session->{'color_'.$colornumber} if ($session->{'color_'.$colornumber});
}
}
unless (open (CSS,">$css_file")) {
&report::reject_report_web('intern','cannot_open_file',{'path' => "$css_file"},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err','skinsedit : can\'t open file (write) %s',$css_file);
return undef;
}
unless (&tt2::parse_tt2($param,'css.tt2' ,\*CSS, $tt2_include_path)) {
my $error = &tt2::get_error();
$param->{'tt2_error'} = $error;
&List::send_notify_to_listmaster('web_tt2_error', $robot,[$error]);
&wwslog('info', "do_skinsedit : error while installing $css_file");
}
close (CSS) ;
## Make the CSS readable to anyone
chmod 0775, "$css_file";
}
$param->{'css_result'} = 1 ;
}
return 1;
}
## Multiple add
sub do_add_request {
&wwslog('info', 'do_add_request(%s)', $in{'email'});
## Access control
return undef unless (defined &check_authz('do_add_request', 'add'));
return 1;
}
####################################################
# do_add
####################################################
# Adds a user to a list (requested by an other user)
#
# IN : -
#
# OUT : 'loginrequest'
# | ($in{'previous_action'} || 'review')
# | undef
####################################################
## TODO: vérifier validité email
sub do_add {
&wwslog('info', 'do_add(%s)', $in{'email'}||$in{'pending_email'});
my $subscriptions = $list->get_subscription_requests();
my %user;
## If a list is not 'open' and allow_subscribe_if_pending has been set to 'off' returns undef.
unless (($list->{'admin'}{'status'} eq 'open') || (&Conf::get_robot_conf($robot, 'allow_subscribe_if_pending') eq 'on')) {
&report::reject_report_web('user','list_not_open',{'status' => $list->{'admin'}{'status'}},$param->{'action'});
&wwslog('info','list not open');
&web_db_log({'target_email' => $in{'email'}||$in{'pending_email'},
'status' => 'error',
'error_type' => 'list_not_open'});
return undef;
}
my $email_regexp = &tools::get_regexp('email');
if ($in{'dump'}) {
foreach (split /\n/, $in{'dump'}) {
if (/^\s*($email_regexp)(\s+(.*))?\s*$/) {
$user{&tools::get_canonical_email($1)} = $5;
}
}
}elsif ($in{'email'} =~ /,/) {
foreach my $pair (split /\0/, $in{'email'}) {
if ($pair =~ /^($email_regexp)(,(.*))?\s*$/) {
$user{&tools::get_canonical_email($1)} = $5;
}
}
}elsif ($in{'email'}) {
foreach my $email (split /\0/, $in{'email'}) {
$user{&tools::get_canonical_email($email)} = $in{'gecos'};
}
}elsif ($in{'pending_email'}) {
foreach my $pair (split /\0/, $in{'pending_email'}) {
my ($email, $gecos);
if ($pair =~ /^($email_regexp)(,(.*))?\s*$/) {
($email, $gecos) = ($1,$5);
$user{&tools::get_canonical_email($email)} = $gecos;
}
}
}else {
&report::reject_report_web('user','no_email',{},$param->{'action'});
&wwslog('info','do_add: no email');
&web_db_log({'target_email' => $in{'email'}||$in{'pending_email'},
'status' => 'error',
'error_type' => 'no_email'});
return undef;
}
my ($total, @new_users, @added_users );
my $comma_emails ;
foreach my $email (keys %user) {
&wwslog('debug', "do_add subscription \$subscriptions->{$email}{custom_attribute} = $subscriptions->{$email}{'custom_attribute'})" );
if (ref($subscriptions->{$email}{'custom_attribute'}) eq 'HASH') {
my $xml = List::createXMLCustomAttribute($subscriptions->{$email}{'custom_attribute'}) ;
&wwslog('debug', "do_add subscription XML \$subscriptions->{$email}{custom_attribute} = $xml;");
}
my $result = $list->check_list_authz('add',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'email' => $in{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $add_is;
my $reason;
if (ref($result) eq 'HASH') {
$add_is = $result->{'action'};
$reason = $result->{'reason'};
}
unless ($add_is =~ /do_it/) {
&report::reject_report_web('auth',$reason,{},$param->{'action'},$list);
&wwslog('info','do_add: %s may not add', $param->{'user'}{'email'});
&web_db_log({'target_email' => $in{'email'}||$in{'pending_email'},
'status' => 'error',
'error_type' => 'internal'});
next;
}
unless (&tools::valid_email($email)) {
&report::reject_report_web('user','incorrect_email',{'email' => $email},$param->{'action'},$list);
&wwslog('info','do_add: incorrect email %s', $email);
&web_db_log({'target_email' => $in{'email'}||$in{'pending_email'},
'status' => 'error',
'error_type' => 'incorrect_email'});
next;
}
my $user_entry = $list->get_subscriber($email);
if (defined($user_entry)) {
&report::reject_report_web('user','user_already_subscriber', {'list' => $list->{'name'},'email' => $email},$param->{'action'},$list);
&wwslog('info','do_add: %s already subscriber', $email);
&web_db_log({'target_email' => $in{'email'}||$in{'pending_email'},
'status' => 'error',
'error_type' => 'already_subscriber'});
next;
}
my $u2 = &List::get_user_db($email);
my $defaults = $list->get_default_user_options();
my $u;
%{$u} = %{$defaults};
$u->{'email'} = $email;
$u->{'gecos'} = $user{$email} || $u2->{'gecos'};
$u->{'date'} = $u->{'update_date'} = time;
$u->{'password'} = $u2->{'password'} || &tools::tmp_passwd($email) ;
$u->{'lang'} = $u2->{'lang'} || $list->{'admin'}{'lang'};
if ($comma_emails) {
$comma_emails = $comma_emails .','. $email;
}else{
$comma_emails = $email;
}
##
push @new_users, $u;
push @added_users, $email; ## List only email addresses ; used later to remove pending subrequests
unless ($in{'quiet'} || $add_is =~ /quiet/i) {
unless ($list->send_file('welcome', $email, $robot,{})) {
&wwslog('err',"Unable to send template 'welcome' to $email");
}
}
}
$total = $list->add_user(@new_users);
unless( defined $total) {
&report::reject_report_web('intern','add_subscriber_db_failed',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_add: failed adding');
&web_db_log({'target_email' => $in{'email'}||$in{'pending_email'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
## Delete subscription request if any
$list->delete_subscription_request(@added_users);
&report::notice_report_web('add_performed', {'total' => $total},$param->{'action'});
foreach my $email (@added_users) {
&web_db_log({'target_email' => $email,
'status' => 'success'});
}
$in{'list'} = $in{'previous_list'} if ($in{'previous_list'});
return $in{'previous_action'} || 'review';
}
####################################################
# do_del
####################################################
# Deletes a user from a list (requested by an other user)
#
# IN : -
#
# OUT : 'loginrequest'
# | ($in{'previous_action'} || 'review') | undef
#
####################################################
## TODO: vérifier validité email
sub do_del {
&wwslog('info', 'do_del()');
$in{'email'} = &tools::unescape_chars($in{'email'});
my $result = $list->check_list_authz('del',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'email' => $in{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $del_is;
my $reason;
if (ref($result) eq 'HASH') {
$del_is = $result->{'action'};
$reason = $result->{'reason'};
}
unless ( $del_is =~ /do_it/) {
&report::reject_report_web('auth',$reason,{},$param->{'action'},$list);
# &List::db_log('wwsympa',$param->{'user'}{'email'},$param->{'auth_method'},$ip,'del',$param->{'list'},$robot,$in{'email'},'may not');
&wwslog('info','do_del: %s may not del', $param->{'user'}{'email'});
&web_db_log({'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
my @emails = split /\0/, $in{'email'};
my ($total, @removed_users);
foreach my $email (@emails) {
my $escaped_email = &tools::escape_chars($email);
my $user_entry = $list->get_subscriber($email);
unless (defined($user_entry)) {
&report::reject_report_web('user','not_subscriber',{'email' => $email},$param->{'action'},$list);
# &List::db_log('wwsympa',$param->{'user'}{'email'},$param->{'auth_method'},$ip,'del',$param->{'list'},$robot,$email,'not subscriber');
&wwslog('info','do_del: %s not subscribed', $email);
&web_db_log({'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'not_subscriber'});
next;
}
push @removed_users, $email;
my $bounce_dir = $list->get_bounce_dir();
if (-f $bounce_dir.'/'.$escaped_email) {
unless (unlink $bounce_dir.'/'.$escaped_email) {
&wwslog('info','do_resetbounce: failed deleting %s', $bounce_dir.'/'.$escaped_email);
&web_db_log({'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'internal'});
next;
}
}
&wwslog('info','do_del: subscriber %s deleted from list %s', $email, $param->{'list'});
unless ($in{'quiet'}) {
unless ($list->send_file('removed', $email, $robot,{})) {
&wwslog('notice',"Unable to send template 'removed' to $email");
}
}
}
$total = $list->delete_user('users' => \@removed_users, 'exclude' =>'1');
unless( defined $total) {
&report::reject_report_web('intern','delete_subscriber_db_failed',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_del: failed');
&web_db_log({'target_email' => $in{'email'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
&report::notice_report_web('del_performed',{'total' => $total},$param->{'action'});
&web_db_log({'target_email' => $in{'email'},
'status' => 'success'});
$param->{'is_subscriber'} = 1;
$param->{'may_signoff'} = 1;
## Skip search because we don't have the expression anymore
delete $in{'previous_action'} if ($in{'previous_action'} eq 'search');
return $in{'previous_action'} || 'review';
}
####################################################
# do_modindex
####################################################
# Web page for an editor to moderate documents and
# and/or to tag message in message topic context
#
# IN : -
#
# OUT : 'loginrequest' | 'admin' | '1' | undef
#
#######################################################
sub do_modindex {
&wwslog('info', 'do_modindex');
my $msg;
my $doc;
## Loads message list
unless (opendir SPOOL, $Conf{'queuemod'}) {
&report::reject_report_web('intern','cannot_open_spool',{'spool'=>$Conf{'queuemod'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_modindex: unable to read spool');
return 'admin';
}
my $list_name = $list->{'name'};
my $list_id = $list->get_list_id();
foreach $msg ( sort grep(!/^\./, readdir SPOOL )) {
next
unless ($msg =~ /^$list_id\_(\w+)$/ ||
$msg =~ /^$list_name\_(\w+)$/);
my $id = $1;
## Load msg
my $mail = new Message("$Conf{'queuemod'}/$msg");
unless (defined $mail) {
&report::reject_report_web('intern','cannot_get_msg',{'msg'=>"$Conf{'queuemod'}/$msg"},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_modindex: unable to parse msg %s', $msg);
closedir SPOOL;
next;
}
$param->{'spool'}{$id}{'size'} = int( (-s "$Conf{'queuemod'}/$msg") / 1024 + 0.5);
$param->{'spool'}{$id}{'subject'} = &tools::decode_header($mail, 'Subject');
$param->{'spool'}{$id}{'subject'} ||= 'no_subject';
$param->{'spool'}{$id}{'date'} = &tools::decode_header($mail, 'Date');
$param->{'spool'}{$id}{'from'} = &tools::decode_header($mail, 'From');
$param->{'spool'}{$id}{'spam_status'} = $mail->{'spam_status'};
foreach my $field ('subject','date','from') {
$param->{'spool'}{$id}{$field} =~ s/&/&/g;
$param->{'spool'}{$id}{$field} =~ s/</g;
$param->{'spool'}{$id}{$field} =~ s/>/>/g;
}
}
closedir SPOOL;
if ($list->is_there_msg_topic()) {
$param->{'request_topic'} = 1;
foreach my $top (@{$list->{'admin'}{'msg_topic'}}) {
if ($top->{'name'}) {
push (@{$param->{'available_topics'}},$top);
}
}
$param->{'topic_required'} = $list->is_msg_topic_tagging_required();
}
my $available_files = &tools::get_templates_list('mail','',$list, {'ignore_global' => 1});
foreach my $file (keys %$available_files) {
if ($file eq 'reject.tt2') {
my $base = $list->{'dir'}.'/mail_tt2/';
my $absolute_file = $base.'reject.tt2';
if (-l $absolute_file){
my $default = readlink ($absolute_file);
if ((-f $default )||( -f $base.$default )) {
$default =~ s/^.*reject_//;
$default =~ s/.tt2$//;
$param->{'default_reject_template'} = $default;
}else{
# link to no existing file. remove link
&wwslog('err','do_modindex: link %s point to un no existing file (%s)', $base.'reject.tt2',$default);
unless (unlink ($absolute_file)){
&wwslog('err','do_modindex: could not unlink %s',$base.'reject.tt2');
}
}
}elsif(-f $absolute_file){
# replace existing reject.tt2 file by a symlink to reject_default.tt2 for compatibility with version older than 6.0
unless (rename ($absolute_file,$base.'reject_default.tt2')){
&wwslog('err','do_modindex: could not rename %,%s',$base.'reject.tt2',$base.'reject_default.tt2');
}
unless (symlink ($base.'reject_default.tt2',$absolute_file)){
&wwslog('err','do_modindex: could not symlink %s,%s',$base.'reject_default.tt2',$absolute_file);
}
$param->{'default_reject_template'} = 'default';
push (@{$param->{'available_files'}},'default');
}
}else{
next unless($file =~ /^reject_/);
$file =~ s/^reject_//;
$file =~ s/.tt2$//;
push (@{$param->{'available_files'}},$file);
}
}
## shared documents awaiting moderation
foreach my $d (@{$param->{'doc_mod_list'}}) {
$d =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/;
my $long_path = $1; # path without the filename
my $fname = $3; # the filename with .moderate
my $path = $long_path; $path =~ s/^.*\/shared//; #the path for the user, without the filename
my $visible_fname = &make_visible_path($fname); # the filename without .moderate
my $visible_path = $path;
$visible_path = &make_visible_path($visible_path);
my %desc_hash;
if ($d && (-e "$long_path.desc.$fname")){
%desc_hash = &get_desc_file("$long_path.desc.$fname");
}
my @info = stat $d;
my $doc = {};
$doc->{'visible_path'} = $visible_path;
$doc->{'visible_fname'} = $visible_fname;
$doc->{'escaped_fname'} = &tools::escape_docname($fname, '/');
$doc->{'escaped_path'} = &tools::escape_docname($path, '/');
$doc->{'fname'} = $fname;
$doc->{'size'} = (-s $d)/1000;
$doc->{'date'} = gettext_strftime "%d %b %Y", localtime($info[9]);
$doc->{'author'} = $desc_hash{'email'};
$doc->{'path'} = $path;
push(@{$param->{'info_doc_mod'}},$doc)
}
unless (($param->{'spool'}) || ($param->{'mod_total_shared'} > 0)) {
&report::notice_report_web('no_msg_document', {'list' => $in{'list'}},$param->{'action'});
&wwslog('err','do_modindex: no message and no document');
}
return 1;
}
### installation of moderated documents of shared
sub do_d_install_shared {
&wwslog('info', 'do_d_install_shared(%s)', $in{'id'});
if ($in{'mode_cancel'}) {
return 'modindex';
}
my $shareddir = $list->{'dir'}.'/shared';
my $file;
my $slash_path;
my $fname;
my $visible_fname;
# list of file already existing
my @list_file_exist;
unless($in{'mode_confirm'} || $in{'mode_cancel'}) {
# file already exists ?
foreach my $id (split /\0/, $in{'id'}) {
$file = "$shareddir$id";
$id =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/;
$slash_path = $1;
$fname = $3;
$visible_fname = &make_visible_path($fname);
if (-e "$file") {
if (-e "$shareddir$slash_path$visible_fname") {
push(@list_file_exist,"$slash_path$visible_fname");
}
}
}
if (@list_file_exist) {
$param->{'list_file'}=\@list_file_exist;
my @id = split(/\0/,$in{'id'});
$param->{'id'} = \@id;
return 1;
}
}
# install the file(s) selected
foreach my $id (split /\0/, $in{'id'}) {
$file = "$shareddir$id";
$id =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/;
$slash_path = $1;
$fname = $3;
my $new_fname; ## new filename without the .moderate extension
if ($fname =~ /^\.(.+)\.moderate$/) {
$new_fname = $1;
}
my $visible_path = &make_visible_path($slash_path);
$visible_fname = &make_visible_path($fname);
if (-e "$file") {
# rename the old file in .old if exists
if (-e "$shareddir$slash_path$new_fname") {
unless (rename "$shareddir$slash_path$new_fname","$shareddir$slash_path$new_fname.old"){
&report::reject_report_web('intern','rename_file',{'old'=>"$shareddir$slash_path$new_fname",
'new'=>"$shareddir$slash_path$new_fname.old" },
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_install_shared : Failed to rename $shareddir$slash_path$new_fname to .old : %s",$!);
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
unless (rename "$shareddir$slash_path.desc.$new_fname","$shareddir$slash_path.desc.$new_fname.old"){
&report::reject_report_web('intern','rename_file',{'old'=>"$shareddir$slash_path.desc.$new_fname",
'new'=>"$shareddir$slash_path.desc.$new_fname.old"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_install_shared : Failed to rename shareddir$slash_path.desc.$new_fname to .old : %s",$!);
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
unless (rename ("$shareddir$id","$shareddir$slash_path$new_fname")){
&report::reject_report_web('intern','rename_file',{'old'=>"$shareddir$id",
'new'=>"$shareddir$slash_path$new_fname"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_install_shared : Failed to rename $file to $shareddir$slash_path$new_fname : $!");
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
unless (rename ("$shareddir$slash_path.desc.$fname","$shareddir$slash_path.desc.$new_fname")){
&report::reject_report_web('intern','rename_file',{'old'=>"$shareddir$slash_path.desc.$fname",
'new'=>"$shareddir$slash_path.desc.$new_fname"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_install_shared : Failed to rename $file to $shareddir$slash_path$new_fname : $!");
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
# send a message to the author
my %context;
$context{'installed_by'} = $param->{'user'}{'email'};
$context{'filename'} = "$visible_path$visible_fname";
my %desc_hash;
if ($id && (-e "$shareddir$slash_path.desc.$visible_fname")){
%desc_hash = &get_desc_file("$shareddir$slash_path.desc.$visible_fname");
}
my $sender = $desc_hash{'email'};
unless ($list->send_file('d_install_shared', $sender, $robot, \%context)) {
&wwslog('notice',"Unable to send template 'd_install_shared' to $sender");
}
}
}
&report::notice_report_web('performed',{},$param->{'action'});
&web_db_log({'status' => 'success'});
return 'modindex';
}
### reject moderated documents of shared
sub do_d_reject_shared {
&wwslog('info', 'do_d_reject_shared(%s)', $in{'id'});
my $shareddir = $list->{'dir'}.'/shared';
my $file;
my $slash_path;
my $fname;
my $visible_fname;
foreach my $id (split /\0/, $in{'id'}) {
$file = "$shareddir$id";
$id =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/;
$slash_path = $1;
$fname = $3;
$visible_fname = &make_visible_path($fname);
my $visible_path = &make_visible_path($slash_path);
unless ($in{'quiet'}) {
my %context;
my $sender;
$context{'rejected_by'} = $param->{'user'}{'email'};
$context{'filename'} = "$visible_path$visible_fname";
my %desc_hash;
if ($id && (-e "$shareddir$slash_path.desc.$fname")){
%desc_hash = &get_desc_file("$shareddir$slash_path.desc.$fname");
}
$sender = $desc_hash{'email'};
unless ($list->send_file('d_reject_shared', $sender, $robot, \%context)) {
&wwslog('notice',"Unable to send template 'd_reject_shared' to $sender");
}
}
unless (unlink($file)) {
&report::reject_report_web('intern','erase_file',{'file' => $file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_d_reject_shared: failed to erase %s', $file);
&web_db_log({'parameters' => $in{'id'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
unless (unlink("$shareddir$slash_path.desc.$fname")) {
&report::reject_report_web('intern','erase_file',{'file' => "$shareddir$slash_path.desc.$fname"},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_reject_shared: failed to erase $shareddir$slash_path.desc.$fname");
&web_db_log({'parameters' => $in{'id'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
&report::notice_report_web('performed',{},$param->{'action'});
&web_db_log({'parameters' => $in{'id'},
'status' => 'success'});
return 'modindex';
}
####################################################
# do_reject
####################################################
# Moderation of messages : rejects messages and notifies
# their senders. If in{'blacklist'} add sender to list blacklist
#
# IN : -
#
# OUT : 'loginrequest' | 'modindex' | undef
#
####################################################
sub do_reject {
# toggle selection javascript have a distinction of spam and ham base on the checkbox name . It is not useful here so join id list and idspam list.
$in{'id'} .= ','.$in{'idspam'} if ($in{'idspam'});
$in{'id'} =~ s/^,//;
$in{'id'} =~ s/\0/,/g;
$in{'message_template'};
## The quiet information might either be provided by the 'quiet' variable
## or by the 'quiet' value of the 'message_template' variable
if ($in{'message_template'} eq 'quiet') {
$in{'quiet'} = 1;
delete $in{'message_template'};
}
if ($in{'blacklist'}) {
$in{'quiet'} = 1;
}
&wwslog('info', 'do_reject(%s)', $in{'id'});
my ($msg, $file);
$param->{'blacklist_added'} = 0;
$param->{'blacklist_ignored'} = 0;
foreach my $id (split (/,/, $in{'id'})) {
## For compatibility concerns
foreach my $list_id ($list->get_list_id(),$list->{'name'}) {
$file = $Conf{'queuemod'}.'/'.$list_id.'_'.$id;
last if (-f $file);
}
## Open the file
unless (open(IN, $file)) {
&report::reject_report_web('user','already_moderated',{},$param->{'action'});
&wwslog('err','do_reject: Unable to open %s', $file);
&web_db_log({'parameters' => $in{'id'},
'status' => 'error',
'error_type' => 'internal'});
next;
}
# extract sender address is needed to report reject to sender and in case the sender is to be added to the blacklist
if (($in{'quiet'} ne '1')||($in{'blacklist'})) {
my $msg;
my $parser = new MIME::Parser;
$parser->output_to_core(1);
unless ($msg = $parser->read(\*IN)) {
&wwslog('err', 'Unable to parse message %s', $file);
next;
}
my @sender_hdr = Mail::Address->parse($msg->head->get('From'));
unless ($#sender_hdr == -1) {
my $rejected_sender = $sender_hdr[0]->address;
unless ($in{'quiet'}) {
my %context;
$context{'subject'} = &tools::decode_header($msg, 'Subject');
$context{'rejected_by'} = $param->{'user'}{'email'};
$context{'template_used'} = $in{'message_template'};
unless ($list->send_file($in{'message_template'}, $rejected_sender, $robot, \%context)) {
&wwslog('notice',"Unable to send template $in{'message_template'} to $rejected_sender");
}
}
if ($in{'blacklist'}) {
if (&tools::add_in_blacklist($rejected_sender,$robot,$list)) {
$param->{'blacklist_added'} += 1;
&wwslog('info',"added $rejected_sender to $list->{'name'} blacklist");
}else{
&wwslog('notice',"Unable to add $rejected_sender to $list->{'name'} blacklist");
$param->{'blacklist_ignored'} += 0;
}
}
}
}
close(IN);
unless (unlink($file)) {
&report::reject_report_web('intern','erase_file',{'file' => $file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_reject: failed to erase %s', $file);
&web_db_log({'parameters' => $in{'id'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
&web_db_log({'parameters' => $in{'id'},
'status' => 'success'});
&report::notice_report_web('performed',{},$param->{'action'});
return 'modindex';
}
####################################################
# do_distribute
####################################################
# Moderation of messages : distributes moderated
# messages and tag it in message moderation context
#
# IN : - id of message to distribute. This value can also be in idspam parameter
#
# OUT : 'loginrequest' | 'modindex' | undef
#
######################################################
sub do_distribute {
$in{'id'} .= ','.$in{'idspam'} if ( $in{'idspam'});
$in{'id'} =~ s/^,//;
$in{'id'} =~ s/\0/,/g;
&wwslog('info', 'do_distribute(%s)', $in{'id'});
my ($msg, $file);
my $time = time;
my $data = {'headers' => {'Message-ID' => &tools::get_message_id($robot),
'X-Sympa-NoWrap' => 'yes'},
'from'=> $param->{'user'}{'email'}};
## msg topics
my @msg_topics;
foreach my $msg_topic (@{$list->{'admin'}{'msg_topic'}}) {
my $var_name = "topic_"."$msg_topic->{'name'}";
if ($in{"$var_name"}) {
push @msg_topics, $msg_topic->{'name'};
}
}
my $list_topics = join(',',@msg_topics);
if (!$list_topics && $list->is_msg_topic_tagging_required()) {
&report::reject_report_web('user','msg_topic_missing',{},$param->{'action'});
&wwslog('info','do_distribute: message(s) without topic but in a required list');
&web_db_log({'parameters' => $in{'id'},
'status' => 'error',
'error_type' => 'no_topic'});
return undef;
}
## messages
foreach my $id (split (/,/, $in{'id'})) { # QUIET DISTRIBUTE
&do_log('notice', 'Moderator approves message: %s %s', $list->{'name'},$id);
my $mail_command = sprintf ("QUIET DISTRIBUTE %s %s\n",$list->{'name'},$id);
$data->{'body'} .= $mail_command;
## For compatibility concerns
foreach my $list_id ($list->get_list_id(),$list->{'name'}) {
$file = $Conf{'queuemod'}.'/'.$list_id.'_'.$id;
last if (-f $file);
}
unless (-f $file) {
&report::reject_report_web('user','already_moderated',{},$param->{'action'});
&wwslog('err','do_distribute: Unable to open %s', $file);
&web_db_log({'parameters' => $in{'id'},
'status' => 'error',
'error_type' => 'internal'});
next;
}
## TAG
if ($list_topics) {
my $parser = new MIME::Parser;
$parser->output_to_core(1);
unless (open FILE, "$file") {
&wwslog('notice', 'do_distribute: Cannot open file %s', $file);
&report::reject_report_web('intern','cannot_open_file',{'file' => $file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&web_db_log({'parameters' => $in{'id'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
my $msg = $parser->parse(\*FILE);
my $head = $msg->head();
my $filetopic = $list->tag_topic(&tools::clean_msg_id($head->get('Message-Id')),$list_topics,'editor');
}
unless (rename($file,"$file.distribute")) {
&report::reject_report_web('intern','rename_file',{'old'=>$file,
'new'=>"$file.distribute"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_distribute: failed to rename %s', $file);
&web_db_log({'parameters' => $in{'id'},
'status' => 'error',
'error_type' => 'internal'});
}
}
$data->{'not_auto_submitted'} = 1;
unless (&mail::mail_file('',&Conf::get_robot_conf($robot, 'sympa'), $data, $robot)) {
&report::reject_report_web('intern','cannot_send_distribute',{'from' => $param->{'user'}{'email'},'listname'=>$list->{'name'}},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_distribute: failed to send message for file %s', $file);
&web_db_log({'parameters' => $in{'id'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
&web_db_log({'parameters' => $in{'id'},
'status' => 'success'});
&report::notice_report_web('performed_soon',{},$param->{'action'});
return 'modindex';
}
####################################################
# do_viewmod
####################################################
# Web page for an editor to moderate a mail and/or
# to tag it in message topic context
#
# IN : -
#
# OUT : 'login,request' | '1' | undef
#
####################################################
sub do_viewmod {
&wwslog('info', 'do_viewmod(%s,%s)', $in{'id'},$in{'file'});
my $msg;
my $tmp_dir;
my $available_files = &tools::get_templates_list('mail','',$list, {'ignore_global' => 1});
foreach my $file (keys %$available_files) {
next unless($file =~ /^reject_/);
$file =~ s/^reject_//;
$file =~ s/.tt2$//;
push (@{$param->{'available_files'}},$file);
}
## For compatibility concerns
foreach my $list_id ($list->get_list_id(),$list->{'name'}) {
$tmp_dir = $Conf{'queuemod'}.'/.'.$list_id.'_'.$in{'id'};
if (-d $tmp_dir) {
last;
}
}
unless (-d $tmp_dir) {
&report::reject_report_web('intern','no_html_message_available',{'dir' => $tmp_dir},$param->{'action'});
&wwslog('err','do_viewmod: no HTML version of the message available in %s', $tmp_dir);
return undef;
}
if ($in{'file'}) {
$in{'file'} =~ /\.(\w+)$/;
$param->{'file_extension'} = $1;
$param->{'file'} = $tmp_dir.'/'.$in{'file'};
$param->{'bypass'} = 1;
}else {
&tt2::add_include_path($tmp_dir) ;
}
$param->{'base'} = sprintf "%s/viewmod/%s/%s/", &Conf::get_robot_conf($robot, 'wwsympa_url'), $param->{'list'}, $in{'id'};
$param->{'id'} = $in{'id'};
if ($list->is_there_msg_topic()) {
$param->{'request_topic'} = 1;
foreach my $top (@{$list->{'admin'}{'msg_topic'}}) {
if ($top->{'name'}) {
push (@{$param->{'available_topics'}},$top);
}
}
$param->{'topic_required'} = $list->is_msg_topic_tagging_required();
}
return 1;
}
## Edition of list/sympa files
## No list -> sympa files (helpfile,...)
## TODO : upload
## TODO : edit family file ???
sub do_editfile {
&wwslog('info', 'do_editfile(%s)', $in{'file'});
$param->{'subtitle'} = sprintf $param->{'subtitle'}, $in{'file'};
unless ($in{'file'}) {
## Messages edition
foreach my $f ('info','homepage','welcome.tt2','bye.tt2','removed.tt2','message.footer','message.header','remind.tt2','invite.tt2','reject.tt2','your_infected_msg.tt2') {
my $filename_for_auth = $f;
$filename_for_auth = 'info.file' if ($filename_for_auth eq 'info');
next unless ($list->may_edit($filename_for_auth, $param->{'user'}{'email'}) eq 'write');
if ($wwslib::filenames{$f}{'gettext_id'}) {
$param->{'files'}{$f}{'complete'} = gettext($wwslib::filenames{$f}{'gettext_id'});
}else {
$param->{'files'}{$f}{'complete'} = $f;
}
$param->{'files'}{$f}{'selected'} = '';
}
return 1;
}
unless (defined $wwslib::filenames{$in{'file'}}) {
&report::reject_report_web('user','file_not_editable',{'file' => $in{'file'}},$param->{'action'});
&wwslog('err','do_editfile: file %s not editable', $in{'file'});
&web_db_log({'parameters' => $in{'file'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
$param->{'file'} = $in{'file'};
$param->{'complete'} = gettext($wwslib::filenames{$in{'file'}}{'gettext_id'});
my $subdir = '';
if ($in{'file'} =~ /\.tt2$/) {
$subdir = 'mail_tt2/';
}
if ($param->{'list'}) {
my $filename_for_auth = $in{'file'};
$filename_for_auth = 'info.file' if ($filename_for_auth eq 'info');
my ($role,$right) = $list->may_edit($filename_for_auth, $param->{'user'}{'email'});
unless ($right eq 'write') {
&report::reject_report_web('auth','edit_right',{'role'=>$role, 'right' => $right},$param->{'action'},$list);
&wwslog('err','do_editfile: not allowed');
&web_db_log({'parameters' => $in{'file'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
## Add list lang to tpl filename
my $file = $in{'file'};
#$file =~ s/\.tpl$/\.$list->{'admin'}{'lang'}\.tpl/;
## Look for the template
$param->{'filepath'} = &tools::get_filename('etc',{},$subdir.$file,$robot, $list);
## There might be no matching file if default template not provided with Sympa
if (defined $param->{'filepath'}) {
## open file and provide filecontent to the parser
## It allows to us the correct file encoding
unless (open FILE, "<", $param->{'filepath'}) {
&report::reject_report_web('intern','cannot_open_file',{'file' => $param->{'filepath'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_editfile: failed to open file %s: %s', $param->{'filepath'},$!);
&web_db_log({'parameters' => $in{'file'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
while () {
Encode::from_to($_, $Conf{'filesystem_encoding'}, 'utf8');
$param->{'filecontent'} .= $_;
}
close FILE;
}else {
$param->{'filepath'} = $list->{'dir'}.'/'.$subdir.$file;
}
## Default for 'homepage' is 'info'
if (($in{'file'} eq 'homepage') &&
! $param->{'filepath'}) {
$param->{'filepath'} = &tools::get_filename('etc',{},$subdir.'info',$robot, $list);
}
}else {
unless (&List::is_listmaster($param->{'user'}{'email'},$robot)) {
&report::reject_report_web('user','missing_arg',{'argument' => 'list'},$param->{'action'});
&wwslog('err','do_editfile: no list');
&web_db_log({'parameters' => $in{'file'},
'status' => 'error',
'error_type' => 'no_list'});
return undef;
}
my $file = $in{'file'};
## Look for the template
if ($file eq 'list_aliases.tt2') {
$param->{'filepath'} = &tools::get_filename('etc',{},$file,$robot,$list);
}else {
#my $lang = &Conf::get_robot_conf($robot, 'lang');
#$file =~ s/\.tpl$/\.$lang\.tpl/;
$param->{'filepath'} = &tools::get_filename('etc',{},$subdir.$file,$robot,$list);
}
}
if (-f $param->{'filepath'} && (! -r $param->{'filepath'})) {
&report::reject_report_web('intern','cannot_read',{'filepath' => $param->{'filepath'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err','do_editfile: cannot read %s', $param->{'filepath'});
&web_db_log({'parameters' => $in{'file'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
&web_db_log({'parameters' => $in{'file'},
'status' => 'success'});
$allow_absolute_path = 1;
return 1;
}
#####################################################################################
## Saving of list files
sub do_savefile {
&wwslog('info', 'do_savefile(%s)', $in{'file'});
$param->{'subtitle'} = sprintf $param->{'subtitle'}, $in{'file'};
if ($param->{'list'}) {
unless ($list->am_i('owner', $param->{'user'}{'email'})) {
&report::reject_report_web('auth','action_owner',{},$param->{'action'},$list);
&wwslog('err','do_savefile: not allowed');
&web_db_log({'parameters' => $in{'file'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
if ($in{'file'} =~ /\.tt2$/) {
$param->{'filepath'} = $list->{'dir'}.'/mail_tt2/'.$in{'file'};
}else {
$param->{'filepath'} = $list->{'dir'}.'/'.$in{'file'};
if (defined $list->{'admin'}{'family_name'}) {
unless ($list->update_config_changes('file',$in{'file'})) {
&report::reject_report_web('intern','update_config_changes',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_savefile: cannot write in config_changes for file %s', $param->{'filepath'});
&web_db_log({'parameters' => $in{'file'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
}
}else {
unless (&List::is_listmaster($param->{'user'}{'email'}),$robot) {
&report::reject_report_web('user','missing_arg',{'argument' => 'list'},$param->{'action'});
&wwslog('err','do_savefile: no list');
&web_db_log({'parameters' => $in{'file'},
'status' => 'error',
'error_type' => 'no_list'});
return undef;
}
if ($robot ne $Conf{'domain'}) {
if ($in{'file'} eq 'list_aliases.tt2') {
$param->{'filepath'} = "$Conf{'etc'}/$robot/$in{'file'}";
}else {
$param->{'filepath'} = "$Conf{'etc'}/$robot/mail_tt2/$in{'file'}";
}
}else {
if ($in{'file'} eq 'list_aliases.tt2') {
$param->{'filepath'} = "$Conf{'etc'}/$in{'file'}";
}else {
$param->{'filepath'} = "$Conf{'etc'}/mail_tt2/$in{'file'}";
}
}
}
unless ((! -e $param->{'filepath'}) or (-w $param->{'filepath'})) {
&report::reject_report_web('intern','cannot_write',{'filepath' => $param->{'filepath'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_savefile: cannot write %s', $param->{'filepath'});
&web_db_log({'parameters' => $in{'file'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
## Keep the old file
if (-e $param->{'filepath'}) {
rename($param->{'filepath'}, "$param->{'filepath'}.orig");
}
## Not empty
if ($in{'content'} && ($in{'content'} !~ /^\s*$/)) {
## Remove DOS linefeeds (^M) that cause problems with Outlook 98, AOL, and EIMS:
$in{'content'} =~ s/\r\n|\r/\n/g;
## Create directory if required
my $dir = $param->{'filepath'};
$dir =~ s/\/[^\/]+$//;
unless (-d $dir) {
unless (mkdir $dir, 0777) {
&report::reject_report_web('intern','cannot_mkdir',{'dir' => $dir},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_savefile: failed to create directory %s: %s', $dir,$!);
&web_db_log({'parameters' => $in{'file'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
## Save new file
unless (open FILE, ">", $param->{'filepath'}) {
&report::reject_report_web('intern','cannot_open_file',{'file' => $param->{'filepath'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_savefile: failed to save file %s: %s', $param->{'filepath'},$!);
&web_db_log({'parameters' => $in{'file'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
my $e = $in{'content'};
Encode::from_to($e, 'utf8', $Conf{'filesystem_encoding'});
print FILE $e;
close FILE;
}elsif (-f $param->{'filepath'}) {
&wwslog('info', 'do_savefile: deleting %s', $param->{'filepath'});
unlink $param->{'filepath'};
}
&web_db_log({'parameters' => $in{'file'},
'status' => 'success'});
&report::notice_report_web('performed',{},$param->{'action'});
# undef $in{'file'};
# undef $param->{'file'};
return 'editfile';
}
## Access to web archives
sub do_arc {
&wwslog('info', 'do_arc(%s, %s)', $in{'month'}, $in{'arc_file'});
my $latest;
my $index = $session->{'arc_mode'} || $wwsconf->{'archive_default_index'};
$index = 'thrd' unless $index and $index =~ /^(thrd|mail)$/;
## Clean arc_file
if ($in{'arc_file'} eq '/') {
delete $in{'arc_file'};
}
## Access control
unless (defined &check_authz('do_arc', 'web_archive.access')) {
$param->{'previous_action'} = 'arc';
$param->{'previous_list'} = $list->{'name'};
return undef;
}
$session->{'archive_sniffer'} = 'false' if ($param->{'user'}{'email'} or $in{'not_a_sniffer'}) ;
if ($list->{'admin'}{'web_archive_spam_protection'} eq 'cookie'){
return 'arc_protect' unless ($session->{'archive_sniffer'} eq 'false') ;
}
my $arc_path = $wwsconf->{'arc_path'}.'/'.$list->get_list_id();
## Calendar
unless (opendir ARC, $arc_path) {
&report::reject_report_web('user','empty_archives',{},$param->{'action'},$list);
&wwslog('err','do_arc: no directory %s', $arc_path);
return undef;
}
foreach my $dir (sort grep(!/^\./,readdir ARC)) {
if ($dir =~ /^(\d{4})-(\d{2})$/ && -d $arc_path.'/'.$dir.'/arctxt') {
$param->{'calendar'}{$1}{$2} = '???';
if (open(IDX, $arc_path.'/'.$dir.'/index')) {
my ($msgs) = ;
chomp ($msgs);
close IDX;
$param->{'calendar'}{$1}{$2} = $msgs if ($msgs);
}
$latest = $dir;
}
}
closedir ARC;
## Read html file
$in{'month'} ||= $latest;
my $arc_month_path = $arc_path.'/'.$in{'month'};
unless ($in{'arc_file'}) {
undef $latest;
unless (opendir ARC, $arc_month_path) {
&wwslog('err',"unable to readdir $arc_month_path");
&report::reject_report_web('user','month_not_found',{'month' => $in{'month'},
'dir' => $arc_month_path,
'listname' => $param->{'list'}},
$param->{'action'},
$list,$param->{'user'}{'email'},
$robot);
}
foreach my $file (grep(/^$index/,readdir ARC)) {
if ($file =~ /^$index(\d+)\.html$/) {
$latest = $1 if ($latest < $1);
}
}
closedir ARC;
$in{'arc_file'} = $index.$latest.".html";
}
## File exist ?
my $arc_file_path = $arc_month_path.'/'.$in{'arc_file'};
unless (-r $arc_file_path) {
&wwslog('err',"unable to read $arc_file_path");
&report::reject_report_web('user','arc_not_found',{'arc_file' => $in{'arc_file'},
'path' => $arc_file_path,
'listname' => $param->{'list'}},
$param->{'action'},
$list,$param->{'user'}{'email'},
$robot);
return undef;
}
## File type
if ($in{'arc_file'} =~ /^(mail\d+|msg\d+|thrd\d+)\.html$/) {
if ($in{'arc_file'} =~/^(thrd|mail)\d+\.html/) {
$session->{'arc_mode'} = $1;
}
if ($param->{'user'}{'email'}){
if ($param->{'user'}{'prefs'}{'arc_mode'} ne $session->{'arc_mode'}) {
# update user pref as soon as connected user change the way he consult archives
$param->{'user'}{'prefs'}{'arc_mode'} = $session->{'arc_mode'};
&List::update_user_db($param->{'user'}{'email'},{data=>&tools::hash_2_string($param->{'user'}{'prefs'})}) ;
}
}
if ($in{'arc_file'} =~ /^(msg\d+)\.html$/) {
## If the file is a message, load the metadata to find out who is the author of the message
my $metadata = &Archive::load_html_message('file_path' => $arc_file_path);
$param->{'include_picture'} = &tools::make_pictures_url('email' => $metadata->{'X-From'}, 'list' => $list);
$param->{'subtitle'} = $metadata->{'X-Subject'};
}
## Provide a filehandle to the TT2 parser (instead of a filename previously)
## It allows to set the appropriate utf8 binmode on the FH
open $param->{'file_handle'}, "<", $arc_file_path;
&tt2::add_include_path($arc_month_path);
}else {
if ($in{'arc_file'} =~ /\.(\w+)$/) {
$param->{'file_extension'} = $1;
}
$param->{'bypass'} = 1;
$param->{'file'} = $arc_file_path;
}
my @stat = stat ($arc_file_path);
$param->{'date'} = $stat[9];
# send page as static if client is a bot. That's prevent crawling all archices every weeks by google, yahoo and others bots
if ($session->{'is_a_crawler'}) {
$param->{'header_date'} = $stat[9];
}
$param->{'base'} = sprintf "%s%s/arc/%s/%s/%s", $param->{'base_url'}, $param->{'path_cgi'}, $param->{'list'}, $in{'month'}, $in{'arc_file'};
$param->{'archive_name'} = $in{'month'};
return 1;
}
## Access to latest web archives
sub do_latest_arc {
&wwslog('info', 'do_latest_arc(%s,%s,%s)', $in{'list'}, $in{'for'}, $in{'count'});
## Access control
return undef unless (defined &check_authz('do_latest_arc', 'web_archive.access'));
## parameters of the query
my $today = time;
my $oldest_day;
if (defined $in{'for'}) {
$oldest_day = $today - (86400 * ($in{'for'}));
$param->{'for'} = $in{'for'};
unless ($oldest_day >= 0){
&report::reject_report_web('user','nb_days_to_much',{'nb_days' => $in{'for'} },$param->{'action'},$list);
&wwslog('err','do_latest_lists: parameter "for" is too big"');
}
}
my $nb_arc;
my $NB_ARC_MAX = 100;
if (defined $in{'count'}) {
if ($in{'count'} > $NB_ARC_MAX) {
$in{'count'} = $NB_ARC_MAX;
}
$param->{'count'} = $in{'count'};
$nb_arc = $in{'count'};
} else {
$nb_arc = $NB_ARC_MAX;
}
my $arc_path = $wwsconf->{'arc_path'}.'/'.$list->get_list_id();
unless (opendir ARC_DIR, $arc_path) {
&report::reject_report_web('user','empty_archives',{},$param->{'action'},$list);
&wwslog('err','do_latest_arc: no directory %s', $arc_path);
return undef;
}
my @months;
my $latest;
foreach my $dir (sort grep(!/^\./,readdir ARC_DIR)) {
if ($dir =~ /^(\d{4})-(\d{2})$/) {
push @months, $dir;
$latest = $dir;
}
}
closedir ARC_DIR;
@months = reverse @months;
my $stop_search;
my @archives;
## year-month directory
foreach my $year_month (@months) {
if ($nb_arc <= 0) {
last;
}
last if $stop_search;
my $arc_month_path = $arc_path.'/'.$year_month.'/arctxt';
unless (opendir MONTH, $arc_month_path) {
&report::reject_report_web('intern','inaccessible_archive',{'path' => $arc_month_path,
'listname' => $list->{'name'}},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_latest_arc: unable to open directory %s', $arc_month_path);
next;
}
## mails in the year-month directory
foreach my $arc (sort {$b <=> $a} grep(!/^\./,readdir MONTH)) {
last if ($nb_arc <= 0);
if ($arc =~ /^(\d)+$/) {
my %msg_info;
use MIME::Parser;
my $parser = new MIME::Parser;
$parser->output_to_core(1);
my $arc_file = $arc_month_path.'/'.$arc;
unless (open (FILE, $arc_file)) {
&wwslog('err', 'Unable to open file %s', $arc_file);
}
my $message;
unless ($message = $parser->read(\*FILE)) {
&wwslog('err', 'Unable to parse message %s', $arc_file);
next;
}
use Mail::Header;
my $hdr = $message->head;
unless (defined $hdr) {
&wwslog('err', 'Unable to parse header of message %s', $arc_file);
next;
}
foreach my $field ('message-id','subject','from') {
my $var = $field; $var =~ s/-/_/g;
$msg_info{$var} = $hdr->get($field);
if (ref $msg_info{$var} eq 'ARRAY') {
$msg_info{$var} = $msg_info{$var}->[0];
}
## Hide full email address
if ($field eq 'from') {
if ($msg_info{$var} =~ /(.+)\<.+\>/) {
$msg_info{$var} = $1;
}else {
my @email = split /\@/, $msg_info{$var};
$msg_info{$var} = $email[0];
}
}
if ($field eq 'message-id') {
$msg_info{$var} = &tools::clean_msg_id($msg_info{'message_id'});
$msg_info{$var} = &tools::escape_chars($msg_info{$var});
$msg_info{'year_month'} = $year_month;
}else {
$msg_info{$var} = &MIME::EncWords::decode_mimewords($msg_info{$var}, Charset=>'utf8');
$msg_info{$var} = &tools::escape_html($msg_info{$var});
}
}
my $date = $hdr->get('Date');
unless (defined $date) {
&wwslog('err', 'No date found in message %s', $arc_file);
next;
}
my @array_date = &time_utils::parse_date($date);
$msg_info{'date_smtp'} = $date;
$msg_info{'date_epoch'} = &get_timelocal_from_date(@array_date[1..$#array_date]);
$msg_info{'date'} = gettext_strftime "%d %b %Y", localtime($msg_info{'date_epoch'});
if ($msg_info{'date_epoch'} < $oldest_day) {
$stop_search = 1;
last;
}
foreach my $key (keys %msg_info) {
chomp($msg_info{$key});
}
push @archives,\%msg_info;
$nb_arc--;
}
}
closedir MONTH;
}
@{$param->{'archives'}} = sort ({$b->{'date_epoch'} <=> $a->{'date_epoch'}} @archives);
return 1;
}
sub get_timelocal_from_date {
my($mday, $mon, $yr, $hr, $min, $sec, $zone) = @_;
my($time) = 0;
$yr -= 1900 if $yr >= 1900; # if given full 4 digit year
$yr += 100 if $yr <= 37; # in case of 2 digit years
if (($yr < 70) || ($yr > 137)) {
warn "Warning: Bad year (", $yr+1900, ") using current\n";
$yr = (localtime(time))[5];
}
$time = &timelocal($sec,$min,$hr,$mday,$mon,$yr);
return $time
}
####################################################
# do_remove_arc
####################################################
#
# request by list owner or message sender to remove message from archive
# Create in the outgoing spool a file containing the message-id of mesage to be removed
#
# IN : list@host yyyy month and a tab of msgid
#
# OUT : 1 | undef
#
####################################################
sub do_remove_arc {
&wwslog('info', 'do_remove_arc : list %s, yyyy %s, mm %s, #message %s', $in{'list'}, $in{'yyyy'}, $in{'month'});
my $arcpath = $wwsconf->{'arc_path'}.'/'.$list->get_list_id().'/'.$in{'yyyy'}.'-'.$in{'month'};
## Access control
# $in{'msgid'} = &tools::unescape_chars($in{'msgid'});
my @msgids = split /\0/, $in{'msgid'};
if ($#msgids == -1) {
&report::reject_report_web('user','may_not_remove_arc',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','remove_arc: no message id found');
&web_db_log({'parameters' => $in{'msgid'},
'msg_id' => $in{'msgid'},
'status' => 'error',
'error_type' => 'no_msgid'});
$param->{'status'} = 'no_msgid';
return undef;
}
my $file = $Conf{'queueoutgoing'}.'/.remove.'.$list->get_list_id().'.'.$in{'yyyy'}.'-'.$in{'month'}.'.'.time;
unless (open REBUILD, ">$file") {
&report::reject_report_web('intern','cannot_open_file',{'file' => $file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_remove: cannot create %s', $file);
&web_db_log({'parameters' => $in{'msgid'},
'msg_id' => $in{'msgid'},
'status' => 'error',
'error_type' => 'internal'});
closedir ARC;
return undef;
}
foreach my $msgid (@msgids) {
chomp $msgid ;
printf REBUILD ('%s||%s',$msgid,$param->{'user'}{'email'}) ; printf REBUILD "\n";
}
close REBUILD;
&wwslog('info', 'do_remove_arc %d messages marked to be removed by archived', $#msgids+1);
&web_db_log({'parameters' => $in{'msgid'},
'msg_id' => $in{'msgid'},
'status' => 'success'});
$param->{'status'} = 'done';
return 1;
}
####################################################
# do_send_me
####################################################
# Sends a web archive message to a
# requesting user
# It uses mail::mail_forward() to do it.
#
# IN : -
#
# OUT : 'arc' | 1 | undef
#
####################################################
sub do_send_me {
&wwslog('info', 'do_send_me(%s, %s, %s, %s', $in{'list'}, $in{'yyyy'}, $in{'month'}, $in{'msgid'});
if (! $in{'msgid'} ||
$in{'msgid'} =~ /NO-ID-FOUND\.mhonarc\.org/) {
&report::reject_report_web('intern','may_not_send_me',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','send_me: no message id found');
$param->{'status'} = 'no_msgid';
return undef;
}
##
my $arcpath = $wwsconf->{'arc_path'}.'/'.$list->get_list_id().'/'.$in{'yyyy'}.'-'.$in{'month'};
opendir ARC, "$arcpath/arctxt";
my $msgfile;
foreach my $file (grep (!/\./,readdir ARC)) {
&wwslog('debug','send_me: scanning %s', $file);
next unless (open MAIL,"$arcpath/arctxt/$file") ;
while () {
last if /^$/ ;
my $idline = $_;
if (/^Message-id\s*:\s*$/i) {
chomp $idline;
$idline .= ;
}
if ($idline =~ /^Message-id\s*:\s*([^>\s]+)>?\s?/i ) {
my $id = $1;
if ($id eq $in{'msgid'}) {
$msgfile = $file ;
}
last ;
}
}
close MAIL ;
}
if ($msgfile) {
unless (open MSG, "$arcpath/arctxt/$msgfile") {
$param->{'status'} = 'message_err';
&wwslog('info', 'do_send_me : could not read file %s',"$arcpath/arctxt/$msgfile");
}
my $msg_string;
while (){
$msg_string .= $_ ;
}
close MSG;
unless (&mail::mail_forward($msg_string,&Conf::get_robot_conf($robot, 'sympa'),\$param->{'user'}{'email'},$robot)) {
$param->{'status'} = 'message_err';
&wwslog('err',"do_send_me : impossible to send archive file to %s",$param->{'user'}{'email'});
return undef;
}
&wwslog('info', 'do_send_me message %s spooled for %s', "$arcpath/arctxt/$msgfile", $param->{'user'}{'email'} );
&report::notice_report_web('performed',{},$param->{'action'});
$in{'month'} = $in{'yyyy'}."-".$in{'month'};
return 'arc';
}else{
&wwslog('info', 'do_send_me : no file match msgid');
$param->{'status'} = 'not_found';
return undef;
}
return 1;
}
####################################################
# do_view_source
####################################################
# Display message as text/plain in archives
#
# IN : -
#
# OUT : 'arc' | 1 | undef
#
####################################################
sub do_view_source {
&wwslog('info', 'do_view_source(%s, %s, %s, %s', $in{'list'}, $in{'yyyy'}, $in{'month'}, $in{'msgid'});
## Access control
unless (defined &check_authz('do_arc', 'web_archive.access')) {
$param->{'previous_action'} = 'arc';
$param->{'previous_list'} = $list->{'name'};
return undef;
}
if (! $in{'msgid'} ||
$in{'msgid'} =~ /NO-ID-FOUND\.mhonarc\.org/) {
&report::reject_report_web('intern','may_not_view_source',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','view_source: no message id found');
$param->{'status'} = 'no_msgid';
return undef;
}
##
my $arcpath = $wwsconf->{'arc_path'}.'/'.$list->get_list_id().'/'.$in{'yyyy'}.'-'.$in{'month'};
opendir ARC, "$arcpath/arctxt";
my $msgfile;
foreach my $file (grep (!/\./,readdir ARC)) {
&wwslog('debug','view_source: scanning %s', $file);
next unless (open MAIL,"$arcpath/arctxt/$file") ;
while () {
last if /^$/ ;
my $idline = $_;
if (/^Message-id\s*:\s*$/i) {
chomp $idline;
$idline .= ;
}
if ($idline =~ /^Message-id\s*:\s*([^>\s]+)>?\s?/i ) {
my $id = $1;
if ($id eq $in{'msgid'}) {
$msgfile = $file ;
}
last ;
}
}
close MAIL ;
}
if ($msgfile) {
unless (open MSG, "$arcpath/arctxt/$msgfile") {
$param->{'status'} = 'message_err';
&wwslog('info', 'do_view_source : could not read file %s',"$arcpath/arctxt/$msgfile");
}
$param->{'bypass'} = 'extreme';
print "Content-Type: text/plain\n\n";
while (){
print $_;
}
close MSG;
}else{
&wwslog('info', 'do_view_source : no file match msgid');
$param->{'status'} = 'not_found';
return undef;
}
return 1;
}
## Output an initial form to search in web archives
sub do_arcsearch_form {
&wwslog('info', 'do_arcsearch_form(%s)', $param->{'list'});
## Access control
return undef unless (defined &check_authz('do_arcsearch_form', 'web_archive.access'));
my $search_base = $wwsconf->{'arc_path'}.'/'.$list->get_list_id();
opendir ARC, "$search_base";
foreach my $dir (sort {$b cmp $a} grep(!/^\./,readdir ARC)) {
if ($dir =~ /^(\d{4})-(\d{2})$/) {
push @{$param->{'yyyymm'}}, $dir;
}
}
closedir ARC;
$param->{'key_word'} = $in{'key_word'};
$param->{'archive_name'} = $in{'archive_name'};
return 1;
}
## Search in web archives
sub do_arcsearch {
&wwslog('info', 'do_arcsearch(%s)', $param->{'list'});
## Access control
return undef unless (defined &check_authz('do_arcsearch', 'web_archive.access'));
use Marc::Search;
my $search = new Marc::Search;
$search->search_base ($wwsconf->{'arc_path'} . '/' . $list->get_list_id());
$search->base_href (&Conf::get_robot_conf($robot, 'wwsympa_url') . '/arc/' . $param->{'list'});
$search->archive_name ($in{'archive_name'});
unless (defined($in{'directories'})) {
# by default search in current month and in the previous non-empty one
my $archive_name = $in{'archive_name'} || '';
$archive_name = POSIX::strftime '%Y-%m', localtime(time)
unless $archive_name =~ /^\d{4}-\d{2}$/;
my @directories = ();
my $search_base = $wwsconf->{'arc_path'}.'/'.$list->get_list_id();
opendir ARC, "$search_base";
foreach my $dir (sort {$b cmp $a} grep(!/^\./,readdir ARC)) {
if ($dir =~ /^(\d{4})-(\d{2})$/) {
if ($archive_name) {
push @directories, $dir if $dir le $archive_name;
$archive_name = '' if $dir lt $archive_name;
}
push @{$param->{'yyyymm'}}, $dir;
}
}
closedir ARC;
$in{'directories'} = join "\0", @directories;
}
if (defined($in{'directories'})) {
$search->directories ($in{'directories'});
foreach my $dir (split/\0/, $in{'directories'}) {
push @{$param->{'directories'}}, $dir;
}
}
if (defined $in{'previous'}) {
$search->body_count ($in{'body_count'});
$search->date_count ($in{'date_count'});
$search->from_count ($in{'from_count'});
$search->subj_count ($in{'subj_count'});
$search->previous ($in{'previous'});
}
## User didn't enter any search terms
if ($in{'key_word'} =~ /^\s*$/) {
&report::reject_report_web('user','missing_arg',{'argument' => 'key_word'},$param->{'action'});
&wwslog('info','do_arcsearch: no search term');
return undef;
}
$param->{'key_word'} = $in{'key_word'};
$search->limit ($in{'limit'});
$search->age (1)
if ($in{'age'} eq 'new');
$search->match (1)
if (($in{'match'} eq 'partial') or ($in{'match'} eq '1'));
$search->clean_words ($in{'key_word'});
my @clean_words = split(/\s+/, $in{'key_word'});
my @words = @clean_words;
foreach my $w (@words) {
$w = &tools::escape_regexp($w);
$w =~ s,/,\\/,g;
$w = '\b' . $w . '\b'
if $in{'match'} eq 'exact';
}
$search->words(\@words);
$search->key_word (join('|',@words));
if ($in{'case'} eq 'off') {
$search->case(1);
$search->key_word ('(?i)' . $search->key_word);
}
if ($in{'how'} eq 'any') {
$search->function2 ($search->match_any(@words));
$search->how ('any');
}elsif ($in{'how'} eq 'all') {
$search->function1 ($search->body_match_all(@clean_words,@words));
$search->function2 ($search->match_all(@words));
$search->how ('all');
}else {
$search->function2 ($search->match_this(@words));
$search->how ('phrase');
}
$search->subj (defined($in{'subj'}));
$search->from (defined($in{'from'}));
$search->date (defined($in{'date'}));
$search->body (defined($in{'body'}));
$search->body (1)
if ( not ($search->subj)
and not ($search->from)
and not ($search->body)
and not ($search->date));
my $searched = $search->search;
if (defined($search->error)) {
&wwslog('info','do_arcsearch_search_error : %s', $search->error);
}
$search->searched($searched);
if ($searched < $search->file_count) {
$param->{'continue'} = 1;
}
foreach my $field ('list','archive_name','age','body','case','date','from','how','limit','match','subj') {
$param->{$field} = $in{$field};
}
$param->{'body_count'} = $search->body_count;
$param->{'clean_words'} = $search->clean_words;
$param->{'date_count'} = $search->date_count;
$param->{'from_count'} = $search->from_count;
$param->{'subj_count'} = $search->subj_count;
$param->{'num'} = $search->file_count + 1;
$param->{'searched'} = $search->searched;
$param->{'res'} = $search->res;
return 1;
}
## Search message-id in web archives
sub do_arcsearch_id {
&wwslog('info', 'do_arcsearch_id(%s,%s,%s)', $param->{'list'},$in{'archive_name'},$in{'msgid'});
## Access control
return undef unless (defined &check_authz('do_arcsearch_id', 'web_archive.access'));
use Marc::Search;
my $search = new Marc::Search;
$search->search_base ($wwsconf->{'arc_path'} . '/' . $list->get_list_id());
$search->base_href (&Conf::get_robot_conf($robot, 'wwsympa_url') . '/arc/' . $param->{'list'});
$search->archive_name ($in{'archive_name'});
# search in current month and in the previous none empty one
my $search_base = $search->search_base;
my $previous_active_dir ;
opendir ARC, "$search_base";
foreach my $dir (sort {$b cmp $a} grep(!/^\./,readdir ARC)) {
if (($dir =~ /^(\d{4})-(\d{2})$/) && ($dir lt $search->archive_name)) {
$previous_active_dir = $dir;
last;
}
}
closedir ARC;
$in{'archive_name'} = $search->archive_name."\0".$previous_active_dir ;
$search->directories ($in{'archive_name'});
# $search->directories ($search->archive_name);
## User didn't enter any search terms
if ($in{'msgid'} =~ /^\s*$/) {
&report::reject_report_web('user','missing_arg',{'argument' => 'msgid'},$param->{'action'});
&wwslog('info','do_arcsearch_id: no search term');
return undef;
}
$in{'msgid'} = &tools::unescape_chars($in{'msgid'});
$param->{'msgid'} = $in{'msgid'};
$search->limit (1);
$search->clean_words ($in{'msgid'});
my @words = split(/\s+/, $in{'msgid'});
foreach my $w (@words) {
$w = &tools::escape_regexp($w);
$w =~ s,/,\\/,g;
}
$search->words (\@words);
$search->key_word (join('|',@words));
$search->function2 ($search->match_this(@words));
$search->id (1);
my $searched = $search->search;
if (defined($search->error)) {
&wwslog('info','do_arcsearch_id_search_error : %s', $search->error);
}
$search->searched($searched);
$param->{'res'} = $search->res;
unless ($#{$param->{'res'}} >= 0) {
&report::reject_report_web('intern_quiet','archive_not_found',{'msgid'=> $in{'msgid'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','No message found in archives matching Message-ID %s', $in{'msgid'});
return 'arc';
}
$param->{'redirect_to'} = $param->{'res'}[0]{'file'};
return 1;
}
# get pendings lists
sub do_get_pending_lists {
&wwslog('info', 'get_pending_lists');
## Checking families and other virtual hosts.
&get_server_details();
my $all_lists = &List::get_lists($robot,
{ 'filter_query' => [ 'status' => 'pending' ] });
foreach my $list ( @$all_lists ) {
$param->{'pending'}{$list->{'name'}}{'subject'} = $list->{'admin'}{'subject'};
$param->{'pending'}{$list->{'name'}}{'by'} = $list->{'admin'}{'creation'}{'email'};
$param->{'pending'}{$list->{'name'}}{'date'} = gettext_strftime "%d %b %y %H:%M", localtime($list->{'admin'}{'creation'}{'date_epoch'});
}
return 1;
}
# get closed lists
sub do_get_closed_lists {
&wwslog('info', 'get_closed_lists');
## Checking families and other virtual hosts.
&get_server_details();
my $all_lists = &List::get_lists($robot,
{ 'filter_query' => [ 'status' => 'closed|family_closed' ] });
foreach my $list ( @$all_lists ) {
$param->{'closed'}{$list->{'name'}}{'subject'} = $list->{'admin'}{'subject'};
$param->{'closed'}{$list->{'name'}}{'by'} = $list->{'admin'}{'creation'}{'email'};
}
return 1;
}
# get ordered latest lists
sub do_get_latest_lists {
&wwslog('info', 'get_latest_lists');
## Checking families and other virtual hosts.
&get_server_details();
my @unordered_lists;
my $all_lists = &List::get_lists($robot);
foreach my $list ( @$all_lists ) {
push @unordered_lists, {'name' => $list->{'name'},
'subject' => $list->{'admin'}{'subject'},
'creation_date' => $list->{'admin'}{'creation'}{'date_epoch'}};
}
foreach my $l (sort {$b->{'creation_date'} <=> $a->{'creation_date'}} @unordered_lists) {
push @{$param->{'latest_lists'}}, $l;
$l->{'creation_date'} = gettext_strftime "%d %b %Y", localtime($l->{'creation_date'});
}
return 1;
}
# get inactive lists
sub do_get_inactive_lists {
&wwslog('info', 'get_inactive_lists');
## Checking families and other virtual hosts.
&get_server_details();
my @unordered_lists;
my $all_lists = &List::get_lists($robot);
foreach my $list ( @$all_lists ) {
## skip closed lists
if ($list->{'admin'}{'status'} eq 'closed') {
next;
}
my $last_message = 0;
if (open COUNT, $list->{'dir'}.'/msg_count') {
while () {
$last_message = $1 if (/^(\d+)\s/ && ($1 > $last_message));
}
close COUNT;
}else {
&wwslog('info', 'Could not open file %s', $list->{'dir'}.'/msg_count');
}
push @unordered_lists, {
'name' => $list->{'name'},
'creator' =>$list->{'admin'}{'creation'}{'email'},
'send_scenario' =>$list->{'admin'}{'send'}{'name'},
'owners' => join(", ", map {$_->{'email'}} @{$list->{'admin'}{'owner'}}),
'editors' => join(", ", map {$_->{'email'}} @{$list->{'admin'}{'editor'}}),
'subscribers_count' => $list->get_total('nocache'),
'subject' => $list->{'admin'}{'subject'},
'msg_count' => $list->get_msg_count(),
'last_message_epoch' => $last_message,
'last_message_date' => (gettext_strftime "%d %b %Y", localtime($last_message*86400)),
'creation_date_epoch' => $list->{'admin'}{'creation'}{'date_epoch'},
'creation_date' => (gettext_strftime "%d %b %Y", localtime($list->{'admin'}{'creation'}{'date_epoch'})),
};
}
foreach my $l (sort {$a->{'last_message_epoch'} <=> $b->{'last_message_epoch'}} @unordered_lists) {
push @{$param->{'inactive_lists'}}, $l;
}
return 1;
}
## show a list parameters
sub do_set_pending_list_request {
&wwslog('info', 'set_pending_list(%s)',$in{'list'});
my $list_dir = $list->{'dir'};
$param->{'list_config'} = $list_dir.'/config';
if (-f $list_dir.'/info'){
$param->{'list_info_file_exists'} = 1;
}
$param->{'list_info'} = $list_dir.'/info';
$param->{'list_subject'} = $list->{'admin'}{'subject'};
$param->{'list_request_by'} = $list->{'admin'}{'creation'}{'email'};
$param->{'list_request_date'} = $list->{'admin'}{'creation'}{'date'};
$param->{'list_serial'} = $list->{'admin'}{'serial'};
$param->{'list_status'} = $list->{'admin'}{'status'};
&tt2::add_include_path($list->{'dir'});
return 1;
}
## show a list parameters
sub do_install_pending_list {
&wwslog('info', 'do_install_pending_list(%s,%s,%s)',$in{'list'},$in{'status'},$in{'notify'});
unless ($in{'status'} && (($in{'status'} eq 'open') || ($in{'status'} eq 'closed'))) {
&report::reject_report_web('user','missing_arg',{'argument' => 'status'},$param->{'action'});
&wwslog('info', 'Missing status parameter',);
&web_db_log({'parameters' => "$in{'status'},$in{'notify'}",
'status' => 'error',
'error_type' => 'missing_parameter'});
return undef;
}
if ($list->{'admin'}{'status'} eq $in{'status'}) {
&report::reject_report_web('user','didnt_change_anything',{},$param->{'action'});
&wwslog('info','view_pending_list: didn t change really the status, nothing to do');
&web_db_log({'parameters' => "$in{'status'},$in{'notify'}",
'status' => 'error',
'error_type' => 'didnt_change_anything'});
return undef ;
}
$list->{'admin'}{'status'} = $in{'status'};
# open TMP, ">/tmp/dump1";
# &tools::dump_var ($list->{'admin'}, 0, \*TMP);
# close TMP;
unless ($list->save_config($param->{'user'}{'email'})) {
&report::reject_report_web('intern','cannot_save_config',{'listname'=> $list->{'name'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','_create_list: Cannot save config file');
&web_db_log({'parameters' => "$in{'status'},$in{'notify'}",
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
# open TMP, ">/tmp/dump2";
# &tools::dump_var ($list->{'admin'}, 0, \*TMP);
# close TMP;
## create the aliases
if ($in{'status'} eq 'open') {
my $aliases = &admin::install_aliases($list,$robot);
if ($aliases == 1) {
$param->{'auto_aliases'} = 1;
}else {
&report::reject_report_web('intern','failed_to_install_aliases',{'listname'=> $list->{'name'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','Failed to install list aliases');
}
}
## Notify listmasters
my @listmasters = split /\s*,\s*/,
&Conf::get_robot_conf($robot, 'listmaster');
if ($in{'status'} eq 'open') {
unless ($list->send_file('list_created', \@listmasters, $robot, {})) {
&wwslog('notice',"Unable to send template 'list_created' to listmaster");
}
}elsif ($in{'status'} eq 'closed') {
unless ($list->send_file('list_rejected', \@listmasters, $robot, {})) {
&wwslog('notice',"Unable to send template 'list_rejected' to listmaster");
}
}
if ($in{'notify'}) {
my $owners = $list->get_owners();
foreach my $i (@{$owners}) {
## Notify all listowners, even if reception is nomail
next unless ($i->{'email'});
if ($in{'status'} eq 'open') {
unless ($list->send_file('list_created', $i->{'email'}, $robot,{})) {
&wwslog('notice',"Unable to send template 'list_created' to $i->{'email'}");
}
}elsif ($in{'status'} eq 'closed') {
unless ($list->send_file('list_rejected', $i->{'email'}, $robot,{})) {
&wwslog('notice',"Unable to send template 'list_rejected' to $i->{'email'}");
}
}
}
}
$param->{'status'} = $in{'status'};
$list = $param->{'list'} = $in{'list'} = undef;
return 'get_pending_lists';
&web_db_log({'parameters' => "$in{'status'},$in{'notify'}",
'status' => 'success'});
return 1;
}
=pod
=head2 sub do_create_list
Creates a list using a list template
=head3 Arguments
=over
=item * I
=back
=head3 Return
=over
=item * I<1>, if no problem is encountered
=item * I, if anything goes wrong
=item * I<'loginrequest'> if no user is logged in at the time the function is called.
=back
=head3 Calls
=over
=item * web_db_log
=item * wwslog
=item * admin::create_list_old
=item * check_param_in
=item * List::send_notify_to_listmaster
=item * report::reject_report_web
=back
=cut
## create a liste using a list template.
sub do_create_list {
&wwslog('info', 'do_create_list(%s,%s,%s)',$in{'listname'},$in{'subject'},$in{'template'});
## Check that all the needed arguments are present.
## This is checked here because it requires to return the incomplete form to the user
foreach my $arg ('listname','subject','template','info','topics') {
unless ($in{$arg}) {
&report::reject_report_web('user','missing_arg',{'argument' => $arg},$param->{'action'});
&wwslog('info','do_create_list: missing param %s', $arg);
&web_db_log({'parameters' => $in{'listname'},
'list' => $in{'listname'},
'status' => 'error',
'error_type' => 'missing_parameter'});
return 'create_list_request';
}
}
## Lowercase listname if required
if ($in{'listname'} =~ /[A-Z]/) {
$in{'listname'} = lc($in{'listname'});
&report::notice_report_web('listname_lowercased',{},$param->{'action'});
}
$param->{'create_action'} = $param->{'create_list'};
&wwslog('info',"do_create_list, get action : $param->{'create_action'} ");
## If the action is forbidden, stop here.
if ($param->{'create_action'} =~ /reject/) {
&report::reject_report_web('auth',$param->{'reason'},{},$param->{'action'},$list);
&wwslog('info','do_create_list: not allowed');
&web_db_log({'parameters' => $in{'listname'},
'list' => $in{'listname'},
'status' => 'error',
'error_type' => 'authorization'});
return 'home';
## If the action is reserved to listmaster, note that it will have to be moderated
}elsif ($param->{'create_action'} =~ /listmaster/i) {
$param->{'status'} = 'pending' ;
## If the action is plainly authorized, note that it will be executed.
}elsif ($param->{'create_action'} =~ /do_it/i) {
$param->{'status'} = 'open' ;
## If the action hasn't an authorization status, stop here.
}else{
&report::reject_report_web('intern','internal_scenario_error_create_list',{},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_create_list: internal error in scenario create_list');
&web_db_log({'parameters' => $in{'listname'},
'list' => $in{'listname'},
'status' => 'error',
'error_type' => 'internal'});
return 'create_list_request';
}
## 'other' topic means no topic
$in{'topics'} = undef if ($in{'topics'} eq 'other');
## Store creation parameters.
my %owner;
$owner{'email'} = $param->{'user'}{'email'};
$owner{'gecos'} = $param->{'user'}{'gecos'};
my $parameters;
push @{$parameters->{'owner'}},\%owner;
$parameters->{'listname'} = $in{'listname'};
$parameters->{'subject'} = $in{'subject'};
$parameters->{'creation_email'} = $param->{'user'}{'email'};
$parameters->{'lang'} = $param->{'lang'};
$parameters->{'status'} = $param->{'status'};
$parameters->{'topics'} = $in{'topics'};
$parameters->{'description'} = $in{'info'};
$parameters->{'custom_input'} = $in{'custom_input'};
## create liste
if (my $testlist = new List($in{'listname'},$robot)){
&report::reject_report_web('user','list_already_exists',{'new_listname'=> $in{'listname'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_create_list: requested list %s already exist (from %s)',$in{'listname'},$param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'listname'},
'list' => $in{'listname'},
'status' => 'error',
'error_type' => 'user'});
return 'create_list_request';
}
my $resul = &admin::create_list_old($parameters,$in{'template'},$robot,"web");
unless(defined $resul) {
&report::reject_report_web('intern','create_list',{},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_create_list: unable to create list %s for %s',$in{'listname'},$param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'listname'},
'list' => $in{'listname'},
'status' => 'error',
'error_type' => 'internal'});
return 'create_list_request';
}
## Create list object
$in{'list'} = $in{'listname'};
&check_param_in();
if ($param->{'create_action'} =~ /do_it/i) {
if ($resul->{'aliases'} == 1) {
$param->{'auto_aliases'} = 1;
}else {
&report::reject_report_web('intern','failed_to_install_aliases',{'listname'=> $in{'listname'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','Failed to install list aliases');
}
}
## notify listmaster
my $list = List->new($in{'listname'}, $robot);
unless (defined $list) {
&wwslog('info',"failed to create list object for list '%s'",$in{'listname'});
&report::reject_report_web('intern','create_list',{},$param->{'action'},'',$param->{'user'}{'email'},$robot);
return 'create_list_request';
}
if ($param->{'create_action'} =~ /notify/) {
&wwslog('info','notify listmaster');
unless (&List::send_notify_to_listmaster('request_list_creation',$robot,
{'list' => $list,
'email' => $param->{'user'}{'email'}})) {
&wwslog('notice',"Unable to send notify 'request_list_creation' to listmaster");
}
}
&web_db_log({'parameters' => $in{'listname'},
'list' => $in{'listname'},
'status' => 'success'});
$in{'list'} = $resul->{'list'}{'name'};
&check_param_in();
$param->{'listname'} = $resul->{'list'}{'name'};
return 1;
}
=pod
=head2 sub do_create_list_request
Sends back the list creation edition form.
=head3 Arguments
=over
=item * I
=back
=head3 Return
=over
=item * I<1>, if no problem is encountered
=item * I, if anything goes wrong
=item * I<'loginrequest'> if no user is logged in at the time the function is called.
=back
=head3 Calls
=over
=item * wwslog
=item * _prepare_edit_form
=item * List::request_action
=item * List::load_topics
=item * tools::get_list_list_tpl
=item * report::reject_report_web
=back
=cut
## Return the creation form
sub do_create_list_request {
&wwslog('info', 'do_create_list_request()');
my $result = &Scenario::request_action('create_list',$param->{'auth_method'},$robot,
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $r_action;
my $reason;
if (ref($result) eq 'HASH') {
$r_action = $result->{'action'};
$reason = $result->{'reason'};
}
$param->{'create_action'} = $r_action;
## Initialize the form
## When returning to the form
foreach my $p ('listname','template','subject','topics','info') {
$param->{'saved'}{$p} = $in{$p};
}
if ($param->{'create_action'} =~ /reject/) {
&report::reject_report_web('auth',$reason,{},$param->{'action'},$list);
&wwslog('info','do_create_list: not allowed');
return undef;
}
my %topics;
unless (%topics = &List::load_topics($robot)) {
&report::reject_report_web('intern','unable_to_load_list_of_topics',{},$param->{'action'},'',$param->{'user'}{'email'},$robot);
}
$param->{'list_of_topics'} = \%topics;
$param->{'list_of_topics'}{$in{'topics'}}{'selected'} = 1
if ($in{'topics'});
unless ($param->{'list_list_tpl'} = &tools::get_list_list_tpl($robot)) {
&report::reject_report_web('intern','unable_to_load_create_list_templates',{},$param->{'action'},'',$param->{'user'}{'email'},$robot);
}
$allow_absolute_path = 1;
foreach my $template (keys %{$param->{'list_list_tpl'}}){
$param->{'tpl_count'} ++ ;
}
$param->{'list_list_tpl'}{$in{'template'}}{'selected'} = 1
if ($in{'template'});
return 1 ;
}
## WWSympa Home-Page
sub do_home {
&wwslog('info', 'do_home');
return 1;
}
sub do_editsubscriber {
&wwslog('info', 'do_editsubscriber(%s)', $in{'email'});
my $subscriber;
$in{'email'} = &tools::unescape_chars($in{'email'});
unless($subscriber = $list->get_subscriber($in{'email'})) {
&report::reject_report_web('intern','subscriber_not_found',{'email' => $in{'email'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_editsubscriber: subscriber %s not found', $in{'email'});
return undef;
}
$param->{'current_subscriber'} = $subscriber;
$param->{'current_subscriber'}{'escaped_email'} = &tools::escape_html($param->{'current_subscriber'}{'email'});
$param->{'current_subscriber'}{'escaped_bounce_address'} = &tools::escape_html($param->{'current_subscriber'}{'bounce_address'});
$param->{'current_subscriber'}{'date'} = gettext_strftime "%d %b %Y", localtime($subscriber->{'date'});
$param->{'current_subscriber'}{'update_date'} = gettext_strftime "%d %b %Y", localtime($subscriber->{'update_date'});
$param->{'current_subscriber'}{'pictures_url'} = &tools::make_pictures_url('email' => $subscriber->{'email'}, 'list' => $list);
## Prefs
$param->{'current_subscriber'}{'reception'} ||= 'mail';
$param->{'current_subscriber'}{'visibility'} ||= 'noconceal';
## Get language from user_table
my $user = &List::get_user_db($in{'email'});
$param->{'current_subscriber'}{'lang'} = &Language::GetLangName($user->{'lang'});
foreach my $m ($list->available_reception_mode) {
$param->{'reception'}{$m}{'description'} =
$list->get_option_title($m, 'reception');
if ($param->{'current_subscriber'}{'reception'} eq $m) {
$param->{'reception'}{$m}{'selected'} = ' selected';
}else {
$param->{'reception'}{$m}{'selected'} = '';
}
}
foreach my $m (qw(conceal noconceal)) {
$param->{'visibility'}{$m}{'description'} =
$list->get_option_title($m, 'visibility');
if ($param->{'current_subscriber'}{'visibility'} eq $m) {
$param->{'visibility'}{$m}{'selected'} = ' selected';
}else {
$param->{'visibility'}{$m}{'selected'} = '';
}
}
## Bounces
if ($subscriber->{'bounce'} =~ /^(\d+)\s+(\d+)\s+(\d+)(\s+(.*))?$/) {
my @bounce = ($1, $2, $3, $5);
$param->{'current_subscriber'}{'first_bounce'} = gettext_strftime "%d %b %Y", localtime($bounce[0]);
$param->{'current_subscriber'}{'last_bounce'} = gettext_strftime "%d %b %Y", localtime($bounce[1]);
$param->{'current_subscriber'}{'bounce_count'} = $bounce[2];
if ($bounce[3] =~ /^(\d+\.(\d+\.\d+))$/) {
$subscriber->{'bounce_code'} = $1;
$subscriber->{'bounce_status'} = $wwslib::bounce_status{$2};
}
$param->{'previous_action'} = $in{'previous_action'};
}
## Additional DB fields
if ($Conf{'db_additional_subscriber_fields'}) {
my @additional_fields = split ',', $Conf{'db_additional_subscriber_fields'};
my %data;
foreach my $field (@additional_fields) {
## Is the Database defined
unless ($Conf{'db_name'}) {
&wwslog('info', 'No db_name defined in configuration file');
return undef;
}
## Check field type (enum or not) with MySQL
$data{$field}{'type'} = &List::get_db_field_type('subscriber_table', $field);
if ($data{$field}{'type'} =~ /^enum\((\S+)\)$/) {
my @enum = split /,/,$1;
foreach my $e (@enum) {
$e =~ s/^\'([^\']+)\'$/$1/;
$data{$field}{'enum'}{$e} = '';
}
$data{$field}{'type'} = 'enum';
$data{$field}{'enum'}{$subscriber->{$field}} = 'selected="selected"'
if (defined $subscriber->{$field});
}else {
$data{$field}{'type'} = 'string';
$data{$field}{'value'} = $subscriber->{$field};
}
}
$param->{'additional_fields'} = \%data;
}
$param->{'previous_action'} = $in{'previous_action'};
return 1;
}
sub do_viewbounce {
&wwslog('info', 'do_viewbounce(%s)', $in{'email'});
my $escaped_email = &tools::escape_chars($in{'email'});
$param->{'lastbounce_path'} = $list->get_bounce_dir().'/'.$escaped_email;
unless (-r $param->{'lastbounce_path'}) {
&report::reject_report_web('user','no_bounce_user',{'email'=>$in{'email'}},$param->{'action'},$list);
&wwslog('info','do_viewbounce: no bounce %s', $param->{'lastbounce_path'});
return undef;
}
$allow_absolute_path = 1;
return 1;
}
## some help for listmaster and developpers
sub do_scenario_test {
&wwslog('info', 'do_scenario_test');
## List available scenarii
unless (opendir SCENARI, Sympa::Constants::DEFAULTDIR.'/scenari/'){
&report::reject_report_web('intern','cannot_open_dir',{'dir' => Sympa::Constants::DEFAULTDIR.'/scenari/'},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info',"do_scenario_test : unable to open %s/scenari", Sympa::Constants::DEFAULTDIR);
return undef;
}
foreach my $scfile (readdir SCENARI) {
if ($scfile =~ /^(\w+)\.(\w+)/ ) {
$param->{'scenario'}{$1}{'defined'}=1 ;
}
}
closedir SCENARI;
my $all_lists = &List::get_lists('*');
foreach my $list ( @$all_lists ) {
$param->{'listname'}{$list->{'name'}}{'defined'}=1 ;
}
foreach my $a ('smtp','md5','smime') {
#$param->{'auth_method'}{$a}{'define'}=1 ;
$param->{'authmethod'}{$a}{'defined'}=1 ;
}
$param->{'scenario'}{$in{'scenario'}}{'selected'} = 'selected="selected"' if $in{'scenario'};
$param->{'listname'}{$in{'listname'}}{'selected'} = 'selected="selected"' if $in{'listname'};
$param->{'authmethod'}{$in{'auth_method'}}{'selected'} = 'selected="selected"' if $in{'auth_method'};
$param->{'email'} = $in{'email'};
if ($in{'scenario'}) {
my $operation = $in{'scenario'};
&wwslog('debug3', 'do_scenario_test: perform scenario_test');
my $result = &Scenario::request_action ($operation,$in{'auth_method'},$robot,
{'listname' => $in{'listname'},
'sender' => $in{'sender'},
'email' => $in{'email'},
'remote_host' => $in{'remote_host'},
'remote_addr' => $in{'remote_addr'}},'debug');
if (ref($result) eq 'HASH'){
$param->{'scenario_action'} = $result->{'action'};
$param->{'scenario_condition'} = $result->{'condition'};
$param->{'scenario_auth_method'} = $result->{'auth_method'};
$param->{'scenario_reason'} = $result->{'reason'};
}
}
return 1;
}
## Bouncing addresses review
sub do_reviewbouncing {
&wwslog('info', 'do_reviewbouncing(%s)', $in{'page'});
my $size = $in{'size'} || $wwsconf->{'review_page_size'};
## Owner
$param->{'page'} = $in{'page'} || 1;
if ($size eq 'all') {
$param->{'total_page'} = $param->{'bounce_total'};
}else {
$param->{'total_page'} = int ( $param->{'bounce_total'} / $size);
$param->{'total_page'} ++
if ($param->{'bounce_total'} % $size);
}
if ($param->{'total_page'} > 0 and ($param->{'page'} > $param->{'total_page'})) {
&report::reject_report_web('user','no_page',{'page' => $param->{'page'}},$param->{'action'});
&wwslog('info','do_reviewbouncing: no page %d', $param->{'page'});
return 'admin';
}
my @users;
## Members list
for (my $i = $list->get_first_bouncing_user(); $i; $i = $list->get_next_bouncing_user()) {
$i->{'bounce'} =~ /^(\d+)\s+(\d+)\s+(\d+)(\s+(.*))?$/;
$i->{'first_bounce'} = $1;
$i->{'last_bounce'} = $2;
$i->{'bounce_count'} = $3;
if ($5 =~ /^(\d+)\.\d+\.\d+$/) {
$i->{'bounce_class'} = $1;
}
## Define color in function of bounce_score
if ($i->{'bounce_score'} <= $list->{'admin'}{'bouncers_level1'}{'rate'}) {
$i->{'bounce_level'} = 0;
}elsif ($i->{'bounce_score'} <= $list->{'admin'}{'bouncers_level2'}{'rate'}){
$i->{'bounce_level'} = 1;
}else{
$i->{'bounce_level'} = 2;
}
push @users, $i;
}
my $record;
foreach my $i (sort
{($b->{'bounce_score'} <=> $a->{'bounce_score'}) ||
($b->{'last_bounce'} <=> $a->{'last_bounce'}) ||
($b->{'bounce_class'} <=> $a->{'bounce_class'}) }
@users) {
$record++;
if (($size ne 'all') && ($record > ( $size * ($param->{'page'} ) ) ) ) {
$param->{'next_page'} = $param->{'page'} + 1;
last;
}
next if (($size ne 'all') && ($record <= ( ($param->{'page'} - 1) * $size)));
$i->{'first_bounce'} = gettext_strftime "%d %b %Y", localtime($i->{'first_bounce'});
$i->{'last_bounce'} = gettext_strftime "%d %b %Y", localtime($i->{'last_bounce'});
## Escape some weird chars
$i->{'escaped_email'} = &tools::escape_chars($i->{'email'});
push @{$param->{'members'}}, $i;
}
if ($param->{'page'} > 1) {
$param->{'prev_page'} = $param->{'page'} - 1;
}
$param->{'size'} = $size;
return 1;
}
sub do_resetbounce {
&wwslog('info', 'do_resetbounce()');
$in{'email'} = &tools::unescape_chars($in{'email'});
my @emails = split /\0/, $in{'email'};
foreach my $email (@emails) {
my $escaped_email = &tools::escape_chars($email);
unless ( $list->is_user($email) ) {
&report::reject_report_web('user','not_subscriber',{'email'=> $email},$param->{'action'},$list);
&wwslog('info','do_del: %s not subscribed', $email);
&web_db_log({'status' => 'error',
'error_type' => 'not_subscriber'});
return undef;
}
unless( $list->update_user($email, {'bounce' => 'NULL', 'update_date' => time, 'score' => 0})) {
&report::reject_report_web('intern','update_subscriber_db_failed',{'sub'=> $email},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_resetbounce: failed update database for %s', $email);
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
my $bounce_dir = $list->get_bounce_dir();
unless (unlink $bounce_dir.'/'.$escaped_email) {
&wwslog('info','do_resetbounce: failed deleting %s', $bounce_dir.'/'.$escaped_email);
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
}
&wwslog('info','do_resetbounce: bounces for %s reset ', $email);
&web_db_log({'status' => 'success'});
}
return $in{'previous_action'} || 'review';
}
## Rebuild an archive using arctxt/
sub do_rebuildarc {
&wwslog('info', 'do_rebuildarc(%s, %s)', $param->{'list'}, $in{'month'});
my $file = $Conf{'queueoutgoing'}.'/.rebuild.'.$list->get_list_id();
unless (open REBUILD, ">$file") {
&report::reject_report_web('intern','cannot_open_file',{'file' => $file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_rebuildarc: cannot create %s', $file);
&web_db_log({'parameters' => $in{'month'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
&wwslog('info', 'File: %s', $file);
print REBUILD ' ';
close REBUILD;
&report::notice_report_web('performed_soon',{},$param->{'action'});
&web_db_log({'parameters' => $in{'month'},
'status' => 'success'});
return 'admin';
}
## Rebuild all archives using arctxt/
sub do_rebuildallarc {
&wwslog('info', 'do_rebuildallarc');
my $all_lists = &List::get_lists($robot);
foreach my $list ( @$all_lists ) {
next unless (defined $list->{'admin'}{'web_archive'});
my $file = $Conf{'queueoutgoing'}.'/.rebuild.'.$list->get_list_id();
unless (open REBUILD, ">$file") {
&report::reject_report_web('intern','cannot_open_file',{'file' => $file},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_rebuildarc: cannot create %s', $file);
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
&wwslog('info', 'File: %s', $file);
print REBUILD ' ';
close REBUILD;
}
&report::notice_report_web('performed_soon',{},$param->{'action'});
&web_db_log({'status' => 'success'});
return 'serveradmin';
}
## Search among lists
sub do_edit_attributes {
&wwslog('info', 'do_edit_attributes(%s)', $in{'filter'});
return 1;
}
## Search among lists
sub do_search_list {
&wwslog('info', 'do_search_list(%s)', $in{'filter'});
unless ($in{'filter'}) {
&report::reject_report_web('user','no_filter',{},$param->{'action'});
&wwslog('info','do_search_list: no filter');
return undef;
}elsif ($in{'filter'} =~ /[<>\\\*\$]/) {
&report::reject_report_web('user','syntax_errors',{'params' => 'filter'},$param->{'action'});
&wwslog('err','do_search_list: syntax error');
return undef;
}
## Regexp
$param->{'filter'} = $in{'filter'};
$param->{'regexp'} = &tools::escape_regexp($param->{'filter'});
## Members list
my $record = 0;
my $all_lists = &List::get_lists($robot);
foreach my $list ( @$all_lists ) {
my $is_admin;
## Search filter
my $regtest = eval { (($list->{'name'} !~ /$param->{'regexp'}/i)
&& ($list->{'admin'}{'subject'} !~ /$param->{'regexp'}/i)) };
unless (defined($regtest)) {
&report::reject_report_web('user','syntax_errors',{'params' => 'filter'},$param->{'action'});
&wwslog('err','do_search_list: syntax error');
return undef;
}
next if $regtest;
my $result = $list->check_list_authz('visibility',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $r_action;
$r_action = $result->{'action'} if (ref($result) eq 'HASH');
next unless ($r_action eq 'do_it');
if ($param->{'user'}{'email'} &&
($list->am_i('owner',$param->{'user'}{'email'}) ||
$list->am_i('editor',$param->{'user'}{'email'})) ) {
$is_admin = 1;
}
$record++;
$param->{'which'}{$list->{'name'}} = {'host' => $list->{'admin'}{'host'},
'subject' => $list->{'admin'}{'subject'},
'admin' => $is_admin,
'export' => 'no'};
}
$param->{'occurrence'} = $record;
foreach my $listname (sort keys %{$param->{'which'}}) {
if ($listname =~ /^([a-z])/){
push @{$param->{'orderedlist'}{$1}}, $listname ;
}else{
push @{$param->{'orderedlist'}{'others'}}, $listname ;
}
}
return 1;
}
sub do_edit_list {
&wwslog('info', 'do_edit_list()');
## Check if the list belong to a family.
my $family;
if (defined $list->{'admin'}{'family_name'}) {
unless ($family = $list->get_family()) {
&report::reject_report_web('intern','unable_get_family',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_edit_list : impossible to get list %s\'s family',$list->{'name'});
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
## This hash will contain all the data gathered from the edit list form.
## The keys are the parameter names.
## The values are either the parameter value or an array containing this value if this is a multiple values parameter.
## The value can be a scalar or a hash.
my $new_admin = {};
## This hash contains the names of all the parameters sent by the form to the FCGI.
## The keys are the parameters name, the value is always 1.
## Used only to parse the data.
my $edited_param = {};
## Parse all the data sent from the web interface to the FCGI.
## Fills the $new_admin and $edited_param hashes.
foreach my $key (sort keys %in) {
next unless ($key =~ /^(single_param|multiple_param)\.(\S+)$/);
$key =~ /^(single_param|multiple_param)\.(\S+)$/;
my ($type, $name) = ($1, $2);
## Tag parameter as present in the form
if ($name =~ /^([^\.]+)(\.)/ ||
$name =~ /^([^\.]+)$/) {
$edited_param->{$1} = 1;
}
## Parameter value
my $value = $in{$key};
next if ($value =~ /^\s*$/);
## If the parameter is a multiple values parameter, store the values into an array.
if ($type eq 'multiple_param') {
my @values = split /\0/, $value;
$value = \@values;
}
my @token = split (/\./, $name);
## make it an entry in $new_admin
my $var = &_shift_var(0, $new_admin, @token);
$$var = $value;
}
## Check that the serial number sent by the form is the same as the one we expect.
## Avoid modifying a list previously modified by another way.
unless ($list->{'admin'}{'serial'} == $in{'serial'}) {
&report::reject_report_web('user','config_changed',{'email' => $list->{'admin'}{'update'}{'email'}},$param->{'action'},$list);
&wwslog('info','do_edit_list: Config file has been modified(%d => %d) by %s. Cannot apply changes', $in{'single_param.serial'}, $list->{'admin'}{'serial'}, $list->{'admin'}{'update'}{'email'});
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
## Check changes & check syntax
## %changed stores the names of the parameters whose values differs from the value in the config file.
## %stores the name of parameter for which values have been deleted. The keys are the parameter name, the values are the index of the deleted value.
## @syntax_error stores the list of parameters for which syntax errors wre founs while evaluating the data sent by the form.
my (%changed, %delete);
my @syntax_error;
## Check family constraints.
## %check_family is a hash whose keys are a parameter name and whose values are the constraints
## defined for this parameter.
my %check_family;
## Getting changes about owners or editors
## If changes occured in the owner or editor definition, these scalars are set to 1.
my $owner_update = 0;
my $editor_update = 0;
######################################################################
## Start of the loop parsing the data sent by the edition form. ##
######################################################################
foreach my $pname (sort List::by_order keys %{$edited_param}) {
## $p will contain the values of the current parameter in the previous list config
## $new_p will contain the values sent by the form for the current parameter.
my ($p, $new_p);
## Check privileges first
next unless ($list->may_edit($pname,$param->{'user'}{'email'}) eq 'write');
## If the list belongs to a family, gather all the constraints for each edited parameter.
if (ref($family) eq 'Family') {
if ((ref($::pinfo{$pname}{'format'}) ne 'HASH') && (!ref($pname))) { # simple parameter
my $constraint = $family->get_param_constraint($pname);
if (ref($constraint) eq 'HASH') { # controlled parameter
$check_family{$pname} = $constraint;
} elsif ($constraint ne '0') { # fixed parameter (free : no control)
next;
}
}
}
## Skip the obsolete parameters.
next if $pinfo->{$pname}{'obsolete'};
## $to_index value will correspond to the number of not empty parameters sent by the form.
my $to_index;
####### Validation, step 1: remove empty entries ###########
## If the parameter can have multiple values...
if ($pinfo->{$pname}{'occurrence'} =~ /n$/) {
## They were either entries removed by the user or empty entries added by wwsympa
## The loop is going backward so we can remove empty entries
my @all = 0..$#{$new_admin->{$pname}};
foreach my $i (reverse @all ) {
## If the parameter has a complex structure
if (ref ($pinfo->{$pname}{'format'}) eq 'HASH') {
## Check each component of the complex parameter.
foreach my $key (keys %{$pinfo->{$pname}{'format'}}) {
## As soon as a required component is found missing, the whole parameter instance is removed.
if ($pinfo->{$pname}{'format'}{$key}{'occurrence'} =~ /^1/ &&
$new_admin->{$pname}[$i]{$key} =~ /^\s*$/ ) {
splice(@{$new_admin->{$pname}}, $i, 1);
last;
}
}
## Else if the parameter has only a scalar value
}else {
## Remove if empty
if ($new_admin->{$pname}[$i] =~ /^\s*$/) {
splice(@{$new_admin->{$pname}}, $i, 1);
next;
}
}
}
## Now, %new_admin contains only entries for which all the mandatory values are accounted for.
## $last_index corresponds to the number of remaining instances of this param sent by the form.
my $last_index = $#{$new_admin->{$pname}};
## If a mandatory parameter is missing, issue an error and stop here.
if ($pinfo->{$pname}{'occurrence'} =~ /^1/ && !($last_index >= 0)){
delete $new_admin->{$pname};
&wwslog('err','Error: Parameter %s is mandatory.', $pname);
&report::reject_report_web('user','mandatory_parameter',{'p_name' => $pname},$param->{'action'},$list);
&web_db_log({'status' => 'error',
'error_type' => 'syntax_errors'});
next;
}
## If there are less entries in the config file than were sent by the form,
## $to_index must correspond to the number of entries sent.
if ($#{$list->{'admin'}{$pname}} < $last_index) {
$to_index = $last_index;
## Otherwise, $to_index must correspond to the number of entries in the config file.
}else {
$to_index = $#{$list->{'admin'}{$pname}};
}
$p = $list->{'admin'}{$pname};
$new_p = $new_admin->{$pname};
## If the parameter can't have multiple values...
}else {
## If the parameter has a complex structure
if (ref ($pinfo->{$pname}{'format'}) eq 'HASH') {
## Check each component of the complex parameter.
foreach my $key (keys %{$pinfo->{$pname}{'format'}}) {
## Remove the full record if a component is emtpy and required
if ($pinfo->{$pname}{'format'}{$key}{'occurrence'} =~ /^1/ &&
$new_admin->{$pname}{$key} =~ /^\s*$/ ) {
delete $new_admin->{$pname};
last;
}
}
## If the parameter contains a simple scalar value.
}else {
## Remove if empty
if ($new_admin->{$pname} =~ /^\s*$/) {
delete $new_admin->{$pname};
}
}
$p = [$list->{'admin'}{$pname}];
$new_p = [$new_admin->{$pname}];
}
####### Validation, step 2: - check if the parameter was modified. ###########
####### - check that the new values have the right syntax. ###########
####### Note: this step is performed for each occurrence of the parameter. ###########
foreach my $i (0..$to_index) {
unless (defined $new_p->[$i]) {
push @{$delete{$pname}}, $i;
$changed{$pname} = 1; next;
}
## If the parameter corresponds to a scenario or a task, mark it as changed if its name was changed.
## Example: 'subscribe'
if ($pinfo->{$pname}{'scenario'} ||
$pinfo->{$pname}{'task'} ) {
if ($p->[$i]{'name'} ne $new_p->[$i]{'name'}) {
$changed{$pname} = 1; next;
}
## If the parameter has a complex structure, we need to check all its components.
## Example: 'owner'
}elsif (ref ($pinfo->{$pname}{'format'}) eq 'HASH') {
## Check each parameter component.
## Example: 'owner->email'
foreach my $key (keys %{$pinfo->{$pname}{'format'}}) {
## Check that the user is allowed to edit this parameter component.
next unless ($list->may_edit("$pname.$key",$param->{'user'}{'email'}) eq 'write');
## If the list belongs to a family, check the possible constraints on this parameter component.
if (ref($family) eq 'Family') {
## Test constraints only if the parameter component is not a complex structure.
if (!ref($key)) {
my $constraint = $family->get_param_constraint("$pname.$key");
if (ref($constraint) eq 'HASH') { # controlled parameter
$check_family{$pname}{$key} = $constraint;
} elsif ($constraint ne '0') { # fixed parameter
next; # Go to the next parameter component.
}
}
}
## If the parameter component corresponds to a task or a scenario, mark it as changed if its name was changed.
if ($pinfo->{$pname}{'format'}{$key}{'scenario'} ||
$pinfo->{$pname}{'format'}{$key}{'task'} ) {
if ($p->[$i]{$key}{'name'} ne $new_p->[$i]{$key}{'name'}) {
$changed{$pname} = 1; next; # Mark as changed and go to the next parameter component.
}
## If the parameter component doesn't correspond to a task or a scenario, we must check its content.
}else{
## Parameter component check, case 1: this parameter component can have multiple occurence.
## Example: 'digest->days'
if ($pinfo->{$pname}{'format'}{$key}{'occurrence'} =~ /n$/) {
## If the new value differs from the previous value, mark as changed and go to the next parameter component.
if ($#{$p->[$i]{$key}} != $#{$new_p->[$i]{$key}}) {
$changed{$pname} = 1; next;
}
## For each occurrence of this parameter component, check value
foreach my $index (0..$#{$p->[$i]{$key}}) {
my $format = $pinfo->{$pname}{'format'}{$key}{'format'};
## If the format has a complex structure, it is the description of a file format.
if (ref ($format)) {
$format = $pinfo->{$pname}{'format'}{$key}{'file_format'};
}
## If this occurrence of the parameter component differs from the corresponding one in the config
## check the syntax and mark as changed.
if ($p->[$i]{$key}[$index] ne $new_p->[$i]{$key}[$index]) {
if (defined($new_p->[$i]{$key}[$index]) && $new_p->[$i]{$key}[$index] !~ /^$format$/i) {
&wwslog('err', "Syntax error : $pname/$i/$key/$index = $new_p->[$i]{$key}[$index]");
push @syntax_error, $pname;
}
$changed{$pname} = 1; next; # Mark as changed and go to the next parameter component.
}
}
## Parameter component check, case 2: this component is limited to one occurence.
## Example: 'owner->email'
}else {
## If the parameter component value differs from the corresponding one in the config, go on.
if ($p->[$i]{$key} ne $new_p->[$i]{$key}) {
my $format = $pinfo->{$pname}{'format'}{$key}{'format'};
## If the format has a complex structure, it is the description of a file format.
if (ref ($format)) {
$format = $pinfo->{$pname}{'format'}{$key}{'file_format'};
}
## Check the syntax and mark as changed if the syntax is correct.
if (defined($new_p->[$i]{$key}) && $new_p->[$i]{$key} !~ /^$format$/i) {
&wwslog('err', "Syntax error : $pname/$i/$key = $new_p->[$i]{$key}");
push @syntax_error, $pname;
}
$changed{$pname} = 1; next; # Mark as changed and go to the next parameter component.
}
}
}
}
## If the parameter has just a scalar value, just check its value.
## Example: 'max_size'
}else {
## If the value differs from the one in the config file, mark parameter as changed if the syntax is correct.
if ($p->[$i] ne $new_p->[$i]) {
unless ($new_p->[$i] =~ /^$pinfo->{$pname}{'file_format'}$/) {
&wwslog('err', "Syntax error : $pname/$i = $new_p->[$i]");
push @syntax_error, $pname;
}
$changed{$pname} = 1;
}
}
}
}
######################################################################
## Validation of the form finished. Start of valid data treatments ##
######################################################################
## Error if no parameter was edited
unless (keys %changed) {
&report::reject_report_web('user','no_parameter_edited',{},$param->{'action'},$list);
&wwslog('info','No parameter was edited by user');
return 'edit_list_request';
}
## Syntax errors
if ($#syntax_error > -1) {
&report::reject_report_web('user','syntax_errors',{'params' => 'filter'},$param->{'action'},$list);
&wwslog('info','do_edit_list: Syntax errors for parameters %s', join(',', @syntax_error));
&web_db_log({'status' => 'error',
'error_type' => 'syntax_errors'});
return undef;
}
## Checking no topic named "other"
foreach my $msg_topic (@{$new_admin->{'msg_topic'}}) {
if ($msg_topic->{'name'} =~ /^other$/i) {
$msg_topic->{'name'} = undef;
$msg_topic->{'title'} = undef;
&report::reject_report_web('user','topic_other',{},$param->{'action'},$list);
&wwslog('notice',"do_edit_list: topic other is a reserved word");
&web_db_log({'status' => 'error',
'error_type' => 'syntax_errors'});
return undef;
}
}
## For changed msg_topic.name
if (defined $new_admin->{'msg_topic'} && $list->modifying_msg_topic_for_subscribers($new_admin->{'msg_topic'})) {
&report::notice_report_web('subscribers_noticed_deleted_topics',{},$param->{'action'});
}
## Delete selected params
foreach my $p (keys %delete) {
if (($p eq 'owner') || ($p eq 'owner_include')) {
$owner_update = 1;
}
if (($p eq 'editor') || ($p eq 'editor_include')) {
$editor_update = 1;
}
## Delete ALL entries
unless (ref ($delete{$p})) {
undef $new_admin->{$p};
next;
}
## Delete selected entries
foreach my $k (reverse @{$delete{$p}}) {
splice @{$new_admin->{$p}}, $k, 1;
}
if (defined $check_family{$p}) { # $p is family controlled
if ($#{$new_admin->{$p}} < 0) {
&report::reject_report_web('user','p_family_controlled',{'param' => $p},$param->{'action'},$list);
&wwslog('info','do_edit_list : parameter %s must have values (family context)',$p);
&web_db_log({'status' => 'error',
'error_type' => 'missing_parameter'});
return undef;
}
}
}
# updating config_changes for deleted parameters
if (ref($family)) {
my @array_delete = keys %delete;
unless ($list->update_config_changes('param',\@array_delete)) {
&report::reject_report_web('intern','update_config_changes',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_edit_list: cannot write in config_changes for deleted parameters from list %s', $list->{'name'});
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
## Update config in memory
my $data_source_updated;
foreach my $parameter (keys %changed) {
my $pname;
if ($parameter =~ /^([\w-]+)\.([\w-]+)$/) {
$pname = $1;
} else{
$pname = $parameter;
}
## If new owners/editors have been added, then notify them
foreach my $admin_type ('owner','editor') {
my (%previous_emails, %new_emails);
## Check previous entries
foreach my $entry (@{$list->{'admin'}{$admin_type}}) {
$previous_emails{$entry->{'email'}} = 1;
}
## Compare with new entries
foreach my $entry (@{$new_admin->{$admin_type}}) {
unless ($previous_emails{$entry->{'email'}}) {
## Notify the new list owner/editor
$list->send_notify_to_user('added_as_listadmin', $entry->{'email'},{'admin_type' => $admin_type, 'delegator' => $param->{'user'}{'email'}});
&report::notice_report_web('user_notified',{'notified_user' => $entry->{'email'}},$param->{'action'});
}
}
}
if (defined $check_family{$pname}) { # $pname is CONTROLLED
&_check_new_values(\%check_family,$pname,$new_admin);
}
## If datasource config changed
if ($pname =~ /^(include_.*|ttl)$/) {
$data_source_updated = 1;
}
$list->{'admin'}{$pname} = $new_admin->{$pname};
if (defined $new_admin->{$pname} || $pinfo->{$pname}{'internal'}) {
delete $list->{'admin'}{'defaults'}{$pname};
}else {
$list->{'admin'}{'defaults'}{$pname} = 1;
}
if (($pname eq 'owner') || ($pname eq 'owner_include')){
$owner_update = 1;
}
if (($pname eq 'editor') || ($pname eq 'editor_include')){
$editor_update = 1;
}
# updating config_changes for changed parameters
if (ref($family)) {
my @array_changed = keys %changed;
unless ($list->update_config_changes('param',\@array_changed)) {
&report::reject_report_web('intern','update_config_changes',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_edit_file: cannot write in config_changes for changed parameters from list %s', $list->{'name'});
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
}
## Save config file
unless ($list->save_config($param->{'user'}{'email'})) {
&report::reject_report_web('intern','cannot_save_config',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_edit_list: Cannot save config file');
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
## Reload config to clean some empty entries in $list->{'admin'}
$list = new List $list->{'name'}, $robot, {'reload_config' => 1};
unless (defined $list) {
&report::reject_report_web('intern','list_reload',{},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_edit_list: error in list reloading');
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
## If list has included data sources, update them and delete sync_include task.
if ($data_source_updated) {
if ($list->on_the_fly_sync_include('use_ttl'=>0)) {
&report::notice_report_web('subscribers_updated',{},$param->{'action'});
}else {
&report::reject_report_web('intern','sync_include_failed',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
}
}
## call sync_include_admin if there are changes about owners or editors and we're in mode include2
unless ($list->sync_include_admin()) {
&report::reject_report_web('intern', 'sync_include_admin_failed',
{}, $param->{'action'}, $list,
$param->{'user'}{'email'}, $robot);
&wwslog('info','do_edit_list: sync_include_admin() failed');
&web_db_log({'status' => 'error',
'error_type' => 'internal'});
return undef;
}
## Tag changed parameters
foreach my $pname (keys %changed) {
$::changed_params{$pname} = 1;
}
## Save stats
$list->savestats();
# print "Content-type: text/plain\n\n";
# &tools::dump_var($list->{'admin'}{'msg_topic'},0);
# &tools::dump_var($param->{'param'},0);
&report::notice_report_web('list_config_updated',{},$param->{'action'});
&web_db_log({'status' => 'success'});
return 'edit_list_request';
}
## Shift tokens to get a reference to the desired
## entry in $var (recursive)
sub _shift_var {
my ($i, $var, @tokens) = @_;
&wwslog('debug3','shift_var(%s,%s,%s)',$i, $var, join('.',@tokens));
my $newvar;
my $token = shift @tokens;
if ($token =~ /^\d+$/) {
return \$var->[$token]
if ($#tokens == -1);
if ($tokens[0] =~ /^\d+$/) {
unless (ref $var->[$token]) {
$var->[$token] = [];
}
$newvar = $var->[$token];
}else {
unless (ref $var->[$token]) {
$var->[$token] = {};
}
$newvar = $var->[$token];
}
}else {
return \$var->{$token}
if ($#tokens == -1);
if ($tokens[0] =~ /^\d+$/) {
unless (ref $var->{$token}) {
$var->{$token} = [];
}
$newvar = $var->{$token};
}else {
unless (ref $var->{$token}) {
$var->{$token} = {};
}
$newvar = $var->{$token};
}
}
if ($#tokens > -1) {
$i++;
return &_shift_var($i, $newvar, @tokens);
}
return $newvar;
}
=pod
=head2 sub do_edit_list_request
Sends back the list config edition form.
=head3 Arguments
=over
=item * I
=back
=head3 Return
=over
=item * I<1>, if no problem is encountered
=item * I, if anything goes wrong
=item * I<'loginrequest'> if no user is logged in at the time the function is called.
=back
=head3 Calls
=over
=item * wwslog
=item * _prepare_edit_form
=item * report::reject_report_web
=back
=cut
## Send back the list config edition form
sub do_edit_list_request {
&wwslog('info', 'do_edit_list_request(%s)', $in{'group'});
if ($in{'group'}) {
$param->{'group'} = $in{'group'};
&_prepare_edit_form ($list);
}
# print "Content-type: text/plain\n\n";
# &tools::dump_var(\%pinfo,0);
# &tools::dump_var($list->{'admin'},0);
# &tools::dump_var($param->{'param'},0);
$param->{'serial'} = $list->{'admin'}{'serial'};
return 1;
}
sub _check_new_values {
my $check_family = shift;
my $pname = shift;
my $new_admin = shift;
&wwslog('debug3', '_check_new_values(%s)',$pname);
my $uncompellable_param = &Family::get_uncompellable_param();
if (ref($::pinfo{$pname}{'format'}) eq 'HASH') { #composed parameter
foreach my $key (keys %{$check_family->{$pname}}) {
my $constraint = $check_family->{$pname}{$key};
my $values = &List::_get_param_value_anywhere($new_admin,"$pname.$key");
my $nb_for = 0;
# exception for uncompellable param
foreach my $p (keys %{$uncompellable_param}) {
if (($pname eq $p) && !($uncompellable_param->{$p})) {
return 1;
}
if (($pname eq $p) && ($key eq $uncompellable_param->{$p})) {
return 1;
}
}
foreach my $p_val (@{$values}) { #each element value
$nb_for++;
if (ref($p_val) eq 'ARRAY') { # multiple values
foreach my $p (@{$p_val}) {
if (!($constraint->{$p}) && (($nb_for == 1) || ($p ne ''))) {
&report::reject_report_web('user','p_family_wrong',{'param' => $pname,'val'=> $p},$param->{'action'});
&wwslog('info', 'do_edit_list : parameter %s has got wrong value : %s (family context)', $pname, $p);
return undef;
}
}
} else { # single value
if (!($constraint->{$p_val}) && (($nb_for == 1) || ($p_val ne ''))) {
&report::reject_report_web('user','p_family_wrong',{'param' => $pname,'val'=> $p_val},$param->{'action'});
&wwslog('info', 'do_edit_list : parameter %s has got wrong value : %s (family context)', $pname, $p_val);
return undef;
}
}
}
}
} else { #simple parameter
# exception for uncompellable param
foreach my $p (keys %{$uncompellable_param}) {
if ($pname eq $p) {
return 1;
}
}
my $constraint = $check_family->{$pname};
my $values = &List::_get_param_value_anywhere($new_admin,$pname);
my $nb_for = 0;
foreach my $p_val (@{$values}) { #each element value
$nb_for++;
if (ref($p_val) eq 'ARRAY') { # multiple values
foreach my $p (@{$p_val}) {
if (!($constraint->{$p}) && (($nb_for == 1) || ($p ne ''))) {
&report::reject_report_web('user','p_family_wrong',{'param' => $pname,'val'=> $p},$param->{'action'});
&wwslog('info', 'do_edit_list : parameter %s has got wrong value : %s (family context)', $pname, $p);
return undef;
}
}
} else { # single value
if (!($constraint->{$p_val}) && (($nb_for == 1) || ($p_val ne ''))) {
&report::reject_report_web('user','p_family_wrong',{'param' => $pname,'val'=> $p_val},$param->{'action'});
&wwslog('info', 'do_edit_list : parameter %s has got wrong value : %s (family context)', $pname, $p_val);
return undef;
}
}
}
}
}
=pod
=head2 sub _prepare_edit_form(LIST)
Prepares config data to be sent in the edition form. Adds to the parameters array a hash for each parameter to be edited.
=head3 Arguments
=over
=item * I<$list>, a List object
=back
=head3 Return
=over
=item * I<1>, if no problem is encountered
=item * I, if anything goes wrong
=back
=head3 Calls
=over
=item * _prepare_data
=item * _restrict_values
=item * wwslog
=item * List::by_order
=item * List::get_family
=item * List::load_topics
=item * List::may_edit
=item * Language::GetLang
=item * Language::SetLang
=item * report::reject_report_web
=item * tools::dup_var
=back
=cut
## Prepare config data to be sent in the
## edition form
sub _prepare_edit_form {
my $list = shift;
my $list_config = &tools::dup_var($list->{'admin'});
my $family;
my $is_form_editable = '0';
## If the list belongs to a family, check if the said family can be retrieved.
if (defined $list_config->{'family_name'}) {
unless ($family = $list->get_family()) {
&report::reject_report_web('intern','unable_get_family',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','_prepare_edit_form : impossible to get list %s\'s family',$list->{'name'});
return undef;
}
}
## For each parameter defined in List.pm, retrieve and prepare for editing
foreach my $pname (sort List::by_order keys %{$pinfo}) {
## Skip comments and default values.
next if ($pname =~ /^(comment|defaults)$/);
## Skip parameters belonging to another group.
next if ($in{'group'} && ($pinfo->{$pname}{'group'} ne $in{'group'}));
## Skip obsolete parameters.
next if $pinfo->{$pname}{'obsolete'};
## Check whether the parameter can be edited by the logged user.
my $may_edit = $list->may_edit($pname,$param->{'user'}{'email'});
## Valid form global edit status as soon as at least one editable parameter is found.
if ($may_edit eq 'write') {
$is_form_editable = '1';
}
## Store in $p a reference to the hash containing the informations relative to the parameter editing.
my $p = &_prepare_data($pname, $pinfo->{$pname}, $list_config->{$pname},$may_edit,$family);
## Store if the parameter is still at its default value or not.
$p->{'default'} = $list_config->{'defaults'}{$pname};
## Store the change state of this parameter, taken from the global variable %changed_params.
$p->{'changed'} = $::changed_params{$pname};
## Exceptions...too many
if ($pname eq 'topics') {
$p->{'type'} = 'enum';
my @topics;
foreach my $topic(@{$p->{'value'}}) {
push @topics, $topic->{'value'};
}
undef $p->{'value'};
my %list_of_topics = &List::load_topics($robot);
if (defined $p->{'constraint'}) {
&_restrict_values(\%list_of_topics,$p->{'constraint'});
}
foreach my $topic (keys %list_of_topics) {
$p->{'value'}{$topic}{'selected'} = 0;
$p->{'value'}{$topic}{'title'} = $list_of_topics{$topic}{'current_title'};
if ($list_of_topics{$topic}{'sub'}) {
foreach my $subtopic (keys %{$list_of_topics{$topic}{'sub'}}) {
$p->{'value'}{"$topic/$subtopic"}{'selected'} = 0;
$p->{'value'}{"$topic/$subtopic"}{'title'} = "$list_of_topics{$topic}{'current_title'}/$list_of_topics{$topic}{'sub'}{$subtopic}{'current_title'}";
}
}
}
foreach my $selected_topic (@topics) {
next unless (defined $selected_topic);
$p->{'value'}{$selected_topic}{'selected'} = 1;
$p->{'value'}{$selected_topic}{'title'} =
sprintf gettext("Unknown (%s)"), $selected_topic
unless (defined $p->{'value'}{$selected_topic}{'title'});
}
}elsif ($pname eq 'digest') {
foreach my $v (@{$p->{'value'}}) {
next unless ($v->{'name'} eq 'days');
if (ref($v->{'value'}) eq 'ARRAY'){
&tools::do_log('debug2','Empty digest parameter. Putting a dummy value.');
$v->{'value'} = undef;
$v->{'type'} = 'enum';
}else{
foreach my $day (keys %{$v->{'value'}}) {
my $t = gettext_strftime "%A", gmtime(0 + ($day +3) * (3600 * 24));
$t = sprintf '%s (%s)', $t, $day
if List::is_listmaster($param->{'user'}{'email'}, $robot);
$v->{'value'}{$day}{'title'} = $t;
}
}
}
}elsif ($pname eq 'lang') {
my $saved_lang = &Language::GetLang();
foreach my $lang (keys %{$p->{'value'}}) {
&Language::SetLang($lang);
my $t = gettext('_language_');
$t = sprintf '%s (%s)', $t, $lang
if List::is_listmaster($param->{'user'}{'email'}, $robot);
$p->{'value'}{$lang}{'title'} = $t;
$p->{'value'}{$lang}{'lang_tag'} = Language::LanguageTag($lang);
}
&Language::SetLang($saved_lang);
} elsif ($pname eq 'available_user_options' or
$pname eq 'default_user_options') {
foreach my $v (@{$p->{'value'}}) {
if ($v->{'name'} eq 'reception') {
foreach my $x (keys %{$v->{'value'}}) {
$v->{'value'}{$x}{'title'} =
$list->get_option_title($x, 'reception',
List::is_listmaster($param->{'user'}{'email'}, $robot));
}
} elsif ($v->{'name'} eq 'visibility') {
foreach my $x (keys %{$v->{'value'}}) {
$v->{'value'}{$x}{'title'} =
$list->get_option_title($x, 'visibility',
List::is_listmaster($param->{'user'}{'email'}, $robot));
}
}
}
} elsif ($pname eq 'status') {
foreach my $x (keys %{$p->{'value'}}) {
$p->{'value'}{$x}{'title'} =
$list->get_option_title($x, 'status',
List::is_listmaster($param->{'user'}{'email'}, $robot));
}
}
push @{$param->{'param'}}, $p;
}
## If at least one param was editable, make the update button appear in the form.
$param->{'is_form_editable'} = $is_form_editable;
return 1;
}
=pod
=head2 sub _prepare_data(STRING $name, HASH_Ref $struct, SCALAR $data, STRING $may_edit, FAMILY $family, STRING $main_p)
Returns a reference to a hash containing the data used to edit the parameter (of name $name, corresponding to the structure $struct in pinfo, with the $may_edit editing status) containing the data in the Sympa web interface.
=head3 Arguments
=over
=item * I<$name> (STRING), the name of the parameter processed
=item * I<$struct> (HASH_Ref), a ref to the hash describing this parameter in %List::pinfo
=item * I<$data> (), the value(s) taken by this parameter in the current list. Can be a reference to a list or the value of a single parameter.
=item * I<$may_edit> (STRING), the editing status of this parameter in the current context.
=item * I<$family> (FAMILY), the family the list belongs to.
=item * I<$main_p> (STRING), the prefix composing the complete name of the parameter.
=back
=head3 Return
=over
=item * I<$p_glob>, a reference to a hash containing the data used to edit the parameter.
=back
=head3 Calls
=over
=item * _restrict_values
=item * _prepare_data
=item * load_data_sources_list
=item * Family::get_param_constraint
=item * List::load_scenario_list
=item * List::load_task_list
=item * List::may_edit
=item * tools::escape_html
=back
=cut
sub _prepare_data {
my ($name, $struct,$data,$may_edit,$family,$main_p) = @_;
# &wwslog('debug2', '_prepare_data(%s, %s)', $name, $data);
# $family and $main_p (recursive call) are optionnal
# if $main_p is needed, $family also
next if ($struct->{'obsolete'});
## Prepare data structure for the parser
my $p_glob = {'name' => $name,
'comment' => $struct->{'comment'}{$param->{'lang'}}
};
## Check if some family constraint modify the editing rights.
my $restrict = 0;
my $constraint;
if ((ref($family) eq 'Family') && ($may_edit eq 'write')) {
if ($main_p && defined $::pinfo{$main_p}) {
if (ref($::pinfo{$main_p}{'format'}) eq 'HASH') { # composed parameter
$constraint = $family->get_param_constraint("$main_p.$p_glob->{'name'}");
}
} else { # simple parameter
if (ref($::pinfo{$p_glob->{'name'}}{'format'}) ne 'HASH') { # simple parameter
$constraint = $family->get_param_constraint($p_glob->{'name'});
}
}
if ($constraint eq '0') { # free parameter
$p_glob->{'may_edit'} = 'write';
} elsif (ref($constraint) eq 'HASH') { # controlled parameter
$p_glob->{'may_edit'} = 'write';
$restrict = 1;
} else { # fixed parameter
$p_glob->{'may_edit'} = 'read';
}
} else {
$p_glob->{'may_edit'} = $may_edit;
}
## Naming the parameter.
if ($struct->{'gettext_id'}) {
$p_glob->{'title'} = gettext($struct->{'gettext_id'});
}else {
$p_glob->{'title'} = $name;
}
## Occurrences : if the parameter can have multiple occurences,
## its values are transfered into the array pointed by $data2
## if they were given in arguments (if not, an empty array is created).
## if it is a single occurence parameter, an array is created with
## its single value.
my $data2;
if ($struct->{'occurrence'} =~ /n$/) {
$p_glob->{'occurrence'} = 'multiple';
if (defined($data)) {
$data2 = $data;
if ($may_edit eq 'write') {
## Add an empty entry
unless (($name eq 'days') || ($name eq 'reception') || ($name eq 'rfc2369_header_fields') || ($name eq 'topics')) {
my $empty_entry;
## Structured parameter
if (ref($struct->{'format'}) eq 'HASH') {
foreach my $sub_parameter (keys %{$struct->{'format'}}) {
## Use default value if defined
if ($struct->{'format'}{$sub_parameter}{'default'}) {
$empty_entry->{$sub_parameter} = $struct->{'format'}{$sub_parameter}{'default'};
}
}
## Simpe parameter
}else {
$empty_entry = undef;
}
push @{$data2}, $empty_entry;
## &wwslog('debug2', 'Add 1 %s', $name);
}
}
}else {
if ($may_edit eq 'write') {
$data2 = [undef];
}
}
}else {
$data2 = [$data];
}
my @all_p;
## Foreach occurrence of param
foreach my $d (@{$data2}) {
my $p = {};
## Type of data
if ($struct->{'scenario'}) {
$p_glob->{'type'} = 'scenario';
my $list_of_scenario;
my $tmp_list_of_scenario = $list->load_scenario_list($struct->{'scenario'},$robot);
## Only get required scenario attributes
foreach my $scenario (keys %{$tmp_list_of_scenario}) {
$list_of_scenario->{$scenario} = {'name' => $tmp_list_of_scenario->{$scenario}{'name'},
'web_title' => $tmp_list_of_scenario->{$scenario}{'web_title'}};
}
$list_of_scenario->{$d->{'name'}}{'selected'} = 1;
$p->{'value'} = $list_of_scenario;
if ($restrict) {
&_restrict_values($p->{'value'},$constraint);
}
}elsif ($struct->{'task'}) {
$p_glob->{'type'} = 'task';
my $list_of_task = $list->load_task_list($struct->{'task'}, $robot);
$list_of_task->{$d->{'name'}}{'selected'} = 1;
$p->{'value'} = $list_of_task;
if ($restrict) {
&_restrict_values($p->{'value'},$constraint);
}
}elsif ($struct->{'datasource'}) {
$p_glob->{'type'} = 'datasource';
my $list_of_data_sources = $list->load_data_sources_list($robot);
$list_of_data_sources->{$d}{'selected'} = 1;
$p->{'value'} = $list_of_data_sources;
if ($restrict) {
&_restrict_values($p->{'value'},$constraint);
}
}elsif (ref ($struct->{'format'}) eq 'HASH') {
$p_glob->{'type'} = 'paragraph';
unless (ref($d) eq 'HASH') {
$d = {};
}
foreach my $k (sort {$struct->{'format'}{$a}{'order'} <=> $struct->{'format'}{$b}{'order'}}
keys %{$struct->{'format'}}) {
## Prepare data recursively
my $m_e = $list->may_edit("$name.$k",$param->{'user'}{'email'});
my $v = &_prepare_data($k, $struct->{'format'}{$k}, $d->{$k},$m_e,$family,$name);
push @{$p->{'value'}}, $v;
}
}elsif ((ref ($struct->{'format'}) eq 'ARRAY') || ($restrict && ($main_p eq 'msg_topic' && $name eq 'keywords'))) {
$p_glob->{'type'} = 'enum';
unless (defined $p_glob->{'value'}) {
## Initialize
foreach my $elt (@{$struct->{'format'}}) {
$p_glob->{'value'}{$elt}{'selected'} = 0;
}
## Check obsolete values ; they should not be printed
if (defined $struct->{'obsolete_values'}) {
foreach my $elt (@{$struct->{'obsolete_values'}}) {
delete $p_glob->{'value'}{$elt};
}
}
}
if (ref ($d)) {
next unless (ref ($d) eq 'ARRAY');
foreach my $v (@{$d}) {
$p_glob->{'value'}{$v}{'selected'} = 1;
}
}else {
$p_glob->{'value'}{$d}{'selected'} = 1 if (defined $d);
}
if ($restrict) {
&_restrict_values($p_glob->{'value'},$constraint);
}
}else {
if ($restrict && ($name ne 'topics')) {
$p_glob->{'type'} = 'enum';
foreach my $elt (keys %{$constraint}) {
$p->{'value'}{&tools::escape_html($elt)}{'selected'} = 0;
}
$p->{'value'}{&tools::escape_html($d)}{'selected'} = 1;
$p->{'length'} = $struct->{'length'};
$p->{'unit'} = gettext($struct->{'gettext_unit'});
} else {
$p_glob->{'type'} = 'scalar';
$p->{'value'} = &tools::escape_html($d);
$p->{'length'} = $struct->{'length'};
$p->{'field_type'} = $struct->{'field_type'};
my $l = length($p->{'value'});
$p->{'hidden_field'} = '*' x $l;
$p->{'unit'} = gettext($struct->{'gettext_unit'});
if ($restrict) { # for topics
$p_glob->{'constraint'} = $constraint;
}
}
}
push @all_p, $p;
}
if (($p_glob->{'occurrence'} eq 'multiple')
&& ($p_glob->{'type'} ne 'enum')) {
$p_glob->{'value'} = \@all_p;
}else {
foreach my $k (keys %{$all_p[0]}) {
$p_glob->{$k} = $all_p[0]->{$k};
}
}
return $p_glob;
}
## Restrict allowed values in the hash
sub _restrict_values {
my $values = shift; #ref on hash of values
my $allowed = shift; #ref on hash of allowed values
&wwslog('debug3', '_restrict_values()');
foreach my $v (keys %{$values}) {
unless (defined $allowed->{$v}) {
delete $values->{$v};
}
}
}
## NOT USED anymore (expect chinese)
sub do_close_list_request {
&wwslog('info', 'do_close_list_request()');
if ($list->{'admin'}{'status'} eq 'closed') {
&report::reject_report_web('user','already_closed',{},$param->{'action'},$list);
&wwslog('info','do_close_list_request: already closed');
return undef;
}
return 1;
}
# in order to rename a list you must be list owner and you must be allowed to create new list
sub do_rename_list_request {
&wwslog('info', 'do_rename_list_request()');
my $result = &Scenario::request_action ('create_list',$param->{'auth_method'},$robot,
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $r_action;
my $reason;
if (ref($result) eq 'HASH') {
$r_action = $result->{'action'};
$reason = $result->{'reason'};
}
unless ($r_action =~ /do_it|listmaster/) {
&report::reject_report_web('auth',$reason,{},$param->{'action'},$list);
&wwslog('info','do_rename_list_request: not owner');
return undef;
}
## Super listmaster can move a list to another robot
if (&List::is_listmaster($param->{'user'}{'email'}, $robot)) {
foreach (keys %{$Conf{'robots'}}) {
if ($_ eq $robot) {
$param->{'robots'}{$_} = 'selected="selected"';
}else {
$param->{'robots'}{$_} = '';
}
}
}
return '1';
}
sub do_copy_list {
&wwslog('info', 'do_copy_list(%s,%s)', $in{'new_listname'}, $in{'new_robot'});
&do_rename_list('copy');
}
# in order to rename a list you must be list owner and you must be allowed to create new list
sub do_rename_list {
my $mode = shift;
if ($in{'new_listname'} =~ /[A-Z]/) {
$in{'new_listname'} = lc($in{'new_listname'});
&report::notice_report_web('listname_lowercased',{},$param->{'action'});
}
&wwslog('info', 'do_rename_list(%s,%s, mode = %s)', $in{'new_listname'}, $in{'new_robot'},$mode);
my $result = &admin::rename_list(list => $list,
new_listname =>$in{'new_listname'},
new_robot => $in{'new_robot'},
mode => $mode,
auth_method => $param->{'auth_method'},
user_email => $param->{'user'}{'email'},
remote_host => $param->{'remote_host'},
remote_addr => $param->{'remote_addr'},
aliases => $param->{'aliases'},
status => $param->{'status'},
);
if ($result eq 'incorrect_listname') {
&report::reject_report_web('user','incorrect_listname', {'bad_listname' => $in{'new_listname'}},$param->{'action'},$list);
&wwslog('info','do_rename_list: incorrect listname %s', $in{'new_listname'});
&web_db_log({'parameters' => "$in{'new_listname'},$in{'new_robot'}",
'status' => 'error',
'error_type' => 'incorrect_listname'});
return 'rename_list_request';
}elsif ($result eq 'authorization') {
&report::reject_report_web('auth','authorization',{},$param->{'action'},$list);
&wwslog('info','do_rename_list: not owner');
&web_db_log({'parameters' => "$in{'new_listname'},$in{'new_robot'}",
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}elsif ($result eq 'internal') {
&report::reject_report_web('intern','unable_to_rename_list',{'new_listname' => $in{'new_listname'}},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err', "can't rename list %s to %s@%s", $list->get_list_address(), $in{'new_listname'}, $in{'new_robot'});
&web_db_log({'parameters' => "$in{'new_listname'},$in{'new_robot'}",
'status' => 'error',
'error_type' => 'internal'});
return undef;
}elsif ($result eq 'list_already_exists') {
&report::reject_report_web('user','list_already_exists',{'new_listname' => $in{'new_listname'}},$param->{'action'},$list);
&wwslog('info', 'Could not rename list %s for %s: new list %s already existing list',
$in{'listname'},$param->{'user'}{'email'},$in{'new_listname'});
&web_db_log({'parameters' => "$in{'new_listname'},$in{'new_robot'}",
'status' => 'error',
'error_type' => 'list_already_exists'});
return undef;
}elsif ($result eq 'incorrect_listname') {
&report::reject_report_web('user','listname_matches_aliases',{'new_listname' => $in{'new_listname'}},$param->{'action'},$list);
&wwslog('info','do_create_list: incorrect listname %s', $in{'new_listname'});
&web_db_log({'parameters' => "$in{'new_listname'},$in{'new_robot'}",
'status' => 'error',
'error_type' => 'incorrect_listname'});
return 'rename_list_request';
}elsif ($result eq 'unknown_robot') {
&wwslog('info',"do_rename_list : unknown robot $in{'new_robot'}");
&report::reject_report_web('user','unknown_robot',{'new_robot' => $in{'new_robot'}},$param->{'action'},$list);
&web_db_log({'parameters' => "$in{'new_listname'},$in{'new_robot'}",
'status' => 'error',
'error_type' => 'unknown_robot'});
return undef;
}
## Were aliases installed?
if ($param->{'aliases'} == 1) {
$param->{'auto_aliases'} = 1;
}else {
$param->{'auto_aliases'} = 0;
}
# set list status to pending if creation list is moderated
if ($param->{'status'} eq 'pending') {
&report::notice_report_web('pending_list',{},$param->{'action'},$list);
}
if ($in{'new_robot'} eq '$robot') {
$param->{'redirect_to'} = "$param->{'base_url'}$param->{'path_cgi'}/admin/$in{'new_listname'}";
}else {
$param->{'redirect_to'} = &Conf::get_robot_conf($in{'new_robot'}, 'wwsympa_url')."/admin/$in{'new_listname'}";
}
$param->{'list'} = $in{'new_listname'};
&web_db_log({'parameters' => "$in{'new_listname'},$in{'new_robot'}",
'status' => 'success'});
$list->save_config($param->{'user'}{'email'});
return 1;
}
sub do_purge_list {
&wwslog('info', 'do_purge_list()');
my @lists = split /\0/, $in{'selected_lists'};
foreach my $l (@lists) {
my $list = new List ($l, $robot);
next unless (defined $list);
$list->purge($param->{'user'}{'email'});
}
&report::notice_report_web('performed',{},$param->{'action'});
&web_db_log({'parameters' => $in{'selected_lists'},
'status' => 'success'});
return 'get_closed_lists';
}
sub do_close_list {
&wwslog('info', "do_close_list($list->{'name'})");
if ($list->{'admin'}{'status'} eq 'closed') {
&report::reject_report_web('user','already_closed',{},$param->{'action'},$list);
&wwslog('info','do_close_list: already closed');
&web_db_log({'status' => 'error',
'error_type' => 'already_closed'});
return undef;
}elsif($list->{'admin'}{'status'} eq 'pending') {
&wwslog('info','do_close_list: closing a pending list makes it purged');
$list->purge($param->{'user'}{'email'});
&report::notice_report_web('list_purged',{},$param->{'action'});
&web_db_log({'status' => 'success'});
return 'home';
}else{
$list->close($param->{'user'}{'email'});
&report::notice_report_web('list_closed',{},$param->{'action'});
&web_db_log({'status' => 'success'});
return 'admin';
}
}
sub do_restore_list {
&wwslog('info', 'do_restore_list()');
unless ($list->{'admin'}{'status'} eq 'closed') {
&report::reject_report_web('user','not_closed',{},$param->{'action'},$list);
&wwslog('info','do_restore_list: list not closed');
&web_db_log({'status' => 'error',
'error_type' => 'not_closed'});
return undef;
}
## Change status & save config
$list->{'admin'}{'status'} = 'open';
$list->save_config($param->{'user'}{'email'});
unless (-f "$list->{'dir'}/subscribers.closed.dump") {
&wwslog('notice', 'No subscribers to restore');
&web_db_log({'status' => 'error',
'error_type' => 'no_subscribers'});
}
my @users = &List::_load_users_file("$list->{'dir'}/subscribers.closed.dump");
## Insert users in database
foreach my $user (@users) {
$list->add_user($user);
}
$list->savestats();
my $aliases = &admin::install_aliases($list,$robot);
if ($aliases == 1) {
$param->{'auto_aliases'} = 1;
}else {
$param->{'aliases'} = $aliases;
$param->{'auto_aliases'} = 0;
}
&report::notice_report_web('list_restored',{},$param->{'action'});
&web_db_log({'status' => 'success'});
return 'admin';
}
sub get_desc_file {
my $file = shift;
my $ligne;
my %hash;
open DESC_FILE,"$file";
while ($ligne = ) {
if ($ligne =~ /^title\s*$/) {
#case title of the document
while (($ligne = ) and ($ligne!~/^\s*$/)) {
$ligne =~ /^\s*(\S.*\S)\s*/;
$hash{'title'} = $hash{'title'}.$1." ";
}
}
if ($ligne =~ /^creation\s*$/) {
#case creation of the document
while (($ligne = ) and ($ligne!~/^\s*$/)) {
if ($ligne =~ /^\s*email\s*(\S*)\s*/) {
$hash{'email'} = $1;
}
if ($ligne =~ /^\s*date_epoch\s*(\d*)\s*/) {
$hash{'date'} = $1;
}
}
}
if ($ligne =~ /^access\s*$/) {
#case access scenari for the document
while (($ligne = ) and ($ligne!~/^\s*$/)) {
if ($ligne =~ /^\s*read\s*(\S*)\s*/) {
$hash{'read'} = $1;
}
if ($ligne =~ /^\s*edit\s*(\S*)\s*/) {
$hash{'edit'} = $1;
}
}
}
}
close DESC_FILE;
return %hash;
}
sub do_show_cert {
return 1;
}
## Function synchronize
## Return true if the file in parameter can be overwrited
## false if it has changes since the parameter date_epoch
sub synchronize {
# args : 'path' , 'date_epoch'
my $path = shift;
my $date_epoch = shift;
my @info = stat $path;
return ($date_epoch == $info[9]);
}
#*******************************************
# Function : d_access_control
# Description : return a hash with privileges
# in read, edit, control
# if first parameter require
# it
#******************************************
## Regulars
# read(/) = default (config list)
# edit(/) = default (config list)
# control(/) = not defined
# read(A/B)= (read(A) && read(B)) ||
# (author(A) || author(B))
# edit = idem read
# control (A/B) : author(A) || author(B)
# + (set owner A/B) if (empty directory &&
# control A)
sub d_access_control {
# Arguments:
# (\%mode,$path)
# if mode->{'read'} control access only for read
# if mode->{'edit'} control access only for edit
# if mode->{'control'} control access only for control
# return the hash (
# $result{'may'}{'read'} == $result{'may'}{'edit'} == $result{'may'}{'control'} if is_author else :
# $result{'may'}{'read'} = 0 or 1 (right or not)
# $result{'may'}{'edit'} = 0(not may edit) or 0.5(may edit with moderation) or 1(may edit ) : it is not a boolean anymore
# $result{'may'}{'control'} = 0 or 1 (right or not)
# $result{'reason'}{'read'} = string for authorization_reject.tt2 when may_read == 0
# $result{'reason'}{'edit'} = string for authorization_reject.tt2 when may_edit == 0
# $result{'scenario'}{'read'} = scenario name for the document
# $result{'scenario'}{'edit'} = scenario name for the document
# Result
my %result;
$result{'reason'} = {};
# Control
# Arguments
my $mode = shift;
my $path = shift;
&wwslog('debug', "d_access_control(%s, %s)", join('/',%$mode), $path);
my $mode_read = $mode->{'read'};
my $mode_edit = $mode->{'edit'};
my $mode_control = $mode->{'control'};
# Useful parameters
my $list_name = $list->{'name'};
my $shareddir = $list->{'dir'}.'/shared';
# document to read
my $doc;
if ($path) {
# the path must have no slash a its end
$path =~ /^(.*[^\/])?(\/*)$/;
$path = $1;
$doc = $shareddir.'/'.$path;
} else {
$doc = $shareddir;
}
# Control for editing
my $may_read = 1;
my $why_not_read = '';
my $may_edit = 1;
my $why_not_edit = '';
my $is_author = 0; # <=> $may_control
## First check privileges on the root shared directory
$result{'scenario'}{'read'} = $list->{'admin'}{'shared_doc'}{'d_read'}{'name'};
$result{'scenario'}{'edit'} = $list->{'admin'}{'shared_doc'}{'d_edit'}{'name'};
## Privileged owner has all privileges
if ($param->{'is_privileged_owner'}) {
$result{'may'}{'read'} = 1;
$result{'may'}{'edit'} = 1;
$result{'may'}{'control'} = 1;
return %result;
}
# if not privileged owner
if ($mode_read) {
my $result = $list->check_list_authz('shared_doc.d_read',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $action;
if (ref($result) eq 'HASH') {
$action = $result->{'action'};
$why_not_read = $result->{'reason'};
}
$may_read = ($action =~ /do_it/i);
}
if ($mode_edit) {
my $result = $list->check_list_authz('shared_doc.d_edit',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $action;
if (ref($result) eq 'HASH') {
$action = $result->{'action'};
$why_not_edit = $result->{'reason'};
}
#edit = 0, 0.5 or 1
$may_edit = &find_edit_mode($action);
$why_not_edit = '' if ($may_edit);
}
## Only authenticated users can edit files
unless ($param->{'user'}{'email'}) {
$may_edit = 0;
$why_not_edit = 'not_authenticated';
}
# if ($mode_control) {
# $result{'may'}{'control'} = 0;
# }
my $current_path = $path;
my $current_document;
my %desc_hash;
my $user = $param->{'user'}{'email'} || 'nobody';
while ($current_path ne "" && $current_path ne '/') {
# no description file found yet
my $def_desc_file = 0;
my $desc_file;
$current_path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/;
$current_document = $3;
my $next_path = $1;
# opening of the description file appropriated
if (-d $shareddir.'/'.$current_path) {
# case directory
# unless ($slash) {
$current_path = $current_path.'/';
# }
if (-e "$shareddir/$current_path.desc"){
$desc_file = $shareddir.'/'.$current_path.".desc";
$def_desc_file = 1;
}
}else {
# case file
if (-e "$shareddir/$next_path.desc.$3"){
$desc_file = $shareddir.'/'.$next_path.".desc.".$3;
$def_desc_file = 1;
}
}
if ($def_desc_file) {
# a description file was found
# loading of acces information
%desc_hash = &get_desc_file($desc_file);
if ($mode_read) {
my $result = $list->check_list_authz('shared_doc.d_read',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'},
'scenario'=> $desc_hash{'read'}});
my $action;
if (ref($result) eq 'HASH') {
$action = $result->{'action'};
$why_not_read = $result->{'reason'};
}
$may_read = $may_read && ( $action=~ /do_it/i);
$why_not_read = '' if ($may_read);
}
if ($mode_edit) {
my $result = $list->check_list_authz('shared_doc.d_edit',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'},
'scenario'=> $desc_hash{'edit'}});
my $action_edit;
if (ref($result) eq 'HASH') {
$action_edit = $result->{'action'};
$why_not_edit = $result->{'reason'};
}
# $may_edit = 0, 0.5 or 1
my $may_action_edit = &find_edit_mode($action_edit);
$may_edit = &merge_edit($may_edit,$may_action_edit);
$why_not_edit = '' if ($may_edit);
}
## Only authenticated users can edit files
unless ($param->{'user'}{'email'}) {
$may_edit = 0;
$why_not_edit = 'not_authenticated';
}
$is_author = $is_author || ($user eq $desc_hash{'email'});
unless (defined $result{'scenario'}{'read'}) {
$result{'scenario'}{'read'} = $desc_hash{'read'};
$result{'scenario'}{'edit'} = $desc_hash{'edit'};
}
## Author has all privileges
if ($is_author) {
$result{'may'}{'read'} = 1;
$result{'may'}{'edit'} = 1;
$result{'may'}{'control'} = 1;
return %result;
}
}
# truncate the path for the while
$current_path = $next_path;
}
if ($mode_read) {
$result{'may'}{'read'} = $may_read;
$result{'reason'}{'read'} = $why_not_read;
}
if ($mode_edit) {
$result{'may'}{'edit'} = $may_edit;
$result{'reason'}{'edit'} = $why_not_edit;
}
# if ($mode_control) {
# $result{'may'}{'control'} = 0;
# }
return %result;
}
## return the mode of editing included in $action : 0, 0.5 or 1
sub find_edit_mode{
my $action=shift;
my $result;
if ($action =~ /editor/i){
$result = 0.5;
} elsif ($action =~ /do_it/i){
$result = 1;
} else {
$result = 0;
}
return $result;
}
## return the mode of editing : 0, 0.5 or 1 :
# do the merging between 2 args of right access edit : "0" > "0.5" > "1"
# instead of a "and" between two booleans : the most restrictive right is
# imposed
sub merge_edit{
my $arg1=shift;
my $arg2=shift;
my $result;
if ($arg1 == 0 || $arg2 == 0){
$result = 0;
}elsif ($arg1 == 0.5 || $arg2 == 0.5){
$result = 0.5;
}else {
$result = 1;
}
return $result;
}
# create the root shared document
sub do_d_admin {
&wwslog('info', 'do_d_admin(%s,%s)', $in{'list'}, $in{'d_admin'});
my %mode;
$mode{'edit'} = 1;
my %access = &d_access_control(\%mode,$in{'path'});
my $dir = $list->{'dir'};
unless ($access{'may'}{'edit'}) {
&wwslog('info',"do_d_admin : permission denied for $param->{'user'}{'email'} ");
&report::reject_report_web('auth',$access{'reason'}{'edit'},{},$param->{'action'},$list);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
if ($in{'d_admin'} eq 'create') {
unless ($list->create_shared()) {
&wwslog('info',"do_d_admin : could not create the shared");
&report::reject_report_web('intern','create_shared',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
return 'd_read';
}elsif($in{'d_admin'} eq 'restore') {
unless (-e "$dir/pending.shared") {
&wwslog('info',"do_d_admin : restore; $dir/pending.shared not found");
&report::reject_report_web('intern','restore_shared',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
if (-e "$dir/shared") {
&wwslog('info',"do_d_admin : restore; $dir/shared already exist");
&report::reject_report_web('intern','restore_shared',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
unless (rename ("$dir/pending.shared", "$dir/shared")){
&wwslog('info',"do_d_admin : restore; unable to rename $dir/pending.shared");
&report::reject_report_web('intern','restore_shared',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
&web_db_log({'parameters' => $in{'path'},
'status' => 'success'});
return 'd_read';
}elsif($in{'d_admin'} eq 'delete') {
unless (-e "$dir/shared") {
&wwslog('info',"do_d_admin : restore; $dir/shared not found");
&report::reject_report_web('intern','delete_shared',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
if (-e "$dir/pending.shared") {
&wwslog('info',"do_d_admin : delete ; $dir/pending.shared already exist");
&report::reject_report_web('intern','delete_shared',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
unless (rename ("$dir/shared", "$dir/pending.shared")){
&wwslog('info',"do_d_admin : restore; unable to rename $dir/shared");
&report::reject_report_web('intern','delete_shared',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
&web_db_log({'parameters' => $in{'path'},
'status' => 'success'});
return 'admin';
}
# Function which sorts a hash of documents
# Sort by various parameters
sub by_order {
my $order = shift;
my $hash = shift;
# $order = 'order_by_size'/'order_by_doc'/'order_by_author'/'order_by_date'
if ($order eq 'order_by_doc') {
$hash->{$a}{'doc'} cmp $hash->{$b}{'doc'}
or $hash->{$b}{'date_epoch'} <=> $hash->{$a}{'date_epoch'};
}
elsif ($order eq 'order_by_author') {
$hash->{$a}{'author'} cmp $hash->{$b}{'author'}
or $hash->{$b}{'date_epoch'} <=> $hash->{$a}{'date_epoch'};
}
elsif ($order eq 'order_by_size') {
$hash->{$a}{'size'} <=> $hash->{$b}{'size'}
or $hash->{$b}{'date_epoch'} <=> $hash->{$a}{'date_epoch'};
}
elsif ($order eq 'order_by_date') {
$hash->{$b}{'date_epoch'} <=> $hash->{$a}{'date_epoch'} or $a cmp $b;
}
else {
$a cmp $b;
}
}
#*******************************************
# Function : do_d_read
# Description : reads a file or a directory
#******************************************
##
## Function do_d_read
sub do_d_read {
&wwslog('info', 'do_d_read(%s)', $in{'path'});
### Useful variables
# current list / current shared directory
my $list_name = $list->{'name'};
# relative path / directory shared of the document
my $path = &no_slash_end($in{'path'});
# moderation
my $visible_path = &make_visible_path($path);
# path of the shared directory
my $shareddir = $list->{'dir'}.'/shared';
# document to read
my $doc;
if ($path) {
$doc = $shareddir.'/'.$path;
} else {
$doc = $shareddir;
}
### is list open ?
unless ($list->{'admin'}{'status'} eq 'open'){
&report::reject_report_web('user','list_not_open',{'status' => $list->{'admin'}{'status'}},$param->{'action'},$list);
&wwslog('err','d_read : access denied for %s because list is not open', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
### Document exists ?
unless (-r "$doc") {
&wwslog('err',"do_d_read : unable to read $shareddir/$path : no such file or directory");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
### Document has non-size zero?
unless (-s "$doc") {
&wwslog('err',"do_d_read : unable to read $shareddir/$path : empty document");
&report::reject_report_web('user','empty_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
### Document isn't a description file
unless ($path !~ /\.desc/) {
&wwslog('err',"do_d_read : $shareddir/$path : description file");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
### Access control
my %mode;
$mode{'read'} = 1;
$mode{'edit'} = 1;
$mode{'control'} = 1;
my %access = &d_access_control(\%mode,$path);
my $may_read = $access{'may'}{'read'};
unless ($may_read) {
&report::reject_report_web('auth',$access{'reason'}{'read'},{},$param->{'action'},$list);
&wwslog('err','d_read : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
my $may_edit = $access{'may'}{'edit'};
my $may_control = $access{'may'}{'control'};
### File or directory ?
if (!(-d $doc)) {
my @tokens = split /\//,$doc;
my $filename = $tokens[$#tokens];
## Jump to the URL
if ($filename =~ /^\..*\.(\w+)\.moderate$/) {
$param->{'file_extension'} = $1;
}elsif ($filename =~ /^.*\.(\w+)$/) {
$param->{'file_extension'} = $1;
}
if ($param->{'file_extension'} eq 'url') {
open DOC, $doc;
my $url = ;
close DOC;
chomp $url;
$param->{'redirect_to'} = $url;
return 1;
}else {
# parameters for the template file
# view a file
$param->{'file'} = $doc;
$param->{'bypass'} = 1;
return 1;
}
}else { # directory
# verification of the URL (the path must have a slash at its end)
# if ($ENV{'PATH_INFO'} !~ /\/$/) {
# $param->{'redirect_to'} = "$param->{'base_url'}$param->{'path_cgi'}/d_read/$list_name/";
# return 1;
# }
## parameters of the current directory
if ($path && (-e "$doc/.desc")) {
my %desc_hash = &get_desc_file("$doc/.desc");
$param->{'doc_owner'} = $desc_hash{'email'};
$param->{'doc_title'} = $desc_hash{'title'};
}
my @info = stat $doc;
$param->{'doc_date'} = gettext_strftime "%d %b %Y", localtime($info[9]);
# listing of all the shared documents of the directory
unless (opendir DIR, "$doc") {
&report::reject_report_web('intern','cannot_open_dir',{'dir' => $doc },$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"d_read : cannot open $doc : $!");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
# array of entry of the directory DIR
my @tmpdir = readdir DIR;
closedir DIR;
my $dir = &get_directory_content(\@tmpdir,$param->{'user'}{'email'},$list,$doc);
# empty directory?
$param->{'empty'} = ($#{$dir} == -1);
# subdirectories hash
my %subdirs;
# file hash
my %files;
## for the exception of index.html
# name of the file "index.html" if exists in the directory read
my $indexhtml;
# boolean : one of the subdirectories or files inside
# can be edited -> normal mode of read -> d_read.tt2;
my $normal_mode;
my $path_doc;
my %desc_hash;
my $may, my $def_desc;
my $user = $param->{'user'}{'email'} || 'nobody';
foreach my $d (@{$dir}) {
# current document
my $path_doc = "$doc/$d";
#case subdirectory
if (-d $path_doc) {
# last update
my @info = stat $path_doc;
if (-e "$path_doc/.desc") {
# check access permission for reading
%desc_hash = &get_desc_file("$path_doc/.desc");
my $result = $list->check_list_authz('shared_doc.d_read',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'},
'scenario' => $desc_hash{'read'}});
my $action;
$action = $result->{'action'} if (ref($result) eq 'HASH');
if (($user eq $desc_hash{'email'}) || ($may_control) ||
($action =~ /do_it/i)) {
$subdirs{$d}{'date_epoch'} = $info[9];
$subdirs{$d}{'date'} = gettext_strftime "%d %b %Y", localtime($info[9]);
# Case read authorized : fill the hash
$subdirs{$d}{'icon'} = $icon_table{'folder'};
$subdirs{$d}{'doc'} = &make_visible_path($d);
$subdirs{$d}{'escaped_doc'} = &tools::escape_docname($d, '/');
# size of the doc
$subdirs{$d}{'size'} = (-s $path_doc)/1000;
# description
$subdirs{$d}{'title'} = $desc_hash{'title'};
$subdirs{$d}{'escaped_title'}=&tools::escape_html($desc_hash{'title'});
# Author
if ($desc_hash{'email'}) {
$subdirs{$d}{'author'} = $desc_hash{'email'};
$subdirs{$d}{'author_mailto'} = &mailto($list,$desc_hash{'email'});
$subdirs{$d}{'author_known'} = 1;
}
# if the file can be read, check for edit access & edit description files access
## only authenticated users can edit a file
if ($param->{'user'}{'email'}) {
my $result = $list->check_list_authz('shared_doc.d_edit',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'},
'scenario' => $desc_hash{'edit'}});
my $action_edit;
$action_edit = $result->{'action'} if (ref($result) eq 'HASH');
#may_action_edit = 0, 0.5 or 1
my $may_action_edit=&find_edit_mode($action_edit);
$may_action_edit=&merge_edit($may_action_edit,$may_edit);
if ($may_control || ($user eq $desc_hash{'email'})){
$subdirs{$d}{'edit'} = 1;# or = $may_action_edit ?
# if index.html, must know if something can be edit in the dir
$normal_mode = 1;
} elsif ($may_action_edit != 0) {
# $may_action_edit = 0.5 or 1
$subdirs{$d}{'edit'} = $may_action_edit;
# if index.html, must know if something can be edit in the dir
$normal_mode = 1;
}
}
if ($may_control || ($user eq $desc_hash{'email'})) {
$subdirs{$d}{'control'} = 1;
}
}
} else {
# no description file = no need to check access for read
# access for edit and control
if ($may_control) {
$subdirs{$d}{'edit'} = 1; # or = $may_action_edit ?
$normal_mode = 1;
} elsif ($may_edit !=0) {
# $may_action_edit = 1 or 0.5
$subdirs{$d}{'edit'} = $may_edit;
$normal_mode = 1;
}
if ($may_control) {$subdirs{$d}{'control'} = 1;}
}
}else {
# case file
$may = 1;
$def_desc = 0;
if (-e "$doc/.desc.$d") {
# a desc file was found
$def_desc = 1;
# check access permission
%desc_hash = &get_desc_file("$doc/.desc.$d");
my $result = $list->check_list_authz('shared_doc.d_read',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'},
'scenario' => $desc_hash{'read'}});
my $action;
$action = $result->{'action'} if (ref($result) eq 'HASH');
unless (($user eq $desc_hash{'email'}) || ($may_control) ||
($action =~ /do_it/i)) {
$may = 0;
}
}
# if permission or no description file
if ($may) {
$path_doc =~ /^([^\/]*\/)*([^\/]+)\.([^\/]+)$/;
## Bookmark
if (($path_doc =~ /\.url$/) || ($path_doc =~ /\.url\.moderate$/)) {
open DOC, $path_doc;
my $url = ;
close DOC;
chomp $url;
$files{$d}{'url'} = $url;
$files{$d}{'anchor'} = &make_visible_path($d);
$files{$d}{'icon'} = $icon_table{'url'};
## MIME - TYPES : icons for template
}elsif (my $type = $mime_types->{$3}) {
# type of the file and apache icon
$type =~ /^([\w\-]+)\/([\w\-]+)$/;
my $mimet = $1;
my $subt = $2;
if ($subt) {
if ($subt =~ /^octet-stream$/) {
$mimet = 'octet-stream';
$subt = 'binary';
}
$files{$d}{'type'} = "$subt file";
}
$files{$d}{'icon'} = $icon_table{$mimet} || $icon_table{'unknown'};
} else {
# unknown file type
$files{$d}{'icon'} = $icon_table{'unknown'};
}
## case html
if ($3 =~ /^html?$/i) {
$files{$d}{'html'} = 1;
$files{$d}{'type'} = 'html file';
$files{$d}{'icon'} = $icon_table{'text'};
}
## exception of index.html
if ($d =~ /^(index\.html?)$/i) {
$indexhtml = $1;
}
## Access control for edit and control
if ($def_desc) {
# check access for edit and control the file
## Only authenticated users can edit files
if ($param->{'user'}{'email'}) {
my $result= $list->check_list_authz('shared_doc.d_edit',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'},
'scenario' => $desc_hash{'edit'}});
my $action_edit;
$action_edit = $result->{'action'} if (ref($result) eq 'HASH');
#may_action_edit = 0, 0.5 or 1
my $may_action_edit=&find_edit_mode($action_edit);
$may_action_edit=&merge_edit($may_action_edit,$may_edit);
if ($may_control || ($user eq $desc_hash{'email'})){
$normal_mode = 1;
$files{$d}{'edit'} = 1; # or = $may_action_edit ?
} elsif ($may_action_edit != 0){
# $may_action_edit = 1 or 0.5
$normal_mode = 1;
$files{$d}{'edit'} = $may_action_edit;
}
if (($user eq $desc_hash{'email'}) || $may_control) {
$files{$d}{'control'} = 1;
}
# fill the file hash
# description of the file
$files{$d}{'title'} = $desc_hash{'title'};
$files{$d}{'escaped_title'}=&tools::escape_html($desc_hash{'title'});
# author
if ($desc_hash{'email'}) {
$files{$d}{'author'} = $desc_hash{'email'};
$files{$d}{'author_known'} = 1;
$files{$d}{'author_mailto'} = &mailto($list,$desc_hash{'email'});
}
} else {
if ($may_edit!=0) {
$files{$d}{'edit'} = $may_edit ;
$normal_mode = 1;
}
if ($may_control) {$files{$d}{'control'} = 1;}
}
# name of the file
if ($d =~ /^(\.).*(.moderate)$/) {
# file not yet moderated can be seen by its author
$files{$d}{'doc'} = &make_visible_path($d);
$files{$d}{'moderate'} = 1;
} else {
$files{$d}{'doc'} = &make_visible_path($d);
}
$files{$d}{'escaped_doc'} = &tools::escape_docname($d, '/');
# last update
my @info = stat $path_doc;
$files{$d}{'date_epoch'} = $info[9];
$files{$d}{'date'} = gettext_strftime "%d %b %Y", localtime($info[9]);
# size
$files{$d}{'size'} = (-s $path_doc)/1000;
}
}
}
}
### Exception : index.html
if ($indexhtml) {
unless ($normal_mode) {
$param->{'file_extension'} = 'html';
$param->{'bypass'} = 1;
$param->{'file'} = "$doc/$indexhtml";
return 1;
}
}
## to sort subdirs
my @sort_subdirs;
my $order = $in{'order'} || 'order_by_doc';
$param->{'order_by'} = $order;
foreach my $k (sort {by_order($order,\%subdirs)} keys %subdirs) {
push @sort_subdirs, $subdirs{$k};
}
## to sort files
my @sort_files;
foreach my $k (sort {by_order($order,\%files)} keys %files) {
push @sort_files, $files{$k};
}
# parameters for the template file
$param->{'list'} = $list_name;
$param->{'may_edit'} = $may_edit;
$param->{'may_control'} = $may_control;
if ($path) {
# building of the parent directory path
if ($path =~ /^(([^\/]*\/)*)([^\/]+)$/) {
$param->{'father'} = $1;
}else {
$param->{'father'} = '';
}
$param->{'escaped_father'} = &tools::escape_docname($param->{'father'}, '/');
# Parameters for the description
if (-e "$doc/.desc") {
my @info = stat "$doc/.desc";
$param->{'serial_desc'} = $info[9];
my %desc_hash = &get_desc_file("$doc/.desc");
$param->{'description'} = $desc_hash{'title'};
}
$param->{'path'} = $path;
$param->{'visible_path'} = $visible_path;
$param->{'escaped_path'} = &tools::escape_docname($param->{'path'}, '/');
}
if (scalar keys %subdirs) {
$param->{'sort_subdirs'} = \@sort_subdirs;
}
if (scalar keys %files) {
$param->{'sort_files'} = \@sort_files;
}
}
$param->{'father_icon'} = $icon_table{'father'};
$param->{'sort_icon'} = $icon_table{'sort'};
## Show expert commands / user page
# for the curent directory
if ($may_edit == 0 && $may_control == 0) {
$param->{'has_dir_rights'} = 0;
} else {
$param->{'has_dir_rights'} = 1;
if ($may_edit == 1) { # (is_author || ! moderated)
$param->{'total_edit'} = 1;
}
}
# set the page mode
if ($in{'show_expert_page'} && $param->{'has_dir_rights'}) {
$session->{'shared_mode'}='expert';
if ($param->{'user'}{'prefs'}{'shared_mode'} ne 'expert') {
# update user pref as soon as connected user change shared mode
$param->{'user'}{'prefs'}{'shared_mode'} = 'expert';
&List::update_user_db($param->{'user'}{'email'},{data=>&tools::hash_2_string($param->{'user'}{'prefs'})}) ;
}
$param->{'expert_page'} = 1;
} elsif ($in{'show_user_page'}) {
$session->{'shared_mode'}='basic';
if ($param->{'user'}{'prefs'}{'shared_mode'} ne 'basic') {
# update user pref as soon as connected user change shared mode
$param->{'user'}{'prefs'}{'shared_mode'} = 'basic';
&List::update_user_db($param->{'user'}{'email'},{data=>&tools::hash_2_string($param->{'user'}{'prefs'})}) ;
}
$param->{'expert_page'} = 0;
} else {
if ( $session->{'shared_mode'} eq 'expert' && $param->{'has_dir_rights'}) {
$param->{'expert_page'} = 1;
} else {
$param->{'expert_page'} = 0;
}
}
#open TMP, ">/tmp/dump1";
#&tools::dump_var($param, 0,\*TMP);
#close TMP;
&web_db_log({'parameters' => $in{'path'},
'status' => 'success'});
return 1;
}
## return a ref on an array of file (or subdirecties) to show to user
sub get_directory_content {
my $tmpdir = shift;
my $user = shift;
my $list = shift;
my $doc = shift;
# array of file not hidden
my @dir = grep !/^\./, @$tmpdir;
# array with documents not yet moderated
my @moderate_dir = grep (/(\.moderate)$/, @$tmpdir);
@moderate_dir = grep (!/^\.desc\./, @moderate_dir);
# the editor can see file not yet moderated
# a user can see file not yet moderated if he is th owner of these files
if ($list->am_i('editor',$user)) {
push(@dir,@moderate_dir);
}else {
my @privatedir = &select_my_files($user,$doc,\@moderate_dir);
push(@dir,@privatedir);
}
return \@dir;
}
## return an array that contains only file from @$refdir that belongs to $user
sub select_my_files {
my ($user,$path,$refdir)=@_;
my @new_dir;
foreach my $d (@$refdir) {
if (-e "$path/.desc.$d") {
my %desc_hash = &get_desc_file("$path/.desc.$d");
if ($user eq $desc_hash{'email'}){
$new_dir[$#new_dir+1]=$d;
}
}
}
return @new_dir;
}
## Useful function to get off the slash at the end of the path
## at its end
sub no_slash_end {
my $path = shift;
## supress ending '/'
$path =~ s/\/+$//;
return $path;
}
## return a visible path from a moderated file or not
sub make_visible_path {
my $path = shift;
my $visible_path = $path;
if ($path =~ /\.url(\.moderate)?$/){
if ($path =~ /^([^\/]*\/)*([^\/]+)\.([^\/]+)$/) {
$visible_path =~ s/\.moderate$//;
$visible_path =~ s/^\.//;
$visible_path =~ s/\.url$//;
}
}elsif ($path =~ /\.moderate$/){
if ($path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/) {
my $name = $3;
$name =~ s/^\.//;
$name =~ s/\.moderate//;
$visible_path = "$2"."$name";
}
}
## Qdecode the visible path
return &tools::qdecode_filename($visible_path);
}
## Access to latest shared documents
sub do_latest_d_read {
&wwslog('info', 'do_latest_d_read(%s,%s,%s)', $in{'list'}, $in{'for'}, $in{'count'});
### is list open ?
unless ($list->{'admin'}{'status'} eq 'open'){
&report::reject_report_web('user','list_not_open',{'status' => $list->{'admin'}{'status'}},$param->{'action'},$list);
&wwslog('err','d_latest_d_read : access denied for %s because list is not open', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
### shared exist ?
my $shareddir = $list->{'dir'}.'/shared';
unless (-r "$shareddir") {
&wwslog('err',"do_latest_d_read : unable to read $shareddir : no such file or directory");
&report::reject_report_web('user','no_shared',{},$param->{'action'},$list);
return undef;
}
### Document has non-size zero?
unless (-s "$shareddir") {
&wwslog('err',"do_latest_d_read : unable to read $shareddir : empty document");
&report::reject_report_web('user','shared_empty',{},$param->{'action'},$list);
return undef;
}
### Access control
my %mode;
$mode{'read'} = 1;
$mode{'control'} = 1;
my %access = &d_access_control(\%mode,$shareddir);
unless ($access{'may'}{'read'}) {
&report::reject_report_web('auth',$access{'reason'}{'read'},{},$param->{'action'},$list);
&wwslog('err','latest_d_read : access denied for %s', $param->{'user'}{'email'});
return undef;
}
## parameters of the query
my $today = time;
my $oldest_day;
if (defined $in{'for'}) {
$oldest_day = $today - (86400 * ($in{'for'}));
$param->{'for'} = $in{'for'};
unless ($oldest_day >= 0){
&report::reject_report_web('user','nb_days_to_much',{'nb_days' => $in{'for'} },$param->{'action'},$list);
&wwslog('err','do_latest_d_read: parameter "for" is too big"');
}
}
my $nb_doc;
my $NB_DOC_MAX = 100;
if (defined $in{'count'}) {
if ($in{'count'} > $NB_DOC_MAX) {
$in{'count'} = $NB_DOC_MAX;
}
$param->{'count'} = $in{'count'};
$nb_doc = $in{'count'};
} else {
$nb_doc = $NB_DOC_MAX;
}
my $documents;
unless ($documents = &directory_browsing('',$oldest_day,$access{'may'}{'control'})) {
&wwslog('err',"do_d_latest_d_read($list) : impossible to browse shared");
&report::reject_report_web('intern','browse_shared',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
return undef;
}
@$documents = sort ({$b->{'date_epoch'} <=> $a->{'date_epoch'}} @$documents);
@{$param->{'documents'}} = splice(@$documents,0,$nb_doc);
return 1;
}
## browse a directory recursively and return documents younger than $oldest_day
sub directory_browsing {
my ($dir,$oldest_day,$may_control) = @_;
&wwslog('debug2',"directory_browsing($dir,$oldest_day)");
my @result;
my $shareddir = $list->{'dir'}.'/shared';
my $path_dir = "$shareddir/$dir";
## listing of all the shared documents of the directory
unless (opendir DIR, "$path_dir") {
&wwslog('err',"directory_browsing($dir) : cannot open the directory : $!");
return undef;
}
my @tmpdir = readdir DIR;
closedir DIR;
# array of file not hidden
my @directory = grep !/^\./, @tmpdir;
my $user = $param->{'user'}{'email'} || 'nobody';
## browsing
foreach my $d (@directory) {
my $path_d = "$path_dir/$d";
#case subdirectory
if (-d $path_d) {
if (-e "$path_d/.desc") {
# check access permission for reading
my %desc_hash = &get_desc_file("$path_d/.desc");
my $result = $list->check_list_authz('shared_doc.d_read',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'},
'scenario' => $desc_hash{'read'}});
my $action;
$action = $result->{'action'} if (ref($result) eq 'HASH');
if (($user eq $desc_hash{'email'}) || ($may_control) ||
($action =~ /do_it/i)) {
my $content_d;
unless($content_d = &directory_browsing("$dir/$d",$oldest_day)) {
&wwslog('err',"directory_browsing($dir) : impossible to browse subdirectory $d");
next;
}
if (ref($content_d) eq "ARRAY") {
push @result,@$content_d;
}
}
}
#case file
} else {
my %file_info;
## last update
my @info = stat $path_d;
$file_info{'date_epoch'} = $info[9];
if ($file_info{'date_epoch'} < $oldest_day) {
next;
}
$file_info{'last_update'} = gettext_strftime "%d %b %Y", localtime($info[9]);
## exception of index.html
if ($d =~ /^(index\.html?)$/i) {
next;
}
my $may = 1;
my $def_desc = 0;
my %desc_hash;
if (-e "$path_dir/.desc.$d") {
# a desc file was found
$def_desc = 1;
# check access permission
%desc_hash = &get_desc_file("$path_dir/.desc.$d");
my $result = $list->check_list_authz('shared_doc.d_read',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'},
'scenario' => $desc_hash{'read'}});
my $action;
$action = $result->{'action'} if (ref($result) eq 'HASH');
unless (($user eq $desc_hash{'email'}) || ($may_control) ||
($action =~ /do_it/i)) {
$may = 0;
}
}
# if permission or no description file
if ($may) {
$path_d =~ /^([^\/]*\/)*([^\/]+)\.([^\/]+)$/;
## Bookmark
if ($path_d =~ /\.url$/) {
open DOC, $path_d;
my $url = ;
close DOC;
chomp $url;
$file_info{'url'} = $url;
$file_info{'anchor'} = &make_visible_path($d);
$file_info{'icon'} = $icon_table{'url'};
## MIME - TYPES : icons for template
}elsif (my $type = $mime_types->{$3}) {
# type of the file and apache icon
$type =~ /^([\w\-]+)\/([\w\-]+)$/;
my $mimet = $1;
my $subt = $2;
if ($subt) {
if ($subt =~ /^octet-stream$/) {
$mimet = 'octet-stream';
$subt = 'binary';
}
}
$file_info{'icon'} = $icon_table{$mimet} || $icon_table{'unknown'};
## UNKNOWN FILE TYPE
} else {
$file_info{'icon'} = $icon_table{'unknown'};
}
## case html
if ($3 =~ /^html?$/i) {
$file_info{'html'} = 1;
$file_info{'icon'} = $icon_table{'text'};
}
## name of the file
$file_info{'name'} = &make_visible_path($d);
$file_info{'escaped_name'} = &tools::escape_docname($d, '/');
## content_directory
if ($dir) {
$file_info{'content_dir'} = &make_visible_path($dir);
} else {
$file_info{'content_dir'} = "/";
}
$file_info{'escaped_content_dir'} = &tools::escape_docname($dir,'/');
if ($def_desc) {
## description
$file_info{'title'} = $desc_hash{'title'};
$file_info{'escaped_title'}=&tools::escape_html($desc_hash{'title'});
## author
if ($desc_hash{'email'}) {
$file_info{'author'} = $desc_hash{'email'};
}
}
push @result,\%file_info;
}
} # else (file)
} # foreach
return \@result;
}
#*******************************************
# Function : do_d_editfile
# Description : prepares the parameters to
# edit a file
#*******************************************
sub do_d_editfile {
&wwslog('info', 'do_d_editfile(%s)', $in{'path'});
# Variables
my $path = &no_slash_end($in{'path'});
my $list_name = $list->{'name'};
my $shareddir = $list->{'dir'}.'/shared';
my $visible_path = &make_visible_path($path);
$param->{'directory'} = -d "$shareddir/$path";
# Control
unless ($path) {
&report::reject_report_web('user','missing_arg',{'argument' => 'file name'},$param->{'action'});
&wwslog('err','do_d_editfile: no file name');
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'missing_parameter'});
return undef;
}
# Existing document? File?
unless (-w "$shareddir/$path") {
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&wwslog('err',"d_editfile : Cannot edit $shareddir/$path : not an existing file");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'no_file'});
return undef;
}
### Document isn't a description file?
unless ($path !~ /\.desc/) {
&wwslog('err',"do_editfile : $shareddir/$path : description file");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
if (($path =~ /\.url$/) ||($path =~ /^\..+\.url.moderate$/)) {
## Get URL of bookmark
open URL, "$shareddir/$path";
my $url = ;
close URL;
chomp $url;
$param->{'url'} = $url;
$visible_path =~ s/\.url$//;
}
### is list open ?
unless ($list->{'admin'}{'status'} eq 'open'){
&report::reject_report_web('user','list_not_open',{'status' => $list->{'admin'}{'status'}},$param->{'action'},$list);
&wwslog('err','d_edit : access denied for %s because list is not open', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
# Access control
my %mode;
$mode{'edit'} = 1;
my %access = &d_access_control(\%mode,$path);
my $may_edit = $access{'may'}{'edit'};
unless ($may_edit > 0) {
&report::reject_report_web('auth',$access{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','d_editfile : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'autorization'});
return undef;
}
## End of controls
$param->{'list'} = $list_name;
$param->{'path'} = $path;
$param->{'visible_path'} = $visible_path;
# test if it's a text file
if (-T "$shareddir/$path") {
$param->{'textfile'} = 1;
$param->{'filepath'} = "$shareddir/$path";
} else {
$param->{'textfile'} = 0;
}
$param->{'use_htmlarea'} = '1' if (($wwsconf->{'htmlarea_url'}) and ($param->{'textfile'}) and ($path =~ /\.html?/));
#Current directory
if ($path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/) {
$param->{'father'} = $1;
}else {
$param->{'father'} = '';
}
$param->{'escaped_father'} = &tools::escape_docname($param->{'father'}, '/');
# Description of the file
my $descfile;
if (-d "$shareddir/$path") {
$descfile = "$shareddir/$1$3/.desc";
}else {
$descfile = "$shareddir/$1.desc.$3";
}
if (-e $descfile) {
my %desc_hash = &get_desc_file($descfile);
$param->{'desc'} = $desc_hash{'title'};
$param->{'doc_owner'} = $desc_hash{'email'};
## Synchronization
my @info = stat $descfile;
$param->{'serial_desc'} = $info[9];
}
## Synchronization
my @info = stat "$shareddir/$path";
$param->{'serial_file'} = $info[9];
## parameters of the current directory
$param->{'doc_date'} = gettext_strftime "%d %b %y %H:%M", localtime($info[9]);
$allow_absolute_path = 1;
$param->{'father_icon'} = $icon_table{'father'};
&web_db_log({'parameters' => $in{'path'},
'status' => 'success'});
return 1;
}
#*******************************************
# Function : do_d_properties
# Description : prepares the parameters to
# change a file properties
#*******************************************
sub do_d_properties {
&wwslog('info', 'do_d_properties(%s)', $in{'path'});
# Variables
my $path = &no_slash_end($in{'path'});
my $list_name = $list->{'name'};
my $shareddir = $list->{'dir'}.'/shared';
my $visible_path = &make_visible_path($path);
$param->{'directory'} = -d "$shareddir/$path";
# Control
unless ($path) {
&report::reject_report_web('user','missing_arg',{'argument' => 'filename'},$param->{'action'});
&wwslog('err','do_d_properties: no file name');
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'no_file'});
return undef;
}
# Existing document? File?
unless (-w "$shareddir/$path") {
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&wwslog('err',"do_d_properties : Cannot edit $shareddir/$path : not an existing file");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'no_file'});
return undef;
}
### Document isn't a description file?
unless ($path !~ /\.desc/) {
&wwslog('err',"do_d_properties : $shareddir/$path : description file");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
if ($path =~ /\.url$/) {
## Get URL of bookmark
open URL, "$shareddir/$path";
my $url = ;
close URL;
chomp $url;
$param->{'url'} = $url;
}
# Access control
my %mode;
$mode{'edit'} = 1;
my %access = &d_access_control(\%mode,$path);
my $may_edit = $access{'may'}{'edit'};
unless ($may_edit > 0) {
&report::reject_report_web('auth',$access{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','do_d_properties : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
## End of controls
$param->{'list'} = $list_name;
$param->{'path'} = $path;
$param->{'visible_path'} = $visible_path;
# test if it's a text file
if (-T "$shareddir/$path") {
$param->{'textfile'} = 1;
$param->{'filepath'} = "$shareddir/$path";
} else {
$param->{'textfile'} = 0;
}
$param->{'use_htmlarea'} = '1' if (($wwsconf->{'htmlarea_url'}) and ($param->{'textfile'}) and ($path =~ /\.html?/));
#Current directory
if ($path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/) {
$param->{'father'} = $1;
}else {
$param->{'father'} = '';
}
$param->{'escaped_father'} = &tools::escape_docname($param->{'father'}, '/');
$param->{'fname'} = &make_visible_path($3);
# Description of the file
my $descfile;
if (-d "$shareddir/$path") {
$descfile = "$shareddir/$1$3/.desc";
}else {
$descfile = "$shareddir/$1.desc.$3";
}
if (-e $descfile) {
my %desc_hash = &get_desc_file($descfile);
$param->{'desc'} = $desc_hash{'title'};
$param->{'doc_owner'} = $desc_hash{'email'};
## Synchronization
my @info = stat $descfile;
$param->{'serial_desc'} = $info[9];
}
## Synchronization
my @info = stat "$shareddir/$path";
$param->{'serial_file'} = $info[9];
## parameters of the current directory
$param->{'doc_date'} = gettext_strftime "%d %b %y %H:%M", localtime($info[9]);
$allow_absolute_path = 1;
$param->{'father_icon'} = $icon_table{'father'};
&web_db_log({'parameters' => $in{'path'},
'status' => 'success'});
return 1;
}
#*******************************************
# Function : do_d_describe
# Description : Saves the description of
# the file
#******************************************
sub do_d_describe {
&wwslog('info', 'do_d_describe(%s)', $in{'path'});
# Variables
my $path = &no_slash_end($in{'path'});
my $visible_path=&make_visible_path($path);
my $list_name = $list->{'name'};
my $shareddir = $list->{'dir'}.'/shared';
#### Controls
### Document isn't a description file?
unless ($path !~ /\.desc/) {
&wwslog('info',"do_d_describe : $shareddir/$path : description file");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
## the path must not be empty (the description file of the shared directory
# doesn't exist)
unless ($path) {
&report::reject_report_web('intern','cannot_describe_shared_directory',{'path' => $path },$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info',"d_describe : Cannot describe $shareddir : root directory");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
# the file to describe must already exist
unless (-e "$shareddir/$path") {
&report::reject_report_web('user','no_doc_to_describe',{'path'=> $visible_path},$param->{'action'},$list);
&wwslog('info',"d_describe : Unable to describe $shareddir/$path : not an existing document");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'no_file'});
return undef;in{'shortname'}
}
# Access control
# Access control
my %mode;
$mode{'edit'} = 1;
my %access = &d_access_control(\%mode,$path);
unless ($access{'may'}{'edit'} > 0) {
&report::reject_report_web('auth',$access{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('info','d_describe : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
## End of controls
if ($in{'content'} !~ /^\s*$/) {
# Description file
$path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/;
my $dir = $1;
my $file = $3;
my $desc_file;
if (-d "$shareddir/$path") {
$desc_file = "$shareddir/$dir$file/.desc";
} else {
$desc_file = "$shareddir/$dir.desc.$file";
}
if (-r "$desc_file"){
# if description file already exists : open it and modify it
my %desc_hash = &get_desc_file ("$desc_file");
# Synchronization
unless (&synchronize($desc_file,$in{'serial'})){
&report::reject_report_web('user','synchro_failed',{},$param->{'action'},$list);
&wwslog('info',"d_describe : Synchronization failed for $desc_file");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
# fill the description file
unless (open DESC,">$desc_file") {
&wwslog('info',"do_d_describe : cannot open $desc_file : $!");
&report::reject_report_web('intern','cannot_open_file',{'file' => $desc_file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
# information modified
print DESC "title\n $in{'content'}\n\n";
# information not modified
print DESC "access\n read $desc_hash{'read'}\n edit $desc_hash{'edit'}\n\n";
print DESC "creation\n";
# time
print DESC " date_epoch $desc_hash{'date'}\n";
# author
print DESC " email $desc_hash{'email'}\n\n";
close DESC;
} else {
# Creation of a description file
unless (open (DESC,">$desc_file")) {
&report::reject_report_web('intern','cannot_open_file',{'file' => $desc_file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info',"d_describe : Cannot create description file $desc_file : $!");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
# fill
# description
print DESC "title\n $in{'content'}\n\n";
# date and author
my @info = stat "$shareddir/$path";
print DESC "creation\n date_epoch ".$info[10]."\n email\n\n";
# access rights
print DESC "access\n";
print DESC " read $access{'scenario'}{'read'}\n";
print DESC " edit $access{'scenario'}{'edit'}\n\n";
close DESC;
}
$in{'path'} = &no_slash_end($dir);
}
&web_db_log({'parameters' => $in{'path'},
'status' => 'success'});
return 'd_read';
}
#*******************************************
# Function : do_d_savefile
# Description : Saves a file edited in a
# text area
#******************************************
sub do_d_savefile {
&wwslog('info', 'do_d_savefile(%s)', $in{'path'});
# Variables
my $path = &no_slash_end($in{'path'});
if ($in{'url'} &&
$in{'previous_action'} eq 'd_read') {
$path .= '/'.$in{'name_doc'} . '.url';
}
my $visible_path = &make_visible_path($path);
my $moderated;
if ($visible_path ne $path) {
$moderated = 1;
}
#my $list_name = $in{'list'};
my $list_name = $list->{'name'};
my $shareddir = $list->{'dir'}.'/shared';
#### Controls
my $creation = 1 unless (-f "$shareddir/$path");
### Document isn't a description file
unless ($path !~ /\.desc/) {
&wwslog('err',"do_d_savefile : $shareddir/$path : description file");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
# Access control
my %mode;
$mode{'edit'} = 1;
my %access = &d_access_control(\%mode,$path);
unless ($access{'may'}{'edit'} > 0) {
&report::reject_report_web('auth',$access{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','do_d_savefile : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
#### End of controls
if (($in{'content'} =~ /^\s*$/) && ($in{'url'} =~ /^\s*$/)) {
&report::reject_report_web('user','no_content',{},$param->{'action'},$list);
&wwslog('err',"do_d_savefile : Cannot save file $shareddir/$path : no content");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'missing_parameter'});
return undef;
}
# Synchronization
unless ($in{'url'}) { # only for files
unless (&synchronize("$shareddir/$path",$in{'serial'})){
&report::reject_report_web('user','synchro_failed',{},$param->{'action'},$list);
&wwslog('err',"do_d_savefile : Synchronization failed for $shareddir/$path");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
}
# Renaming of the old file
############""" pas les url ?
rename ("$shareddir/$path","$shareddir/$path.old")
unless ($creation);
my $dir;
my $file;
if ($path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/){
$dir = $1;
$file = $3;
}
if ($in{'url'}) {
##############
# if ($access{'may'}{'edit'} == 0.5) {
# open URL, ">$shareddir/$dir.$file.moderate";
# }else {
open URL, ">$shareddir/$path";
# }
print URL "$in{'url'}\n";
close URL;
}else {
# Creation of the shared file
unless (open FILE, ">$shareddir/$path") {
rename("$shareddir/$path.old","$shareddir/$path");
&report::reject_report_web('user','cannot_overwrite', {'reason' => $1,
'path' => $visible_path }
,$param->{'action'},$list);
&wwslog('err',"do_d_savefile : Cannot open for replace $shareddir/$path : $!");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
print FILE $in{'content'};
close FILE;
}
unlink "$shareddir/$path.old";
# Description file
if (-e "$shareddir/$dir.desc.$file"){
# if description file already exists : open it and modify it
my %desc_hash = &get_desc_file ("$shareddir/$dir.desc.$file");
open DESC,">$shareddir/$dir.desc.$file";
# information not modified
print DESC "title\n $desc_hash{'title'}\n\n";
print DESC "access\n read $desc_hash{'read'}\n edit $desc_hash{'edit'}\n\n";
print DESC "creation\n";
# date
print DESC ' date_epoch '.$desc_hash{'date'}."\n";
# information modified
# author
print DESC " email $param->{'user'}{'email'}\n\n";
close DESC;
} else {
# Creation of a description file if author is known
unless (open (DESC,">$shareddir/$dir.desc.$file")) {
&wwslog('info',"do_d_savefile: cannot create description file $shareddir/$dir.desc.$file");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
}
# description
print DESC "title\n \n\n";
# date of creation and author
my @info = stat "$shareddir/$path";
print DESC "creation\n date_epoch ".$info[10]."\n email $param->{'user'}{'email'}\n\n";
# Access
print DESC "access\n";
print DESC " read $access{'scenario'}{'read'}\n";
print DESC " edit $access{'scenario'}{'edit'}\n\n";
close DESC;
}
# shared_moderated
#######################
if (($access{'may'}{'edit'} == 0.5) && ($creation)) {
unless (rename "$shareddir/$path","$shareddir/$dir.$file.moderate"){
&report::reject_report_web('intern','rename_file',{'old'=>"$shareddir/$path",
'new'=>"$shareddir/$dir.$file.moderate"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_savefile : Failed to rename $path to $dir.$file.moderate : $!");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
}
unless (rename "$shareddir/$dir.desc.$file","$shareddir/$dir.desc..$file.moderate"){
&report::reject_report_web('intern','rename_file',{'old'=>"$shareddir/$dir.desc.$file",
'new'=>"$shareddir/$dir.desc..$file.moderate"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_savefile : Failed to rename $dir.desc.$file to $dir.desc..$file.moderate : $!");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
}
if (!$in{'url'}){
$in{'path'}=$path;
$param->{'path'}=$path;
}else {
$visible_path = $file;
$visible_path =~ s/\.url$//
}
unless ($list->send_notify_to_editor('shared_moderated',{'filename' => $visible_path,
'who' => $param->{'user'}{'email'}})) {
&wwslog('notice',"Unable to send notify 'shared_moderated' to $list->{'name'} list editor");
}
&report::notice_report_web('to_moderate', {'path' => $visible_path},$param->{'action'});
}
&report::notice_report_web('save_success', {'path' => $visible_path},$param->{'action'});
&web_db_log({'parameters' => $in{'path'},
'status' => 'success'});
if ($in{'previous_action'}) {
return $in{'previous_action'};
}else {
$in{'path'} =~ s/([^\/]+)$//;
$param->{'path'} =~ s/([^\/]+)$//;
return 'd_read';
}
}
#*******************************************
# Function : do_d_overwrite
# Description : Overwrites a file with a
# uploaded file
#******************************************
sub do_d_overwrite {
&wwslog('info', 'do_d_overwrite(%s)', $in{'path'});
# Variables
my $path = &no_slash_end($in{'path'});
my $visible_path = &make_visible_path($path);
#my $list_name = $in{'list'};
my $list_name = $list->{'name'};
# path of the shared directory
my $shareddir = $list->{'dir'}.'/shared';
# Parameters of the uploaded file
my $fh = $query->upload('uploaded_file');
my $fn = $query->param('uploaded_file');
# name of the file
my $fname;
if ($fn =~ /([^\/\\]+)$/) {
$fname = $1;
}
### uploaded file must have a name
unless ($fname) {
&report::reject_report_web('user','missing_arg',{'argument' => 'file name'},$param->{'action'});
&wwslog('info',"do_d_overwrite : No file specified to overwrite");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'no_file'});
return undef;
}
####### Controls
### Document isn't a description file?
unless ($path !~ /\.desc/) {
&wwslog('err',"do_d_overwrite : $shareddir/$path : description file");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
# the path to replace must already exist
unless (-e "$shareddir/$path") {
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&wwslog('err',"do_d_overwrite : Unable to overwrite $shareddir/$path : not an existing file");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'no_file'});
return undef;
}
# the path must represent a file
if (-d "$shareddir/$path") {
&report::reject_report_web('user','doc_already_a_dir',{'path'=> $visible_path},$param->{'action'},$list);
&wwslog('err',"do_d_overwrite : Unable to create $shareddir/$path : a directory named $path already exists");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'already_exists'});
return undef;
}
# Access control
my %mode;
$mode{'edit'} = 1;
my %access = &d_access_control(\%mode,$path);
unless ($access{'may'}{'edit'} > 0) {
&report::reject_report_web('auth',$access{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','do_d_overwrite : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
#### End of controls
# Synchronization
unless (&synchronize("$shareddir/$path",$in{'serial'})){
&report::reject_report_web('user','synchro_failed',{},$param->{'action'},$list);
&wwslog('err',"do_d_overwrite : Synchronization failed for $shareddir/$path");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
# Renaming of the old file
rename ("$shareddir/$path","$shareddir/$path.old");
# Creation of the shared file
unless (open FILE, ">:bytes", "$shareddir/$path") {
&report::reject_report_web('user','cannot_overwrite', {'reason' => $!,
'path' => $visible_path }
,$param->{'action'},$list);
&wwslog('err',"d_overwrite : Cannot open for replace $shareddir/$path : $!");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'cannot_overwrite'});
return undef;
}
while (<$fh>) {
print FILE;
}
close FILE;
# Description file
my ($dir, $file);
if ($path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/) {
$dir = $1;
$file = $3;
}
if (-e "$shareddir/$dir.desc.$file"){
# if description file already exists: open it and modify it
my %desc_hash = &get_desc_file ("$shareddir/$dir.desc.$file");
open DESC,">$shareddir/$dir.desc.$file";
# information not modified
print DESC "title\n $desc_hash{'title'}\n\n";
print DESC "access\n read $desc_hash{'read'}\n edit $desc_hash{'edit'}\n\n";
print DESC "creation\n";
# time
print DESC " date_epoch $desc_hash{'date'}\n";
# information modified
# author
print DESC " email $param->{'user'}{'email'}\n\n";
close DESC;
} else {
# Creation of a description file
unless (open (DESC,">$shareddir/$dir.desc.$file")) {
&wwslog('info',"do_d_overwrite : Cannot create description file $shareddir/$dir.desc.$file");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
# description
print DESC "title\n \n\n";
# date of creation and author
my @info = stat "$shareddir/$path";
print DESC "creation\n date_epoch ".$info[10]."\n email $param->{'user'}{'email'}\n\n";
# access rights
print DESC "access\n";
print DESC " read $access{'scenario'}{'read'}\n";
print DESC " edit $access{'scenario'}{'edit'}\n\n";
close DESC;
}
# shared_moderated
if (($access{'may'}{'edit'} == 0.5) && ($path eq $visible_path)) {
unless (rename "$shareddir/$path","$shareddir/$dir.$file.moderate"){
&report::reject_report_web('intern','rename_file',{'old'=>"$shareddir/$path",
'new'=>"$shareddir/$dir.$file.moderate"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_overwrite : Failed to rename $path to $dir.$file.moderate : $!");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
unless (rename "$shareddir/$dir.desc.$file","$shareddir/$dir.desc..$file.moderate"){
&report::reject_report_web('intern','rename_file',{'old'=>"$shareddir/$dir.desc.$file",
'new'=>"$shareddir/$dir.desc..$file.moderate"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_overwrite : Failed to rename $dir.desc.$file to $dir.desc..$file.moderate : $!");
&web_db_log({'parameters' => $in{'path'},
'status' => 'error',
'error_type' => 'internal'});
}
unless ($list->send_notify_to_editor('shared_moderated',{'filename' => $visible_path,
'who' => $param->{'user'}{'email'}})) {
&wwslog('notice',"Unable to send notify 'shared_moderated' to $list->{'name'} list editor");
}
$in{'path'}="$dir.$file.moderate";
&report::notice_report_web('to_moderate',{'path' => $visible_path},$param->{'action'});
}
# Removing of the old file
unlink "$shareddir/$path.old";
$in{'list'} = $list_name;
#$in{'path'} = $dir;
# message of success
&report::notice_report_web('upload_success', {'path' => $visible_path});
&web_db_log({'parameters' => $in{'path'},
'status' => 'success'});
return 'd_editfile';
}
#*******************************************
# Function : do_d_upload
# Description : Creates a new file with a
# uploaded file
#******************************************
sub do_d_upload {
# Parameters of the uploaded file (from d_read.tt2)
my $fn = $in{'uploaded_file'};
# name of the file, without path
my ($fname, $visible_fname);
if ($fn =~ /([^\/\\]+)$/) {
$fname = &tools::qencode_filename($1);
$visible_fname = &make_visible_path($fname);
}
# param from d_upload.tt2
if ($in{'shortname'}){
$fname = $in{'shortname'};
}
&wwslog('info', 'do_d_upload(%s/%s)', $in{'path'},$fname);
# Variables
my $path = &no_slash_end($in{'path'});
my $visible_path = &make_visible_path($path);
# path of the shared directory
my $shareddir = $list->{'dir'}.'/shared';
# name of the file
my $longname = "$shareddir/$path/$fname";
$longname =~ s/\/+/\//g;
# ## $path must have a slash at its end
# $path = &format_path('with_slash',$path);
#my $list_name = $in{'list'};
my $list_name = $list->{'name'};
## Controls
# uploaded file must have a name
unless ($fname) {
&report::reject_report_web('user','no_name',{},$param->{'action'},$list);
&wwslog('err',"do_d_upload : No file specified to upload");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_file','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
## Check quota
if ($list->{'admin'}{'shared_doc'}{'quota'}) {
if ($list->get_shared_size() >= $list->{'admin'}{'shared_doc'}{'quota'} * 1024){
&report::reject_report_web('user','shared_full',{},$param->{'action'},$list);
&wwslog('err',"do_d_upload : Shared Quota exceeded for list $list->{'name'}");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'shared_full','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
# The name of the file must be correct and musn't not be a description file
if ($fname =~ /^\./
|| $fname =~ /\.desc/
|| $fname =~ /[~\#\[\]]$/) {
# unless ($fname =~ /^\w/ and
# $fname =~ /\w$/ and
# $fname =~ /^[\w\-\.]+$/ and
# $fname !~ /\.desc/) {
&report::reject_report_web('user','incorrect_name',{'name' => $fname},$param->{'action'},$list);
&wwslog('err',"do_d_upload : Unable to create file $fname : incorrect name");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'bad_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# the file must be uploaded in a directory existing
unless (-d "$shareddir/$path") {
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&wwslog('err',"do_d_upload : $shareddir/$path : not a directory");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# Access control for the directory where there is the uploading
my %mode;
$mode{'edit'} = 1;
$mode{'control'} = 1; # for the exception index.html
my %access_dir = &d_access_control(\%mode,$path);
if ($access_dir{'may'}{'edit'} == 0) {
&report::reject_report_web('auth',$access_dir{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','do_d_upload : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# Lowercase for file name
# $fname = $fname;
## when the file already exists :
# the temporary name of the uploaded file : with .duplicate
my $tmpname="."."$fname".".duplicate";
my $longtmpname="$shareddir/$path/$tmpname";
$longtmpname =~ s/\/+/\//g;
# the temporary desc of the uploaded file : with .duplicate
my $tmpdesc=".desc."."$tmpname";
my $longtmpdesc="$shareddir/$path/$tmpdesc";
$longtmpdesc =~ s/\/+/\//g;
# if we aren't in mode_delete nor in mode_rename nor in mode_cancel and the file already exists
# then we create of a temporary file
if ((-e "$longname") &&
($in{'mode_delete'} eq undef) &&
($in{'mode_rename'} eq undef) &&
($in{'mode_cancel'} eq undef)) {
#access control for the file already existing
my %mode;
$mode{'edit'} = 1;
my %access_file = &d_access_control(\%mode,"$path/$fname");
unless ($access_file{'may'}{'edit'} > 0) {
&report::reject_report_web('auth',$access_file{'reason'}{'edit'},{},$param->{'action'},$list);
return undef;
}
if (-e "$longtmpname"){
# if exists a temp file younger than 5 minutes that belongs to another user : upload refused
my @info = stat $longtmpname;
my $timeold = time - $info[10];
if ($timeold<=300){
my %desc_hash = &get_desc_file($longtmpdesc);
unless($desc_hash{'email'} eq $param->{'user'}{'email'}){
&report::reject_report_web('user','cannot_upload',{'path' => "$visible_path/$visible_fname",
'reason' => "file being uploaded by $desc_hash{'email'} at this time" },
$param->{'action'},$list);
&wwslog('err',"do_d_upload : Unable to upload $longtmpname : file being uploaded at this time ");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
}
&creation_shared_file($shareddir,$path,$tmpname);
&creation_desc_file($shareddir,$path,$tmpname,%access_file);
my @info = stat $longname;
$param->{'serial_file'} = $info[9];
$param->{'path'} = $path;
$param->{'shortname'} = $fname;
return 1;
}
# for the moderation
my $longmodname = "$shareddir/$path/"."."."$fname".".moderate";
$longmodname =~ s/\/+/\//g;
my $longmoddesc="$shareddir/$path/".".desc.."."$fname".".moderate";
$longmoddesc =~ s/\/+/\//g;
# when a file is already waiting for moderation
my $file_moderated;
if (-e "$longmodname"){
my %desc_hash = &get_desc_file("$longmoddesc");
$file_moderated = 1;
unless($desc_hash{'email'} eq $param->{'user'}{'email'}){
&report::reject_report_web('user','cannot_upload',{'path' => "$path/$fname",
'reason' => "file already exists but not yet moderated"},
$param->{'action'},$list);
&wwslog('err',"do_d_upload : Unable to create $longname : file already exists but not yet moderated");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_already_exists','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
## Exception index.html
unless ($fname !~ /^index.html?$/i) {
unless ($access_dir{'may'}{'control'}) {
&report::reject_report_web('user','index_html',{'dir' => $path,
'reason' => "d_access_control"},
$param->{'action'},$list);
&wwslog('err',"do_d_upload : $param->{'user'}{'email'} not authorized to upload a INDEX.HTML file in $path");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
# if we're in mode_delete or mode_rename or mode_cancel, the temp file and his desc file must exist
if ($in{'mode_delete'} ||
$in{'mode_rename'} ||
$in{'mode_cancel'}) {
unless(-e $longtmpname){
&report::reject_report_web('user','no_uploaded_file',{},$param->{'action'},$list);
&wwslog('err',"do_d_upload : there isn't any temp file for the uploaded file $fname");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_file','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
unless(-e $longtmpdesc){
&report::reject_report_web('user','no_uploaded_file',{},$param->{'action'},$list);
&wwslog('err',"do_d_upload : there isn't any desc temp file for the uploaded file $fname");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_file','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
## End of controls
# in mode_delete the file is going to be overwritten
if ($in{'mode_delete'}) {
# Synchronization
unless (&synchronize("$longname",$in{'serial'})){
&report::reject_report_web('user','synchro_failed',{},$param->{'action'},$list);
&wwslog('err',"do_d_upload : Synchronization failed for $longname");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'synchro_failed','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# Renaming the tmp file and the desc file
if ($access_dir{'may'}{'edit'} == 1 ){
# Renaming of the old file
my $longgoodname="$shareddir/$path/$fname";
$longgoodname =~ s/\/+/\//g;
unless (rename "$longgoodname","$longgoodname.old"){
&report::reject_report_web('intern','rename_file',{'old'=>"$longgoodname",
'new'=>"$longgoodname.old"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_upload : Failed to rename %s to .old : %s",$longgoodname, $!);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# Renaming of the old desc
my $longgooddesc="$shareddir/$path/".".desc."."$fname";
$longgooddesc =~ s/\/+/\//g;
unless (rename "$longgooddesc","$longgooddesc.old"){
&report::reject_report_web('intern','rename_file',{'old'=>"$longgooddesc",
'new'=>"$longgooddesc.old"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_upload : Failed to rename %s to .old : %s", $longgooddesc, $!);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
# the tmp file
unless (rename "$longtmpname","$longgoodname"){
&report::reject_report_web('intern','rename_file',{'old'=>"$longtmpname",
'new'=>"$longgoodname"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_upload : Failed to rename %s to %s : %s", $longtmpname, $longgoodname, $!);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
# the tmp desc file
unless (rename "$longtmpdesc","$longgooddesc"){
&report::reject_report_web('intern','rename_file',{'old'=>"$longtmpdesc",
'new'=>"$longgooddesc"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_upload : Failed to rename %s to %s : %s", $longtmpdesc, $longgooddesc, $!);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
}elsif ($access_dir{'may'}{'edit'} == 0.5 ){
unless (rename "$longtmpname","$longmodname"){
&report::reject_report_web('intern','rename_file',{'old'=>"$longtmpname",
'new'=>"$longmodname"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_upload : Failed to rename %s to %s : %s", $longtmpname, $longmodname, $!);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
unless (rename "$longtmpdesc","$longmoddesc"){
&report::reject_report_web('intern','rename_file',{'old'=>"$longtmpdesc",
'new'=>"$longmoddesc"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_upload : Failed to rename %s to %s : %s", $longtmpdesc, $longmoddesc, $!);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
unless ($list->send_notify_to_editor('shared_moderated',{'filename' => "$path/$fname",
'who' => $param->{'user'}{'email'}})) {
&wwslog('notice',"Unable to send notify 'shared_moderated' to $list->{'name'} list editor");
}
}else {
&report::reject_report_web('auth',$access_dir{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','do_d_upload : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# $in{'list'} = $list_name;
# message of success
&report::notice_report_web('upload_success', {'path' => $fname},$param->{'action'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 'd_read';
}
# in mode_rename the file is going to be renamed
if ($in{'mode_rename'}) {
my $longnewname="$shareddir/$path/$in{'new_name'}";
$longnewname =~ s/\/+/\//g;
# Control new document name
unless ($in{'new_name'}) {
&report::reject_report_web('user','missing_arg',{'argument' => 'new name'},$param->{'action'});
&wwslog('err',"do_d_upload : new name missing to rename the uploaded file");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'missing_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
if ($in{'new_name'} =~ /^\./
|| $in{'new_name'} =~ /\.desc/
|| $in{'new_name'} =~ /[~\#\[\]\/]$/) {
&report::reject_report_web('user','incorrect_name',{'name' => $in{'new_name'}},$param->{'action'},$list);
&wwslog('err',"do_d_upload : Unable to create file $in{'new_name'} : incorrect name");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'bad_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
if (($fname =~ /\.url$/) && ($in{'new_name'} !~ /\.url$/)) {
&report::reject_report_web('user','incorrect_name',{'name' => $in{'new_name'}},$param->{'action'},$list);
&wwslog('err',"do_d_upload : New file name $in{'new_name'} does not match URL filenames");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'bad_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
if (-e $longnewname){
&report::reject_report_web('user','doc_already_exist',{'name' => $in{'new_name'}},$param->{'action'},$list);
&wwslog('err',"do_d_upload : $in{'new_name'} is an existing name");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_already_exists','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# when a file is already waiting for moderation
if (-e "$shareddir/$path/.$in{'new_name'}.moderate"){
&report::reject_report_web('user','doc_already_exist',{'name' => $in{'new_name'}},$param->{'action'},$list);
&wwslog('err',"do_d_upload : $in{'new_name'} is an existing name for a not yet moderated file" );
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_already_exists','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# when a file is being uploaded
if (-e "$shareddir/$path/.$in{'new_name'}.duplicate"){
&report::reject_report_web('user','doc_already_exist',{'name' => $in{'new_name'}},$param->{'action'},$list);
&wwslog('err',"do_d_upload : $in{'new_name'} is an existing name for a file being uploaded ");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_already_exists','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
# Renaming the tmp file and the desc file
if ($access_dir{'may'}{'edit'} == 1 ){
unless (rename "$longtmpname","$longnewname"){
&report::reject_report_web('intern','rename_file',{'old'=>"$longtmpname",
'new'=>"$longnewname"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_upload : Failed to rename %s to %s : %s", $longtmpname, $longnewname, $!);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
my $longnewdesc="$shareddir/$path/.desc.$in{'new_name'}";
$longnewdesc =~ s/\/+/\//g;
unless (rename "$longtmpdesc","$longnewdesc"){
&report::reject_report_web('intern','rename_file',{'old'=>"$longtmpdesc",
'new'=>"$longnewdesc"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_upload : Failed to rename %s to %s : %s", $longtmpdesc, $longnewdesc, $!);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
}elsif ($access_dir{'may'}{'edit'} == 0.5 ){
unless (rename "$longtmpname","$shareddir/$path/.$in{'new_name'}.moderate"){
&report::reject_report_web('intern','rename_file',{'old'=>"$longtmpname",
'new'=>"$shareddir/$path/.$in{'new_name'}.moderate"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_upload : Failed to rename $longtmpname to $shareddir/$path/.$in{'new_name'}.moderate : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
unless (rename "$longtmpdesc","$shareddir/$path/.desc..$in{'new_name'}.moderate"){
&report::reject_report_web('intern','rename_file',{'old'=>"$longtmpdesc",
'new'=>"$shareddir/$path/.desc..$in{'new_name'}.moderate"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_upload : Failed to rename $longtmpdesc to $shareddir/$path/.desc..$in{'new_name'}.moderate: $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
unless ($list->send_notify_to_editor('shared_moderated',{'filename' => "$path/$in{'new_name'}",
'who' => $param->{'user'}{'email'}})) {
&wwslog('notice',"Unable to send notify 'shared_moderated' to $list->{'name'} list editor");
}
}else {
&report::reject_report_web('auth',$access_dir{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','do_d_upload : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# $in{'list'} = $list_name;
# message of success
&report::notice_report_web('upload_success', {'path' => $fname},$param->{'action'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 'd_read';
}
# in mode_cancel, we delete the temp file and his desc
if ($in{'mode_cancel'}) {
# removing of the temp file
unless (unlink($longtmpname)) {
&report::reject_report_web('intern','erase_file',{'file' => $longtmpname},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_d_upload: failed to erase the temp file %s', $longtmpname);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# removing of the description temp file
unless (unlink($longtmpdesc)) {
&report::reject_report_web('intern','erase_file',{'file' => $longtmpdesc},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_d_upload: failed to erase the desc temp file %s', $longtmpdesc);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
return 'd_read';
}
## usual case
# shared_moderated
if ($access_dir{'may'}{'edit'} == 0.5 ) {
my $modname="."."$fname".".moderate";
&creation_shared_file($shareddir,$path,$modname);
&creation_desc_file($shareddir,$path,$modname,%access_dir);
unless ($file_moderated){
unless ($list->send_notify_to_editor('shared_moderated',{'filename' => "$path/$fname",
'who' => $param->{'user'}{'email'}})) {
&wwslog('notice',"Unable to send notify 'shared_moderated' to $list->{'name'} list editor");
}
}
&report::notice_report_web('to_moderate', {'path' => $fname},$param->{'action'});
} else {
&creation_shared_file($shareddir,$path,$fname);
&creation_desc_file($shareddir,$path,$fname,%access_dir);
}
$in{'list'} = $list_name;
&report::notice_report_web('upload_success', {'path' => $visible_fname},$param->{'action'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$visible_path,$visible_fname",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 'd_read';
}
## Creation of a picture file
sub creation_picture_file {
my($root_dir, $path ,$fname)=@_;
unless(-d $root_dir.'/'.$path) {
&wwslog('notice',"creation_picture_file : Create dir $root_dir/$path/");
unless (&tools::mkdir_all($root_dir.'/'.$path, 0755)){
&wwslog('err',"creation_picture_file : Unable to create dir $root_dir/$path/");
return undef;
}
unless (open(FF,">$root_dir".'/'.$path.'/index.html')){
&wwslog('err',"creation_picture_file : Unable to create dir $root_dir/$path/index.html");
}
chmod 0755, $root_dir.'/'.$path.'/index.html';
close FF;
}
my $fh = $query->upload('uploaded_file');
unless (open FILE, ">:bytes", "$root_dir/$path/$fname") {
&report::reject_report_web('intern','cannot_upload',{'path' => "$path/$fname"},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"creation_picture_file : Cannot open file $root_dir/$path/$fname : $!");
return undef;
}
while (<$fh>) {
print FILE;
}
close FILE;
chmod 0755, "$root_dir/$path/$fname";
}
## Creation of a shared file
sub creation_shared_file {
my($shareddir,$path,$fname)=@_;
unless(-d $shareddir.'/'.$path) {
&wwslog('notice',"creation_shared_file : Create dir $shareddir/$path/");
unless (mkdir($shareddir.'/'.$path,0755)){
&wwslog('err',"creation_shared_file : Unable to create dir $shareddir/$path/");
return undef;
}
}
my $fh = $query->upload('uploaded_file');
unless (open FILE, ">:bytes", "$shareddir/$path/$fname") {
&report::reject_report_web('intern','cannot_upload',{'path' => "$path/$fname"},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"creation_shared_file : Cannot open file $shareddir/$path/$fname : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
while (<$fh>) {
print FILE;
}
close FILE;
## XSS Protection for HTML files.
if (lc($fname) =~ /\.html?/) {
my $sanitized_file = &tools::sanitize_html_file('robot' => $robot,
'file' => "$shareddir/$path/$fname");
if (defined $sanitized_file) {
open HTMLFILE, ">:bytes", "$shareddir/$path/$fname";
print HTMLFILE $sanitized_file;
close HTMLFILE;
}
else {
&do_log('err','Unable to sanitize file %s',$fname);
}
}
}
## Creation of the description file
sub creation_desc_file {
my($shareddir,$path,$fname,%access)=@_;
unless (open (DESC,">$shareddir/$path/.desc.$fname")) {
&wwslog('err',"creation_desc_file: cannot create description file $shareddir/.desc.$path/$fname");
}
print DESC "title\n \n\n";
print DESC "creation\n date_epoch ".time."\n email $param->{'user'}{'email'}\n\n";
print DESC "access\n";
print DESC " read $access{'scenario'}{'read'}\n";
print DESC " edit $access{'scenario'}{'edit'}\n";
close DESC;
}
#*******************************************
# Function : do_d_unzip
# Description : unzip a file or a tree structure
# from an uploaded zip file
#******************************************
sub do_d_unzip {
# Parameters of the uploaded file (from d_read.tt2)
my $fn = $in{'unzipped_file'};
# name of the file, without path
my $fname;
if ($fn =~ /([^\/\\]+)$/) {
$fname = $1;
}
&wwslog('info', 'do_d_unzip(%s/%s)', $in{'path'},$fname);
# Variables
my $path = &no_slash_end($in{'path'});
# path of the shared directory
my $shareddir = $list->{'dir'}.'/shared';
# name of the file
my $longname = "$shareddir/$path/$fname";
$longname =~ s/\/+/\//g;
## Controls
my $listname = $list->{'name'};
# uploaded file must have a name
unless ($fname) {
&report::reject_report_web('user','no_name',{},$param->{'action'},$list);
&wwslog('err',"do_d_unzip(%s/%s) : No file specified to upload",$path,$fname);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_file','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# must have .zip extension
unless ($fname =~ /^.+\.zip$/) {
&report::reject_report_web('user','incorrect_name',{'name' => "$fname",
'reason' => "must have the '.zip' extension"},$param->{'action'},$list);
&wwslog('err',"do_d_unzip(%s/%s) : the file must have '.zip' extension",$path,$fname);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'bad_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
## Check quota
if ($list->{'admin'}{'shared_doc'}{'quota'}) {
if ($list->get_shared_size() >= $list->{'admin'}{'shared_doc'}{'quota'} * 1024){
&report::reject_report_web('user','shared_full',{},$param->{'action'},$list);
&wwslog('err',"do_d_unzip(%s/%s) : Shared Quota exceeded for list $list->{'name'}",$path,$fname);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'shared_full','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
# The name of the file must be correct and must not be a description file
if ($fname =~ /^\./
|| $fname =~ /\.desc/
|| $fname =~ /[~\#\[\]]$/) {
&report::reject_report_web('user','incorrect_name',{'name' => "$fname"},$param->{'action'},$list);
&wwslog('err',"do_d_unzip(%s/%s) : incorrect name",$path,$fname);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'bad_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# the file must be uploaded in a directory existing
unless (-d "$shareddir/$path") {
&report::reject_report_web('user','no_such_document',{'path'=> $path},$param->{'action'},$list);
&wwslog('err',"do_d_unzip(%s/%s) : $shareddir/$path : not a directory",$path,$fname);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# Access control for the directory where there is the uploading
# only for (is_author || !moderated)
my %mode;
$mode{'edit'} = 1;
my %access_dir = &d_access_control(\%mode,$path);
if ($access_dir{'may'}{'edit'} == 0) {
&report::reject_report_web('auth',$access_dir{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','do_d_unzip(%s/%s) : access denied for %s',$path,$fname, $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
if ($access_dir{'may'}{'edit'} == 0.5) {
&report::reject_report_web('auth','edit_moderated',{},$param->{'action'},$list);
&wwslog('err','do_d_unzip(%s/%s) : access denied for %s',$path,$fname, $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
## End of control
# directory for the uploaded file
my $date = time;
my $zip_dir_name = $listname.$date.$$;
my $zip_abs_dir = $Conf{'tmpdir'}.'/'.$zip_dir_name;
unless (mkdir ("$zip_abs_dir",0777)) {
&report::reject_report_web('intern','cannot_mkdir',{'dir' => $zip_abs_dir},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_unzip($path/$fname) : Unable to create $zip_abs_dir : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
### directory for unzipped files
unless (mkdir ("$zip_abs_dir"."/zip",0777)) {
&report::reject_report_web('intern','cannot_mkdir',{'dir' => "$zip_abs_dir"."/zip"},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_unzip($path/$fname) : Unable to create $zip_abs_dir/zip : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
### uploaded of the file.zip
my $fh = $query->upload('unzipped_file');
unless (open FILE, ">:bytes", "$zip_abs_dir/$fname") {
&report::reject_report_web('intern','cannot_upload',{'path' => "$path/$fname"},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_unzip($path/$fname) : Cannot open file $zip_abs_dir/$fname : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
while (<$fh>) {
print FILE;
}
close FILE;
### unzip the file
my $status = &d_unzip_shared_file($zip_abs_dir,$fname,$path);
unless (defined($status)) {
&report::reject_report_web('intern','cannot_unzip',{'path' => "$zip_abs_dir/$fname", 'name' => $fname},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_unzip($path/$fname) : Unable to unzip the file $zip_abs_dir/$fname");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
unless ($status) {
&report::reject_report_web('intern','cannot_unzip',{'name' => "$fname"},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
}
### install the file hierarchy
unless (&d_install_file_hierarchy("$zip_abs_dir/zip",$shareddir,$path,\%access_dir)) {
&wwslog('err',"do_d_unzip($path/$fname) : unable to install file hierarchy");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
## remove tmp directories and files
# &tools::remove_dir($zip_abs_dir);
$in{'list'} = $listname;
&report::notice_report_web('unzip_success', {'path' => $fname},$param->{'action'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 'd_read'
}
## unzip a shared file in the tmp directory
sub d_unzip_shared_file {
my ($zip_abs_dir,$fname) = @_;
&wwslog('info', 'd_unzip_shared_file(%s/%s)', $zip_abs_dir,$fname);
my $status = 1;
my $zip = Archive::Zip->new();
my $az = $zip->read( "$zip_abs_dir/$fname" );
unless ($az == AZ_OK){
&wwslog('err',"unzip_shared_file : Unable to read the zip file $zip_abs_dir/$fname : $az");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$zip_abs_dir,$fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
my @memberNames = $zip->memberNames();
foreach my $name (@memberNames) {
my $az = $zip->extractMember($name, $zip_abs_dir.'/zip/'.$name);
unless ($az == AZ_OK) {
&wwslog('err',"unzip_shared_file : Unable to extract member $name of the zip file $zip_abs_dir/$fname : $az");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$zip_abs_dir,$fname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
$status = 0;
}
}
## Qencode 8bit filenames afterward
## The suspected charset is the one that is associated to the user's language
&tools::qencode_hierarchy($zip_abs_dir.'/zip', &Language::GetCharset());
return $status;
}
## Install file hierarchy from $tmp_dir directory to $shareddir/$path directory
sub d_install_file_hierarchy {
my ($tmp_dir,$shareddir,$path,$access_dir)=@_;
&wwslog('debug2', 'd_install_file_hierarchy(%s,%s)',$tmp_dir,$path);
$tmp_dir = &no_slash_end($tmp_dir);
$shareddir = &no_slash_end($shareddir);
$path = &no_slash_end($path);
my $fatal_error = 0;
unless (opendir DIR,"$tmp_dir") {
&report::reject_report_web('intern','cannot_open_dir',{'dir' => $tmp_dir},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','d_install_file_hierarchy(%s) : impossible to open %s directory',$path,$tmp_dir);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$tmp_dir,$shareddir,$path,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
my @from_dir = readdir DIR;
closedir DIR;
foreach my $doc (@from_dir) {
next
if($doc eq '.' || $doc eq '..');
if (-d "$tmp_dir/$doc") {
if ($fatal_error) {
&report::reject_report_web('user','directory_no_copied',{'name'=> "$path/$doc",
'reason' => "quota exceeded"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
}else {
unless (&d_copy_rec_dir("$tmp_dir","$path","$shareddir/$path",$doc)){
$fatal_error = 1;
&report::reject_report_web('user','directory_no_copied',{'name'=> "$path/$doc",
'reason' => "quota exceeded"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
# return undef;
}
}
} else {
if ($fatal_error) {
&report::reject_report_web('user','file_no_copied',{'name'=> "$path/$doc",
'reason' => "quota exceeded"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
}else {
unless (&d_copy_file("$tmp_dir","$path","$shareddir/$path",$doc,$access_dir)) {
&wwslog('err',"d_install_hierarchy($path) : fatal error from d_copy_file($doc)");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$tmp_dir,$shareddir,$path,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
$fatal_error = 1;
&report::reject_report_web('user','file_no_copied',{'name'=> "$path/$doc",
'reason' => "quota exceeded"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
}
# return undef;
}
}
}
if ($fatal_error) {
return undef;
}else {
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$tmp_dir,$shareddir,$path,$access_dir",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
}
## copy $dname from $from to $list->{shared}/$path if rights are ok
sub d_copy_rec_dir {
my ($from,$path,$dest_dir,$dname) = @_;
&wwslog('debug3', 'd_copy_rec_dir(%s,%s,%s)',$from,$dest_dir,$dname);
$from = &no_slash_end($from);
$path = &no_slash_end($path);
$dest_dir = &no_slash_end($dest_dir);
my $fatal_error = 0;
# Access control on the directory $path where there is the copy
# Copy allowed only for (is_author || !moderate)
my %mode;
$mode{'edit'} = 1;
$mode{'control'} = 1;
my %access_dir = &d_access_control(\%mode,$path);
unless ($access_dir{'may'}{'edit'} == 1) {
&report::reject_report_web('user','directory_no_copied',{'name'=> $dname,
'reason' => "no edition right on father directory"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','d_copy_rec_dir(%s): access denied for %s',$path,$param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$dname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
my $may;
unless ($may = &d_test_existing_and_rights($path,$dname,$dest_dir)) {
&report::reject_report_web('user','directory_no_copied',{'name'=> $dname },
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','d_copy_rec_dir(%s) : error while calling "test_existing_and_rights(%s/%s)"',$dname,$dest_dir,$dname);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$dname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
unless ($may->{'exists'}) {
# The name of the directory must be correct and musn't not be a description file
if ($dname =~ /^\./
|| $dname =~ /\.desc/
|| $dname =~ /[~\#\[\]]$/) {
&report::reject_report_web('user','incorrect_name',{'name' => "$dname"},$param->{'action'},$list);
&wwslog('err',"d_copy_rec_dir : $dname : incorrect name");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$dname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'bad_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
## Exception index.html
unless ($dname !~ /^index.html?$/i) {
&report::reject_report_web('user','index_html',{'dir' => $path},$param->{'action'},$list);
&wwslog('err',"d_copy_rec_dir : the directory cannot be called INDEX.HTML ");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$dname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
## directory creation
unless (mkdir ("$dest_dir/$dname",0777)) {
&report::reject_report_web('user','directory_no_copied',{'name'=> "$dname"},$param->{'action'},$list);
&wwslog('err',"d_copy_rec_dir : Unable to create directory $dest_dir/$dname : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$dname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
## desc directory creation
unless (open (DESC,">$dest_dir/$dname/.desc")) {
&wwslog('err',"d_copy_rec_dir: cannot create description file $dest_dir/$dname/.desc");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$dname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
print DESC "title\n \n\n";
print DESC "creation\n date_epoch ".time."\n email $param->{'user'}{'email'}\n\n";
print DESC "access\n";
print DESC " read $access_dir{'scenario'}{'read'}\n";
print DESC " edit $access_dir{'scenario'}{'edit'}\n";
close DESC;
}
if ($may->{'rights'} || !($may->{'exists'})) {
unless (opendir DIR,"$from/$dname") {
&report::reject_report_web('user','directory_no_copied',{'name'=> "$dname"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','d_copy_rec_dir(%s) : impossible to open %s directory',$dname,$from);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$dname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
my @from_dir = readdir DIR;
closedir DIR;
foreach my $doc (@from_dir) {
if ($doc eq '.' || $doc eq '..') {
next;
}
if (-d "$from/$dname/$doc") {
if ($fatal_error) {
&report::reject_report_web('user','directory_no_copied',{'name'=> "$dname/$doc",
'reason' => "quota exceeded"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
}else {
unless (&d_copy_rec_dir("$from/$dname","$path/$dname","$dest_dir/$dname",$doc)){
$fatal_error = 1;
&report::reject_report_web('user','directory_no_copied',{'name'=> "$dname/$doc",
'reason' => "quota exceeded"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
# return undef;
}
}
}else {
if ($fatal_error) {
&report::reject_report_web('user','file_no_copied',{'name'=> "$dname/$doc",
'reason' => "quota exceeded"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
}else {
unless (&d_copy_file("$from/$dname","$path/$dname","$dest_dir/$dname",$doc,\%access_dir)){
&wwslog('err',"d_copy_rec_dir($path/$dname) : fatal error from d_copy_file($doc)");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$dname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
$fatal_error = 1;
&report::reject_report_web('user','file_no_copied',{'name'=> "$dname/$doc",
'reason' => "quota exceeded"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
}
# return undef;
}
}
}
}else{
&report::reject_report_web('user','directory_no_copied',{'name'=> $dname,
'reason' => "no edition right on the father directory"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"d_copy_rec_file : impossible to copy content directory $dname, the user doesn't have edit rights on directory $path");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$dname",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
if ($fatal_error) {
return undef;
} else {
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$dname",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
}
## copy $from/$fname to $list->{shared}/$path if rights are ok
sub d_copy_file {
my ($from,$path,$dest_dir,$fname,$access_dir) = @_;
&wwslog('debug3', 'd_copy_file(%s,%s,%s',$from,$dest_dir,$fname);
$from = &no_slash_end($from);
$path = &no_slash_end($path);
$dest_dir = &no_slash_end($dest_dir);
my $may;
unless ($may = &d_test_existing_and_rights($path,$fname,$dest_dir)) {
&report::reject_report_web('user','file_no_copied',{'name'=> "$fname",
'reason' => "quota exceeded"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','d_copy_file(%s) : error while calling "test_existing_and_rights(%s/%s)"',$fname,$dest_dir,$fname);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$fname,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_no_copied','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
if ($may->{'rights'} || !($may->{'exists'})) {
# The name of the file must be correct and musn't not be a description file
if ($fname =~ /^\./
|| $fname =~ /\.desc/
|| $fname =~ /[~\#\[\]]$/) {
&report::reject_report_web('user','incorrect_name',{'name' => "$fname"},$param->{'action'},$list);
&wwslog('err',"d_copy_file : $fname : incorrect name");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$fname,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'bad_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
## Exception index.html
unless ($fname !~ /^index.html?$/i) {
unless ($access_dir->{'may'}{'control'}) {
&report::reject_report_web('user','index_html',{'dir' => $path},$param->{'action'},$list);
&wwslog('err',"d_copy_file : the user is not authorized to upload a INDEX.HTML file in $dest_dir");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$fname,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
}
## Check quota
if ($list->{'admin'}{'shared_doc'}{'quota'}) {
if ($list->get_shared_size() >= $list->{'admin'}{'shared_doc'}{'quota'} * 1024){
&report::reject_report_web('user','shared_full',{},$param->{'action'},$list);
&wwslog('err',"d_copy_file : Shared Quota exceeded for list $list->{'name'} on file $path/$fname");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$fname,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'shared_full','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
## if already existing :delete it
unlink ("$dest_dir/$fname")
if (-e "$dest_dir/$fname");
unlink ("$dest_dir/.desc.$fname")
if (-e "$dest_dir/.desc.$fname");
## # if exists a temp file younger than 5 minutes that belongs to another user : file copy refused
if (-e "$dest_dir/.$fname.duplicate") {
my @info = stat "$dest_dir/.$fname.duplicate";
my $timeold = time - $info[10];
if ($timeold <= 300){
my %desc_hash = &get_desc_file("$dest_dir/.desc..$fname.duplicate");
unless($desc_hash{'email'} eq $param->{'user'}{'email'}){
&report::reject_report_web('user','file_no_copied',{'name'=> "$path/$fname",
'reason' => "file being uploading by $desc_hash{'email'} at this time"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"d_copy_file : unable to copy $path/$fname : file being uploaded at this time ");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$fname,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_no_copied','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
}
unlink ("$dest_dir/.$fname.duplicate");
unlink ("$dest_dir/.desc..$fname.duplicate")
if (-e "$dest_dir/.desc..$fname.duplicate");
}
if (-e "$dest_dir/.$fname.moderate") {
my %desc_hash = &get_desc_file("$dest_dir/.$fname.moderate");
unless($desc_hash{'email'} eq $param->{'user'}{'email'}){
&report::reject_report_web('user','file_no_copied',{'name'=> "$path/$fname",
'reason' => "file awaiting for moderation, uploaded by $desc_hash{'email'}"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"d_copy_file : unable to copy $path/$fname : file awaiting for moderation");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$fname,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_no_copied','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
unlink ("$dest_dir/.$fname.moderate");
unlink ("$dest_dir/.desc..$fname.moderate")
if (-e "$dest_dir/.desc..$fname.moderate");
}
## file copy
unless (open FROM_FILE,"$from/$fname") {
&report::reject_report_web('user','file_no_copied',{'name'=> "$path/$fname"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"d_copy_file : impossible to open $from/$fname");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$fname,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_no_copied','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
my $visible_fname = &make_visible_path($fname);
unless (open DEST_FILE, ">$dest_dir/$fname") {
&report::reject_report_web('user','file_no_copied',{'name'=> "$path/$visible_fname"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"d_copy_file : Cannot create file $dest_dir/$fname : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$fname,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_no_copied','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
while () {
print DEST_FILE;
}
close FROM_FILE;
close DEST_FILE;
## XSS Protection for HTML files.
if (lc($fname) =~ /\.html?/) {
my $sanitized_file = &tools::sanitize_html_file('robot' => $robot,
'file' => "$dest_dir/$fname");
if (defined $sanitized_file) {
open HTMLFILE, ">:bytes", "$dest_dir/$fname";
print HTMLFILE $sanitized_file;
close HTMLFILE;
}
else {
&do_log('err','Unable to sanitize file %s',$fname);
}
}
## desc file creation
unless (open (DESC,">$dest_dir/.desc.$fname")) {
&wwslog('err',"d_copy_file: cannot create description file $dest_dir/.desc.$fname");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$fname,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
print DESC "title\n \n\n";
print DESC "creation\n date_epoch ".time."\n email $param->{'user'}{'email'}\n\n";
print DESC "access\n";
print DESC " read $access_dir->{'scenario'}{'read'}\n";
print DESC " edit $access_dir->{'scenario'}{'edit'}\n";
close DESC;
## information
&report::notice_report_web('file_erased',{'path'=> "$path/$visible_fname"},$param->{'action'})
if ($may->{'exists'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$fname,$access_dir",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}else{
&report::reject_report_web('user','file_no_copied',{'name'=> "$path/$fname",
'reason' => "you do not have total edit right on the file"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"d_copy_file : impossible to copy file $fname, the user doesn't have total edit rights on the file");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$from,$path,$dest_dir,$fname,$access_dir",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_no_copied','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
return 1;
}
## return information on file or dir : existing and edit rights for the user in $param
sub d_test_existing_and_rights {
my ($path,$name,$dest_dir) = @_;
$path = &no_slash_end($path);
$name = &no_slash_end($name);
$dest_dir = &no_slash_end($dest_dir);
my $return;
$return->{'exists'} = 0;
$return->{'rights'} = 0;
if ((-e "$dest_dir/$name") ||
(-e "$dest_dir/.$name.duplicate") ||
(-e "$dest_dir/.$name.moderate")) {
$return->{'exists'} = 1;
my %mode;
$mode{'edit'} = 1;
my %access = &d_access_control(\%mode,"$path/$name");
$return->{'rights'} = 1
if $access{'may'}{'edit'} == 1;
}
return $return;
}
#*******************************************
# Function : do_d_delete
# Description : Delete an existing document
# (file or directory)
#******************************************
sub do_d_delete {
&wwslog('info', 'do_d_delete(%s)', $in{'path'});
#useful variables
my $path = &no_slash_end($in{'path'});
my $visible_path = &make_visible_path($path);
#Current directory and document to delete
$path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/;
my $current_directory = &no_slash_end($1);
my $document = $3;
# path of the shared directory
#my $list_name = $in{'list'};
my $list_name = $list->{'name'};
my $shareddir = $list->{'dir'}.'/shared';
#### Controls
## must be something to delete
unless ($document) {
&report::reject_report_web('user','missing_arg',{'argument' => 'doccument'},$param->{'action'});
&wwslog('err',"do_d_delete : no document to delete has been specified");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'missing_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
### Document isn't a description file?
unless ($document !~ /^\.desc/) {
&wwslog('err',"do_d_delete : $shareddir/$path : description file");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'description_file','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
### Document exists?
unless (-e "$shareddir/$path") {
&wwslog('err',"do_d_delete : $shareddir/$path : no such file or directory");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# removing of the document
my $doc = "$shareddir/$path";
# Access control
my %mode;
$mode{'edit'} = 1;
my %access = &d_access_control(\%mode,$path);
unless ($access{'may'}{'edit'} > 0) {
&report::reject_report_web('auth',$access{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','do_d_delete : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
## Directory
if (-d "$shareddir/$path") {
# test of emptiness
opendir DIR, "$doc";
my @readdir = readdir DIR;
close DIR;
# test for "ordinary" files
my @test_normal = grep !/^\./, @readdir;
my @test_hidden = grep !(/^\.desc$/ | /^\.(\.)?$/ | /^[^\.]/), @readdir;
if (($#test_normal != -1) || ($#test_hidden != -1)) {
&report::reject_report_web('user','full_directory',{'directory'=> $path},$param->{'action'},$list);
&wwslog('err',"do_d_delete : Failed to erase $doc : directory not empty");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# removing of the description file if exists
if (-e "$doc/\.desc") {
unless (unlink("$doc/.desc")) {
&report::reject_report_web('intern','erase_file',{'file' => "$doc/.desc"},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_delete : Failed to erase $doc/.desc : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
# removing of the directory
rmdir $doc;
## File
}else {
# removing of the document
unless (unlink($doc)) {
&report::reject_report_web('intern','erase_file',{'file' => "$doc"},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_d_delete: failed to erase %s', $doc);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# removing of the description file if exists
if (-e "$shareddir/$current_directory/.desc.$document") {
unless (unlink("$shareddir/$current_directory/.desc.$document")) {
&wwslog('err',"do_d_delete: failed to erase $shareddir/$current_directory/.desc.$document");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
}
}
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
$in{'list'} = $list_name;
$in{'path'} = $current_directory;
return 'd_read';
}
#*******************************************
# Function : do_d_rename
# Description : Rename a document
# (file or directory)
#******************************************
sub do_d_rename {
&wwslog('info', 'do_d_rename(%s)', $in{'path'});
#useful variables
my $path = &no_slash_end($in{'path'});
#moderation
my $visible_path = &make_visible_path($path);
my $moderate;
if ($path =~ /\.moderate$/) {
$moderate=1;
}
#Current directory and document to delete
my $current_directory;
if ($path =~ /^(.*)\/([^\/]+)$/) {
$current_directory = &no_slash_end($1);
}else {
$current_directory = '.';
}
$path =~ /(^|\/)([^\/]+)$/;
my $document = $2;
# path of the shared directory
my $list_name = $list->{'name'};
my $shareddir = $list->{'dir'}.'/shared';
#### Controls
## must be something to delete
unless ($document) {
&report::reject_report_web('user','missing_arg',{'argument' => 'document'},$param->{'action'});
&wwslog('err',"do_d_rename : no document to rename has been specified");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_file','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
### Document isn't a description file?
unless ($document !~ /^\.desc/) {
&wwslog('err',"do_d_rename : $shareddir/$path : description file");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_such_document','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
### Document exists?
unless (-e "$shareddir/$path") {
&wwslog('err',"do_d_rename : $shareddir/$path : no such file or directory");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_such_document','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
if ($in{'new_name'} =~ /^\./
|| $in{'new_name'} =~ /\.desc/
|| $in{'new_name'} =~ /[~\#\[\]\/]$/) {
&report::reject_report_web('user','incorrect_name',{'name' => $in{'new_name'}},$param->{'action'},$list);
&wwslog('err',"do_d_rename : Unable to create file $in{'new_name'} : incorrect name");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'bad_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
if (($document =~ /\.url$/) && ($in{'new_name'} !~ /\.url$/)) {
&report::reject_report_web('user','incorrect_name',{'name' => $in{'new_name'}},$param->{'action'},$list);
&wwslog('err',"do_d_rename : New file name $in{'new_name'} does not match URL filenames");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'bad_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
my $doc = "$shareddir/$path";
# Access control
my %mode;
$mode{'edit'} = 1;
my %access = &d_access_control(\%mode,$path);
unless ($access{'may'}{'edit'} > 0) {
&report::reject_report_web('auth',$access{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','do_d_rename : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
if ($moderate){
&do_log('notice', "RENAME: $doc, $shareddir/$current_directory/$in{'new_name'}");
unless (rename $doc, "$shareddir/$current_directory/.$in{'new_name'}.moderate") {
&report::reject_report_web('intern','rename_file',{'old'=>$doc,
'new'=>"$shareddir/$current_directory/.$in{'new_name'}.moderate"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_rename : Failed to rename %s to %s : %s", $doc, "$shareddir/$current_directory/$in{'new_name'}", $!);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}else {
&do_log('notice', "RENAME: $doc, $shareddir/$current_directory/$in{'new_name'}");
unless (rename $doc, "$shareddir/$current_directory/$in{'new_name'}") {
&report::reject_report_web('intern','rename_file',{'old'=>$doc,
'new'=>"$shareddir/$current_directory/$in{'new_name'}"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_rename : Failed to rename %s to %s : %s", $doc, "$shareddir/$current_directory/$in{'new_name'}", $!);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
## Rename description file
my $desc_file = "$shareddir/$current_directory/.desc.$document";
my $new_desc_file = $desc_file;
if (-f $desc_file) {
if ($moderate){
$new_desc_file =~ s/\Q$document/\.$in{'new_name'}\.moderate/;
}else {
$new_desc_file =~ s/\Q$document/$in{'new_name'}/;
}
unless (rename $desc_file, $new_desc_file) {
&report::reject_report_web('intern','rename_file',{'old'=>$desc_file,
'new'=> $new_desc_file},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_rename : Failed to rename $desc_file : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
$in{'list'} = $list_name;
if ($current_directory eq '.') {
$in{'path'} = '';
} else {
$in{'path'} = $current_directory.'';
}
return 'd_read';
}
#*******************************************
# Function : do_d_create_dir
# Description : Creates a new file / directory
#******************************************
sub do_d_create_dir {
&wwslog('info', 'do_d_create_dir(%s)', $in{'name_doc'});
#useful variables
my $path = &no_slash_end($in{'path'});
#my $list_name = $in{'list'};
my $list_name = $list->{'name'};
my $name_doc = $in{'name_doc'};
$param->{'list'} = $list_name;
$param->{'path'} = $path;
## Q-decode file path and names
$param->{'decoded_path'} = &tools::qdecode_filename($param->{'path'});
$param->{'decoded_name_doc'} = &tools::qdecode_filename($name_doc);
my $type = $in{'type'} || 'directory';
my $desc_file;
### Controls
# Must be a directory to create (directory name not empty)
unless ($name_doc) {
&report::reject_report_web('user','no_name',{},$param->{'action'},$list);
&wwslog('err',"do_d_create_dir : Unable to create : no name specified!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'missing_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# The name of the directory must be correct
if ($name_doc =~ /^\./
|| $name_doc =~ /\.desc/
|| $name_doc =~ /[~\#\[\]\/]$/) {
&report::reject_report_web('user','incorrect_name',{'name' => $name_doc},$param->{'action'},$list);
&wwslog('err',"do_d_create_dir : Unable to create directory $name_doc : incorrect name");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'bad_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# Access control
my %mode;
$mode{'edit'} = 1;
my %access = &d_access_control(\%mode, $path);
if ($type eq 'directory') { ## only when (is_author || !moderated)
if ($access{'may'}{'edit'} == 0) {
&report::reject_report_web('auth',$access{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','do_d_create_dir : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
if ($access{'may'}{'edit'} == 0.5) {
&report::reject_report_web('auth','dir_edit_moderated',{},$param->{'action'},$list);
&wwslog('err','do_d_create_dir : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
} else {
if ($access{'may'}{'edit'} == 0) {
&report::reject_report_web('auth',$access{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('err','do_d_create_dir : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
# path of the shared directory
my $shareddir = $list->{'dir'}.'/shared';
my $document = "$shareddir/$path/$name_doc";
$param->{'document'} = $document;
# the file musn't already exists
if (-e $document){
&report::reject_report_web('user','doc_already_exist',{'name' => "$path/$name_doc"},$param->{'action'},$list);
&wwslog('err',"do_d_create_dir : cannot create $path/$name_doc : file already exists");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_already_exists','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# if the file .moderate exists, only its author can erase it
my $doc_moderate = "$shareddir/$path/"."."."$name_doc".".moderate";
my $file_moderated;
if (-e "$doc_moderate"){
$file_moderated = 1;
my $desc="$shareddir/$path/".".desc.."."$name_doc".".moderate";
$desc =~ s/\/+/\//g;
my %desc_hash = &get_desc_file("$desc");
unless($desc_hash{'email'} eq $param->{'user'}{'email'}){
&report::reject_report_web('user','cannot_upload',{'path' => "$path/$name_doc",
'reason' => "file already exists but not yet moderated"},$param->{'action'},$list);
&wwslog('err',"do_d_create_dir : Unable to create $doc_moderate : file already exists but not yet moderated");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'file_already_exists','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
### End of controls
if ($type eq 'directory') {
# Creation of the new directory
unless (mkdir ("$document",0777)) {
&report::reject_report_web('intern','cannot_mkdir',{'dir' => $document},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_create_dir : Unable to create $document : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
$desc_file = "$document/.desc";
}else {
# Creation of the new file
unless (open FILE, ">$document") {
&report::reject_report_web('intern','cannot_open_file',{'file' => "$path/$name_doc"},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_create_dir : Unable to create $document : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
close FILE;
$desc_file = "$shareddir/$path/.desc.$name_doc";
}
# Creation of a default description file
unless (open (DESC,">$desc_file")) {
&report::reject_report_web('intern','cannot_open_file',{'file' => "$desc_file"},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
}
print DESC "title\n \n\n";
print DESC "creation\n date_epoch ".time."\n email $param->{'user'}{'email'}\n\n";
print DESC "access\n";
print DESC " read $access{'scenario'}{'read'}\n";
print DESC " edit $access{'scenario'}{'edit'}\n\n";
close DESC;
# moderation
if ($access{'may'}{'edit'} == 0.5 && ($type ne 'directory')) {
unless (rename "$shareddir/$path/$name_doc","$shareddir/$path/.$name_doc.moderate"){
&report::reject_report_web('intern','rename_file',{'old'=>"$shareddir/$path/$name_doc",
'new'=>"$shareddir/$path/.$name_doc.moderate"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_create_dir : Failed to rename $path/$name_doc to $path/.$name_doc.moderate : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
unless (rename "$desc_file","$shareddir/$path/.desc..$name_doc.moderate"){
&report::reject_report_web('intern','rename_file',{'old'=>$desc_file,
'new'=>"$shareddir/$path/.desc..$name_doc.moderate"},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_d_create_dir : Failed to rename $desc_file to $path/.desc..$name_doc.moderate : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
unless ($file_moderated){
unless ($list->send_notify_to_editor('shared_moderated',{'filename' => $param->{'decoded_path'}.'/'.$param->{'decoded_name_doc'},
'who' => $param->{'user'}{'email'}})) {
&wwslog('notice',"Unable to send notify 'shared_moderated' to $list->{'name'} list editor");
}
}
}
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
if ($type eq 'directory') {
return 'd_read';
}
if ($access{'may'}{'edit'} == 0.5) {
$in{'path'} = "$path/.$name_doc.moderate";
}else {
$in{'path'} = "$path/$name_doc";
}
return 'd_editfile';
}
############## Control
#*******************************************
# Function : do_d_control
# Description : prepares the parameters
# to edit access for a doc
#*******************************************
sub do_d_control {
&wwslog('info', "do_d_control $in{'path'}");
# Variables
my $path = &no_slash_end($in{'path'});
#my $list_name = $in{'list'};
my $list_name = $list->{'name'};
# path of the shared directory
my $shareddir = $list->{'dir'}.'/shared';
#moderation
my $visible_path = &make_visible_path($path);
unless ($path) {
&report::reject_report_web('user','missing_arg',{'argument' => 'document'},$param->{'action'});
&wwslog('info','do_d_control: no document name');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'missing_parameter','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# Existing document?
unless (-e "$shareddir/$path") {
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&wwslog('info',"do_d_control : Cannot control $shareddir/$path : not an existing document");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_such_document','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
### Document isn't a description file?
unless ($path !~ /\.desc/) {
&wwslog('info',"do_d_control : $shareddir/$path : description file");
&report::reject_report_web('user','no_such_document',{'path'=> $visible_path},$param->{'action'},$list);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_such_document','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# Access control
my %mode;
$mode{'control'} = 1;
my %access = &d_access_control(\%mode,$path);
unless ($access{'may'}{'control'}) {
&report::reject_report_web('auth',$access{'reason'}{'edit'},{},$param->{'action'},$list);
&wwslog('info','d_control : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
## End of controls
#Current directory
if ($path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/) {
$param->{'father'} = &no_slash_end($1);
}else {
$param->{'father'} = '';
}
$param->{'escaped_father'} = &tools::escape_docname($param->{'father'}, '/');
my $desc_file;
# path of the description file
if (-d "$shareddir/$path") {
$desc_file = "$shareddir/$1$3/.desc";
} else {
$desc_file = "$shareddir/$1.desc.$3";
}
# Description of the file
my $read;
my $edit;
if (-e $desc_file) {
## Synchronization
my @info = stat "$desc_file";
$param->{'serial_desc'} = $info[9];
my %desc_hash = &get_desc_file("$desc_file");
# rights for read and edit
$read = $desc_hash{'read'};
$edit = $desc_hash{'edit'};
# owner of the document
$param->{'owner'} = $desc_hash{'email'};
$param->{'doc_title'} = $desc_hash{'title'};
}else {
$read = $access{'scenario'}{'read'};
$edit = $access{'scenario'}{'edit'};
}
## other info
my @info = stat "$shareddir/$path";
$param->{'doc_date'} = gettext_strftime "%d %b %y %H:%M", localtime($info[9]);
# template parameters
$param->{'list'} = $list_name;
$param->{'path'} = $path;
$param->{'visible_path'} = $visible_path;
my $lang = $param->{'lang'};
## Scenario list for READ
my $tmp_list_of_scenario = $list->load_scenario_list('d_read',$robot);
## Only get required scenario attributes
foreach my $scenario (keys %{$tmp_list_of_scenario}) {
$param->{'scenari_read'}{$scenario} = {'name' => $tmp_list_of_scenario->{$scenario}{'name'},
'web_title' => $tmp_list_of_scenario->{$scenario}{'web_title'}};
}
$param->{'scenari_read'}{$read}{'selected'} = 'selected="selected"';
## Scenario list for EDIT
my $tmp_list_of_scenario = $list->load_scenario_list('d_edit',$robot);
## Only get required scenario attributes
foreach my $scenario (keys %{$tmp_list_of_scenario}) {
$param->{'scenari_edit'}{$scenario} = {'name' => $tmp_list_of_scenario->{$scenario}{'name'},
'web_title' => $tmp_list_of_scenario->{$scenario}{'web_title'}};
}
$param->{'scenari_edit'}{$edit}{'selected'} = 'selected="selected"';
## father directory
if ($path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/) {
$param->{'father'} = &no_slash_end($1);
}else {
$param->{'father'} = '';
}
$param->{'escaped_father'} = &tools::escape_docname($param->{'father'}, '/');
$param->{'set_owner'} = 1;
$param->{'father_icon'} = $icon_table{'father'};
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
#*******************************************
# Function : do_d_change_access
# Description : Saves the description of
# the file
#******************************************
sub do_d_change_access {
&wwslog('info', 'do_d_change_access(%s)', $in{'path'});
# Variables
my $path = &no_slash_end($in{'path'});
my $list_name = $list->{'name'};
# path of the shared directory
my $shareddir = $list->{'dir'}.'/shared';
#### Controls
## the path must not be empty (the description file of the shared directory
# doesn't exist)
unless ($path) {
&report::reject_report_web('intern','cannot_describe_shared_directory',{'path' => $path },$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info',"do_d_change_access : Cannot change access $shareddir : root directory");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# the document to describe must already exist
unless (-e "$shareddir/$path") {
&report::reject_report_web('user','no_doc_to_describe',{'path'=> $path},$param->{'action'},$list);
&wwslog('info',"d_change_access : Unable to change access $shareddir/$path : no such document");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_file','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# Access control
my %mode;
$mode{'control'} = 1;
my %access = &d_access_control(\%mode,$path);
unless ($access{'may'}{'control'}) {
&report::reject_report_web('auth','action_listmaster_or_privileged_owner_or_author',{},$param->{'action'},$list);
&wwslog('info','d_change_access : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
## End of controls
# Description file
$path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/;
my $dir = $1;
my $file = $3;
my $desc_file;
if (-d "$shareddir/$path") {
$desc_file = "$shareddir/$1$3/.desc";
} else {
$desc_file = "$shareddir/$1.desc.$3";
}
if (-e "$desc_file"){
# if description file already exists : open it and modify it
my %desc_hash = &get_desc_file ("$desc_file");
# Synchronization
unless (&synchronize($desc_file,$in{'serial'})){
&report::reject_report_web('user','synchro_failed',{},$param->{'action'},$list);
&wwslog('info',"d_change_access : Synchronization failed for $desc_file");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'synchro_failed','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
unless (open DESC,">$desc_file") {
&wwslog('info',"d_change_access : cannot open $desc_file : $!");
&report::reject_report_web('intern','cannot_open_file',{'file' => $desc_file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# information not modified
print DESC "title\n $desc_hash{'title'}\n\n";
# access rights
print DESC "access\n read $in{'read_access'}\n";
print DESC " edit $in{'edit_access'}\n\n";
print DESC "creation\n";
# time
print DESC " date_epoch $desc_hash{'date'}\n";
# author
print DESC " email $desc_hash{'email'}\n\n";
close DESC;
} else {
# Creation of a description file
unless (open (DESC,">$desc_file")) {
&report::reject_report_web('intern','cannot_open_file',{'file' => $desc_file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info',"d_change_access : Cannot create description file $desc_file : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
print DESC "title\n \n\n";
my @info = stat "$shareddir/$path";
print DESC "creation\n date_epoch ".$info[10]."\n email\n\n";
print DESC "access\n read $in{'read_access'}\n";
print DESC " edit $in{'edit_access'}\n\n";
close DESC;
}
return 'd_control';
}
sub do_d_set_owner {
&wwslog('info', 'do_d_set_owner(%s)', $in{'path'});
# Variables
my $desc_file;
my $path = &no_slash_end($in{'path'});
#moderation
my $visible_path = &make_visible_path($path);
#my $list_name = $in{'list'};
my $list_name = $list->{'name'};
# path of the shared directory
my $shareddir = $list->{'dir'}.'/shared';
#### Controls
## the path must not be empty (the description file of the shared directory
# doesn't exist)
unless ($path) {
&report::reject_report_web('intern','cannot_describe_shared_directory',{'path' => $path },$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info',"do_d_set_owner : Cannot change access $shareddir : root directory");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# the email must look like an email "somebody@somewhere"
unless (&tools::valid_email($in{'content'})) {
&report::reject_report_web('user','incorrect_email',{'email' => $in{'content'}},$param->{'action'},$list);
&wwslog('info',"d_set_owner : $in{'content'} : incorrect email");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'incorrect_email','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# Access control
## father directory
$path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/;
my $dir = $1;
my $file = $3;
if (-d "$shareddir/$path") {
$desc_file = "$shareddir/$dir$file/.desc";
}else {
$desc_file = "$shareddir/$dir.desc.$file";
}
my %mode;
$mode{'control'} = 1;
## must be authorized to control father directory
#my %access = &d_access_control(\%mode,$1);
my %access = &d_access_control(\%mode,$path);
unless ($access{'may'}{'control'}) {
&report::reject_report_web('auth','action_listmaster_or_privileged_owner_or_author',{},$param->{'action'},$list);
&wwslog('info','d_set_owner : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authentication','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
my $may_set = 1;
unless ($may_set) {
&report::reject_report_web('user','full_directory',{'directory'=> $visible_path},$param->{'action'},$list);
&wwslog('info',"d_set_owner : cannot set owner of a full directory");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'full_directory','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
## End of controls
my %desc_hash;
if (-e "$desc_file"){
# if description file already exists : open it and modify it
%desc_hash = &get_desc_file ("$desc_file");
# Synchronization
unless (&synchronize($desc_file,$in{'serial'})) {
&report::reject_report_web('user','synchro_failed',{},$param->{'action'},$list);
&wwslog('info',"d_set_owner : Synchronization failed for $desc_file");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'synchro_failed','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
unless (open DESC,">$desc_file") {
&wwslog('info',"d_set_owner : cannot open $desc_file : $!");
&report::reject_report_web('intern','cannot_open_file',{'file' => $desc_file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# information not modified
print DESC "title\n $desc_hash{'title'}\n\n";
print DESC "access\n read $desc_hash{'read'}\n";
print DESC " edit $desc_hash{'edit'}\n\n";
print DESC "creation\n";
# time
print DESC " date_epoch $desc_hash{'date'}\n";
#information modified
# author
print DESC " email $in{'content'}\n\n";
close DESC;
} else {
# Creation of a description file
unless (open (DESC,">$desc_file")) {
&report::reject_report_web('intern','cannot_open_file',{'file' => $desc_file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info',"d_set_owner : Cannot create description file $desc_file : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
print DESC "title\n $desc_hash{'title'}\n\n";
my @info = stat "$shareddir/$path";
print DESC "creation\n date_epoch ".$info[10]."\n email $in{'content'}\n\n";
print DESC "access\n read $access{'scenario'}{'read'}\n";
print DESC " edit $access{'scenario'}{'edit'}\n\n";
close DESC;
}
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'name_doc'}",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
## ONLY IF SET_OWNER can be performed even if not control of the father directory
$mode{'control'} = 1;
my %access = &d_access_control(\%mode,$path);
unless ($access{'may'}{'control'}) {
## father directory
$path =~ /^(([^\/]*\/)*)([^\/]+)(\/?)$/;
$in{'path'} = &no_slash_end($1);
return 'd_read';
}
## ELSE
return 'd_control';
}
## Protecting archives from Email Sniffers
sub do_arc_protect {
&wwslog('info', 'do_arc_protect()');
return 1;
}
####################################################
# do_remind
####################################################
# Sends a remind command to sympa.pl.
#
# IN : -
#
# OUT : 'loginrequest' | 'admin' | undef
#
#####################################################
sub do_remind {
&wwslog('info', 'do_remind()');
## Access control
return undef unless (defined &check_authz('do_remind', 'remind'));
my $extention = time.".".int(rand 9999) ;
my $mail_command;
## Sympa will require a confirmation
my $result = $list->check_list_authz('remind','smtp',
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $r_action;
my $reason;
if (ref($result) eq 'HASH') {
$r_action = $result->{'action'};
$reason = $result->{'reason'};
}
if ($r_action =~ /reject/i) {
&report::reject_report_web('auth',$reason,{},$param->{'action'},$list);
&wwslog('info','remind : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}else {
$mail_command = sprintf "REMIND %s", $param->{'list'};
}
my $time = time;
my $data = {'headers' => {'Message-ID' => '<'.$time.'@wwsympa>',
'X-Sympa-NoWrap' => 'yes'},
'from'=> $param->{'user'}{'email'},
'body' => $mail_command};
$data->{'not_auto_submitted'} = 1;
unless (&mail::mail_file('',&Conf::get_robot_conf($robot, 'sympa'),$data,$robot)) {
&report::reject_report_web('intern','cannot_send_remind',{'from' => $param->{'user'}{'email'},'listname'=>$list->{'name'}},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_remind: failed to send message for command REMIND');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
&report::notice_report_web('performed_soon',{},$param->{'action'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 'admin';
}
## Load list certificat
sub do_load_cert {
&wwslog('info','do_load_cert(%s)', $param->{'list'});
my @cert = $list->get_cert('der');
unless (@cert) {
&report::reject_report_web('user','missing_cert',{},$param->{'action'},$list);
&wwslog('info','do_load_cert: no cert for this list');
return undef;
}
# don't you just HATE it when every single browser seems to want a
# different content-type for certificates? order is important, as
# everybody calls themselves "mozilla", and opera identifies as
# IE if told so (but Opera doesn't do S/MIME anyways, it seems)
my ($ua, $ct) = ($ENV{HTTP_USER_AGENT}, 'application/x-x509-email-cert');
if ($ua =~ /MSIE/) {
$ct = 'application/pkix-cert';
}
$param->{'bypass'} = 'extreme';
print "Content-type: $ct\n\n";
foreach my $l (@cert) {
print "$l";
}
return 1;
}
#*******************************************
# Function : do_upload_pictures
# Description : Creates a new pictures with a
# uploaded file
#******************************************
sub do_upload_pictures {
# Parameters of the uploaded file (from suboptions.tt2)
my $fn = $query->param('uploaded_file');
&wwslog('info', 'do_upload_pictures(%s,%s)',$fn,$param->{'user'}{'email'});
# name of the file, without path
my $fname;
if ($fn =~ /([^\/\\]+)$/) {
$fname = $1;
}
# type of the file
my $filetype;
if ($fn =~ /\.(jpg|jpeg|png|gif)$/i) {
$filetype = lc $1;
}
else {$filetype = undef};
my $filename = &tools::md5_fingerprint($param->{'user'}{'email'});
my $fullfilename = $filename.'.'.$filetype;
#uploaded file must have a name
unless ($fname) {
&report::reject_report_web('user','no_name',{},$param->{'action'});
&wwslog('err',"do_upload_pictures : No file specified to upload");
return 'suboptions';
}
unless($filetype) {
&report::reject_report_web('user','cannot_upload',{'path' => $fullfilename,
'reason' => "your file does not have an authorized format." },$param->{'action'});
&wwslog('err',"do_upload_pictures : unauthorized format");
return 'suboptions';
}
my $filetmp;
#check if there is not already a file for the user with a different extension
foreach my $ext ('.gif','.png','.jpg','.jpeg') {
my $file = &Conf::get_robot_conf($robot,'pictures_path').'/'.$in{'list'}.'@'.$robot.'/'.$filename;
if(-f $file.$ext) {
rename($file.$ext,$file.$ext.'.tmp');
$filetmp = $file.$ext;
last;
}
}
unless(&creation_picture_file(&Conf::get_robot_conf($robot,'pictures_path'),$param->{'list'}.'@'.$robot,$fullfilename)) {
&report::reject_report_web('user','upload_failed', {'path' => $fullfilename},$param->{'action'});
&wwslog('err','do_upload_pictures : Failed to create file %s/%s@%s%s',&Conf::get_robot_conf($robot,'pictures_path'),$param->{'list'},$robot,$filename);
return 'suboptions';
}
my $uploadedfile = &Conf::get_robot_conf($robot,'pictures_path').'/'.$in{'list'}.'@'.$robot.'/'.$fullfilename;
my @info = stat($uploadedfile);
my $size = $info[7];
unless($size <= $Conf{'pictures_max_size'}) {
unlink($uploadedfile);
rename($filetmp.'.tmp',$filetmp);
&report::reject_report_web('user','cannot_upload',{'path' => $fullfilename,
'reason' => "Your file exceeds the authorized size." },$param->{'action'});
&wwslog('err',"do_upload_pictures : Failed to upload pictures");
return 'suboptions';
}
# message of success
unlink($filetmp.'.tmp');
&wwslog('info',"do_upload_pictures : Upload of the pictures succeeded");
return 'suboptions';
}
## Delete a picture file
sub do_delete_pictures {
&wwslog('info', 'do_delete_pictures(%s,%s,%s)', $param->{'list'},$robot,$param->{'user'}{'email'});
my $email = $param->{'user'}{'email'};
#deleted file must exist
unless(&tools::pictures_filename('email' => $email, 'list' => $list)) {
&report::reject_report_web('user','no_name',{},$param->{'action'},$list);
&wwslog('err',"do_delete_pictures : No file exists to delete");
return 'suboptions';
}
unless($list->delete_user_picture($email)) {
&report::reject_report_web('intern','erase_file',{'file' => &tools::pictures_filename('email' => $email, 'list' => $list)},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"do_delete_pictures : Failed to erase ".&tools::pictures_filename('email' => $email, 'list' => $list));
return undef;
}
else {
&wwslog('notice',"do_delete_pictures : File deleted successfull");
return 'suboptions';
}
}
####################################################
# do_change_email_request
####################################################
# Checks a user's new email address and passes it
# to 'change_email'
#
# IN : -
#
# OUT : '1' | 'change_email'
#
####################################################
## Checks a users new email address by sending a ticket to the new email address
## and demanding that they click it to verify. Leads to 'change_email'
sub do_change_email_request {
&wwslog('info','do_change_email_request(%s)', $in{'new_email'});
unless ($param->{'one_time_ticket'} = &Auth::create_one_time_ticket($in{'new_email'},$robot,'change_email/'.$param->{'user'}{'email'},$ip)){
&do_log('notice',"Unable to create one_time_ticket for $in{'new_email'}, service do_change_email_request");
}else{
&do_log('notice',"ticket : $param->{'one_time_ticket'}");
}
$param->{'new_email'} = $in{'new_email'};
my $tt2_param = {'type' => 'ticket_to_send',
'one_time_ticket' => $param->{'one_time_ticket'},
'to' => $in{'new_email'},
};
unless (&List::send_global_file('user_notification', $in{'new_email'}, $robot, $tt2_param)) {
&do_log('notice',"Unable to send template 'user_notification' to $in{'new_email'}");
return undef;
}
return '1';
}
####################################################
# do_change_email
####################################################
# Changes a user's email address in Sympa environment
#
# IN : -
#
# OUT : '1' | 'pref' | undef
#
####################################################
## Change a user's email address in Sympa environment
sub do_change_email {
&wwslog('info','do_change_email(%s)', $in{'email'});
my ($old_email, $new_email);
my $edited_by_listmaster;
unless ($in{'email'} || ($in{'old_email'} && $in{'new_email'})) {
&report::reject_report_web('user','Missing argument',{},$param->{'action'});
&wwslog('err',"Lacking parameter : $in{'email'} or $in{'old_email'} or $in{'new_email'} ");
&web_db_log({'parameters' => $in{'email'},$in{'old_email'},$in{'new_email'},
'status' => 'error',
'error_type' => 'user'});
}
## There are two ways to access this function 'change_email'. One from the preferences page and one from the serveradmin page
## If the process comes from server admin it needs the variables 'old_email' and 'new_email'.
if ($in{'old_email'} && $in{'new_email'}) {
## if variables old_email and new_email are present $edited_by_listmaster is set to one
## so that at the end of the function we can return to the SympaAdmin page
## instead of the preferences page
$edited_by_listmaster = 1;
unless (&List::is_listmaster ($param->{'user'}{'email'}, $robot)) {
&report::reject_report_web('auth','User is not Listmaster',{},$param->{'action'});
&wwslog('err','do_change_email : not listmaster');
&web_db_log({'parameters' => $in{'email'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
$old_email = $in{'old_email'};
$new_email = $in{'new_email'};
}else {
$old_email = $in{'email'};
$new_email = $param->{'user'}{'email'};
}
my ($password, $newuser);
if ($newuser = &List::get_user_db($old_email)) {
$password = $newuser->{'password'};
}
## Change email as list MEMBER
foreach my $list ( &List::get_which($old_email,$robot, 'member') ) {
my $l = $list->{'name'};
my $user_entry = $list->get_subscriber($old_email);
if ($user_entry->{'included'} == 1) {
## Notify list owner
$list->send_notify_to_owner('failed_to_change_included_member',{'current_email' => $old_email,
'new_email' => $new_email,
'datasource' => $list->get_datasource_name($user_entry->{'id'})});
&report::reject_report_web('user','change_member_email_failed_included',{'listname'=>$list->{'name'}},
$param->{'action'},$list,$old_email,$robot);
&wwslog('err', 'could not change member email for list %s because member is included', $l);
next;
}
## Check if user is already member of the list with his new address
## then we just need to remove the old address
if ($list->is_user($new_email)) {
unless ($list->delete_user('users' => [$old_email]) ) {
&report::reject_report_web('intern','delete_subscriber_db_failed',{'sub'=>$new_email},
$param->{'action'},$list,$old_email,$robot);
&wwslog('info', 'do_change_email: could not remove email from list %s', $l);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$new_email",'target_email' => "$new_email",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $old_email,'client' => $ip,'daemon' => $daemon_name});
}
}else {
unless ($list->update_user($old_email, {'email' => $new_email, 'update_date' => time}) ) {
&report::reject_report_web('intern','update_subscriber_db_failed',{'sub'=>$new_email},
'old_email' => $old_email,
$param->{'action'},$list,$old_email,$robot);
&wwslog('info', 'do_change_email: could not change email for list %s', $l);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$new_email",'target_email' => "$new_email",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $old_email,'client' => $ip,'daemon' => $daemon_name});
}
}
}
&report::notice_report_web('performed',{},$param->{'action'});
## Change email as list OWNER/MODERATOR
my %updated_lists;
foreach my $role ('owner', 'editor') {
foreach my $list ( &List::get_which($old_email,$robot, $role) ) {
## Check if admin is include via an external datasource
my $admin_user = $list->get_admin_user($role, $old_email);
if ($admin_user->{'included'}) {
## Notify listmaster
&List::send_notify_to_listmaster('failed_to_change_included_admin',$robot,{'list' => $list,
'current_email' => $old_email,
'new_email' => $new_email,
'datasource' => $list->get_datasource_name($admin_user->{'id'})});
&report::reject_report_web('user','change_admin_email_failed_included',{'listname'=>$list->{'name'}},
$param->{'action'},$list,$old_email,$robot);
&wwslog('err', 'could not change %s email for list %s because admin is included', $role, $list->{'name'});
next;
}
## Go through owners/editors of the list
foreach my $admin (@{$list->{'admin'}{$role}}) {
next unless (lc($admin->{'email'}) eq lc($old_email));
## Update entry with new email address
$admin->{'email'} = $new_email;
$updated_lists{$list->{'name'}}++;
}
## Update Db cache for the list
$list->sync_include_admin();
$list->save_config($param->{'session'}{'email'});
}
}
## Notify listmasters that list owners/moderators email have changed
if (keys %updated_lists) {
&List::send_notify_to_listmaster('listowner_email_changed',$robot,
{'list' => $list,
'previous_email' => $old_email,
'new_email' => $new_email,
'updated_lists' => keys %updated_lists})
}
## Update User_table and remove existing entry first (to avoid duplicate entries)
&List::delete_user_db($new_email,);
unless ( &List::update_user_db($old_email,
{'email' => $new_email,
})) {
&report::reject_report_web('intern','update_user_db_failed',{'user'=>$new_email,
'old_email' => $old_email},
$param->{'action'},'',$old_email,$robot);
&wwslog('info','change_email: update failed');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$new_email",'target_email' => "$new_email",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $old_email,'client' => $ip,'daemon' => $daemon_name});
return undef;
}
## Update netidmap_table
unless ( &List::update_email_netidmap_db($robot, $old_email, $new_email) ){
&report::reject_report_web('intern','update_netidmap_failed',{'user'=>$new_email,
'old_email' => $old_email},
$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','change_email: update failed');
&web_db_log({'target_email' => $old_email,
'status' => 'error',
'error_type' => 'internal'});
return undef;
}
## Update the data structure that tells which lists the current user is member/owner/editor of
unless ($edited_by_listmaster == 1) {
@{$param->{'get_which'}} = &List::get_which($param->{'user'}{'email'},$robot,'member');
@{$param->{'get_which_owner'}} = &List::get_which($param->{'user'}{'email'},$robot,'owner');
@{$param->{'get_which_editor'}} = &List::get_which($param->{'user'}{'email'},$robot,'editor');
}
if ($edited_by_listmaster == 1) {
return 'serveradmin';
}
if ($in{'previous_action'}) {
$in{'list'} = $in{'previous_list'};
return $in{'previous_action'};
}elsif ($edited_by_listmaster == 1) {
return 'serveradmin';
}
return 'pref';
}
####################################################
# do_suspend_request
####################################################
# Suspend a subscription to one or more lists #
# for a given period: start date and end date #
# (or unlimited). The user may at any time #
# stop the suspension. #
# #
# IN : - #
# OUT : 'loginrequest' #
# | 'info' | undef #
# #
####################################################
sub do_suspend_request {
&wwslog('info', 'do_suspend_request', $in{'action'}); #Action = suspend_request
my $email = $param->{'user'}{'email'};
my $data;
## Sets the date of the field "start date" to "today"
my @d_day = localtime(time);
$param->{'d_day'} = ($d_day[3])."-".($d_day[4]+1)."-".($d_day[5]+1900);
my $display_resume = 0;
## We display in the table the lists of the subscriber and the state in which they are.
## reception : - nomail/digest/mail ||
## - . suspended From XX/XX/XXXX To XX/XX/XXXX
my @lists = &List::get_which($email, $robot, 'member');
foreach my $list (@lists) {
my $member_info = $list->get_subscriber($param->{'user'}{'email'});
if(($member_info->{'enddate'} < time) && ($member_info->{'enddate'})){
## If end date is < time, update the BDD by deleting the suspending's data
&List::restore_suspended_subscription($param->{'user'}{'email'},$list->{'name'},$list->{'domain'});
}
my $final_start_date = gettext_strftime "%d %b %Y", localtime($member_info->{'startdate'});
my $final_end_date;
if($member_info->{'suspend'} == 1){
$display_resume = 1;
}
if($member_info->{'enddate'}){
$final_end_date = gettext_strftime "%d %b %Y", localtime($member_info->{'enddate'});
}else{
$final_end_date = undef;
}
$member_info->{'reception'} ||= 'mail';
$member_info->{'visibility'} ||= 'noconceal';
foreach my $mode ($list->available_reception_mode) {
$param->{'reception'}{$list->{'name'}}{$mode}{'description'} =
$list->get_option_title($mode, 'reception');
if ($member_info->{'reception'} eq $mode) {
$param->{'reception'}{$list->{'name'}}{$mode}{'selected'} =
' selected';
}else {
$param->{'reception'}{$list->{'name'}}{$mode}{'selected'} = '';
}
}
my $subscription = {'listname' => $list->{'name'},
'listdomain' => $list->{'domain'},
'listreception' => $member_info->{'reception'},
'listsuspend' => $member_info->{'suspend'},
'liststartdate' =>$final_start_date,
'listenddate' => $final_end_date,
'display' => $display_resume,
'visibility' => $member_info->{'visibility'},
'reception' => $param->{'reception'}{$list->{'name'}},
};
push @{$param->{'suspend_list'}}, $subscription;
}
return 1;
}
####################################################
# do_suspend_request_action
####################################################
# Suspend a subscription for lists. #
# Action from the suspend form. #
# #
# IN : %in : HASH with the form's values #
# OUT : 'pref' : action #
# | 'info' | undef #
####################################################
sub do_suspend_request_action {
&wwslog('info', 'do_suspend_request_action', $in{'action'});
my $day1;
my $month1;
my $year1;
my $day2;
my $month2;
my $year2;
my @lists;
my $data;
if($in{'sub_action'} eq 'suspendsave'){
# to retrieve the selected list
@lists = split /\0/, $in{'listname'};
my @list_selected;
foreach my $list (@lists){
unless($list eq ''){
push @list_selected, $list;
}
}
if($list_selected[0] eq ''){
&report::reject_report_web('user','missing_arg',{'argument' => 'must picked one or more list(s) you are subscribed'}, $param->{'action'});
&wwslog('info','suspend_request: must picked one or more list(s) you are subscribed');
return 'suspend_request';
}
if ($in{'date_deb'}){
($day1, $month1, $year1) = split(/\-/, $in{'date_deb'});
$month1 = $month1-1;
if (($day1 =~ /([0-9]*)/) && ($month1 =~ /([0-9]*)/) && ($year1 =~ /([0-9]*)/)){
if (((1<=$day1) && ($day1<=31)) && ((0<=$month1) && ($month1<=11)) && (1900<=$year1)){
## Return an epoch date
$data->{'startdate'} = timelocal(0, 0, 0, $day1,$month1,$year1);
}else{
&report::reject_report_web('user','missing_arg',{'argument' => 'Start Date doesn\'t exist.'}, $param->{'action'});
&wwslog('info','suspend_request: Date doesn\'t exist.');
return 'suspend_request';
}
}else{
&report::reject_report_web('user','missing_arg',{'argument' => 'Start Date doesn\'t exist.'}, $param->{'action'});
&wwslog('info','suspend_request: Date doesn\'t exist.');
return 'suspend_request';
}
## Case 1 : Start date & End date (without indefinite)
if (($in{'date_fin'}) && (!$in{'indefinite'})){
($day2, $month2, $year2) = split(/\-/, $in{'date_fin'});
$month2 = $month2-1;
if (($day2 =~ /([0-9]*)/) && ($month2 =~ /([0-9]*)/) && ($year2 =~ /([0-9]*)/)){
if (((1<=$day2) && ($day2<=31)) && ((0<=$month2) && ($month2<=11)) && (1900<=$year2)){
## Return an epoch date
$data->{'enddate'} = timelocal(0, 0, 0, $day2,$month2,$year2);
}else{
&report::reject_report_web('user','missing_arg',{'argument' => 'End Date doesn\'t exist.'}, $param->{'action'});
&wwslog('info','suspend_request: Date doesn\'t exist.');
return 'suspend_request';
}
}else{
&report::reject_report_web('user','missing_arg',{'argument' => 'End Date doesn\'t exist.'}, $param->{'action'});
&wwslog('info','suspend_request: Date doesn\'t exist.');
return 'suspend_request';
}
unless($data->{'startdate'} <= $data->{'enddate'}){
&report::reject_report_web('user','missing_arg',{'argument' => 'The start date must be less than the end date.'}, $param->{'action'});
&wwslog('info','suspend_request: The start date must be less than the end date.');
return 'suspend_request';
}
## Case 2 : Start date & without indefinite (without end date)
}elsif((!$in{'date_fin'}) && ($in{'indefinite'})){
$data->{'enddate'} = undef;
}else{
&report::reject_report_web('user','missing_arg',{'argument' => 'Choose end date (dd/mm/yyyy) or indefinite end date'}, $param->{'action'});
&wwslog('info','suspend_request: missing argument for the end date or syntax error : dd/mm/yyyy or must choose a end date or indefinite end date');
return 'suspend_request';
}
}else{
&report::reject_report_web('user','missing_arg',{'argument' => 'Miss start date (dd/mm/yyyy)'}, $param->{'action'});
&wwslog('info','suspend_request: missing argument for the start date or syntax error : dd/mm/yyyy');
return 'suspend_request';
}
## Suspend subscription
foreach my $list (@list_selected){
unless(&List::suspend_subscription($param->{'user'}{'email'}, $list, $data, $robot)){
&wwslog('info','Can\'t do List suspend_subscription');
return 'suspend_request';
}
}
&report::notice_report_web('performed',{},$in{'sub_action'});
}
## Restore suspended subscription
elsif($in{'sub_action'} eq 'suspendstop'){
# to renew membership lists selected
@lists = split /\0/, $in{'listname'};
foreach my $line (@lists) {
&List::restore_suspended_subscription($param->{'user'}{'email'}, $line, $robot);
}
if($lists[0] eq ''){
&report::reject_report_web('user','missing_arg',{'argument' => 'must picked one or more list(s)'}, $param->{'action'});
&wwslog('info','suspend_request: must picked one or more list(s)');
return 'suspend_request';
}
&report::notice_report_web('performed',{},"Resume the subscription for the list(s)");
}
## Unsubscribe from the selected lists
elsif($in{'sub_action'} eq 'signoff'){
# lists selected
@lists = split /\0/, $in{'listname'};
my $report = "";
foreach my $line (@lists) {
my $unsub_list = new List ($line, $robot);
unless ($unsub_list) {
&wwslog('info', 'List %s unknown' , $unsub_list);
return undef;
}
my %result = &unsubscribe($param->{'user'}{'email'}, $unsub_list);
if ($result{'success'} == 1) {
if ($result{'details'} eq 'sent_to_owner') {
$report .= sprintf(gettext("Your unsubscription request to list %s was sent to the list owner."),$unsub_list->{'name'});
}else{
$report .= sprintf(gettext("You were successfully unsubscribed from list %s."),$unsub_list->{'name'});
}
}else{
if ($result{'category_error'} eq 'auth') {
$report .= sprintf(gettext("Unsubscription from list %s denied: Unsubscription from this list is closed."),$unsub_list->{'name'});
}else{
$report .= sprintf(gettext("Unsubscription from list %s failed."),$unsub_list->{'name'});
}
}
$report .= "\n";
}
if($lists[0] eq ''){
&report::reject_report_web('user','missing_arg',{'argument' => 'must picked one or more list(s)'}, $param->{'action'});
&wwslog('info','suspend_request: must picked one or more list(s)');
return 'suspend_request';
}
&report::notice_report_web($report,{},'');
}else{
&report::reject_report_web('user','unknown_action',{},$in{'sub_action'},$list);
&wwslog('info','unknown action %s', $in{'sub_action'});
return undef;
}
return 'suspend_request';
}
####################################################
# do_compose_mail
####################################################
sub do_compose_mail {
&wwslog('info', 'do_compose_mail', $in{'subaction'});
unless ($param->{'may_post'}) {
&report::reject_report_web('auth',$param->{'may_post_reason'},{},$param->{'action'},$list);
&wwslog('info','do_compose_mail: may not send message');
return undef;
}
# Set the subaction to html_news_letter or undef
$param->{'subaction'} = $in{'subaction'};
if ($in{'to'}) {
# In archive we hide email replacing @ by ' '. Here we must do the reverse transformation
$in{'to'} =~ s/ /\@/g;
$param->{'to'} = $in{'to'};
}else{
$param->{'to'} = $list->get_list_address();
}
foreach my $recipient (split(',',$param->{'to'})) {
($param->{'recipients'}{$recipient}{'local_to'},$param->{'recipients'}{$recipient}{'domain_to'}) = split ('@',$recipient);
}
$param->{'mailto'}= &mailto($list,$param->{'to'});
# headers will be encoded later.
#XXX$param->{'subject'}= &MIME::Words::encode_mimewords($in{'subject'});
$param->{'subject'} = $in{'subject'};
$param->{'in_reply_to'}= '<'.$in{'in_reply_to'}.'>';
$param->{'message_id'} = &tools::get_message_id($robot);
if ($list->is_there_msg_topic()) {
$param->{'request_topic'} = 1;
foreach my $top (@{$list->{'admin'}{'msg_topic'}}) {
if ($top->{'name'}) {
push (@{$param->{'available_topics'}},$top);
}
}
$param->{'topic_required'} = $list->is_msg_topic_tagging_required();
}
$param->{'merge_feature'} = $list->{'admin'}{'merge_feature'} eq 'on';
return 1;
}
####################################################
# do_send_mail
####################################################
# Sends a message to a list by the Web interface
# or an html page getting its url.
# Need MIME::Lite - MIME::Lite::HTML - EMAIL::DATE::FORMAT
# It uses mail::mail_file() to do it.
#
# IN : -
#
# OUT : 'loginrequest'
# | 'info' | undef
#
####################################################
sub do_send_mail {
&wwslog('info', 'do_send_mail');
# Get the sender mail
my $from = $param->{'user'}{'email'};
my $to;
# Send the message to the list or to the sender as clicking the send to the list or to me.
# First if : send to the list
if ($in{'sub_action'} eq 'sendmailtolist'){
# In archive we hide email replacing @ by ' '. Here we must do the reverse transformation
$in{'to'} =~ s/ /\@/g;
$to = $in{'to'};
unless ($in{'to'}) {
unless ($param->{'list'}) {
&report::reject_report_web('user','missing_arg',{'argument' => 'list'},$param->{'action'});
&wwslog('info','do_send_mail: no list');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_list','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
unless ($param->{'may_post'}) {
&report::reject_report_web('auth',$param->{'may_post_reason'},{},$param->{'action'},$list);
&wwslog('info','do_send_mail: may not send message');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
$to = $list->get_list_address();
}
}
# Send the mail to the sender. To test his message
# Second if : send to the sender "send to me"
if($in{'sub_action'} eq 'sendmailtome') {
#Set the sender mail to the addressee
$to = $from;
}
if (defined $param->{'subscriber'}) {
$from = &tools::addrencode($from, $param->{'subscriber'}{'gecos'},
&Language::GetCharset());
}
##--------------- TOPICS --------------------
my $list_topics;
if ($list->is_there_msg_topic()) {
my @msg_topics;
foreach my $msg_topic (@{$list->{'admin'}{'msg_topic'}}) {
my $var_name = "topic_"."$msg_topic->{'name'}";
if ($in{"$var_name"}) {
push @msg_topics, $msg_topic->{'name'};
}
}
$list_topics = join(',',@msg_topics);
}
if (!$list_topics && $list->is_msg_topic_tagging_required()) {
&report::reject_report_web('user','msg_topic_missing',{},$param->{'action'});
&wwslog('info','do_send_mail: message(s) without topic but in a required list');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_topic','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
if ($list_topics) {
my $filetopic = $list->tag_topic($in{'message_id'},$list_topics,'sender');
}
##--------------- send an html page or a message --------------------
if ($in{'html_news_letter'}) {
# url should not be empty -> missing argument
if ($in{'url'} =~ /^\s*$/) {
&report::reject_report_web('user','missing_arg',{'argument' => 'url'},$param->{'action'});
($Log::log_level >= 0) && &wwslog('info','Missing url');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_url','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
# Generate a newsletter from an HTML URL and send it to a list by the Web interface.
# Else you must use parse routine of MIME::Lite::HTML and send of MIME::Lite.
my $mailHTML = new MIME::Lite::HTML(
{
From => $from,
To => $to,
Headers => {'In-Reply-To' => $in{'in_reply_to'}, 'Message-ID' => $in{'message_id'}},
'return_path' => &Conf::get_robot_conf($robot, 'sympa'),
Subject => $in{'subject'},
HTMLCharset => 'utf-8',
TextCharset => 'utf-8',
TextEncoding => '8bit',
HTMLEncoding => '8bit',
remove_jscript => '1', #delete the scripts in the html
}
);
my $pages_url;
$pages_url = $in{'url'};
# parse return the MIME::Lite part to send
my $MIMEmail = $mailHTML->parse($pages_url);
$in{'body'} = $MIMEmail->as_string;
}else{
## Message body should not be empty
if ($in{'body'} =~ /^\s*$/) {
&report::reject_report_web('user','missing_arg',{'argument' => 'body'},$param->{'action'});
($Log::log_level >= 0) && &wwslog('info','Missing body');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_body','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
$in{'body'} = "\n".$in{'body'};
}
my $data = {'headers' => {'Message-ID' => $in{'message_id'}},
'subject' => $in{'subject'},
'return_path' => &Conf::get_robot_conf($robot, 'sympa'),
'to' => $to,
'body' => "From: $from\n" . $in{'body'},
'sign_mode' => '',
'header_possible' => '1'};
$data->{'headers'}{'In-Reply-To'} = $in{'in_reply_to'} if (($in{'in_reply_to'}) && $in{'in_reply_to'} ne '<>');
$data->{'not_auto_submitted'} = 1;
unless (&mail::mail_file('', $to, $data, $robot)) {
&report::reject_report_web('intern','cannot_send_mail',{'from' => $param->{'user'}{'email'},'listname'=>$list->{'name'}},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_send_mail: failed to send message for $to list');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
&report::notice_report_web('performed',{},$param->{'action'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 'info';
}
####################################################
# do_request_topic
####################################################
# Web page for a sender to tag his mail in message
# topic context.
#
# IN : -
#
# OUT : '1' | 'loginrequest' | undef
#
####################################################
sub do_request_topic {
&wwslog('info', 'do_request_topic(%s)', $in{'authkey'});
unless ($list->is_there_msg_topic()) {
&report::reject_report_web('user','no_topic',{},$param->{'action'},$list);
&wwslog('info','do_request_topic: list without topic message');
return undef;
}
foreach my $top (@{$list->{'admin'}{'msg_topic'}}) {
if ($top->{'name'}) {
push (@{$param->{'available_topics'}},$top);
}
}
$param->{'to'} = $list->get_list_address();
$param->{'mailto'}= &mailto($list,$param->{'to'});
$param->{'authkey'} = $in{'authkey'};
my $listname = $list->{'name'};
my $authqueue = &Conf::get_robot_conf($robot,'queueauth');
my $filename = "$authqueue\/$listname\_$in{'authkey'}";
## For compatibility concerns
foreach my $list_id ($list->get_list_id(),$list->{'name'}) {
$filename = $authqueue.'/'.$list_id.'_'.$in{'authkey'};
last if (-f $filename);
}
my $parser;
unless ($parser = new MIME::Parser) {
&report::reject_report_web('intern','cannot_parse_message',{'file' => $filename},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('notice', 'Cannot parse message %s', $filename);
return undef;
}
$parser->output_to_core(1);
unless (open FILE, "$filename") {
&report::reject_report_web('intern','cannot_open_file',{'file' => $filename},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('notice', 'Cannot open file %s', $filename);
return undef;
}
my $msg = $parser->parse(\*FILE);
my $head = $msg->head();
# headers will be encoded later.
$param->{'subject'} = tools::decode_header($head, 'Subject');
$param->{'subject'} = &tools::escape_html($param->{'subject'});
$param->{'from'} = tools::decode_header($head, 'From');
$param->{'from'} = &tools::escape_html($param->{'from'});
$param->{'date'} = tools::decode_header($head, 'Date');
$param->{'date'} = &tools::escape_html($param->{'date'});
$param->{'message_id'} = &tools::clean_msg_id($head->get('Message-Id'));
my $body = $msg->bodyhandle();
if ($body) {
$param->{'body'} = $body->as_string();
}else{
$param->{'body'} = '';
}
$param->{'topic_required'} = $list->is_msg_topic_tagging_required();
return 1;
}
####################################################
# do_tag_topic_by_sender
####################################################
# Tag a mail by its sender : tag the mail and
# send a command CONFIRM for it
#
# IN : -
#
# OUT : 'loginrequest' | 'info' | undef
#
####################################################
sub do_tag_topic_by_sender {
&wwslog('info', 'do_tag_topic_by_sender');
my $parser;
my $listname = $list->{'name'};
my $authqueue = &Conf::get_robot_conf($robot,'queueauth');
my $filename = "$authqueue\/$listname".'@'."$robot\_$in{'authkey'}";
my $mail ;
unless($mail = new Message($filename,'noxsympato')) {
&report::reject_report_web('intern','cannot_parse_message',{'file' => $filename},$param->{'action'});
&wwslog('info','do_tag_topic_by_sender: cannot parse message %s',$filename);
return undef;
}
my $sender = $mail->{'sender'};
unless ($list->is_there_msg_topic()) {
&report::reject_report_web('user','no_topic',{},$param->{'action'},$list);
&wwslog('info','do_tag_topic_by_sender: list without topic message');
return undef;
}
my @msg_topics;
foreach my $msg_topic (@{$list->{'admin'}{'msg_topic'}}) {
my $var_name = "topic_"."$msg_topic->{'name'}";
if ($in{"$var_name"}) {
push @msg_topics, $msg_topic->{'name'};
}
}
my $list_topics = join(',',@msg_topics);
if (!$list_topics && $list->is_msg_topic_tagging_required()) {
&report::reject_report_web('user','msg_topic_missing',{},$param->{'action'},$list);
&wwslog('info','do_tag_topic_by_sender: message without topic but in a required list');
return undef;
}
## TAG
my $filetopic = $list->tag_topic($in{'message_id'},$list_topics,'sender');
## CONFIRM
my $time = time;
my $data = {'headers' => {'Message-ID' => '<'.$time.'@wwsympa>',
'X-Sympa-NoWrap' => 'yes'},
'from'=> $sender};
$data->{'body'} = sprintf ("QUIET CONFIRM %s\n",$in{'authkey'});
my $queueauth = &Conf::get_robot_conf($robot, 'queueauth');
my $filemsg = "$queueauth/$list->{'name'}_$in{'authkey'}";
## For compatibility concerns
foreach my $list_id ($list->get_list_id(),$list->{'name'}) {
$filemsg = $queueauth.'/'.$list_id.'_'.$in{'authkey'};
last if (-f $filemsg);
}
unless ($filemsg && (-r $filemsg)) {
&report::reject_report_web('intern','tag_topic_by_sender_failed',{'key' => $in{'authkey'}},$param->{'action'},$robot);
&wwslog('err', 'do_tag_topic_by_sender: Unable to find message %s from %s, auth failed', $in{'authkey'},$param->{'user'}{'email'});
return undef;
}
$data->{'not_auto_submitted'} = 1;
unless (&mail::mail_file('',&Conf::get_robot_conf($robot, 'sympa'),$data,$robot)) {
&report::reject_report_web('intern','cannot_send_mail',{'from' => $param->{'user'}{'email'},'listname'=>$list->{'name'}},
$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err','do_tag_topic_by_sender: failed to send message for file %s', $filemsg);
return undef;
}
&report::notice_report_web('performed_soon',{},$param->{'action'});
return 'info';
}
sub do_search_user {
&wwslog('info', 'do_search_user');
if ($in{'email'} =~ /[<>\\\*\$]/) {
&report::reject_report_web('user','syntax_errors',{'params' => 'email'},$param->{'action'});
&wwslog('err','do_search_user: syntax error');
return undef;
}
foreach my $role ('member','owner','editor') {
foreach my $list ( &List::get_which($in{'email'},$robot, $role) ) {
my $l = $list->{'name'};
next unless (defined $list);
$param->{'which'}{$l}{'subject'} = $list->{'admin'}{'subject'};
$param->{'which'}{$l}{'host'} = $list->{'admin'}{'host'};
# show the requestor role not the requested one
if ( ($list->am_i('owner',$param->{'user'}{'email'}) || $list->am_i('editor',$param->{'user'}{'email'})) ) {
$param->{'which'}{$l}{'admin'} = 1;
}
if ($role eq 'member') {
$param->{'which'}{$l}{'is_member'} = 1;
$param->{'which'}{$l}{'reception'} = $list->{'user'}{'reception'};
$param->{'which'}{$l}{'include_source'} = $list->{'user'}{'include_source'};
$param->{'which'}{$l}{'bounce'} = $list->{'user'}{'bounce'} ;
$param->{'which'}{$l}{'topic'} = $list->{'user'}{'topic'} ;
$param->{'which'}{$l}{'included'} = $list->{'user'}{'included'} if ($list->{'user'}{'included'} == 1) ;
$param->{'which'}{$l}{'subscribed'} = $list->{'user'}{'subscribed'} if ($list->{'user'}{'subscribed'} == 1);
my $un = $list->{'user'}{'subscribed'};
# $param->{'which'}{$l}{'subscribed'} = 1;
}elsif ($role eq 'owner') {
$param->{'which'}{$l}{'is_owner'} = 1;
}elsif ($role eq 'editor') {
$param->{'which'}{$l}{'is_editor'} = 1;
}
}
}
$param->{'email'} = $in{'email'};
unless (defined $param->{'which'}) {
&report::reject_report_web('user','no_entry',{'email' => $in{'email'}},$param->{'action'});
&wwslog('info','do_search_user: no entry for %s', $in{'email'});
return 'serveradmin';
}
return 1;
}
## Set language
sub do_set_lang {
&wwslog('info', 'do_set_lang(%s)', $in{'lang'});
$session->{'lang'} = $in{'lang'} ;
$param->{'lang'} = $in{'lang'};
$param->{'lang_tag'} = Language::LanguageTag($param->{'lang'});
if ($param->{'user'}{'email'}) {
if (&List::is_user_db($param->{'user'}{'email'})) {
unless (&List::update_user_db($param->{'user'}{'email'}, {'lang' => $in{'lang'}})) {
&report::reject_report_web('intern','update_user_db_failed',{'user'=>$param->{'user'}{'email'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_set_lang: update failed');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'lang'}",'target_email' => "$param->{'user'}{'email'}",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}else {
unless (&List::add_user_db({'email' => $param->{'user'}{'email'}, 'lang' => $in{'lang'}})) {
&report::reject_report_web('intern','add_user_db_failed',{'user'=>$param->{'user'}{'email'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_set_lang: update failed');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'lang'}",'target_email' => "$param->{'user'}{'email'}",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
}
if ($in{'previous_action'}) {
## Some actions don't make sense with GET method, redirecting to other functions
if ($in{'previous_action'} eq 'arcsearch') {
$in{'previous_action'} = 'arc';
}
$in{'list'} = $in{'previous_list'};
return $in{'previous_action'};
}
return &Conf::get_robot_conf($robot, 'default_home');
}
## Function do_attach
sub do_attach {
&wwslog('info', 'do_attach(%s,%s)', $in{'dir'},$in{'file'});
### Useful variables
# current list / current shared directory
my $list_name = $list->{'name'};
# path of the urlized directory
my $urlizeddir = $list->{'dir'}.'/urlized';
# document to read
my $doc = $urlizeddir.'/'.$in{'dir'}.'/'.$in{'file'};
### Document exist ?
unless (-e "$doc") {
&wwslog('info',"do_attach : unable to read $doc : no such file or directory");
&report::reject_report_web('user','no_such_document',{'path' => $in{'dir'}.'/'.$in{'file'}},$param->{'action'},$list);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'dir'},$in{'file'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_file','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
### Document has non-size zero?
unless (-s "$doc") {
&wwslog('info',"do_attach : unable to read $doc : empty document");
&report::reject_report_web('user','empty_document',{'path' => $in{'dir'}.'/'.$in{'file'}},$param->{'action'},$list);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'dir'},$in{'file'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'empty_file','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
## Access control
return undef unless (defined &check_authz('do_attach', 'web_archive.access'));
# parameters for the template file
# view a file
$param->{'file'} = $doc;
$param->{'bypass'} = 'asis';
## File type
if ($in{'file'} =~ /\.(\w+)$/) {
$param->{'file_extension'} = $1;
}
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'dir'},$in{'file'}",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
sub do_subindex {
&wwslog('info', 'do_subindex');
my $subscriptions = $list->get_subscription_requests();
foreach my $sub (keys %{$subscriptions}) {
$subscriptions->{$sub}{'date'} = gettext_strftime "%d %b %Y", localtime($subscriptions->{$sub}{'date'});
}
$param->{'subscriptions'} = $subscriptions;
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
sub do_ignoresub {
&wwslog('info', 'do_ignoresub');
my @users;
foreach my $pair (split /\0/, $in{'pending_email'}) {
if ($pair =~ /,/) {
push @users, $`;
}
}
foreach my $u (@users) {
unless ($list->delete_subscription_request($u)) {
&report::reject_report_web('intern','del_sub_request',{'sub'=>$u},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('info','do_ignoresub: delete_subscription_request(%s) failed', $u);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 'subindex';
}
}
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 'subindex';
}
sub do_stats {
&wwslog('info', 'do_stats');
$param->{'shared_size'} = int (($list->get_shared_size + 512)/1024);
$param->{'arc_size'} = int (($list->get_arc_size($wwsconf->{'arc_path'}) + 512)/1024);
return 1;
}
## setting the topics list for templates
sub export_topics {
my $robot = shift;
wwslog ('debug2',"export_topics($robot)");
my %topics = &List::load_topics($robot);
unless (%topics) {
&wwslog('err','No topics defined');
return undef;
}
## Remove existing topics
$param->{'topics'} = undef;
my $total = 0;
foreach my $t (sort {$topics{$a}{'order'} <=> $topics{$b}{'order'}} keys %topics) {
my $result = &Scenario::request_action ('topics_visibility', $param->{'auth_method'},$robot,
{'topicname' => $t,
'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $action;
$action = $result->{'action'} if (ref($result) eq 'HASH');
next unless ($action =~ /do_it/);
my $current = $topics{$t};
$current->{'id'} = $t;
## For compatibility reasons
$current->{'mod'} = $total % 3;
$current->{'mod2'} = $total % 2;
push @{$param->{'topics'}}, $current;
$total++;
}
push @{$param->{'topics'}}, {'id' => 'topicsless',
'mod' => $total,
'sub' => {}
};
$param->{'topics'}[int($total / 2)]{'next'} = 1;
}
# manage blacklist
sub do_blacklist {
&wwslog('info', 'do_blacklist(%s)', $param->{'list'});
unless ($param->{'list'}){
&report::reject_report_web('user','missing_arg',{'argument' => 'list'},$param->{'action'});
&wwslog('info','do_blacklist: no list');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$param->{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'no_list','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
unless($param->{'is_owner'}|| $param->{'is_editor'} || $param->{'is_listmaster'}) {
&wwslog('info','do_blacklist : not listmaster or list owner or list editor');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$param->{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
my $file = $list->{'dir'}.'/search_filters/blacklist.txt';
$param->{'rows'} = 0 ;
if (defined $in{'blacklist'}){
&wwslog('info','do_blacklist : submit blacklist update');
my $dir = $list->{'dir'}.'/search_filters';
unless ((-d $dir) || mkdir ($dir, 0755)) {
&report::reject_report_web('intern','unable to create dir');
&wwslog('info','do_blacklist : unable to create dir %s',$dir);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$param->{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
my $file = $dir.'/blacklist.txt';
unless (open BLACKLIST, "> $file"){
&report::reject_report_web('intern','unable to create file');
&wwslog('info','do_blacklist : unable to create file %s',$file);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$param->{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
my @lines = split(/\r\n|\r|\n/, $in{'blacklist'});
$param->{'ignored'} = 0;
my $count = 0; # count utils lines in order to remove empty blacklist file
foreach my $line (@lines) {
if ($line =~ /\*.*\*/) {
$param->{'ignored_linest'} .= $line."\n";
$param->{'ignored'} += 1;
}else{
print BLACKLIST "$line\n";
$param->{'blacklist'} .= $line."\n";
$param->{'rows'} += 1;
$count += 1 unless ($line =~ /^\s*$/o || /^[\#\;]/o);
}
}
close BLACKLIST;
if ($count == 0) {
unless (unlink $file) {
&report::reject_report_web('intern','unable to remove empty blacklist file');
&wwslog('info','do_blacklist : unable to remove empty blacklist file %s',$file);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$param->{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
&wwslog('info','do_blacklist : removed empty blacklist file %s',$file);
}
}else{
if (-f $file) {
unless (open BLACKLIST, $file) {
&report::reject_report_web('intern','unable to open file',{'file' => $file,$param->{'action'},'',$param->{'user'}{'email'}},$robot);
&wwslog('err','unable to read %s',$file);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$param->{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
while () {
$param->{'blacklist'} .= $_ ;
$param->{'rows'} += 1;
}
close BLACKLIST;
}
}
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$param->{'list'}",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
# output in text/plain format a scenario
sub do_dump_scenario {
&wwslog('info', "do_dump_scenario($param->{'list'}), $in{'pname'}");
my $scenario = new Scenario ('function' => $in{'pname'},
'robot' => $robot,
'name' => $list->{'admin'}{$in{'pname'}}{'name'},
'directory' => $list->{'dir'});
unless (defined $scenario) {
&report::reject_report_web('intern','cannot_open_file',{},$param->{'action'},$list);
&wwslog('info','failed to load scenario');
return undef;
}
($param->{'dumped_scenario'}, $param->{'scenario_path'}) = ($scenario->{'data'}, $scenario->{'file_path'});
$param->{'pname'} = $in{'pname'};
$param->{'scenario_name'} = $list->{'admin'}{$in{'pname'}}{'name'};
if ($in{'new_scenario_name'}) {
# in this case it's a submit.
my $scenario_dir = $list->{'dir'}.'/scenari/';
my $scenario_file = $scenario_dir.$in{'pname'}.'.'.$in{'new_scenario_name'} ;
if ($param->{'dumped_scenario'} eq $in{'new_scenario_content'}){
&wwslog('info','do_dump_scenario: scenario unchanged');
$param->{'result'} = 'unchanged';
return 1;
}
unless (-d $scenario_dir) {
unless (mkdir ($scenario_dir, 0777)) {
&do_log('err',"do_dump_scenario: cannot_create_dir %s : %s ", $scenario_dir, $!);
&report::reject_report_web('intern','cannot_create_dir',{'file' => $scenario_dir,$param->{'action'},'',$param->{'user'}{'email'}},$robot);
return undef;
}
}
unless (open SCENARIO , ">$scenario_file") {
&wwslog('info','do_dump_scenario: cannot_open_file %s', $scenario_file);
&report::reject_report_web('intern','cannot_open_file',{'file' => $scenario_file,$param->{'action'},'',$param->{'user'}{'email'}},$robot);
return undef;
}
print SCENARIO $in{'new_scenario_content'};
close SCENARIO;
# load the new scenario in the list config.
if ($in{'new_scenario_name'} eq $in{'scenario_name'}) {
$param->{'result'} = 'success';
}else{
$param->{'result'} = 'success_new_name';
}
}
return 1 ;
}
## Subscribers' list
sub do_dump {
&wwslog('info', "do_dump($param->{'list'})");
## Whatever the action return, it must never send a complex html page
$param->{'bypass'} = 1;
$param->{'content_type'} = "text/plain";
$param->{'file'} = undef ;
## Access control
unless (defined &check_authz('do_dump', 'review')) {
undef $param->{'bypass'};
return undef;
}
$list->dump();
$param->{'file'} = $list->{'dir'}.'/subscribers.db.dump';
if ($in{'format'} eq 'light') {
unless (open (DUMP,$param->{'file'} )) {
&report::reject_report_web('intern','cannot_open_file',{'file' => $param->{'file'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog ('info', 'unable to open file %s\n',$param->{'file'} );
return undef;
}
unless (open (LIGHTDUMP,">$param->{'file'}.light")) {
&report::reject_report_web('intern','cannot_open_file',{'file' => "$param->{'file'}.light"},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err','unable to create file %s.light\n',$param->{'file'} );
return undef;
}
while (){
next unless ($_ =~ /^email\s(.*)/);
print LIGHTDUMP "$1\n";
}
close LIGHTDUMP;
close DUMP;
$param->{'file'} = "$list->{'dir'}/subscribers.db.dump.light";
} else {
$param->{'file'} = "$list->{'dir'}/select.dump";
&wwslog('info','opening %s',$param->{'file'});
unless (open (DUMP,">$param->{'file'}")) {
&report::reject_report_web('intern','file_update_failed',{},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err','unable to create file %s\n',$param->{'file'} );
return undef;
}
if ($in{'format'} eq 'bounce') {
$in{'size'} = 'all';
do_reviewbouncing();
print DUMP "# Exported bouncing subscribers\n";
print DUMP "# Email\t\tName\tBounce score\tBounce count\tFirst bounce\tLast bounce\n";
foreach my $user (@{$param->{'members'}}){
print DUMP "$user->{'email'}\t$user->{'gecos'}\t$user->{'bounce_score'}\t$user->{'bounce_count'}\t$user->{'first_bounce'}\t$user->{'last_bounce'}\n";
}
}
else {
$in{'filter'} = $in{'format'};
do_search();
print DUMP "# Exported subscribers with search filter \"$in{'format'}\"\n";
foreach my $user (@{$param->{'members'}}){
print DUMP "$user->{'email'}\t$user->{'gecos'}\n";
}
}
close DUMP;
}
return 1;
}
## returns a mailto according to list spam protection parameter
sub mailto {
my $list = shift;
my $email = shift;
my $gecos = shift;
my $next_one;
my $mailto = '';
my @addresses;
my %recipients;
@addresses = split (',',$email);
$gecos = $email unless ($gecos);
$gecos =~ s/&/&/g;
$gecos =~ s/</g;
$gecos =~ s/>/>/g;
foreach my $address (@addresses) {
($recipients{$address}{'local'},$recipients{$address}{'domain'}) = split ('@',$address);
}
if ($list->{'admin'}{'spam_protection'} eq 'none') {
$mailto .= "$gecos";
}elsif($list->{'admin'}{'spam_protection'} eq 'javascript') {
if ($gecos =~ /\@/) {
$gecos =~ s/@/\" + \"@\" + \"/;
}
$mailto .= "";
}elsif($list->{'admin'}{'spam_protection'} eq 'at') {
foreach my $address (@addresses) {
$mailto .= " AND " if ($next_one);
$mailto .= "$recipients{$address}{'local'} AT $recipients{$address}{'domain'}";
$next_one = 1;
}
}
return $mailto;
}
## Returns a spam-protected form of email address
sub get_protected_email_address {
my ($local_part, $domain_part) = @_;
if($list->{'admin'}{'spam_protection'} eq 'javascript') {
my $return = "";
return ($return);
}elsif($list->{'admin'}{'spam_protection'} eq 'at') {
return ("$local_part AT $domain_part");
}else {
return($local_part.'@'.$domain_part);
}
}
## view logs stored in RDBMS
## this function as been writen in order to allow list owner and listmater to views logs
## of there robot or there is real problems with privacy policy and law in such services.
##
sub do_viewlogs {
&wwslog('info', 'do_viewlogs(%s)', $in{'page'});
$param->{'page'} = int($in{'page'}) || 1;
$param->{'size'} = int($in{'size'}) || $wwsconf->{'viewlogs_page_size'};
$param->{'sortby'} = $in{'sortby'} || 'email';
$param->{'total_results'} = 0;
my @date = &Log::get_log_date();
$param->{'date_from_formated'} = gettext_strftime "%Y-%m-%d-%H-%M-%S", localtime($date[0]);
$param->{'date_to_formated'} = gettext_strftime "%Y-%m-%d-%H-%M-%S", localtime($date[1]);
#display and search parameters preparation
my $select = {};
$select->{'robot'} = $robot;
$select->{'list'} = $param->{'list'};
foreach my $p ('target_type','target','date_from','date_to','type','ip') {
$param->{$p} = $in{$p};
$select->{$p} = $in{$p};
}
if($in{'target_type'} or $in{'page'} or $in{'size'}) {
#sending of search parameters for the query
my $line = &Log::get_first_db_log($select);
while(defined $line->{'date'}) {
$line->{'date'} = gettext_strftime "%d %b %Y %H:%M:%S", localtime($line->{'date'});
# can be wrapped
$line->{'parameters'} =~ s/,(?!\s)/, /g
if $line->{'parameters'};
push @{$param->{'log_entries'}}, $line;
$line = &Log::get_next_db_log();
}
#display the number of rows of the query.
$param->{'total_results'} = $#{$param->{'log_entries'}} + 1;
$param->{'total_results'} = &Log::return_rows_nb() if(&Log::return_rows_nb() != 0);
unless($param->{'total_results'}) {
&report::reject_report_web('user','no_logs',{},$param->{'action'});
&wwslog('info','do_viewlogs: no results');
return 1;
}
$param->{'total_page'} = int($param->{'total_results'} / $param->{'size'});
$param->{'total_page'}++ if($param->{'total_results'} % $param->{'size'});
if($param->{'page'} > $param->{'total_page'}) {
&report::reject_report_web('user','no_page',{'page' => $param->{'page'}},$param->{'action'});
# &List::db_log('wwsympa',$param->{'user'}{'email'},$param->{'auth_method'},$ip,'review',$param->{'list'},$robot,'','out of pages');
&wwslog('info','do_viewlogs: no page %d', $param->{'page'});
return undef;
}
my $offset = 0;
if($param->{'page'} > 1) {
$offset = (($param->{'page'} - 1) * $param->{'size'});
$param->{'prev_page'} = $param->{'page'} - 1;
}
unless(($offset + $param->{'size'}) >= $param->{'total_results'}) {
$param->{'next_page'} = $param->{'page'} + 1;
}
@{$param->{'log_entries'}} = sort { lc $a->{$param->{'sortby'}} cmp lc $b->{$param->{'sortby'}} } @{$param->{'log_entries'}};
my $last = $offset + $param->{'size'};
$last = $param->{'total_results'} - 1 if($last >= $param->{'total_results'});
@{$param->{'log_entries'}} = @{$param->{'log_entries'}}[$offset..$last];
}
return 1;
}
sub do_arc_manage {
&wwslog('info', "do_arc_manage ($in{'list'})");
## Access control
unless (defined &check_authz('do_arc', 'web_archive.access')) {
return undef;
}
my $search_base = $wwsconf->{'arc_path'}.'/'.$list->get_list_id();
opendir ARC, "$search_base";
foreach my $dir (sort {$b cmp $a} grep(!/^\./,readdir ARC)) {
if ($dir =~ /^(\d{4})-(\d{2})$/) {
push @{$param->{'yyyymm'}}, $dir;
}
}
closedir ARC;
return 1;
}
## create a zip file with archives from (list,month)
sub do_arc_download {
&wwslog('info', "do_arc_download ($in{'list'})");
## Access control
unless (defined &check_authz('do_arc', 'web_archive.access')) {
return undef;
}
##zip file name:listname_archives.zip
my $zip_file_name = $in{'list'}.'_archives.zip';
my $zip_abs_file = $Conf{'tmpdir'}.'/'.$zip_file_name;
my $zip = Archive::Zip->new();
#Search for months to put in zip
unless (defined($in{'directories'})) {
&report::reject_report_web('user','select_month',{},$param->{'action'});
&wwslog('info','do_arc_download : no archives specified');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'select_month','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 'arc_manage';
}
#for each selected month
foreach my $dir (split/\0/, $in{'directories'}) {
## Tainted vars problem
if ($dir =~ /^(\d+\-\d+)$/) {
$dir = $1;
}
my $abs_dir = $wwsconf->{'arc_path'}.'/'.$list->get_list_id().'/'.$dir.'/arctxt';
##check arc directory
unless (-d $abs_dir) {
&report::reject_report_web('intern','arc_not_found',{'arc_file' => $dir,
'listname' => $in{'list'},
'path' => $abs_dir},
$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','archive %s not found',$dir);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
next;
}
$zip->addDirectory($abs_dir, $in{'list'}.'_'.$dir);
unless (opendir SPOOL, $abs_dir) {
&report::reject_report_web('intern','cannot_open_dir',{'dir' =>$abs_dir },$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_arc_download: unable to open %s', $abs_dir);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
foreach my $msg (sort grep(!/^\./, readdir SPOOL)) {
unless ($zip->addFile ($abs_dir.'/'.$msg, $in{'list'}.'_'.$dir.'/'.$msg)) {
&report::reject_report_web('intern','add_file_zip',{'file' => "$abs_dir/$msg"},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','do_arc_download: failed to add %s file to archive', $abs_dir.'/'.$msg);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
}
closedir SPOOL;
## create and fill a new folder in zip
#$zip->addTree ($abs_dir, $in{'list'}.'_'.$dir);
}
## check if zip isn't empty
if ($zip->numberOfMembers()== 0) {
&report::reject_report_web('intern','inaccessible_archive',{'listname' => $in{'list'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('info','Error : empty directories');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
##writing zip file
unless ($zip->writeToFileNamed($zip_abs_file) == AZ_OK){
&report::reject_report_web('intern','write_file_zip',{'zipfile'=>$zip_abs_file},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog ('info', 'Error while writing Zip File %s\n',$zip_file_name);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
##Sending Zip to browser
$param->{'bypass'} ='extreme';
printf("Content-Type: application/zip;\nContent-disposition: attachment; filename=\"%s\";\n\n",$zip_file_name);
##MIME Header
unless (open (ZIP,$zip_abs_file)) {
&report::reject_report_web('intern','cannot_open_file',{'file' => $zip_abs_file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog ('info', 'Error while reading Zip File %s\n',$zip_abs_file);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
print ;
close ZIP ;
## remove zip file from server disk
unless (unlink ($zip_abs_file)){
&report::reject_report_web('intern','erase_file',{'file' => $zip_abs_file},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog ('info', 'Error while unlinking File %s\n',$zip_abs_file);
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
}
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'list'}",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
sub do_arc_delete {
my @abs_dirs;
&wwslog('info', "do_arc_delete ($in{'list'})");
## Access control
unless (defined &check_authz('do_arc', 'web_archive.access')) {
return undef;
}
unless (defined $in{'directories'}){
&report::reject_report_web('user','select_month',{},$param->{'action'});
&wwslog('info','No Archives months selected');
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'list'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'select_month','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 'arc_manage';
}
## if user want to download archives before delete
&wwslog('notice', "ZIP: $in{'zip'}");
if ($in{'zip'} == 1) {
&do_arc_download();
}
foreach my $dir (split/\0/, $in{'directories'}) {
push(@abs_dirs ,$wwsconf->{'arc_path'}.'/'.$list->get_list_id().'/'.$dir);
}
unless (tools::remove_dir(@abs_dirs)) {
&wwslog('info','Error while Calling tools::remove_dir');
}
&report::notice_report_web('performed',{},$param->{'action'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'list'}",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 'arc_manage';
}
sub do_css {
&wwslog('debug', 'do_css(%s)', $in{'file'});
$param->{'bypass'} = 'extreme';
print "Content-type: text/css; charset=utf-8\n\n";
$param->{'css'} = $in{'file'};
## Do not include locale subdirectories.
## The css.tt2 by each locales will override styles in main CSS.
my $tt2_include_path = tools::make_tt2_include_path($robot, 'web_tt2', '', '');
unless (&tt2::parse_tt2($param, 'css.tt2' ,\*STDOUT, $tt2_include_path)) {
my $error = &tt2::get_error();
$param->{'tt2_error'} = $error;
&List::send_notify_to_listmaster('web_tt2_error', $robot, [$error]);
&wwslog('info', "do_css/$in{'file'} : error");
}
return;
}
sub do_rss_request {
&wwslog('info', "do_rss_request");
my $result = $list->check_list_authz('visibility',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $sub_is;
my $reason;
if (ref($result) eq 'HASH') {
$sub_is = $result->{'action'};
$reason = $result->{'reason'};
}
if ($sub_is =~ /reject/) {
&wwslog('info', 'RSS not accessible because list %s is not visible to user %s',$list->get_list_id,$param->{'user'}{'email'});
&web_db_log({'parameters' => $param->{'user'}{'email'},
'status' => 'error',
'error_type' => 'authorization'});
return undef;
}
my $args ;
$in{'count'} ||= 20;
$in{'for'} ||= 10;
$args = 'count='.$in{'count'}.'&' if ($in{'count'}) ;
$args .= 'for='.$in{'for'} if ($in{'for'});
if ($list ) {
$param->{'latest_arc_url'} = &Conf::get_robot_conf($robot, 'wwsympa_url')."/rss/latest_arc/".$list->{'name'}."?".$args;
$param->{'latest_d_read_url'} = &Conf::get_robot_conf($robot, 'wwsympa_url')."/rss/latest_d_read/".$list->{'name'}."?".$args;
}
$param->{'active_lists_url'} = &Conf::get_robot_conf($robot, 'wwsympa_url')."/rss/active_lists?".$args;
$param->{'latest_lists_url'} = &Conf::get_robot_conf($robot, 'wwsympa_url')."/rss/latest_lists?".$args;
$param->{'output'} = 1;
return 1;
}
sub do_wsdl {
&wwslog('info', "do_wsdl ()");
my $sympawsdl = &tools::get_filename('etc',{}, 'sympa.wsdl', $robot);
unless (-r $sympawsdl){
&report::reject_report_web('intern','err_404',{},$param->{'action'});
&wwslog('err','could not find $sympawsdl');
return undef;
}
my $soap_url= &Conf::get_robot_conf($robot,'soap_url');
unless (defined $soap_url) {
&report::reject_report_web('user','no_soap_service',{},$param->{'action'});
&wwslog('err','No SOAP service was defined in sympa.conf (soap_url parameter)');
return undef;
}
$param->{'bypass'} = 'extreme';
print "Content-type: text/xml\n\n";
$param->{'conf'}{'soap_url'} = $soap_url;
## Get the directory path, without the file name
my $wsdl_path = $sympawsdl;
$wsdl_path =~ s/\/sympa.wsdl//;
&tt2::parse_tt2($param, 'sympa.wsdl' , \*STDOUT, [$wsdl_path]);
# unless (open (WSDL,$sympawsdl)) {
# &error_message('404');
# &wwslog('info','could not open $sympawsdl');
# return undef;
# }
# print ;
# close WSDL;
return 1;
}
## Synchronize list members with data sources
sub do_sync_include {
&wwslog('info', "do_sync_include($in{'list'})");
unless ($list->sync_include()) {
&report::reject_report_web('intern','sync_include_failed',{},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
return undef;
}
&report::notice_report_web('subscribers_updated',{},$param->{'action'});
return 'review';
}
## Review lists from a family
sub do_review_family {
&wwslog('info', 'do_review_family');
my $family = Family->new($in{'family_name'}, $robot);
unless (defined $family) {
&report::reject_report_web('user','unknown_family',{'family'=>$in{'family_name'}},$param->{'action'},'',$param->{'user'}{'email'},$robot);
&wwslog('err', 'do_review_family: incorrect family %s', $in{'family_name'});
return undef;
}
my $all_lists = $family->get_family_lists();
foreach my $flist (@{$all_lists}) {
unless (defined $flist) {
wwslog('err', 'incorrect list');
next;
}
push @{$param->{'family_lists'}},
{ 'name' => $flist->{'name'},
'status' => $flist->{'admin'}{'status'},
'instantiation_date' =>
(gettext_strftime "%d %b %Y at %H:%M:%S", localtime $flist->{'admin'}{'latest_instantiation'}{'date_epoch'}),
'subject' => $flist->{'admin'}{'subject'},
};
}
return 1;
}
################################################################
## do_ca : executes a custom action
##
## IN:
## - 'custom_action': ther name of the custom action (and subsequent tt2 file to use, see below)
## - '@cap': an array of parameters.
##
## Custom actions are used to display user defined templates.
## To use it, follow these steps:
## 1- create a new file "your_action.tt2" and put it in the relevant dir (either etc or expl)
## 2- in this file, add the HTML fragment you want to insert to the web interface. You don't need the section or the tag.
## 3- you can type your action URL: http://your-sympa-server-root-url/ca/your_action/param2/param2/param3/...
##
## the HTML code in 'your_action.tt2' can make use of the parameters this way: [% cap.1 %] for param1, [% cap.2 %] for param, and so on.
###############################################################
sub do_ca {
&wwslog('info', 'custom action: %s (robot %s) with params: (%s, %s, %s, %s, %s)',$in{'custom_action'},$robot,$in{'cap'});
$param->{'custom_action'} = $in{'custom_action'};
$param->{'cap'} = [split '/',$in{'cap'}];
return 1;
}
################################################################
## do_ca : executes a custom action in list context
##
## IN:
## - 'custom_action': ther name of the custom action (and subsequent tt2 file to use, see below)
## - 'list': the nalme of the list (without the '@robot' part) in the context of which the action is executed.
## - '@lcap': an array of parameters.
##
## Custom actions are used to display user defined templates.
## To use it, follow these steps:
## 1- create a new file "your_action.tt2" and put it in the relevant dir (either etc or expl)
## 2- in this file, add the HTML fragment you want to insert to the web interface. You don't need the section or the tag.
## 3- you can type your action URL: http://your-sympa-server-root-url/ca/your_action/param2/param2/param3/...
##
## the HTML code in 'your_action.tt2' can make use of the parameters this way: [% lcap.1 %] for param1, [% lcap.2 %] for param, and so on.
###############################################################
sub do_lca {
&wwslog('info', 'List custom action: %s for list %s (robot %s) with params: (%s, %s, %s, %s, %s)',$in{'custom_action'},$in{'list'},$robot,$in{'lcap'});
$param->{'custom_action'} = $in{'custom_action'};
$param->{'cap'} = [split '/',$in{'cap'}];
return 1;
}
## Prepare subscriber data to be prompted on the web interface
## Used by review, search,...
sub _prepare_subscriber {
my $user = shift;
my $additional_fields = shift;
my $sources = shift;
## Add user
$user->{'date'} = gettext_strftime "%d %b %Y", localtime($user->{'date'});
$user->{'update_date'} = gettext_strftime "%d %b %Y", localtime($user->{'update_date'});
## Reception mode and topics
$user->{'reception'} ||= 'mail';
if (($user->{'reception'} eq 'mail') && $user->{'topics'}) {
$user->{'reception'} = sprintf gettext("topic (%s)"), $user->{'topics'};
}
$user->{'email'} =~ /\@(.+)$/;
$user->{'domain'} = $1;
$user->{'pictures_url'} = &tools::make_pictures_url('email' => $user->{'email'}, 'list' => $list);
## Escape some weird chars
$user->{'escaped_email'} = &tools::escape_chars($user->{'email'});
## Check data sources
$user->{'sources'} = $list->get_datasource_name($user->{'id'}) if ($user->{'id'});
if (@{$additional_fields}) {
my @fields;
foreach my $f (@{$additional_fields}) {
push @fields, $user->{$f};
}
$user->{'additional'} = join ',', @fields;
}
return 1;
}
## New d_read function using SharedDocument module
## The following features should be tested :
## * inheritance on privileges
## X moderation
## * escaping special chars
sub new_d_read {
&wwslog('info', 'new_d_read(%s)', $in{'path'});
### action relative to a list ?
unless ($param->{'list'}) {
&report::reject_report_web('user','missing_arg',{'argument' => 'list'},$param->{'action'});
&wwslog('err','do_d_read: no list');
return undef;
}
# current list / current shared directory
my $list_name = $list->{'name'};
my $document = new SharedDocument ($list, $in{'path'}, $param);
unless (defined $document) {
&report::reject_report_web('intern','new_document_failed',{'path'=>$in{'path'}},$param->{'action'},$list,$param->{'user'}{'email'},$robot);
&wwslog('err',"d_read : cannot open $document->{'absolute_path'} : $!");
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'internal','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
my $path = $document->{'path'};
my $visible_path = $document->{'visible_path'};
my $shareddir = $document->{'shared_dir'};
my $doc = $document->{'absolute_path'};
my $ref_access = $document->{'access'}; my %access = %{$ref_access};
$param->{'doc_owner'} = $document->{'owner'};
$param->{'doc_title'} = $document->{'title'};
$param->{'doc_date'} = $document->{'date'};
### Access control
unless ($access{'may'}{'read'}) {
&report::reject_report_web('auth',$access{'reason'}{'read'},{},$param->{'action'},$list);
&wwslog('err','d_read : access denied for %s', $param->{'user'}{'email'});
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'error','error_type' => 'authorization','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return undef;
}
my $may_edit = $access{'may'}{'edit'};
my $may_control = $access{'may'}{'control'};
$param->{'may_edit'} = $may_edit;
$param->{'may_control'} = $may_control;
### File or directory ?
if ($document->{'type'} eq 'url') {
$param->{'file_extension'} = $document->{'file_extension'};
$param->{'redirect_to'} = $document->{'url'};
return 1;
}elsif ($document->{'type'} eq 'file') {
$param->{'file'} = $document->{'absolute_path'};
$param->{'bypass'} = 1;
return 1;
}else { # directory
$param->{'empty'} = $#{$document->{'subdir'}} == -1;
# subdirectories hash
my %subdirs;
# file hash
my %files;
## for the exception of index.html
# name of the file "index.html" if exists in the directory read
my $indexhtml;
# boolean : one of the subdirectories or files inside
# can be edited -> normal mode of read -> d_read.tt2;
my $normal_mode;
my $path_doc;
my %desc_hash;
my $may, my $def_desc;
foreach my $subdocument (@{$document->{'subdir'}}) {
my $d = $subdocument->{'filename'};
my $path_doc = $subdocument->{'path'};
## Subdir
if ($subdocument->{'type'} eq 'directory') {
if ($subdocument->{'access'}{'may'}{'read'}) {
$subdirs{$d} = $subdocument->dup();
$subdirs{$d}{'doc'} = $subdocument->{'visible_filename'};
$subdirs{$d}{'escaped_doc'} = $subdocument->{'escaped_filename'};
if ($param->{'user'}{'email'}) {
if ($subdocument->{'access'}{'may'}{'control'} == 1) {
$subdirs{$d}{'edit'} = 1; # or = $may_action_edit ?
# if index.html, must know if something can be edit in the dir
$normal_mode = 1;
}elsif ($subdocument->{'access'}{'may'}{'edit'} != 0) {
# $may_action_edit = 0.5 or 1
$subdirs{$d}{'edit'} = $subdocument->{'access'}{'may'}{'edit'};
# if index.html, must know if something can be edit in the dir
$normal_mode = 1;
}
if ($subdocument->{'access'}{'may'}{'control'}) {
$subdirs{$d}{'control'} = 1;
}
}
}
}else {
# case file
if ($subdocument->{'access'}{'may'}{'read'}) {
$files{$d} = $subdocument->dup();
$files{$d}{'doc'} = $subdocument->{'visible_filename'};
$files{$d}{'escaped_doc'} = $subdocument->{'escaped_filename'};
## exception of index.html
if ($d =~ /^(index\.html?)$/i) {
$indexhtml = $1;
}
if ($param->{'user'}{'email'}) {
if ($subdocument->{'access'}{'may'}{'edit'} == 1) {
$normal_mode = 1;
$files{$d}{'edit'} = 1; # or = $may_action_edit ?
} elsif ($subdocument->{'access'}{'may'}{'edit'} != 0){
# $may_action_edit = 1 or 0.5
$normal_mode = 1;
$files{$d}{'edit'} = $subdocument->{'access'}{'may'}{'edit'};
}
if ($subdocument->{'access'}{'may'}{'control'}) {
$files{$d}{'control'} = 1;
}
}
}
}
}
### Exception : index.html
if ($indexhtml) {
unless ($normal_mode) {
$param->{'file_extension'} = 'html';
$param->{'bypass'} = 1;
$param->{'file'} = $document->{'absolute_path'};
return 1;
}
}
## to sort subdirs
my @sort_subdirs;
my $order = $in{'order'} || 'order_by_doc';
$param->{'order_by'} = $order;
foreach my $k (sort {by_order($order,\%subdirs)} keys %subdirs) {
push @sort_subdirs, $subdirs{$k};
}
## to sort files
my @sort_files;
foreach my $k (sort {by_order($order,\%files)} keys %files) {
push @sort_files, $files{$k};
}
# parameters for the template file
$param->{'list'} = $list_name;
$param->{'father'} = $document->{'father_path'};
$param->{'escaped_father'} = $document->{'escaped_father_path'} ;
$param->{'description'} = $document->{'title'};
$param->{'serial_desc'} = $document->{'serial_desc'};
$param->{'path'} = $document->{'path'};
$param->{'visible_path'} = $document->{'visible_path'};
$param->{'escaped_path'} = $document->{'escaped_path'};
if (scalar keys %subdirs) {
$param->{'sort_subdirs'} = \@sort_subdirs;
}
if (scalar keys %files) {
$param->{'sort_files'} = \@sort_files;
}
}
$param->{'father_icon'} = $icon_table{'father'};
$param->{'sort_icon'} = $icon_table{'sort'};
## Show expert commands / user page
# for the curent directory
if ($may_edit == 0 && $may_control == 0) {
$param->{'has_dir_rights'} = 0;
} else {
$param->{'has_dir_rights'} = 1;
if ($may_edit == 1) { # (is_author || ! moderated)
$param->{'total_edit'} = 1;
}
}
# set the page mode
if ($in{'show_expert_page'} && $param->{'has_dir_rights'}) {
$session->{'shared_mode'}='expert';
if ($param->{'user'}{'prefs'}{'shared_mode'} ne 'expert') {
# update user pref as soon as connected user change shared mode
$param->{'user'}{'prefs'}{'shared_mode'} = 'expert';
&List::update_user_db($param->{'user'}{'email'},{data=>&tools::hash_2_string($param->{'user'}{'prefs'})}) ;
}
$param->{'expert_page'} = 1;
} elsif ($in{'show_user_page'}) {
$session->{'shared_mode'}='basic';
if ($param->{'user'}{'prefs'}{'shared_mode'} ne 'basic') {
# update user pref as soon as connected user change shared mode
$param->{'user'}{'prefs'}{'shared_mode'} = 'basic';
&List::update_user_db($param->{'user'}{'email'},{data=>&tools::hash_2_string($param->{'user'}{'prefs'})}) ;
}
$param->{'expert_page'} = 0;
} else {
if ($session->{'shared_mode'} eq 'expert' && $param->{'has_dir_rights'}) {
$param->{'expert_page'} = 1;
} else {
$param->{'expert_page'} = 0;
}
}
&web_db_log({'robot' => $robot,'list' => $list->{'name'},'action' => $param->{'action'},'parameters' => "$in{'path'}",'target_email' => "",'msg_id' => '','status' => 'success','error_type' => '','user_email' => $param->{'user'}{'email'},'client' => $ip,'daemon' => $daemon_name});
return 1;
}
## Check authorizations to the current action
## used in common cases where actions fails unless result is 'do_it'
## It does not apply to actions that can be moderated
sub check_authz {
my ($subname, $action) = @_;
my $result = $list->check_list_authz($action,$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'} || 'nobody',
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $r_action;
my $reason;
if (ref($result) eq 'HASH') {
$r_action = $result->{'action'};
$reason = $result->{'reason'};
}
unless ($r_action =~ /do_it/i) {
unless(prevent_visibility_bypass()) {
&report::reject_report_web('auth',$reason,{'login'=> $param->{'need_login'}},$param->{'action'});
}
&wwslog('info','check_authz: access denied in %s for %s', $subname, $param->{'user'}{'email'});
return undef;
}
return 1;
}
sub get_server_details {
## All Robots are shown to super listmaster
if (&List::is_listmaster($param->{'user'}{'email'})) {
$param->{'main_robot'} = 1;
$param->{'robots'} = $Conf{'robots'};
}
## Families
my @families = &Family::get_available_families($robot);
if (@families) {
$param->{'families'} = \@families;
}
}
sub get_icon {
my $type = shift;
return $icon_table{$type};
}
sub get_mime_type {
my $type = shift;
return $mime_types->{$type};
}
sub do_maintenance {
&wwslog('notice', 'do_maintenance()');
return 1;
}
sub do_automatic_lists_management_request {
&wwslog('notice', 'Starting');
$param->{'automatic_lists_description'} = &Conf::load_automatic_lists_description();
return 1;
}
sub do_automatic_lists_management {
&wwslog('notice', 'Starting');
return 1;
}
sub do_automatic_lists_request {
&wwslog('notice', 'Starting');
# check authorization
my $family;
unless ($family = new Family($in{'family'},$robot)) {
&wwslog('err', "Failed to instantiate family %s. This family does not exist.",$in{'family'});
&List::send_notify_to_listmaster('automatic_list_creation_failed',$robot,["Failed to instantiate family $in{'family'}. This family does not exist."]);
return undef;
}
unless ($family->is_allowed_to_create_automatic_lists(('auth_level' => 'md5', 'sender' => $session->{'email'}, 'message' => undef, 'listname' => ''))){
&report::reject_report_web('auth',"You are not allowed to create list in this family",{},$param->{'action'});
&wwslog('err', 'Access to automatic list creation form denied to user %s.', $session->{'email'});
return undef;
}
$param->{'family'} = $family;
return 1;
}
sub do_automatic_lists {
&wwslog('notice', 'Starting');
my $family_name = $in{'family'};
my $family;
my @list_name_parts;
my $families_config = &Conf::get_robot_conf($robot,'automatic_list_families');
my $family_config = $families_config->{$family_name};
my $listname = $family_config->{'prefix'}.$family_config->{'prefix_separator'};
foreach my $input (keys %in) {
next unless ($input =~ /automatic_list_part_(\d+)/);
$list_name_parts[$1] = $in{$input};
}
foreach my $list_name_part (@list_name_parts) {
$listname .= "$list_name_part$family_config->{'classes_separator'}";
}
my $sep = $family_config->{'classes_separator'}.'$';
if ($listname =~ /(.*)($sep)/) {
$listname = $1;
}
$list = new List ($listname, $robot);
unless (defined $list) {
## Automatic creation of a mailing list, based on a family
unless ($family = new Family($family_name,$robot)) {
&Log::do_log('err', "Failed to create the dynamic list $listname: family $family_name does not exist.");
&List::send_notify_to_listmaster('automatic_list_creation_failed',$robot,["Failed to create the dynamic list $listname: family $family_name does not exist."]);
return undef;
}
unless ($list = $family->create_automatic_list(('listname' => $listname,'auth_level' => 'md5','sender'=>$session->{'email'}))) {
&Log::do_log('err', "Failed to create the dynamic list $listname.");
&List::send_notify_to_listmaster('automatic_list_creation_failed',$robot,["Failed to create the dynamic list $listname."]);
return undef;
}
}
$in{'list'} = $listname;
return 'compose_mail';
}
sub prevent_visibility_bypass {
&wwslog('debug2', 'Starting');
if (defined $list && ref $list eq 'List') {
my $result = $list->check_list_authz('visibility',$param->{'auth_method'},
{'sender' => $param->{'user'}{'email'},
'remote_host' => $param->{'remote_host'},
'remote_addr' => $param->{'remote_addr'}});
my $sub_is;
my $reason;
if (ref($result) eq 'HASH') {
$sub_is = $result->{'action'};
$reason = $result->{'reason'};
}
if ($sub_is =~ /reject/) {
&wwslog('info', 'visibility: List must remain hidden. Returning "home" to prevent visibility bypass');
return "home";
}else {
return undef;
}
}
return undef;
}
sub purely_closed {
my $action = shift;
my $scenario = new Scenario ('robot' => $robot,
'directory' => $list->{'dir'},
'file_path' => $list->{'admin'}{$action}{'file_path'},
'options' => undef);
return $scenario->is_purely_closed;
}
=pod
=head1 AUTHORS
=over
=item * Serge Aumont
=item * Olivier Salaun
=back
=cut