FS::svc_Radius_Mixin
FS::svc_Tower_Mixin
FS::svc_IP_Mixin
- FS::svc_Common );
+ FS::Password_Mixin
+ FS::svc_Common
+ );
+
+use strict;
use vars qw( $DEBUG $me $conf $skip_fuzzyfiles
$dir_prefix @shells $usernamemin
$usernamemax $passwordmin $passwordmax
use FS::Msgcat qw(gettext);
use FS::UI::bytecount;
use FS::UI::Web;
+use FS::PagedSearch qw( psearch ); # XXX in v4, replace with FS::Cursor
use FS::part_pkg;
use FS::part_svc;
use FS::svc_acct_pop;
$smtpmachine = $conf->config('smtpmachine');
$radius_password = $conf->config('radius-password') || 'Password';
$radius_ip = $conf->config('radius-ip') || 'Framed-IP-Address';
- @pw_set = ( 'A'..'Z' ) if $conf->exists('password-generated-allcaps');
+ @pw_set = FS::svc_acct->pw_set;
}
);
@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
-@pw_set = ( 'a'..'z', 'A'..'Z', '0'..'9', '(', ')', '#', '.', ',' );
sub _cache {
my $self = shift;
'display_weight' => 10,
'cancel_weight' => 50,
'ip_field' => 'slipip',
+ 'manual_require' => 1,
'fields' => {
'dir' => 'Home directory',
'uid' => {
disable_default => 1,
disable_fixed => 1,
disable_select => 1,
+ required => 1,
},
'password_selfchange' => { label => 'Password modification',
type => 'checkbox',
label => 'Quota', #Mail storage limit
type => 'text',
disable_inventory => 1,
- disable_select => 1,
},
'file_quota'=> {
label => 'File storage limit',
type => 'text',
disable_inventory => 1,
- disable_select => 1,
},
'file_maxnum'=> {
label => 'Number of files limit',
type => 'text',
disable_inventory => 1,
- disable_select => 1,
},
'file_maxsize'=> {
label => 'File size limit',
type => 'text',
disable_inventory => 1,
- disable_select => 1,
},
- '_password' => 'Password',
+ '_password' => { label => 'Password',
+ #required => 1
+ },
'gid' => {
label => 'GID',
def_info => 'when blank, defaults to UID',
'domsvc' => {
label => 'Domain',
type => 'select',
+ select_svc => 1,
select_table => 'svc_domain',
select_key => 'svcnum',
select_label => 'domain',
disable_inventory => 1,
+ required => 1,
},
'pbxsvc' => { label => 'PBX',
type => 'select-svc_pbx.html',
'child_objects' => $self->child_objects,
%options,
);
+
+ $error ||= $self->insert_password_history;
+
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
#welcome email
my @welcome_exclude_svcparts = $conf->config('svc_acct_welcome_exclude');
- unless ( grep { $_ eq $self->svcpart } @welcome_exclude_svcparts ) {
+ unless ($FS::svc_Common::noexport_hack or ( grep { $_ eq $self->svcpart } @welcome_exclude_svcparts )) {
my $error = '';
my $msgnum = $conf->config('welcome_msgnum', $agentnum);
if ( $msgnum ) {
}
}
- my $error = $self->SUPER::delete; # usergroup here
+ foreach my $svc_phone (
+ qsearch( 'svc_phone', { 'forward_svcnum' => $self->svcnum })
+ ) {
+ $svc_phone->set('forward_svcnum', '');
+ my $error = $svc_phone->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my $error = $self->delete_password_history
+ || $self->SUPER::delete; # usergroup here
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
my $dbh = dbh;
$error = $new->SUPER::replace($old, @_); # usergroup here
+
+ # don't need to record this unless the password was changed
+ if ( $old->_password ne $new->_password ) {
+ $error ||= $new->insert_password_history;
+ }
+
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error if $error;
$recref->{_password} = $1;
} else {
return gettext('illegal_password'). " $passwordmin-$passwordmax ".
- FS::Msgcat::_gettext('illegal_password_characters').
- ": ". $recref->{_password};
+ FS::Msgcat::_gettext('illegal_password_characters');
}
if ( $password_noampersand ) {
$self->cust_svc->seconds_since(@_);
}
-=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END
-
-Returns the numbers of seconds this account has been online between
-TIMESTAMP_START (inclusive) and TIMESTAMP_END (exclusive), according to an
-external SQL radacct table, specified via sqlradius export. Sessions which
-started in the specified range but are still open are counted from session
-start to the end of the range (unless they are over 1 day old, in which case
-they are presumed missing their stop record and not counted). Also, sessions
-which end in the range but started earlier are counted from the start of the
-range to session end. Finally, sessions which start before the range but end
-after are counted for the entire range.
-
-TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
-L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
-
-=cut
-
-#note: POD here, implementation in FS::cust_svc
-sub seconds_since_sqlradacct {
- my $self = shift;
- $self->cust_svc->seconds_since_sqlradacct(@_);
-}
-
-=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
-
-Returns the sum of the given attribute for all accounts (see L<FS::svc_acct>)
-in this package for sessions ending between TIMESTAMP_START (inclusive) and
-TIMESTAMP_END (exclusive).
-
-TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
-L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
-
-=cut
-
-#note: POD here, implementation in FS::cust_svc
-sub attribute_since_sqlradacct {
- my $self = shift;
- $self->cust_svc->attribute_since_sqlradacct(@_);
-}
-
-=item get_session_history TIMESTAMP_START TIMESTAMP_END
-
-Returns an array of hash references of this customers login history for the
-given time range. (document this better)
-
-=cut
-
-sub get_session_history {
- my $self = shift;
- $self->cust_svc->get_session_history(@_);
-}
-
=item last_login_text
Returns text describing the time of last login.
$self->last_login ? ctime($self->last_login) : 'unknown';
}
-=item get_cdrs TIMESTAMP_START TIMESTAMP_END [ 'OPTION' => 'VALUE ... ]
+=item psearch_cdrs OPTIONS
-=cut
+Returns a paged search (L<FS::PagedSearch>) for Call Detail Records
+associated with this service. For svc_acct, "associated with" means that
+either the "src" or the "charged_party" field of the CDR matches either
+the "username" field of the service or the username@domain label.
-sub get_cdrs {
- my($self, $start, $end, %opt ) = @_;
-
- my $did = $self->username; #yup
-
- my $prefix = $opt{'default_prefix'}; #convergent.au '+61'
-
- my $for_update = $opt{'for_update'} ? 'FOR UPDATE' : '';
-
- #SELECT $for_update * FROM cdr
- # WHERE calldate >= $start #need a conversion
- # AND calldate < $end #ditto
- # AND ( charged_party = "$did"
- # OR charged_party = "$prefix$did" #if length($prefix);
- # OR ( ( charged_party IS NULL OR charged_party = '' )
- # AND
- # ( src = "$did" OR src = "$prefix$did" ) # if length($prefix)
- # )
- # )
- # AND ( freesidestatus IS NULL OR freesidestatus = '' )
-
- my $charged_or_src;
- if ( length($prefix) ) {
- $charged_or_src =
- " AND ( charged_party = '$did'
- OR charged_party = '$prefix$did'
- OR ( ( charged_party IS NULL OR charged_party = '' )
- AND
- ( src = '$did' OR src = '$prefix$did' )
- )
- )
- ";
- } else {
- $charged_or_src =
- " AND ( charged_party = '$did'
- OR ( ( charged_party IS NULL OR charged_party = '' )
- AND
- src = '$did'
- )
- )
- ";
+=cut
+sub psearch_cdrs {
+ my($self, %options) = @_;
+ my @fields;
+ my %hash;
+ my @where;
+
+ my $did = dbh->quote($self->username);
+ my $diddomain = dbh->quote($self->label);
+
+ my $prefix = $options{'default_prefix'} || ''; #convergent.au '+61'
+ my $prefixdid = dbh->quote($prefix . $self->username);
+
+ my $for_update = $options{'for_update'} ? 'FOR UPDATE' : '';
+
+ if ( $options{inbound} ) {
+ # these will be selected under their DIDs
+ push @where, "FALSE";
+ }
+
+ my @orwhere;
+ if (!$options{'disable_charged_party'}) {
+ push @orwhere,
+ "charged_party = $did",
+ "charged_party = $prefixdid",
+ "charged_party = $diddomain"
+ ;
+ }
+ if (!$options{'disable_src'}) {
+ push @orwhere,
+ "src = $did AND charged_party IS NULL",
+ "src = $prefixdid AND charged_party IS NULL",
+ "src = $diddomain AND charged_party IS NULL"
+ ;
+ }
+ push @where, '(' . join(' OR ', @orwhere) . ')';
+
+ # $options{'status'} = '' is meaningful; for the rest of them it's not
+ if ( exists $options{'status'} ) {
+ $hash{'freesidestatus'} = $options{'status'};
+ }
+ if ( $options{'cdrtypenum'} ) {
+ $hash{'cdrtypenum'} = $options{'cdrtypenum'};
+ }
+ if ( $options{'calltypenum'} ) {
+ $hash{'calltypenum'} = $options{'calltypenum'};
+ }
+ if ( $options{'begin'} ) {
+ push @where, 'startdate >= '. $options{'begin'};
+ }
+ if ( $options{'end'} ) {
+ push @where, 'startdate < '. $options{'end'};
+ }
+ if ( $options{'nonzero'} ) {
+ push @where, 'duration > 0';
+ }
+
+ my $extra_sql = join(' AND ', @where);
+ if ($extra_sql) {
+ if (keys %hash) {
+ $extra_sql = " AND ".$extra_sql;
+ } else {
+ $extra_sql = " WHERE ".$extra_sql;
+ }
}
-
- qsearch(
- 'select' => "$for_update *",
+ return psearch({
+ 'select' => '*',
'table' => 'cdr',
- 'hashref' => {
- #( freesidestatus IS NULL OR freesidestatus = '' )
- 'freesidestatus' => '',
- },
- 'extra_sql' => $charged_or_src,
+ 'hashref' => \%hash,
+ 'extra_sql' => $extra_sql,
+ 'order_by' => "ORDER BY startdate $for_update",
+ });
+}
- );
+=item get_cdrs (DEPRECATED)
+
+Like psearch_cdrs, but returns all the L<FS::cdr> objects at once, in a
+single list. Arguments are the same as for psearch_cdrs.
+
+=cut
+sub get_cdrs {
+ my $self = shift;
+ my $psearch = $self->psearch_cdrs(@_);
+ qsearch ( $psearch->{query} )
}
# sub radius_groups has moved to svc_Radius_Mixin
$self->domain. '/maildirs/'. $self->username. '/';
}
+=item password_svc_check
+
+Override, for L<FS::Password_Mixin>. Not really intended for other use.
+
+=cut
+
+sub password_svc_check {
+ my ($self, $password) = @_;
+ foreach my $field ( qw(username finger) ) {
+ foreach my $word (split(/\W+/,$self->get($field))) {
+ next unless length($word) > 2;
+ if ($password =~ /$word/i) {
+ return qq(Password contains account information '$word');
+ }
+ }
+ }
+ return '';
+}
+
=back
=head1 CLASS METHODS