Only in sympa: .git
diff -crB sympa-6.1.23/po/en.po sympa/po/en.po
*** sympa-6.1.23/po/en.po	2014-09-11 23:00:31.000000000 +1200
--- sympa/po/en.po	2014-12-17 10:01:57.905979659 +1300
***************
*** 274,280 ****
  "Use it with care: it allows you to modify some of the list parameters. The "
  "list of the parameters you can modify depends on your privileges."
  msgstr ""
! "<strong>Edit list config</strong> must be used with care. It allows you to "
  "modify some of the list parameters. The list of parameters you can modify "
  "depends on your privilege."
  
--- 274,280 ----
  "Use it with care: it allows you to modify some of the list parameters. The "
  "list of the parameters you can modify depends on your privileges."
  msgstr ""
! "Edit list config must be used with care. It allows you to "
  "modify some of the list parameters. The list of parameters you can modify "
  "depends on your privilege."
  
***************
*** 286,292 ****
  #: ../web_tt2/admin.tt2:9
  msgid "Editing of various files and messages attached to your list."
  msgstr ""
! "<strong>Customizing</strong>: editing of various files and messages attached "
  "to your list."
  
  #: ../web_tt2/admin.tt2:12
--- 286,292 ----
  #: ../web_tt2/admin.tt2:9
  msgid "Editing of various files and messages attached to your list."
  msgstr ""
! "Editing of various files and messages attached "
  "to your list."
  
  #: ../web_tt2/admin.tt2:12
***************
*** 316,322 ****
  #: ../web_tt2/admin.tt2:19
  msgid "Allows you to download and delete list archives."
  msgstr ""
! "<strong>Manage Archives</strong>: Allows you to download and delete list "
  "archives."
  
  #: ../web_tt2/admin.tt2:22
--- 316,322 ----
  #: ../web_tt2/admin.tt2:19
  msgid "Allows you to download and delete list archives."
  msgstr ""
! "Allows you to download and delete list "
  "archives."
  
  #: ../web_tt2/admin.tt2:22
***************
*** 327,333 ****
  #: ../web_tt2/admin.tt2:22
  msgid "Manages non-delivery reports (also called bounces)."
  msgstr ""
! "<strong>Bounces</strong>: Manage non-delivery reports (also called bounces)."
  
  #: ../web_tt2/admin.tt2:25
  msgid "Logs:"
--- 327,333 ----
  #: ../web_tt2/admin.tt2:22
  msgid "Manages non-delivery reports (also called bounces)."
  msgstr ""
! "Manage non-delivery reports (also called bounces)."
  
  #: ../web_tt2/admin.tt2:25
  msgid "Logs:"
***************
*** 377,383 ****
  "Completely removes the current list. Listmaster privileges are required to "
  "restore a list."
  msgstr ""
! "<strong>Remove list</strong>: completely removes the current list. "
  "Listmaster privileges are required to restore a list once it has been "
  "removed."
  
--- 377,383 ----
  "Completely removes the current list. Listmaster privileges are required to "
  "restore a list."
  msgstr ""
! "Completely removes the current list. "
  "Listmaster privileges are required to restore a list once it has been "
  "removed."
  
***************
*** 405,411 ****
  #: ../web_tt2/admin.tt2:64
  msgid "Initializes the shared document web space."
  msgstr ""
! "<strong>Create shared</strong>: initializes the shared document web space."
  
  #: ../web_tt2/admin.tt2:68 ../web_tt2/admin_menu.tt2:105
  msgid "Delete Shared"
--- 405,411 ----
  #: ../web_tt2/admin.tt2:64
  msgid "Initializes the shared document web space."
  msgstr ""
! "Initializes the shared document web space."
  
  #: ../web_tt2/admin.tt2:68 ../web_tt2/admin_menu.tt2:105
  msgid "Delete Shared"
***************
*** 422,428 ****
  "Closes the shared document web space. It can be restored using \"Restore "
  "shared\" button."
  msgstr ""
! "<strong>Delete shared</strong> closes it. It can be restored using \"Restore "
  "shared\" button"
  
  #: ../web_tt2/admin.tt2:72
--- 422,428 ----
  "Closes the shared document web space. It can be restored using \"Restore "
  "shared\" button."
  msgstr ""
! "Closes the shared document web space. It can be restored using \"Restore "
  "shared\" button"
  
  #: ../web_tt2/admin.tt2:72
***************
*** 433,439 ****
  #: ../web_tt2/admin.tt2:72
  msgid "Restores the previously closed shared document web space."
  msgstr ""
! "<strong>Create shared</strong>: initializes the shared document web space."
  
  #. (count)
  #: ../web_tt2/active_lists.tt2:17 ../web_tt2/active_lists.tt2:5
--- 433,439 ----
  #: ../web_tt2/admin.tt2:72
  msgid "Restores the previously closed shared document web space."
  msgstr ""
! "Re-initializes the shared document web space."
  
  #. (count)
  #: ../web_tt2/active_lists.tt2:17 ../web_tt2/active_lists.tt2:5
Only in sympa: rpm
diff -crB sympa-6.1.23/soap/sympasoap.pm sympa/soap/sympasoap.pm
*** sympa-6.1.23/soap/sympasoap.pm	2014-09-11 02:08:55.000000000 +1200
--- sympa/soap/sympasoap.pm	2014-12-17 10:01:58.247037666 +1300
***************
*** 297,303 ****
  	$email = $session->{'email'};
  	$session_id = $session->{'id_session'};
      }
!     unless ($email or ($email eq 'unkown')  ) {
        &do_log('err', "Failed to authenticate user with session ID $session_id");
        die SOAP::Fault->faultcode('Client')
  	->faultstring('Could not get email from cookie')
--- 297,303 ----
  	$email = $session->{'email'};
  	$session_id = $session->{'id_session'};
      }
!     unless ($email or ($email eq 'unknown')  ) {
        &do_log('err', "Failed to authenticate user with session ID $session_id");
        die SOAP::Fault->faultcode('Client')
  	->faultstring('Could not get email from cookie')
***************
*** 353,359 ****
  	    ->faultstring('Incorrect number of parameters')
  	    ->faultdetail('Use : <appname> <apppassword> <vars> <service>');
      }
!     my $proxy_vars = &Auth::remote_app_check_password($appname, $apppassword, $robot);
          
      unless (defined $proxy_vars) {
  	&do_log('notice', "authenticateRemoteAppAndRun(): authentication failed");
--- 353,361 ----
  	    ->faultstring('Incorrect number of parameters')
  	    ->faultdetail('Use : <appname> <apppassword> <vars> <service>');
      }
! # SJS START
!     my( $proxy_vars, $set_vars ) = &Auth::remote_app_check_password($appname, $apppassword, $robot, $service);
! # SJS END
          
      unless (defined $proxy_vars) {
  	&do_log('notice', "authenticateRemoteAppAndRun(): authentication failed");
***************
*** 382,387 ****
--- 384,395 ----
  	}
  	$ENV{$id}=$value	if ($proxy_vars->{$id}) ;	
      }		
+ # SJS START
+     # Set fixed variables
+     foreach my $var ( keys %$set_vars ) {
+         $ENV{$var} = $set_vars->{$var};
+     }
+ # SJS END
      &{$service}($self,@$parameters);
  }
  
***************
*** 1401,1406 ****
--- 1409,1592 ----
      return SOAP::Data->name('return')->value(\@result);
  }
  
+ # SJS START
+ sub getDetails {
+     my $class = shift;
+     my $listname  = shift;
+     my $subscriber ;
+     my $list;
+     my %result = ();
+     
+     my $sender = $ENV{'USER_EMAIL'};
+     my $robot = $ENV{'SYMPA_ROBOT'};
+ 
+     unless ($sender) {
+ 		die SOAP::Fault->faultcode('Client')
+ 		    ->faultstring('User not authentified')
+ 		    ->faultdetail('You should login first');
+    	}
+ 
+     unless ($listname) {
+ 		die SOAP::Fault->faultcode('Client')
+ 		    ->faultstring('Incorrect number of parameters')
+ 		    ->faultdetail('Use : <list>');
+     }
+ 	
+     &Log::do_log('debug', 'SOAP getDetails(%s,%s,%s)', $listname,$robot,$sender);
+ 
+     $list = new List ($listname, $robot);
+ 	if(!$list) {
+ 		die SOAP::Fault->faultcode('Client')
+ 		    ->faultstring('List does not exist')
+ 		    ->faultdetail('Use : <list>');
+ 	}
+     if($subscriber = $list->get_subscriber($sender)) { 
+ 		$result{'gecos'} = $subscriber->{'gecos'};
+ 		$result{'reception'} = $subscriber->{'reception'};
+ 		$result{'subscribeDate'} = $subscriber->{'date'};
+ 		$result{'updateDate'} = $subscriber->{'update_date'};
+ 		$result{'custom'} = [];
+ 		if( $subscriber->{'custom_attribute'} ) {
+ 			foreach my $k ( keys %{$subscriber->{'custom_attribute'}} ) {
+ 				push @{$result{'custom'}},{key=>$k, value=>$subscriber->{'custom_attribute'}{$k}{value}} if($k);
+ 			}
+ 		}
+     } else {
+ 		die SOAP::Fault->faultcode('Client')
+ 		    ->faultstring('Not a subscriber to this list')
+ 		    ->faultdetail('Use : <list>');
+ 	}
+ 
+     return SOAP::Data->name('return')->value(\%result);
+ }
+ sub setDetails {
+     my $class = shift;
+     my $listname  = shift;
+     my $subscriber ;
+     my $list;
+ 	my %newcustom = ();
+ 	my %user = ();
+ 	my $gecos = shift;
+ 	my $reception = shift;
+ 	my ($key,$value);
+     
+     my $sender = $ENV{'USER_EMAIL'};
+     my $robot = $ENV{'SYMPA_ROBOT'};
+ 
+     unless ($sender) {
+ 	die SOAP::Fault->faultcode('Client')
+ 	    ->faultstring('User not authentified')
+ 	    ->faultdetail('You should login first');
+     }
+ 
+     unless ($listname) {
+ 		die SOAP::Fault->faultcode('Client')
+ 		    ->faultstring('Incorrect number of parameters')
+ 		    ->faultdetail('Use : <list> <gecos> <reception> [ <key> <value> ] ...');
+     }
+ 	
+     &Log::do_log('debug', 'SOAP setDetails(%s,%s,%s)', $listname,$robot,$sender);
+     $list = new List ($listname, $robot);
+     if(!$list) {
+ 		die SOAP::Fault->faultcode('Client')
+ 			->faultstring('List does not exist')
+ 	    	->faultdetail('Use : <list> <gecos> <reception> [ <key> <value> ] ...');
+ 	}
+     $subscriber = $list->get_subscriber($sender);
+     if(!$subscriber) {
+ 		die SOAP::Fault->faultcode('Client')
+ 			->faultstring('Not a subscriber to this list')
+ 	    	->faultdetail('Use : <list> <gecos> <reception> [ <key> <value> ] ...');
+ 	}
+ 
+ 	# Set subscriber values; return 1 for success
+     %user = ();
+ 	$user{'gecos'} = $gecos if(defined $gecos);
+ 	$user{'reception'} = $reception 
+ 		# ideally, this should check against the available_user_options
+ 		# values from the $list config
+ 		if($reception and $reception=~/^(mail|nomail|digest|digestplain|summary|notice|txt|html|urlize|not_me)$/);
+ 	if( @_ ) { # do we have any custom attributes passed?
+ 	    %newcustom = %{$subscriber->{'custom_attribute'}};
+ 		while ( @_ ) {
+ 			$key = shift;
+ 			next if(!$key);
+ 			$value = shift;
+ 			if( !defined $value or $value eq '' ) {
+ 		    	undef $newcustom{$key};
+ 			} else {
+ #				$newcustom{$key} = $list->{'admin'}{'custom_attribute'}{$key}
+ #					if(!defined $newcustom{$key});
+ 	    		$newcustom{$key}{value} = $value;
+ 			}
+ 		}
+     	$user{'custom_attribute'} = &List::createXMLCustomAttribute(\%newcustom);
+ 	}
+     die SOAP::Fault->faultcode('Server')
+       ->faultstring('Unable to set user details')
+ 	  ->faultdetail("SOAP setDetails : update user failed")
+ 	      unless $list->update_user($sender,\%user);
+ 	
+     return SOAP::Data->name('result')->type('boolean')->value(1);
+ }
+ sub setCustom {
+     my( $class, $listname, $key, $value ) = @_;
+     my $subscriber ;
+     my $list;
+     my $rv;
+     my %newcustom;
+     my %user;
+     
+     my $sender = $ENV{'USER_EMAIL'};
+     my $robot = $ENV{'SYMPA_ROBOT'};
+ 
+     unless ($sender) {
+ 	die SOAP::Fault->faultcode('Client')
+ 	    ->faultstring('User not authentified')
+ 	    ->faultdetail('You should login first');
+     }
+ 
+     unless ($listname and $key) {
+ 	die SOAP::Fault->faultcode('Client')
+ 	    ->faultstring('Incorrect number of parameters')
+ 	    ->faultdetail('Use : <list> <key> <value>');
+     }
+ 	
+     &Log::do_log('debug', 'SOAP setCustom(%s,%s,%s,%s)', $listname,$robot,$sender,$key);
+ 
+     $list = new List ($listname, $robot);
+     if(!$list) {
+ 		die SOAP::Fault->faultcode('Client')
+ 			->faultstring('List does not exist')
+ 	    	->faultdetail('Use : <list> <key> <value>');
+ 	}
+     $subscriber = $list->get_subscriber($sender);
+     if(!$subscriber) {
+ 		die SOAP::Fault->faultcode('Client')
+ 			->faultstring('Not a subscriber to this list')
+ 	    	->faultdetail('Use : <list> <key> <value> ');
+ 	}
+     %newcustom = %{$subscriber->{'custom_attribute'}};
+     #if(! defined $list->{'admin'}{'custom_attribute'}{$key} ) {
+     #	return SOAP::Data->name('result')->type('boolean')->value(0);
+     #}
+ 	if( $value eq '' ) {
+     	undef $newcustom{$key};
+ 	} else {
+ #		$newcustom{$key} = $list->{'admin'}{'custom_attribute'}{$key}
+ #			if(!defined $newcustom{$key} and defined $list->{'admin'}{'custom_attribute'});
+     	$newcustom{$key}{value} = $value;
+ 	}
+     %user = ();
+     $user{'custom_attribute'} = &List::createXMLCustomAttribute(\%newcustom);
+     die SOAP::Fault->faultcode('Server')
+       ->faultstring('Unable to set user attributes')
+ 	  ->faultdetail("SOAP setCustom : update user failed")
+ 	      unless $list->update_user($sender,\%user);
+ 	
+     return SOAP::Data->name('result')->type('boolean')->value(1);
+ }
+ 
  ## Return a structure in SOAP data format
  ## either flat (string) or structured (complexType)
  sub struct_to_soap {
diff -crB sympa-6.1.23/src/lib/Conf.pm sympa/src/lib/Conf.pm
*** sympa-6.1.23/src/lib/Conf.pm	2014-06-11 03:41:23.000000000 +1200
--- sympa/src/lib/Conf.pm	2014-12-17 10:01:58.450072193 +1300
***************
*** 109,114 ****
--- 109,124 ----
  							      'proxy_for_variables'=> {'format' => '.*',	    
  										      'occurrence' => '0-n',
  										      'split_char' => ','
+ # SJS START
+ 										  },
+ 							      'set_variables'=> {'format' => '\S+=.*',	    
+ 										      'occurrence' => '0-n',
+ 										      'split_char' => ','
+ 										  },
+ 							      'allow_commands'=> {'format' => '\S+',	    
+ 										      'occurrence' => '0-n',
+ 										      'split_char' => ','
+ # SJS END
  										  }
  							  }
  					    }
diff -crB sympa-6.1.23/src/lib/List.pm sympa/src/lib/List.pm
*** sympa-6.1.23/src/lib/List.pm	2014-09-11 02:08:55.000000000 +1200
--- sympa/src/lib/List.pm	2015-06-03 16:47:37.724214112 +1200
***************
*** 276,282 ****
  		      host lang web_archive archive digest digest_max_size available_user_options 
  		      default_user_options msg_topic msg_topic_keywords_apply_on msg_topic_tagging reply_to_header reply_to forced_reply_to * 
  		      verp_rate welcome_return_path remind_return_path merge_feature user_data_source include_file include_remote_file 
! 		      include_list include_remote_sympa_list include_ldap_query
                        include_ldap_2level_query include_sql_query include_admin ttl distribution_ttl creation update 
  		      status serial custom_attribute);
  
--- 276,282 ----
  		      host lang web_archive archive digest digest_max_size available_user_options 
  		      default_user_options msg_topic msg_topic_keywords_apply_on msg_topic_tagging reply_to_header reply_to forced_reply_to * 
  		      verp_rate welcome_return_path remind_return_path merge_feature user_data_source include_file include_remote_file 
! 		      include_list include_list_subset include_remote_sympa_list include_ldap_query
                        include_ldap_2level_query include_sql_query include_admin ttl distribution_ttl creation update 
  		      status serial custom_attribute);
  
***************
*** 851,860 ****
  							      'gettext_id' => "extracted attribute",
  							      'order' => 8
  							      },
  						  'select' => {'format' => ['all','first'],
  							       'default' => 'first',
  							       'gettext_id' => "selection (if multiple)",
! 							       'order' => 9
  							       },
  					          'scope' => {'format' => ['base','one','sub'],
  							      'default' => 'sub',
--- 851,869 ----
  							      'gettext_id' => "extracted attribute",
  							      'order' => 8
  							      },
+                                                 # SJS add for gecos support
+                                                   'gecosattr' => {'format' => '\w+',
+                                                               'length' => 15,
+                                                               'default' => 'displayName',
+                                                               'gettext_id' => "gecos attribute",
+                                                               'order' => 9
+                                                               },
+                                                # SJS END
+ 
  						  'select' => {'format' => ['all','first'],
  							       'default' => 'first',
  							       'gettext_id' => "selection (if multiple)",
! 							       'order' => 10
  							       },
  					          'scope' => {'format' => ['base','one','sub'],
  							      'default' => 'sub',
***************
*** 969,974 ****
--- 978,991 ----
  							      'gettext_id' => "second-level extracted attribute",
  							      'order' => 15
  							      },
+                           # SJS add for gecos support
+                           'gecosattr2' => {'format' => '\w+',
+                                   'length' => 15,
+                                   'default' => 'displayName',
+                                   'gettext_id' => "second-level gecos attribute",
+                                   'order' => 15.5
+                           },
+                           # SJS END
  						  'select2' => {'format' => ['all','first','regex'],
  							       'default' => 'first',
  							       'gettext_id' => "second-level selection",
***************
*** 1022,1027 ****
--- 1039,1071 ----
  			       'gettext_id' => "List inclusion",
  			       'group' => 'data_source'
  			       },
+ 	    'include_list_subset' => {
+ 			'format' => {
+ 				'name' => {
+ 					'format' => '.+',
+ 				    'occurrence' => '1',
+ 				    'gettext_id' => "Short name for this source",
+ 				    'length' => 15,
+ 				    'order' => 1
+ 				},
+ 				'list' => { 
+ 					'format'=>&tools::get_regexp('listname').'(\@'.&tools::get_regexp('host').')?',
+ 				    'occurrence' => '1',
+ 				    'gettext_id' => "List name",
+ 				    'order' => 2
+ 				},
+ 				'filter' => {
+ 					'format' => '\w+!?=.*',
+ 					'gettext_id' => "Filtering rule",
+ 					'comment' => 'This should use the internal id for one of the custom fields, and either have id=regexp or id!=regexp',
+ 					'default' => 'fieldid=regexp',
+ 					'order' => 3
+ 				}
+ 			},
+ 			'occurrence' => '0-n',
+ 			'gettext_id' => "List inclusion with filter",
+ 			'group' => 'data_source'
+ 		},
  	    'include_remote_sympa_list' => {'format' => {'host' => {'format' => &tools::get_regexp('host'),
  							    'occurrence' => '1',
  							    'gettext_id' => "remote host",
***************
*** 2793,2798 ****
--- 2837,2845 ----
  
      my $message = $param{'message'};
      my $apply_dkim_signature = $param{'apply_dkim_signature'};
+ # SJS ADD
+     my $test_mode = $param{'test_mode'};
+ 
  
      do_log('debug2', 'List::distribute_msg(%s, %s, %s, %s, %s, %s, apply_dkim_signature=%s)', $self->{'name'}, $message->{'msg'}, $message->{'size'}, $message->{'filename'}, $message->{'smime_crypted'}, $apply_dkim_signature );
  
***************
*** 2816,2821 ****
--- 2863,2882 ----
  	}
      }
  
+ 
+ # SJS ADD
+     if($test_mode) {
+         my $originalFromHeader = $hdr->get('From');
+         my @addresses = Mail::Address->parse($originalFromHeader);
+         $hdr->add('X-Sympa-Test', 'YES') ;
+         if(@addresses) {
+               $hdr->add('X-Sympa-Receipient', $addresses[0]->address );
+         } else {
+               $hdr->add('X-Sympa-Receipient', $self->{'name'}."-owner\@".$self->{'domain'} );
+         }
+     }
+ # SJS END
+ 
      ## Munge the From header if we are using DMARC Protection mode
      if ( $self->{'admin'}{'dmarc_protection'}{'mode'} ) {
  	Log::do_log('debug','DMARC protection on');
***************
*** 3062,3068 ****
      }
  
      ## Archives
!     $self->archive_msg($message);
      
      ## Change the reply-to header if necessary. 
      if ($self->{'admin'}{'reply_to_header'}) {
--- 3123,3129 ----
      }
  
      ## Archives
!     $self->archive_msg($message) unless($test_mode);
      
      ## Change the reply-to header if necessary. 
      if ($self->{'admin'}{'reply_to_header'}) {
***************
*** 3156,3162 ****
      }
  
      ## Blindly send the message to all users.
!     my $numsmtp = $self->send_msg('message'=> $message, 'apply_dkim_signature'=>$apply_dkim_signature);
      unless (defined ($numsmtp)) {
  	return $numsmtp;
      }
--- 3217,3223 ----
      }
  
      ## Blindly send the message to all users.
!     my $numsmtp = $self->send_msg('message'=> $message, 'apply_dkim_signature'=>$apply_dkim_signature, 'test_mode'=>$test_mode );
      unless (defined ($numsmtp)) {
  	return $numsmtp;
      }
***************
*** 3489,3494 ****
--- 3550,3564 ----
  	&do_log('err', 'No recipient for sending %s', $tpl);
  	return undef;
      }
+ 
+     # SJS START
+     # If multiple recipients as CSV then convert to list
+     if(!ref $who and $who =~ /,/) {
+        my (@a) = ();
+        foreach ( Mail::Address->parse($who) ) { push @a, $_->format; }
+        $who = \@a;
+     }
+     # SJS END
      
      ## Unless multiple recipients
      unless (ref ($who)) {
***************
*** 8593,8604 ****
  
  ## include a list as subscribers.
  sub _include_users_list {
!     my ($users, $includelistname, $robot, $default_user_options, $tied) = @_;
!     do_log('debug2', 'List::_include_users_list');
  
      my $total = 0;
      
      my $includelist;
      
      ## The included list is local or in another local robot
      if ($includelistname =~ /\@/) {
--- 8663,8691 ----
  
  ## include a list as subscribers.
  sub _include_users_list {
!     my ($users, $incl, $robot, $default_user_options, $tied) = @_;
!     my ( $includelistname );
!     my ( $filterfield, $filtervalue, $filternot ) = ('','',0);
  
      my $total = 0;
      
      my $includelist;
+ 
+     # SJS START
+     if( ref $incl ) {
+ 	# Include with filter
+ 	$includelistname = $incl->{'list'};
+ 	if( $incl->{'filter'} and $incl->{'filter'}=~/(\w+)(!?)=(.*)/ ) {
+ 	    ($filterfield,$filternot,$filtervalue)=($1,$2,$3);
+ 	}
+     	do_log('debug2', 'Including %s Filter %s %s= %s',
+ 	    $includelistname,$filterfield,$filternot,$filtervalue);
+     } else {
+ 	$includelistname = $incl; # just a straight include
+ 	$filterfield = "";
+     	do_log('debug2', 'Including %s',$includelistname);
+     }
+     # SJS END
      
      ## The included list is local or in another local robot
      if ($includelistname =~ /\@/) {
***************
*** 8612,8622 ****
  	return undef;
      }
      
!     my $id = Datasource::_get_datasource_id($includelistname);
  
      for (my $user = $includelist->get_first_user(); $user; $user = $includelist->get_next_user()) {
  	my %u;
  
  	## Check if user has already been included
  	if ($users->{$user->{'email'}}) {
  	    if ($tied) {
--- 8699,8730 ----
  	return undef;
      }
      
!     my $id = Datasource::_get_datasource_id($incl);
  
      for (my $user = $includelist->get_first_user(); $user; $user = $includelist->get_next_user()) {
  	my %u;
  
+ 	# SJS START: filter the user
+ 	if( $filterfield ) {
+ 	    my( $keep ) = 0;
+ 	    $keep = 1 if($filternot);
+ 	    if($user->{'custom_attribute'}) {
+ 			if( !ref $user->{'custom_attribute'} ) { # should never happen
+ 				my %custom_attr = &parseCustomAttribute($user->{'custom_attribute'});
+ 				$user->{'custom_attribute'} = \%custom_attr;
+ 			}
+ 			foreach my $k ( keys %{$user->{'custom_attribute'}} ) {
+ 			    next if ( $k ne $filterfield );
+ 		 	    if( $user->{'custom_attribute'}{$k}{'value'} =~ /$filtervalue/ ) {
+ 					if($filternot) { $keep = 0; } else { $keep = 1; }
+ 					last;
+ 			    }
+ 			}
+ 	    }
+ 	    next if(!$keep);
+ 	}
+ 	# SJS END
+ 
  	## Check if user has already been included
  	if ($users->{$user->{'email'}}) {
  	    if ($tied) {
***************
*** 8633,8638 ****
--- 8741,8766 ----
  	$u{'gecos'} = $user->{'gecos'};
  	$u{'id'} = join (',', split(',', $u{'id'}), $id);
  
+ 	# SJS START: copy over custom attributes
+ 	if($user->{'custom_attribute'}) {
+ 		# Convert custom_attribute from XML to hash
+ 		if( !ref $user->{'custom_attribute'} ) { 
+ 			my %custom_attr = &parseCustomAttribute($user->{'custom_attribute'});
+ 			$user->{'custom_attribute'} = \%custom_attr;
+ 		}
+ 		# this should never happen, but just to be safe
+ 		if( defined $u{'custom_attribute'} and !ref $u{'custom_attribute'} ) { 
+ 			my %custom_attr = &parseCustomAttribute($u{'custom_attribute'});
+ 			$u{'custom_attribute'} = \%custom_attr;
+ 		}
+ 		foreach my $k ( keys %{$user->{'custom_attribute'}} ) {
+ 			$u{'custom_attributes'}{$k} = $user->{'custom_attribute'}{$k};
+ 		}
+ 		# convert back to XML
+ 		$u{'custom_attribute'} = &createXMLCustomAttribute($u{'custom_attributes'});
+ 	}
+ 	# SJS END
+ 
  	$u{'visibility'} = $default_user_options->{'visibility'} if (defined $default_user_options->{'visibility'});
  	$u{'reception'} = $default_user_options->{'reception'} if (defined $default_user_options->{'reception'});
  	$u{'profile'} = $default_user_options->{'profile'} if (defined $default_user_options->{'profile'});
***************
*** 8719,8725 ****
  	    $total++;
  	}
  	$u{'email'} = $email;
! 	$u{'gecos'} = $gecos;
  	$u{'id'} = join (',', split(',', $u{'id'}), $id);
  
  	$u{'visibility'} = $default_user_options->{'visibility'} if (defined $default_user_options->{'visibility'});
--- 8847,8853 ----
  	    $total++;
  	}
  	$u{'email'} = $email;
! 	$u{'gecos'} = $gecos if($gecos);
  	$u{'id'} = join (',', split(',', $u{'id'}), $id);
  
  	$u{'visibility'} = $default_user_options->{'visibility'} if (defined $default_user_options->{'visibility'});
***************
*** 8802,8808 ****
  		$total++;
  	    }
  	    $u{'email'} = $email;
! 	    $u{'gecos'} = $gecos;
  	    $u{'id'} = join (',', split(',', $u{'id'}), $id);
  	    
  	    $u{'visibility'} = $default_user_options->{'visibility'} if (defined $default_user_options->{'visibility'});
--- 8930,8936 ----
  		$total++;
  	    }
  	    $u{'email'} = $email;
! 	    $u{'gecos'} = $gecos if($gecos);
  	    $u{'id'} = join (',', split(',', $u{'id'}), $id);
  	    
  	    $u{'visibility'} = $default_user_options->{'visibility'} if (defined $default_user_options->{'visibility'});
***************
*** 8843,8848 ****
--- 8971,8979 ----
      my $ldap_filter = $param->{'filter'};
      my $ldap_attrs = $param->{'attrs'};
      my $ldap_select = $param->{'select'};
+ # SJS start
+     my $ldap_gecosattr = $param->{'gecosattr'};
+ # SJS END
      
      ## LDAP and query handler
      my ($ldaph, $fetch);
***************
*** 8865,8871 ****
      do_log('debug2', 'Searching on server %s ; suffix %s ; filter %s ; attrs: %s', $param->{'host'}, $ldap_suffix, $ldap_filter, $ldap_attrs);
      $fetch = $ldaph->search ( base => "$ldap_suffix",
  			      filter => "$ldap_filter",
! 			      attrs => [ "$ldap_attrs" ],
  			      scope => "$param->{'scope'}");
      if ($fetch->code()) {
  	do_log('err','Ldap search (single level) failed : %s (searching on server %s ; suffix %s ; filter %s ; attrs: %s)', 
--- 8996,9005 ----
      do_log('debug2', 'Searching on server %s ; suffix %s ; filter %s ; attrs: %s', $param->{'host'}, $ldap_suffix, $ldap_filter, $ldap_attrs);
      $fetch = $ldaph->search ( base => "$ldap_suffix",
  			      filter => "$ldap_filter",
! # SJS change start
! #			      attrs => [ "$ldap_attrs" ],
!                               attrs => [ "$ldap_attrs", "$ldap_gecosattr" ],
! # SJS end
  			      scope => "$param->{'scope'}");
      if ($fetch->code()) {
  	do_log('err','Ldap search (single level) failed : %s (searching on server %s ; suffix %s ; filter %s ; attrs: %s)', 
***************
*** 8882,8901 ****
      while (my $e = $fetch->shift_entry) {
  
  	my $entry = $e->get_value($ldap_attrs, asref => 1);
  	
  	## Multiple values
  	if (ref($entry) eq 'ARRAY') {
  	    foreach my $email (@{$entry}) {
  		my $cleanmail = &tools::clean_email($email);
  		next if ($emailsViewed{$cleanmail});
! 		push @emails, $cleanmail;
  		$emailsViewed{$cleanmail} = 1;
  		last if ($ldap_select eq 'first');
  	    }
  	}else {
  	    my $cleanmail = &tools::clean_email($entry);
  	    unless ($emailsViewed{$cleanmail}) {
! 		push @emails, $cleanmail;
  		$emailsViewed{$cleanmail} = 1;
  	    }
  	}
--- 9016,9047 ----
      while (my $e = $fetch->shift_entry) {
  
  	my $entry = $e->get_value($ldap_attrs, asref => 1);
+ # SJS start
+         my $gecosentry = $e->get_value($ldap_gecosattr, asref => 1);
+         if (ref($gecosentry) eq 'ARRAY') {
+               $gecosentry = $gecosentry->[0];
+         }
+ # SJS END
  	
  	## Multiple values
  	if (ref($entry) eq 'ARRAY') {
  	    foreach my $email (@{$entry}) {
  		my $cleanmail = &tools::clean_email($email);
  		next if ($emailsViewed{$cleanmail});
! # SJS START
! #		push @emails, $cleanmail;
!                 push @emails, [ $cleanmail, $gecosentry ];
! # SJS END
  		$emailsViewed{$cleanmail} = 1;
  		last if ($ldap_select eq 'first');
  	    }
  	}else {
  	    my $cleanmail = &tools::clean_email($entry);
  	    unless ($emailsViewed{$cleanmail}) {
! # SJS START
! #		push @emails, $cleanmail;
!                 push @emails, [ $cleanmail, $gecosentry ];
! # SJS END
  		$emailsViewed{$cleanmail} = 1;
  	    }
  	}
***************
*** 8905,8912 ****
  	do_log('notice','Can\'t unbind from  LDAP server %s', $param->{'host'});
  	return undef;
      }
!     
!     foreach my $email (@emails) {
  	next if ($email =~ /^\s*$/);
  
  	$email = &tools::clean_email($email);
--- 9051,9062 ----
  	do_log('notice','Can\'t unbind from  LDAP server %s', $param->{'host'});
  	return undef;
      }
! 
! # SJS START    
! #    foreach my $email (@emails) {
!      foreach my $emailgecos (@emails) {
!         my ($email,$gecos) = @$emailgecos;
! # SJS END
  	next if ($email =~ /^\s*$/);
  
  	$email = &tools::clean_email($email);
***************
*** 8924,8929 ****
--- 9074,9082 ----
  	}
  
  	$u{'email'} = $email;
+ # SJS start
+         $u{'gecos'} = $gecos if($gecos);
+ # SJS END
  	$u{'date'} = time;
  	$u{'update_date'} = time;
  	$u{'id'} = join (',', split(',', $u{'id'}), $id);
***************
*** 8974,8979 ****
--- 9127,9135 ----
      my $ldap_select2 = $param->{'select2'};
      my $ldap_scope2 = $param->{'scope2'};
      my $ldap_regex2 = $param->{'regex2'};
+ # SJS start
+     my $ldap_gecosattr2 = $param->{'gecosattr2'};
+ # SJS END
      my @sync_errors = ();
      
      ## LDAP and query handler
***************
*** 9041,9047 ****
  	$fetch = $ldaph->search(
  	    base => "$suffix2",
  	    filter => "$filter2",
! 	    attrs => [ "$ldap_attrs2" ], # FIXME: multiple attrs?
  	    scope => "$ldap_scope2"
  	);
  	if ($fetch->code()) {
--- 9197,9206 ----
  	$fetch = $ldaph->search(
  	    base => "$suffix2",
  	    filter => "$filter2",
! # SJS change start
! #	    attrs => [ "$ldap_attrs2" ], # FIXME: multiple attrs?
!             attrs => [ "$ldap_attrs2", "$ldap_gecosattr2" ],
! # SJS end
  	    scope => "$ldap_scope2"
  	);
  	if ($fetch->code()) {
***************
*** 9055,9060 ****
--- 9214,9222 ----
  	
  	while (my $e = $fetch->shift_entry) {
  	    my $entry = $e->get_value($ldap_attrs2, asref => 1);
+ # SJS start
+             my $gecosentry = $e->get_value($ldap_gecosattr2);
+ # SJS END
  
  	    ## Multiple values
  	    if (ref($entry) eq 'ARRAY') {
***************
*** 9062,9075 ****
  		    my $cleanmail = &tools::clean_email($email);
  		    next if (($ldap_select2 eq 'regex') && ($cleanmail !~ /$ldap_regex2/));
  		    next if ($emailsViewed{$cleanmail});
! 		    push @emails, $cleanmail;
  		    $emailsViewed{$cleanmail} = 1;
  		    last if ($ldap_select2 eq 'first');
  		}
  	    }else {
  		my $cleanmail = &tools::clean_email($entry);
  		unless( (($ldap_select2 eq 'regex') && ($cleanmail !~ /$ldap_regex2/))||$emailsViewed{$cleanmail}) {
! 		    push @emails, $cleanmail;
  		    $emailsViewed{$cleanmail} = 1;
  		}
  	    }
--- 9224,9243 ----
  		    my $cleanmail = &tools::clean_email($email);
  		    next if (($ldap_select2 eq 'regex') && ($cleanmail !~ /$ldap_regex2/));
  		    next if ($emailsViewed{$cleanmail});
! # SJS START
! #		    push @emails, $cleanmail;
! 		    push @emails, [ $cleanmail, $gecosentry ];
! # SJS END
  		    $emailsViewed{$cleanmail} = 1;
  		    last if ($ldap_select2 eq 'first');
  		}
  	    }else {
  		my $cleanmail = &tools::clean_email($entry);
  		unless( (($ldap_select2 eq 'regex') && ($cleanmail !~ /$ldap_regex2/))||$emailsViewed{$cleanmail}) {
! # SJS START
! #		    push @emails, $cleanmail;
! 		    push @emails, [ $cleanmail, $gecosentry ];
! # SJS END
  		    $emailsViewed{$cleanmail} = 1;
  		}
  	    }
***************
*** 9081,9087 ****
  	return undef;
      }
      
!     foreach my $email (@emails) {
  	next if ($email =~ /^\s*$/);
  
  	$email = &tools::clean_email($email);
--- 9249,9259 ----
  	return undef;
      }
      
! # SJS START    
! #    foreach my $email (@emails) {
!      foreach my $emailgecos (@emails) {
! 	my ($email,$gecos) = @$emailgecos;
! # SJS END
  	next if ($email =~ /^\s*$/);
  
  	$email = &tools::clean_email($email);
***************
*** 9102,9107 ****
--- 9274,9282 ----
  	$u{'date'} = time;
  	$u{'update_date'} = time;
  	$u{'id'} = join (',', split(',', $u{'id'}), $id);
+ # SJS start
+ 	$u{'gecos'} = $gecos if($gecos);
+ # SJS END
  
  	$u{'visibility'} = $default_user_options->{'visibility'} if (defined $default_user_options->{'visibility'});
  	$u{'reception'} = $default_user_options->{'reception'} if (defined $default_user_options->{'reception'});
***************
*** 9149,9154 ****
--- 9324,9332 ----
  
      foreach my $row (@{$array_of_users}) {
  	my $email = $row->[0]; ## only get first field
+         # SJS add gecos support
+         my $gecos = $row->[1];
+ 
  	## Empty value
  	next if ($email =~ /^\s*$/);
  
***************
*** 9167,9172 ****
--- 9345,9353 ----
  	}
  
  	$u{'email'} = $email;
+         # SJS add gecos support
+         $u{'gecos'} = $gecos if($gecos);
+         # SJS END
  	$u{'date'} = time;
  	$u{'update_date'} = time;
  	$u{'id'} = join (',', split(',', $u{'id'}), $id);
***************
*** 9232,9238 ****
  	}
  	&do_log('debug3', 'Got lock for writing on %s', $db_file);
  
! 	foreach my $type ('include_list','include_remote_sympa_list','include_file','include_ldap_query','include_ldap_2level_query','include_sql_query','include_remote_file') {
  	    last unless (defined $total);
  	    
  	    foreach my $tmp_incl (@{$admin->{$type}}) {
--- 9413,9419 ----
  	}
  	&do_log('debug3', 'Got lock for writing on %s', $db_file);
  
! 	foreach my $type ('include_list','include_list_subset','include_remote_sympa_list','include_file','include_ldap_query','include_ldap_2level_query','include_sql_query','include_remote_file') {
  	    last unless (defined $total);
  	    
  	    foreach my $tmp_incl (@{$admin->{$type}}) {
***************
*** 9265,9270 ****
--- 9446,9460 ----
  			$included = _include_users_list (\%users, $incl, $self->{'domain'}, $admin->{'default_user_options'}, 'tied');
  
  		    }
+ 		}elsif ($type eq 'include_list_subset') {
+ 		    $depend_on->{$name} = 1 ;
+ 		    if (&_inclusion_loop ($name,$incl->{'list'},$depend_on)) {
+ 			do_log('err','loop detection in list inclusion : could not include again %s in %s',$incl->{'list'},$name);
+ 		    }else{
+ 			$depend_on->{$incl->{'list'}} = 1;
+ 			$included = _include_users_list (\%users, $incl, $self->{'domain'}, $admin->{'default_user_options'}, 'tied');
+ 
+ 		    }
  		}elsif ($type eq 'include_remote_sympa_list') {
  		    $included = $self->_include_users_remote_sympa_list(\%users, $incl, $dir, $self->{'domain'}, $admin->{'default_user_options'}, 'tied');
  		}elsif ($type eq 'include_file') {
***************
*** 9355,9361 ****
      my @errors;
      my $result;
  
!     foreach my $type ('include_list','include_remote_sympa_list','include_file','include_ldap_query','include_ldap_2level_query','include_sql_query','include_remote_file') {
  	last unless (defined $total);
  	    
  	foreach my $tmp_incl (@{$admin->{$type}}) {
--- 9545,9551 ----
      my @errors;
      my $result;
  
!     foreach my $type ('include_list','include_list_subset','include_remote_sympa_list','include_file','include_ldap_query','include_ldap_2level_query','include_sql_query','include_remote_file') {
  	last unless (defined $total);
  	    
  	foreach my $tmp_incl (@{$admin->{$type}}) {
***************
*** 9402,9407 ****
--- 9592,9608 ----
  			push @errors, {'type' => $type, 'name' => $incl};
  		    }
  		}
+ 	    }elsif ($type eq 'include_list_subset') {
+ 		$depend_on->{$name} = 1 ;
+ 		if (&_inclusion_loop ($name,$incl->{'list'},$depend_on)) {
+ 		    do_log('err','loop detection in list inclusion : could not include again %s in %s',$incl->{'list'},$name);
+ 		}else{
+ 		    $depend_on->{$incl->{'list'}} = 1;
+ 		    $included = _include_users_list (\%users, $incl, $self->{'domain'}, $admin->{'default_user_options'});
+ 		    unless (defined $included){
+ 			push @errors, {'type' => $type, 'name' => $incl->{'name'}};
+ 		    }
+ 		}
  	    }elsif ($type eq 'include_file') {
  		$included = _include_users_file (\%users, $incl, $admin->{'default_user_options'});
  		unless (defined $included){
***************
*** 9480,9486 ****
  	} else {
  	    $include_admin_user = &_load_include_admin_user_file($self->{'domain'},$include_file);
  	}
! 	foreach my $type ('include_list','include_remote_sympa_list','include_file','include_ldap_query','include_ldap_2level_query','include_sql_query','include_remote_file') {
  	    last unless (defined $total);
  	    
  	    foreach my $tmp_incl (@{$include_admin_user->{$type}}) {
--- 9681,9687 ----
  	} else {
  	    $include_admin_user = &_load_include_admin_user_file($self->{'domain'},$include_file);
  	}
! 	foreach my $type ('include_list','include_list_subset','include_remote_sympa_list','include_file','include_ldap_query','include_ldap_2level_query','include_sql_query','include_remote_file') {
  	    last unless (defined $total);
  	    
  	    foreach my $tmp_incl (@{$include_admin_user->{$type}}) {
***************
*** 9515,9520 ****
--- 9716,9729 ----
  			$depend_on->{$incl} = 1;
  			$included = _include_users_list (\%admin_users, $incl, $self->{'domain'}, \%option);
  		    }
+ 		}elsif ($type eq 'include_list_subset') {
+ 		    $depend_on->{$name} = 1 ;
+ 		    if (&_inclusion_loop ($name,$incl->{'list'},$depend_on)) {
+ 			do_log('err','loop detection in list inclusion : could not include again %s in %s',$incl->{'list'},$name);
+ 		    }else{
+ 			$depend_on->{$incl->{'list'}} = 1;
+ 			$included = _include_users_list (\%admin_users, $incl, $self->{'domain'}, \%option);
+ 		    }
  		}elsif ($type eq 'include_file') {
  		    $included = _include_users_file (\%admin_users, $incl, \%option);
  		}elsif ($type eq 'include_remote_file') {
***************
*** 9623,9629 ****
  	
  	$pname = $1;   
  	
! 	unless(($pname eq 'include_list')||($pname eq 'include_remote_sympa_list')||($pname eq 'include_file')||($pname eq 'include_remote_file')||
  	       ($pname eq 'include_ldap_query')||($pname eq 'include_ldap_2level_query')||($pname eq 'include_sql_query'))   {
  	    &do_log('info', 'Unknown parameter "%s" in %s', $pname, $file);
  	    next;
--- 9832,9838 ----
  	
  	$pname = $1;   
  	
! 	unless(($pname eq 'include_list')||($pname eq 'include_list_subset')||($pname eq 'include_remote_sympa_list')||($pname eq 'include_file')||($pname eq 'include_remote_file')||
  	       ($pname eq 'include_ldap_query')||($pname eq 'include_ldap_2level_query')||($pname eq 'include_sql_query'))   {
  	    &do_log('info', 'Unknown parameter "%s" in %s', $pname, $file);
  	    next;
***************
*** 10339,10344 ****
--- 10548,10560 ----
  	    printf SUB "%s %s\n", $k, $s->{$k} unless ($s->{$k} eq '');
  	    
  	}
+ # SJS Addition
+       if( defined $s->{custom_attribute} ) {
+           foreach $k ( keys %{$s->{custom_attribute}} ) {
+               printf SUB "custom_%s %s\n", $k, $s->{custom_attribute}{$k}{value} unless ($s->{custom_attribute}{$k}{value} eq '');
+           }
+       }
+ # SJS End
  	print SUB "\n";
      }
      close SUB;
***************
*** 12871,12877 ****
  sub has_include_data_sources {
      my $self = shift;
  
!     foreach my $type ('include_file','include_list','include_remote_sympa_list','include_sql_query','include_remote_file',
  		      'include_ldap_query','include_ldap_2level_query','include_admin','owner_include','editor_include') {
  	if (ref($self->{'admin'}{$type}) eq 'ARRAY' && $#{$self->{'admin'}{$type}} >= 0) {
  	    return 1;
--- 13087,13093 ----
  sub has_include_data_sources {
      my $self = shift;
  
!     foreach my $type ('include_file','include_list','include_list_subset','include_remote_sympa_list','include_sql_query','include_remote_file',
  		      'include_ldap_query','include_ldap_2level_query','include_admin','owner_include','editor_include') {
  	if (ref($self->{'admin'}{$type}) eq 'ARRAY' && $#{$self->{'admin'}{$type}} >= 0) {
  	    return 1;
diff -crB sympa-6.1.23/src/lib/mail.pm sympa/src/lib/mail.pm
*** sympa-6.1.23/src/lib/mail.pm	2014-05-29 01:33:21.000000000 +1200
--- sympa/src/lib/mail.pm	2014-12-17 10:01:58.453072704 +1300
***************
*** 220,232 ****
  	# "phrase" <email> won't be allowed.
  	if (ref ($rcpt)) {
  	    if ($data->{'to'}) {
! 		$to = $data->{'to'};
  	    }else {
! 		$to = join(",\n   ", @{$rcpt});
  	    }
  	}else{
! 	    $to = $rcpt;
! 	}   
  	$headers .= "To: $to\n";
      }     
      unless ($header_ok{'from'}) {
--- 220,245 ----
  	# "phrase" <email> won't be allowed.
  	if (ref ($rcpt)) {
  	    if ($data->{'to'}) {
! # SJS CHANGE: fix MIME encoding of To line
! #	     $to = $data->{'to'};
! 	        my (@a) = ();
! 	        foreach ( Mail::Address->parse($data->{'to'}) ) { push @a, $_->format; }
! 	        $to = join(",\n  ",@a);
  	    }else {
! #	     $to = join(",\n   ", @{$rcpt});
! 	        my (@a) = ();
! 	        foreach my $r ( @$rcpt ) { foreach ( Mail::Address->parse($r) ) { push @a, $_->format; } }
! 	        $to = join(",\n  ",@a);
  	    }
  	}else{
! #	 $to = $rcpt;
! 	    # Why do it like this?  Because if $to ends up with a line >40chr
! 	    # it will not reencode correctly with encode_mimewords
! 	    my (@a) = ();
! 	    foreach ( Mail::Address->parse($rcpt) ) { push @a, $_->format; }
! 	    $to = join(",\n  ",@a);
! # SJS END
!         }
  	$headers .= "To: $to\n";
      }     
      unless ($header_ok{'from'}) {
***************
*** 458,463 ****
--- 471,480 ----
  	my @tab =  @sendto ; push @sendtobypacket, \@tab ;# do not replace this line by push @sendtobypacket, \@sendto !!!
      }
  
+ # SJS catch null message case
+     return 0 if ($numsmtp == 0);
+ # SJS END
+ 
      return $numsmtp if (&sendto('msg_header' => $msg_header, 
  				'msg_body' => $msg_body,
  				'from' => $from,
***************
*** 604,614 ****
      if ($encrypt eq 'smime_crypted') {
          # encrypt message for each rcpt and send the message
  	# this MUST be moved to the bulk mailer. This way, merge will be applied after the SMIME encryption is applied ! This is a bug !
! 	foreach my $unique_rcpt (@{$rcpt}) {
! 	    my $email = lc(@{$unique_rcpt}[0]);
! 	    if (($email !~ /@/) || ($#{@$unique_rcpt} != 0)) {
! 		do_log('err',"incorrect call for encrypt with incorrect number of recipient"); 
! 		# internal check, if encryption is on packet should be unique and shoud contain only one rcpt 
  		return undef;
  	    }
  	    my $encrypted_msg_as_string;
--- 621,639 ----
      if ($encrypt eq 'smime_crypted') {
          # encrypt message for each rcpt and send the message
  	# this MUST be moved to the bulk mailer. This way, merge will be applied after the SMIME encryption is applied ! This is a bug !
! # SJS START
! # Adding Olaf Menkens patch to make s/mime encryption work
! #       foreach my $unique_rcpt (@{$rcpt}) {
! #           my $email = lc(@{$unique_rcpt}[0]);
! #           if (($email !~ /@/) || ($#{@$unique_rcpt} != 0)) {
! #               do_log('err',"incorrect call for encrypt with incorrect number of recipient");
! #               # internal check, if encryption is on packet should be unique and shoud contain only one rcpt
!         foreach my $rcpt_list (@{$rcpt}) {
!           foreach my $unique_rcpt (@{$rcpt_list}) {
!             my $email = lc($unique_rcpt);
!             if ($email !~ /@/) {
!                 do_log('err','Incorrect email address %s', $email);
! # SJS END
  		return undef;
  	    }
  	    my $encrypted_msg_as_string;
***************
*** 628,633 ****
--- 653,661 ----
  		return undef;
  	    }
  	    $tag_as_last = 0;
+ # SJS START
+           }
+ # SJS END
  	}
      }else{
  	$msg = $msg_header->as_string . "\n" . $msg_body;   
diff -crB sympa-6.1.23/src/lib/Scenario.pm sympa/src/lib/Scenario.pm
*** sympa-6.1.23/src/lib/Scenario.pm	2014-09-11 02:08:55.000000000 +1200
--- sympa/src/lib/Scenario.pm	2014-12-17 10:01:58.392062338 +1300
***************
*** 218,224 ****
  #      -$debug : adds keys in the returned HASH 
  #
  # OUT : undef | ref(HASH) containing keys :
! #        -action : 'do_it'|'reject'|'request_auth'
  #           |'owner'|'editor'|'editorkey'|'listmaster'
  #        -reason : defined if action == 'reject' 
  #           and in scenario : reject(reason='...')
--- 218,225 ----
  #      -$debug : adds keys in the returned HASH 
  #
  # OUT : undef | ref(HASH) containing keys :
! # SJS CHANGE
! #        -action : 'do_it'|'test_it'|'reject'|'request_auth'
  #           |'owner'|'editor'|'editorkey'|'listmaster'
  #        -reason : defined if action == 'reject' 
  #           and in scenario : reject(reason='...')
***************
*** 494,500 ****
  		}
  
  		## Check syntax of returned action
! 		unless ($action =~ /^(do_it|reject|request_auth|owner|editor|editorkey|listmaster|ham|spam|unsure)/) {
  		    &do_log('err', "Matched unknown action '%s' in scenario", $rule->{'action'});
  		    return undef;
  		}
--- 495,502 ----
  		}
  
  		## Check syntax of returned action
! # SJS CHANGE
! 		unless ($action =~ /^(do_it|reject|request_auth|owner|editor|editorkey|listmaster|ham|spam|unsure|test_it)/) {
  		    &do_log('err', "Matched unknown action '%s' in scenario", $rule->{'action'});
  		    return undef;
  		}
***************
*** 881,887 ****
  
  	}elsif ($condition_key eq 'is_owner') {
  	    foreach my $arg (@arg) {
! 		if ($list2->am_i('owner', $arg)) {
  		    $ok = $arg;
  		    last;
  		}
--- 883,890 ----
  
  	}elsif ($condition_key eq 'is_owner') {
  	    foreach my $arg (@arg) {
! # SJS CHANGE: make test strict
! 		if ($list2->am_i('owner', $arg,{strict=>1})) {
  		    $ok = $arg;
  		    last;
  		}
***************
*** 894,900 ****
  
  	}elsif ($condition_key eq 'is_editor') {
  	    foreach my $arg (@arg) {
! 		if ($list2->am_i('editor', $arg)) {
  		    $ok = $arg;
  		    last;
  		}
--- 897,904 ----
  
  	}elsif ($condition_key eq 'is_editor') {
  	    foreach my $arg (@arg) {
! # SJS CHANGE: make test strict
! 		if ($list2->am_i('editor', $arg,{strict=>1})) {
  		    $ok = $arg;
  		    last;
  		}
diff -crB sympa-6.1.23/src/lib/tools.pm sympa/src/lib/tools.pm
*** sympa-6.1.23/src/lib/tools.pm	2014-09-11 02:08:55.000000000 +1200
--- sympa/src/lib/tools.pm	2014-12-17 10:01:58.431068965 +1300
***************
*** 119,124 ****
--- 119,128 ----
      $http_host_re =~ s/([^\s\w\x80-\xFF])/\\$1/g;
      my $hss = HTML::StripScripts::Parser->new({ Context => 'Document',
  						AllowSrc        => 1,
+ # Stop stripping hrefs!  SJS
+                                                 AllowHref => 1,
+                                                 AllowMailto => 1,
+ # SJS END
  						Rules => {
  						    '*' => {
  							src => qr{^http://$http_host_re},
diff -crB sympa-6.1.23/src/sympa.pl.in sympa/src/sympa.pl.in
*** sympa-6.1.23/src/sympa.pl.in	2014-06-20 02:58:42.000000000 +1200
--- sympa/src/sympa.pl.in	2014-12-17 10:01:58.467075086 +1300
***************
*** 1998,2009 ****
      }
  	
      ## message topic context	
!     if (($action =~ /^do_it/) && ($context->{'topic_needed'})) {
          $action = 'editorkey' if ($list->{'admin'}{'msg_topic_tagging'} eq 'required_moderator');
  	$action = 'request_auth' if ($list->{'admin'}{'msg_topic_tagging'} eq 'required_sender');
      }
! 
!     if (($action =~ /^do_it/) || ($main::daemon_usage == DAEMON_MESSAGE)) {
  
  	if (($main::daemon_usage == DAEMON_MESSAGE) || ($main::daemon_usage == DAEMON_ALL)) {
  
--- 1998,2010 ----
      }
  	
      ## message topic context	
! # SJS CHANGE: test result
!     if (($action =~ /^(do_it|test_it)/) && ($context->{'topic_needed'})) {
          $action = 'editorkey' if ($list->{'admin'}{'msg_topic_tagging'} eq 'required_moderator');
  	$action = 'request_auth' if ($list->{'admin'}{'msg_topic_tagging'} eq 'required_sender');
      }
! # SJS CHANGE: test result
!     if (($action =~ /^(do_it|test_it)/) || ($main::daemon_usage == DAEMON_MESSAGE)) {
  
  	if (($main::daemon_usage == DAEMON_MESSAGE) || ($main::daemon_usage == DAEMON_ALL)) {
  
***************
*** 2014,2020 ****
  
  	    my $numsmtp;
  	    eval { $numsmtp =$list->distribute_msg('message'=> $message,
! 						   'apply_dkim_signature'=>$apply_dkim_signature)};
  	    ## Keep track of known message IDs...if any
  	    $msgid_table{$list->get_list_id()}{$messageid}++ if ($messageid);
  	    
--- 2015,2024 ----
  
  	    my $numsmtp;
  	    eval { $numsmtp =$list->distribute_msg('message'=> $message,
! 						   'apply_dkim_signature'=>$apply_dkim_signature,
! # SJS CHANGE
!                                                  'test_mode'=>(($action=~/test_it/)?1:0)
! 	    )};
  	    ## Keep track of known message IDs...if any
  	    $msgid_table{$list->get_list_id()}{$messageid}++ if ($messageid);
  	    
diff -crB sympa-6.1.23/wwsympa/Auth.pm sympa/wwsympa/Auth.pm
*** sympa-6.1.23/wwsympa/Auth.pm	2014-01-07 22:04:38.000000000 +1300
--- sympa/wwsympa/Auth.pm	2014-12-17 10:01:58.612099733 +1300
***************
*** 354,361 ****
  # check trusted_application_name et trusted_application_password : return 1 or undef;
  sub remote_app_check_password {
      
!     my ($trusted_application_name,$password,$robot) = @_;
!     do_log('debug','Auth::remote_app_check_password (%s,%s)',$trusted_application_name,$robot);
      
      my $md5 = &tools::md5_fingerprint($password);
      
--- 354,361 ----
  # check trusted_application_name et trusted_application_password : return 1 or undef;
  sub remote_app_check_password {
      
!     my ($trusted_application_name,$password,$robot,$service) = @_;
!     do_log('debug','Auth::remote_app_check_password (%s,%s,%s)',$trusted_application_name,$robot,$service);
      
      my $md5 = &tools::md5_fingerprint($password);
      
***************
*** 376,394 ****
   	    if ($md5 eq $application->{'md5password'}) {
   		# &do_log('debug', 'Auth::remote_app_check_password : authentication succeed for %s',$application->{'name'});
   		my %proxy_for_vars ;
   		foreach my $varname (@{$application->{'proxy_for_variables'}}) {
   		    $proxy_for_vars{$varname}=1;
   		}		
!  		return (\%proxy_for_vars);
   	    }else{
   		&do_log('info', 'Auth::remote_app_check_password: bad password from %s', $trusted_application_name);
!  		return undef;
   	    }
   	}
      }				 
      # no matching application found
      &do_log('info', 'Auth::remote_app-check_password: unknown application name %s', $trusted_application_name);
!     return undef;
  }
   
  # create new entry in one_time_ticket table using a rand as id so later access is authenticated
--- 376,414 ----
   	    if ($md5 eq $application->{'md5password'}) {
   		# &do_log('debug', 'Auth::remote_app_check_password : authentication succeed for %s',$application->{'name'});
   		my %proxy_for_vars ;
+ # SJS START
+  		my %set_vars ;
+ # SJS END
   		foreach my $varname (@{$application->{'proxy_for_variables'}}) {
   		    $proxy_for_vars{$varname}=1;
   		}		
! # SJS START
!  		foreach my $varname (@{$application->{'set_variables'}}) {
!  		    $set_vars{$1}=$2 if($varname =~ /(\S+)=(.*)/);
!  		}
! 		if( $application->{'allow_commands'} ) {
!  		    foreach my $cmdname (@{$application->{'allow_commands'}}) {
!  		        
!  			return (\%proxy_for_vars,\%set_vars) if($cmdname eq $service);
!  		    }
!  		    &do_log('info', 'Auth::remote_app_check_password: Illegal command %s received from %s', $service, $trusted_application_name);
!  		    return (undef,undef);
! 		}
!  		return (\%proxy_for_vars,\%set_vars);
! # SJS END
   	    }else{
   		&do_log('info', 'Auth::remote_app_check_password: bad password from %s', $trusted_application_name);
! # SJS START
!  		return (undef,undef);
! # SJS END
   	    }
   	}
      }				 
      # no matching application found
      &do_log('info', 'Auth::remote_app-check_password: unknown application name %s', $trusted_application_name);
! # SJS START
!     return (undef,undef);
! # SJS END
  }
   
  # create new entry in one_time_ticket table using a rand as id so later access is authenticated
diff -crB sympa-6.1.23/wwsympa/wwsympa.fcgi.in sympa/wwsympa/wwsympa.fcgi.in
*** sympa-6.1.23/wwsympa/wwsympa.fcgi.in	2014-09-13 03:52:51.000000000 +1200
--- sympa/wwsympa/wwsympa.fcgi.in	2015-02-18 10:07:30.502220982 +1300
***************
*** 1493,1498 ****
--- 1493,1500 ----
  		 $param->{'which_info'}{$l}{'host'} = $list->{'admin'}{'host'};
  		 $param->{'which_info'}{$l}{'info'} = 1;
  		 $param->{'which_info'}{$l}{'admin'} = 1;
+ # SJS ADD
+ 		 $param->{'which_info'}{$l}{'owner'} = 1;
  	     }
  	     foreach my $list (@{$param->{'get_which_editor'}}) {
  		 my $l = $list->{'name'};
***************
*** 1501,1506 ****
--- 1503,1510 ----
  		 $param->{'which_info'}{$l}{'host'} = $list->{'admin'}{'host'};
  		 $param->{'which_info'}{$l}{'info'} = 1;
  		 $param->{'which_info'}{$l}{'admin'} = 1;
+ # SJS ADD
+ 		 $param->{'which_info'}{$l}{'editor'} = 1;
  	     }
  	 }
  	 ## Set cookies unless client use https authentication
***************
*** 3998,4004 ****
  	 }
       }
  
!      my $all_lists = &List::get_lists($robot);
       foreach my $list ( @$all_lists ) {
  
  	 my $sender = $param->{'user'}{'email'} || 'nobody';
--- 4002,4029 ----
  	 }
       }
  
! # SJS START : optimise for MySQL
! #    my $all_lists = &List::get_lists($robot);
!      my $statement;
!      if ($Conf::Conf{'db_list_cache'} eq 'on') {
! 	 if ($Conf::Conf{'db_type'} eq 'mysql') {
! 	    $statement = sprintf "topics_list RLIKE '%s'",  $in{'topic'};
! 	}elsif($Conf::Conf{'db_type'} eq 'Oracle') {
! 	    $statement = sprintf "topics_list REGEXP_LIKE '%s'",  $in{'topic'};
! 	}elsif($Conf::Conf{'db_type'} eq 'Pg') {
! 	    $statement = sprintf "topics_list ~ '%s'",  $in{'topic'};
! 	}elsif($Conf::Conf{'db_type'} eq 'Sybase') {
! 	    $statement = sprintf "topics_list REGEXP '%s'", $in{'topic'};
! 	}
!     }
!     my $all_lists;
!     if ($statement) {
! 	my @lists = &List::get_lists_db($statement) || undef;
! 	$all_lists = &List::get_lists($robot, undef, @lists);
!     }else{
! 	$all_lists = &List::get_lists($robot);
!     }
! # SJS END
       foreach my $list ( @$all_lists ) {
  
  	 my $sender = $param->{'user'}{'email'} || 'nobody';
***************
*** 9670,9678 ****
  	  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'});	      
  	    }
  	  }
--- 9695,9706 ----
  	  foreach my $entry (@{$new_admin->{$admin_type}}) {
  
  	    unless ($previous_emails{$entry->{'email'}}) {
! # SJS Change : only if reception=mail
!               if( $entry->{'reception'} ne 'nomail' ) {
! 	        ## Notify the new list owner/editor
! 	        $list->send_notify_to_user('added_as_listadmin', $entry->{'email'},{'admin_type' => $admin_type, 'delegator' => $param->{'user'}{'email'}});
!               }
! # SJS end
  	      &report::notice_report_web('user_notified',{'notified_user' => $entry->{'email'}},$param->{'action'});	      
  	    }
  	  }
