# vim: set cindent expandtab ts=4 sw=4: # # Copyright (c) 1998-2005 Chi-Keung Ho. All rights reserved. # # This programe 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. # # Extmail - a high-performance webmail to maildir # $Id$ package Ext::App::Pref; use strict; use Exporter; use vars qw(@ISA @EXPORT); @ISA = qw(Exporter Ext::App); use Ext::App; use Ext::Config; use Ext::Utils; use Fcntl qw(:flock); use Ext::POP3; use MIME::Base64; use vars qw(%lang_pref $lang_charset); # load locale use Ext::Lang; # locale handler sub init { my $self = shift; $self->SUPER::init(@_); return unless($self->valid||$self->permit); $self->add_methods(pref_list => \&pref_list); $self->add_methods(pref_save => \&pref_save); $self->add_methods(pop3_list => \&pop3_list); $self->add_methods(pop3_save => \&pop3_save); $self->{default_mode} = 'pref_list'; Ext::Storage::Maildir::init($self->get_working_path); $self->_initme; $self; } sub _initme { initlang($_[0]->userconfig->{lang}, __PACKAGE__); $_[0]->{tpl}->assign( lang_charset => $lang_charset ); $_[0]->{tpl}->assign( \%lang_pref ); } sub pref_list { my $self = shift; my $tpl = $self->{tpl}; my $q = $self->{query}; # 2005-09-07, bug fix # here we should use SUPER->userconfig to get user.cf, # if user.cf not exist, SUPER->userconfig will initialize # varibles, old code does not handle this procedure, local # $CFG to be compatible with other code, reduce typing :-) # # my $config = Ext::Config->new(file => $ENV{MAILDIR}.'/user.cf'); my $CFG = $self->userconfig; # localize init the lang, because App level will asign the lang # undefined unless end user save preference the first time, so # we must initialize it, ouch XXX FIXME $CFG->{lang} = $CFG->{lang} ? $CFG->{lang} : guess_intl(); $tpl->assign( #SID => $q->cgi('sid'), NICK_NAME => $CFG->{nick_name}, FULL_HEADER => $CFG->{full_header}, CCSENT => $CFG->{ccsent}, TRYLOCAL => $CFG->{trylocal}, SHOW_HTML => $CFG->{show_html}, COMPOSE_HTML => $CFG->{compose_html}, CONV_LINK => $CFG->{conv_link}, ADDR2ABOOK => $CFG->{addr2abook}, POP_ON => $CFG->{pop_on}, # XXX pop3 ); my $pref_page_size = pref_page_size(); my $pref_sort = pref_sort(); my $pref_lang = $self->pref_lang(); # method my $pref_theme = $self->pref_theme(); # method my $pref_poptimeout = $self->pref_poptimeout(); # method my $pref_popfiles = $self->pref_popfiles(); # show prefer screen type foreach(@{$self->list_screen}) { my $selected = 0; $selected = 1 if ($CFG->{screen} eq $_); $tpl->assign( 'LOOP_SCREEN', SCREEN_TYPE => $_, SCREEN_NAME => $self->get_screen($_)->[2], SCREEN_CHK => $selected, ); } # show prefer page size foreach(@$pref_page_size ) { my $selected = 0; $selected = 1 if($CFG->{page_size} eq $_); $tpl->assign( 'LOOP_PAGE_SIZE', PAGE_SIZE => $_, PAGE_SIZE_CHK => $selected, ); } # show timezone my $pref_timezone = pref_timezone(); foreach my $t (@$pref_timezone) { my $selected = 0; $selected = 1 if($CFG->{timezone} eq $t->{'value'}); $tpl->assign( 'LOOP_TIMEZONE', TIMEZONE_VALUE => $t->{value}, TIMEZONE_NAME => $t->{key}, TIMEZONE_CHK => $selected, ); } # show sort order by default foreach (keys %$pref_sort) { my $selected = 0; $selected = 1 if($CFG->{sort} eq $pref_sort->{$_}); $tpl->assign( 'LOOP_SORT', SORT_NAME => $lang_pref{$_}, SORT_VAL => $pref_sort->{$_}, SORT_CHK => $selected, ); } # show prefer language, for template language foreach (@$pref_lang) { my $selected = 0; $selected = 1 if ($CFG->{lang} eq $_->{lang}); $tpl->assign( 'LOOP_LANG', LANG => $_->{lang}, LANG_DESC => $_->{desc}, LANG_CHK => $selected, ); } # show prefer template foreach (sort keys %$pref_theme) { my $selected = 0; my $desc = $pref_theme->{$_}->{$CFG->{lang}} || $_; $selected = 1 if ($CFG->{template} eq $_); $tpl->assign( 'LOOP_THEME', THEME => $_, THEME_DESC => $desc, THEME_CHK => $selected, ); } # show pop3 relate setting foreach (@$pref_poptimeout) { my $selected = 0; $selected = 1 if ($CFG->{pop_timeout} eq $_); $tpl->assign( 'LOOP_POPTIMEOUT', TIMEOUT => $_, TIMEOUT_CHECK => $selected, ); } foreach (@$pref_popfiles) { my $selected = 0; $selected = 1 if ($CFG->{pop_files} eq $_); $tpl->assign( 'LOOP_POPFILES', POPFILES => $_, POPFILES_CHECK => $selected, ); } $tpl->assign( SIGNATURE => read_signature() ); } # pref_save must be called before any tpl was assign and output # or some of tpl will have different value. sub pref_save { my $self = shift; my $tpl = $self->{tpl}; my $q = $self->{query}; my $user_config = './user.cf'; my $config = Ext::Config->new(file => $user_config); my $CFG = $config->dump || {}; my $oldconfig = $self->userconfig; $CFG->{nick_name} = $q->cgi('nick_name'); $CFG->{full_header} = $q->cgi('full_header')? 1:0; $CFG->{ccsent} = $q->cgi('ccsent')? 1:0; $CFG->{trylocal} = $q->cgi('trylocal') eq 1? 1:0; $CFG->{show_html} = $q->cgi('show_html') ? 1:0; $CFG->{compose_html} = $q->cgi('compose_html') ?1:0; $CFG->{conv_link} = $q->cgi('conv_link') ? 1:0; $CFG->{addr2abook} = $q->cgi('addr2abook') ? 1:0; $CFG->{pop_on} = $q->cgi('pop_on') ? 1:0; $CFG->{pop_timeout} = $q->cgi('pop_timeout'); $CFG->{pop_files} = $q->cgi('pop_files'); $CFG->{page_size} = $q->cgi('page_size'); $CFG->{screen} = $q->cgi('screen_type'); $CFG->{timezone} = $q->cgi('timezone'); $CFG->{sort} = $q->cgi('sort') || 'by_time'; $CFG->{lang} = $q->cgi('lang'); $CFG->{template} = $q->cgi('template'); $CFG->{charset} = 'UTF-8'; # XXX :-) now everything is UTF8 $config->{cfg} = $CFG; # XXX without it, newly create # user.cf would be save correctly! $config->save(file => $user_config); save_signature($q->cgi('signature')); undef $config; # new update style mechanism - 2005-09-26, assign a refresh # flag to notice template to force update the whole world if($oldconfig->{template} ne $q->cgi('template') or $oldconfig->{lang} ne $q->cgi('lang')) { $tpl->assign(REFRESH => 1); } # after save all, force userconfig() to update cache to # currently locale, XXX $self->userconfig(1); # re-initialize the world $self->global_tpl; $self->_initme; # call password after re-initialize $self->pref_change_passwd; $self->pref_list; # if you want to redirect, uncomment the following code #$self->{tpl}->{noprint} = 1; #$self->redirect("?__mode=pref_list&sid=".$q->cgi('sid')); } sub pref_change_passwd { my $self = shift; my $q = $self->{query}; my $tpl = $self->{tpl}; my $sys = $self->{sysconfig}; return unless(my $oldpwd = $q->cgi('oldpw')); my $pass_fail = $lang_pref{'change_passwd_fail'}; my $pass_ok = $lang_pref{'change_passwd_ok'}; my $pass_short = $lang_pref{'change_passwd_short'}; if($q->cgi('newpw1') eq $q->cgi('newpw2')) { my $newpwd = $q->cgi('newpw1'); return unless($self->pre_auth); # prepare auth # check new password length if(length($newpwd) < $sys->{SYS_MIN_PASS_LEN}) { $pass_short = sprintf($pass_short, $sys->{SYS_MIN_PASS_LEN}); $tpl->assign( CHGPWD_FAIL => $pass_short); return; } if ($ENV{OPTIONS} =~ /disablepwdchange/) { $tpl->assign( CHGPWD_FAIL => $lang_pref{'change_passwd_disabled'} ); return; } my $auth = $self->{auth_handler}; if($auth->change_passwd($ENV{USERNAME}, $oldpwd, $newpwd)) { $tpl->assign( CHGPWD_OK => $pass_ok ); }else { $tpl->assign( CHGPWD_FAIL => $pass_fail ); } }else { $tpl->assign( CHGPWD_FAIL => $pass_fail ); } } sub pref_page_size { my @pref_page_size = (10,20,50,100); \@pref_page_size; } sub pref_sort { my %sort_order = ( 'by_time' => 'Ts', 'by_date' => 'Dt', 'by_size' => 'Sz', 'by_from'=> 'Fr', 'by_subject' => 'Sj', 'by_status' => 'Fs'); \%sort_order; } sub pref_lang { my $self = shift; my $ref = langlist(); # func in Ext::Lang $ref; } sub pref_theme { my $self = shift; my $html_dir = $self->{sysconfig}->{SYS_TEMPLDIR}; my %theme; opendir(DIR, $html_dir) or die "Can't opendir $html_dir, $!\n"; foreach my $d ( grep { !/^\./ } readdir DIR) { if(-d "$html_dir/$d" && -r "$html_dir/$d/README") { open(FD, "< $html_dir/$d/README"); # ignore error while() { next if(/^#/); # ignore comments chomp; my($i18n, $desc) = (/([^:]+):(.*)/); $theme{$d}->{$i18n} = ($desc?$desc:$i18n); } close FD; } } close DIR; \%theme; } sub save_signature { my $s = shift; open (FD, "> signature.cf") or die "Can't write to signature.cf, $!\n"; print FD $s; close FD; } sub read_signature { return "" if (!-r 'signature.cf'); open (FD, "< signature.cf"); # ignore error local $/ = undef; my $buf = ; close FD; return $buf; } sub pop3_list { my $self = shift; my $tpl = $self->{tpl}; my $q = $self->{query}; my $arr = parse_pop3config(); if (scalar @$arr) { my $num = 1; for my $hash (@$arr) { $tpl->assign( 'POP3LIST_LOOP', NUM => $num, POP3_UID => $hash->{uid}, POP3_BACKUP => ($hash->{backup} eq 'on' ? 1 : 0), POP3_ACTIVE => ($hash->{active} eq 'on' ? 1 : 0), POP3_SERVER => "$hash->{host}:$hash->{port}", ); $num ++; } } else { $tpl->assign( NOPOP3DEF => 'No pop3 accounts defined!'); } if (my $uid = $q->cgi('edit')) { my $edit = ''; for my $hash (@$arr) { next if (lc $hash->{uid} ne lc $uid); $edit = $hash; last; } if ($edit) { $tpl->assign( EDIT_ACCOUNT => 1, EDIT_UID => $edit->{uid}, EDIT_HOST => $edit->{host}, EDIT_PORT => $edit->{port}, EDIT_BACKUP => $edit->{backup} eq 'on' ? 1 : 0, EDIT_ACTIVE => $edit->{active} eq 'on' ? 1 : 0, ); } else { $tpl->assign( ERROR => "No such $uid" ); } } $tpl->{template} = 'pref_pop3.html'; 1; } sub pop3_save { my $self = shift; my $tpl = $self->{tpl}; my $q = $self->{query}; my $arr = parse_pop3config(); my $err = ''; my $uid = $q->cgi('username'); my $pass = $q->cgi('passwd'); my $host = $q->cgi('host'); my $port = $q->cgi('port'); my $backup = $q->cgi('backup'); my $active = $q->cgi('active'); # fallback setting; if (!$host or !$host =~ /[^\.]/) { $uid =~ /@(.*)$/; if (my $domain = $1) { $host = "pop3.$domain"; } } if (!$port =~ /^\d+$/) { $port = '110'; } if ($q->cgi('doedit')) { if (scalar @$arr) { my $write = 0; if (!($uid =~ /^[^@]+@\S+$/) || !$pass) { $err = 'Username or password incomplete'; } # check for modification to the specific entry for (my $i=0; $i < scalar @$arr; $i++) { my $h = $arr->[$i]; if ( lc $h->{uid} eq lc $uid ) { if (length $pass) { $pass = encode_base64($pass); $pass =~ s/[\r\n\s]+//g; $h->{passwd} = $pass; } else { $h->{passwd} = encode_base64($h->{passwd}); $h->{passwd} =~ s/[\r\n\s]+//g; } $h->{uid} = $uid; $h->{host} = $host; $h->{port} = $port || '110'; $h->{backup} = $backup ? 'on' : 'off'; $h->{active} = $active ? 'on' : 'off'; $arr->[$i] = $h; $write = 1; } else { $h->{passwd} = encode_base64($h->{passwd}); $h->{passwd} =~ s/[\r\n\s]+//g; $arr->[$i] = $h; } } if ($write) { open (FD, "> pop3config.cf") or die "Can't write to pop3config.cf, $!\n"; flock (FD, LOCK_EX); for my $h (@$arr) { my $option =''; $option .= 'backup='.($h->{backup} eq 'on' ? 'on' : 'off').','; $option .= 'active='.($h->{active} eq 'on' ? 'on' : 'off').','; $option .= 'color=cccccc'; # XXX FIXME print FD "$h->{uid} $h->{passwd} $h->{host} $h->{port} $option\n"; } flock (FD, LOCK_UN); close FD; } else { $err = 'Request not permited'; } } else { $err = 'Empty pop3config, should add new entry first'; } } elsif ($q->cgi('doadd')) { if (my $total = scalar @$arr) { if ($total >= 6) { $err = 'Can only add max 6 entries, abort'; }elsif (!($uid=~/^[^@]+@\S+$/) || !$pass) { $err = 'Filled information incomplete'; } for my $hash (@$arr) { if (lc $hash->{uid} eq lc $uid) { $err = "$uid exists!"; last; } } if (!$err) { open (FD, ">> ./pop3config.cf") or die "$!\n"; flock (FD, LOCK_EX); $pass = encode_base64($pass); $pass =~ s/[\r\n\s]+//g; my $option =''; $option .= 'backup='.($backup? 'on' : 'off').','; $option .= 'active='.($active? 'on' : 'off').','; $option .= 'color=cccccc'; # XXX FIXME print FD "$uid $pass $host $port $option\n"; flock (FD, LOCK_UN); close FD; } } else { if (!($uid =~ /^[^@]+@\S+$/) || !$pass) { $err = 'Username or password incomplete'; } else { # new add open (FD, " > ./pop3config.cf") or die "$!\n"; flock (FD, LOCK_EX); $pass = encode_base64($pass); $pass =~ s/[\r\n\s]+//g; my $option = ''; $option .= 'backup='.($backup? 'on' : 'off').','; $option .= 'active='.($active? 'on' : 'off').','; $option .= 'color=cccccc'; # XXX FIXME print FD "$uid $pass $host $port $option\n"; flock (FD, LOCK_UN); close FD; } } } elsif ($q->cgi('dodelete')) { my $a = $q->cgi_full_names; my @del = grep { /^REMOVE-/ } @$a; open (FD, " > ./pop3config.cf") or die "$!\n"; flock (FD, LOCK_EX); for my $h (@$arr) { my $remove = 0; for my $d (@del) { if (lc $h->{uid} eq lc $q->cgi($d)) { $remove= 1; last; } } if (!$remove) { $h->{passwd} = encode_base64($h->{passwd}); $h->{passwd} =~ s/[\r\n\s]+//g; my $option = ''; $option .= 'backup='.($h->{backup} eq 'on' ? 'on' : 'off').','; $option .= 'active='.($h->{active} eq 'on' ? 'on' : 'off').','; $option .= 'color=cccccc'; # XXX FIXME print FD "$h->{uid} $h->{passwd} $h->{host} $h->{port} $option\n"; } } flock (FD, LOCK_UN); close FD; } if ($err) { $tpl->assign(ERRMSG => $err); } $self->pop3_list; } sub pref_timezone { my @t = ( {key => '+13:00', value => '+1300'}, {key => '+12:00', value => '+1200'}, {key => '+11:00', value => '+1100'}, {key => '+10:00', value => '+1000'}, {key => '+09:00', value => '+0900'}, {key => '+08:00', value => '+0800'}, {key => '+07:00', value => '+0700'}, {key => '+06:00', value => '+0600'}, {key => '+05:00', value => '+0500'}, {key => '+04:00', value => '+0400'}, {key => '+03:00', value => '+0300'}, {key => '+02:00', value => '+0200'}, {key => '+01:00', value => '+0100'}, {key => '00:00', value => '+0000'}, {key => '-01:00', value => '-0100'}, {key => '-02:00', value => '-0200'}, {key => '-03:00', value => '-0300'}, {key => '-04:00', value => '-0400'}, {key => '-05:00', value => '-0500'}, {key => '-06:00', value => '-0600'}, {key => '-07:00', value => '-0700'}, {key => '-08:00', value => '-0800'}, {key => '-09:00', value => '-0900'}, {key => '-10:00', value => '-1000'}, {key => '-11:00', value => '-1100'}, ); \@t; } sub pref_poptimeout { shift; ['15','30']; } sub pref_popfiles { shift; ['15','30','50','100']; } sub pre_run { 1 } sub post_run { my $template = $_[0]->{query}->cgi('screen') || $_[0]->{tpl}->{template} || 'pref.html'; reset_working_path(); $_[0]->{tpl}->process($template); $_[0]->{tpl}->print; } 1;