1 # Vend::UserDB - Interchange user database functions
3 # $Id: UserDB.pm,v 2.66 2009-05-01 13:50:01 pajamian Exp $
5 # Copyright (C) 2002-2008 Interchange Development Group
6 # Copyright (C) 1996-2002 Red Hat, Inc.
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
20 $VERSION = substr(q$Revision: 2.66 $, 10);
24 @S_FIELDS @B_FIELDS @P_FIELDS @I_FIELDS
33 no warnings qw(uninitialized numeric);
35 my $ready = new Vend::Safe;
46 ::logGlobal("SHA passwords disabled: $@");
49 # The object encryption methods take three arguments: object, password, and
50 # mystery meat. If called in the context of new_account(), the mystery meat
51 # is the salt (which is not always used). If called in the context of
52 # login(), then the mystery meat is the entire password field from the
53 # database (with salt, if applicable).
55 default => \&enc_default,
57 md5_salted => \&enc_md5_salted,
63 my ($pwd, $salt) = @_;
64 return crypt($pwd, $salt);
69 return Digest::MD5::md5_hex(shift);
72 # This particular md5_salted encryption stores the salt with the password
73 # in colon-separated format: /.+:(..)/. It is compatible with Zen Cart.
74 # Detecting context based on the length of the mystery meat is a little
75 # hokey; it would be more ideal to specify or detect the context
76 # explicitly in/from the object itself (or as a named/separate parameter).
78 my ($obj, $password, $mystery_meat) = @_;
82 my $mystery_meat_length = length $mystery_meat;
83 if ($mystery_meat_length == 35) {
84 # Extract only the salt; we don't need the database password here.
85 my (undef, $db_salt) = split(':', $mystery_meat);
86 $encrypted = Digest::MD5::md5_hex($db_salt . $password);
87 $return_salt = $db_salt;
90 if ($mystery_meat_length != 2) {
91 # Assume the mystery meat is a salt and soldier on anyway.
92 ::logError("Unrecognized salt for md5_salted encryption.");
94 $return_salt = $mystery_meat;
95 $encrypted = Digest::MD5::md5_hex($return_salt . $password);
98 return "$encrypted:$return_salt";
104 $obj->log_either('SHA passwords unavailable. Is Digest::SHA installed?');
107 return Digest::SHA::sha1_hex(shift);
110 # Maps the length of the encrypted data to the algorithm that
111 # produces it. This method will have to be re-evaluated if competing
112 # algorithms are introduced which produce the same-length value.
122 UserDB.pm -- Interchange User Database Functions
126 userdb $function, %options
130 The Interchange user database saves information for users, including shipping,
131 billing, and preference information. It allows the user to return to a
132 previous session without the requirement for a "cookie" or other persistent
135 It is object-oriented and called via the [userdb] usertag, which calls the
138 It restores and manipulates the form values normally stored in the user session
139 values -- the ones set in forms and read through the C<[value variable]> tags.
140 A special function allows saving of shopping cart contents.
142 The preference, billing, and shipping information is keyed so that different
143 sets of information may be saved, providing and "address_book" function that
144 can save more than one shipping and/or billing address. The set to restore
145 is selected by the form values C<s_nickname>, C<b_nickname>, and C<p_nickname>.
153 $obj->login(); # Form values are
154 # mv_username, mv_password
158 $obj->new_account(); # Form values are
159 # mv_username, mv_password, mv_verify
163 $obj->change_pass(); # Form values are
164 # mv_username, mv_password_old, mv_password, mv_verify(new)
166 Get, set user information:
170 $obj->clear_values();
172 Save, restore filed user information:
174 $obj->get_shipping();
175 $obj->set_shipping();
180 $obj->get_preferences();
181 $obj->set_preferences();
186 =head2 Shipping Address Book
188 The shipping address book saves information relevant to shipping the
189 order. In its simplest form, this can be the only address book needed.
190 By default these form values are included:
202 The values are saved with the $obj->set_shipping() method and restored
203 with $obj->get_shipping. A list of the keys available is kept in the
204 form value C<address_book>, suitable for iteration in an HTML select
205 box or in a set of links.
231 The accounts book saves information relevant to billing the
232 order. By default these form values are included:
243 mv_credit_card_exp_month
244 mv_credit_card_exp_year
245 mv_credit_card_reference
247 The values are saved with the $obj->set_billing() method and restored
248 with $obj->get_billing. A list of the keys available is kept in the
249 form value C<accounts>, suitable for iteration in an HTML select
250 box or in a set of links.
271 mv_credit_card_exp_month
272 mv_credit_card_exp_year
273 mv_credit_card_reference
279 Preferences are miscellaneous session information. They include
280 by default the fields C<email>, C<fax>, C<phone_night>,
281 and C<fax_order>. The field C<p_nickname> acts as a key to select
286 There are several database locations that have special purposes. These
287 fields are not saved as user values.
291 =item USERNAME default: username
293 The username or key field of the database table.
295 =item BILLING default: accounts
297 Billing address hash field.
299 =item SHIPPING default: address_book
301 Shipping address hash field.
303 =item PREFERENCES default: preferences
305 Miscellaneous information hash field.
307 =item FEEDBACK default: feedback
309 Customer feedback hash field.
311 =item PRICING default: price_level
313 Customer pricing level marker.
315 =item CARTS default: carts
317 Saved carts hash field.
319 =item PASSWORD default: password
321 Customer password info. If C<crypt> is set, may be encrypted.
323 =item LAST default: mod_time
327 =item EXPIRATION default: expiration
329 Expiration of account.
331 =item OUTBOARD_KEY default: (none)
333 Key information for linking to another table of address or other info.
335 =item GROUPS default: groups
337 Groups they should be logged into.
339 =item SUPER default: super
341 Whether they are a superuser (admin).
343 =item ACL default: acl
345 =item FILE_ACL default: file_acl
347 =item DB_ACL default: db_acl
349 Location of access control information.
351 =item CREATED_DATE_ISO default: (none)
353 =item CREATED_DATE_UNIX default: (none)
355 =item UPDATED_DATE_ISO default: (none)
357 =item UPDATED_DATE_UNIX default: (none)
361 =item MERGED_USER default: (none)
363 The user id of another account this was merged into. If present, and data (should
364 be a valid user id) is present in the field, the user will be logged as that username.
370 # user name and password restrictions
371 $USERNAME_GOOD_CHARS = '[-A-Za-z0-9_@.]';
373 @P_FIELDS = qw ( p_nickname email fax email_copy phone_night mail_list fax_order );
377 s_nickname b_nickname
388 @B_to_S{values %S_to_B} = keys %S_to_B;
392 my ($class, %options) = @_;
395 if( $Vend::Cfg->{UserDB} ) {
396 if( $options{profile} ) {
397 $loc = $Vend::Cfg->{UserDB_repository}{$options{profile}};
400 $options{profile} = 'default';
401 $loc = $Vend::Cfg->{UserDB};
403 $loc = {} unless $loc;
405 while ( ($k,$v) = each %$loc) {
406 $options{$k} = $v unless defined $options{$k};
410 if($options{billing}) {
411 $options{billing} =~ s/[,\s]+$//;
412 $options{billing} =~ s/^[,\s]+//;
413 @B_FIELDS = split /[\s,]+/, $options{billing};
415 if($options{shipping}) {
416 $options{shipping} =~ s/[,\s]+$//;
417 $options{shipping} =~ s/^[,\s]+//;
418 @S_FIELDS = split /[\s,]+/, $options{shipping};
420 if($options{preferences}) {
421 $options{preferences} =~ s/[,\s]+$//;
422 $options{preferences} =~ s/^[,\s]+//;
423 @P_FIELDS = split /[\s,]+/, $options{preferences};
425 if($options{ignore}) {
426 $options{ignore} =~ s/[,\s]+$//;
427 $options{ignore} =~ s/^[,\s]+//;
428 @I_FIELDS = split /[\s,]+/, $options{ignore};
431 USERNAME => $options{username} ||
433 $CGI::values{mv_username} ||
435 OLDPASS => $options{oldpass} || $CGI::values{mv_password_old} || '',
436 PASSWORD => $options{password} || $CGI::values{mv_password} || '',
437 VERIFY => $options{verify} || $CGI::values{mv_verify} || '',
438 NICKNAME => $options{nickname} || '',
439 PROFILE => $options{profile} || '',
441 USERMINLEN => $options{userminlen} || 2,
442 PASSMINLEN => $options{passminlen} || 4,
443 VALIDCHARS => $options{validchars} ? ('[' . $options{validchars} . ']') : $USERNAME_GOOD_CHARS,
444 CRYPT => defined $options{'crypt'}
446 : ! $::Variable->{MV_NO_CRYPT},
447 CGI => ( defined $options{cgi} ? is_yes($options{cgi}) : 1),
449 DB_ID => $options{database} || 'userdb',
450 OPTIONS => \%options,
451 OUTBOARD => $options{outboard} || '',
453 USERNAME => $options{user_field} || 'username',
454 BILLING => $options{bill_field} || 'accounts',
455 SHIPPING => $options{addr_field} || 'address_book',
456 PREFERENCES => $options{pref_field} || 'preferences',
457 FEEDBACK => $options{feedback_field} || 'feedback',
458 PRICING => $options{pricing_field} || 'price_level',
459 ORDERS => $options{ord_field} || 'orders',
460 CARTS => $options{cart_field} || 'carts',
461 PASSWORD => $options{pass_field} || 'password',
462 LAST => $options{time_field} || 'mod_time',
463 EXPIRATION => $options{expire_field} || 'expiration',
464 OUTBOARD_KEY=> $options{outboard_key_col},
465 GROUPS => $options{groups_field}|| 'groups',
466 MERGED_USER => $options{merged_user},
467 SUPER => $options{super_field}|| 'super',
468 ACL => $options{acl} || 'acl',
469 FILE_ACL => $options{file_acl} || 'file_acl',
470 DB_ACL => $options{db_acl} || 'db_acl',
471 CREATED_DATE_ISO => $options{created_date_iso},
472 CREATED_DATE_UNIX => $options{created_date_epoch},
473 UPDATED_DATE_ISO => $options{updated_date_iso},
474 UPDATED_DATE_UNIX => $options{updated_date_epoch},
482 return $self if $options{no_open};
484 set_db($self) or die errmsg("user database %s does not exist.", $self->{DB_ID}) . "\n";
486 return $Vend::user_object = $self;
491 my $user = new Vend::UserDB no_open => 1, %options;
494 push @out, $user->{LOCATION}{USERNAME};
495 push @out, $user->{LOCATION}{PASSWORD};
496 push @out, $user->{LOCATION}{LAST};
497 push @out, @S_FIELDS, @B_FIELDS, @P_FIELDS;
498 push @out, $user->{LOCATION}{ORDERS};
499 push @out, $user->{LOCATION}{SHIPPING};
500 push @out, $user->{LOCATION}{BILLING};
501 push @out, $user->{LOCATION}{PREFERENCES};
504 my $delimiter = $options{delimiter} || "\t";
505 if($delimiter =~ /csv|comma/i) {
509 my $separator = $options{separator} || "\n";
512 print join $delimiter, @out;
515 if ($options{verbose}) {
518 if(length $delimiter == 1) {
519 $msg .= sprintf '\0%o', ord($delimiter);
525 $msg .= "Separator=";
526 if(length $separator == 1) {
527 $msg .= sprintf '\0%o', ord($separator);
532 $msg .= "\nNicknames: ";
533 $msg .= "SHIPPING=$S_FIELDS[0] ";
534 $msg .= "BILLING=$B_FIELDS[0] ";
535 $msg .= "PREFERENCES=$P_FIELDS[0] ";
536 $msg .= "\nFields:\n";
537 $msg .= join "\n", @out;
542 $type = 4, $ext = '.csv', last SWITCH if $csv;
543 $type = 6, last SWITCH if $delimiter eq "\t";
544 $type = 5, last SWITCH if $delimiter eq "|";
545 $type = 3, last SWITCH
546 if $delimiter eq "\n%%\n" && $separator eq "\n%%%\n";
547 $type = 2, last SWITCH
548 if $delimiter eq "\n" && $separator eq "\n\n";
552 my $id = $user->{DB_ID};
553 $msg .= "Database line in catalog.cfg should be:\n\n";
554 $msg .= "Database $id $id.txt $type";
564 if(! $self->{OPTIONS}{logfile}) {
565 return logError($msg);
573 my $time = $self->{OPTIONS}{unix_time} ? time() :
574 POSIX::strftime("%Y%m%d%H%M", localtime());
576 logData( ($self->{OPTIONS}{logfile} || $Vend::Cfg->{LogFile}),
579 $CGI::remote_host || $CGI::remote_addr,
586 my ($self,%options) = @_;
588 if(! defined $self->{PRESENT}{$self->{LOCATION}{ACL}}) {
589 $self->{ERROR} = errmsg('No ACL field present.');
593 if(not $options{location}) {
594 $self->{ERROR} = errmsg('No location to check.');
598 my $acl = $self->{DB}->field($self->{USERNAME}, $self->{LOCATION}{ACL});
599 $acl =~ /(\s|^)$options{location}(\s|$)/;
604 my ($self,%options) = @_;
606 if(!$self->{PRESENT}{$self->{LOCATION}{ACL}}) {
607 $self->{ERROR} = errmsg('No ACL field present.');
611 if(!$options{location}) {
612 $self->{ERROR} = errmsg('No location to set.');
616 my $acl = $self->{DB}->field($self->{USERNAME}, $self->{LOCATION}{ACL});
617 if($options{'delete'}) {
618 $acl =~ s/(\s|^)$options{location}(\s|$)/$1$2/;
621 $acl =~ s/(\s|^)$options{location}(\s|$)/$1$2/;
622 $acl .= " $options{location}";
625 $self->{DB}->set_field( $self->{USERNAME}, $self->{LOCATION}{ACL}, $acl);
626 return $acl if $options{show};
631 my ($self, $loc, %options) = @_;
632 return undef unless $options{location};
633 $options{mode} = 'r' if ! defined $options{mode};
634 my $acl = $self->{DB}->field( $self->{USERNAME}, $loc);
635 my $f = $ready->reval($acl);
636 return undef unless exists $f->{$options{location}};
637 return 1 if ! $options{mode};
638 if($options{mode} =~ /^\s*expire\b/i) {
639 my $cmp = $f->{$options{location}};
640 return $cmp < time() ? '' : 1;
642 return 1 if $f->{$options{location}} =~ /$options{mode}/i;
647 my ($self, $loc, %options) = @_;
648 return undef unless $self->{OPTIONS}{location};
649 if($options{mode} =~ /^\s*expires?\s+(.*)/i) {
650 $options{mode} = adjust_time($1);
652 my $acl = $self->{DB}->field( $self->{USERNAME}, $loc );
653 my $f = $ready->reval($acl) || {};
654 if($options{'delete'}) {
655 delete $f->{$options{location}};
658 $f->{$options{location}} = $options{mode} || 'rw';
660 my $return = $self->{DB}->set_field( $self->{USERNAME}, $loc, uneval_it($f) );
661 return $return if $options{show};
667 return $self->_set_acl($self->{LOCATION}{FILE_ACL}, @_);
672 return $self->_set_acl($self->{LOCATION}{DB_ACL}, @_);
677 return $self->_check_acl($self->{LOCATION}{FILE_ACL}, @_);
682 return $self->_check_acl($self->{LOCATION}{DB_ACL}, @_);
686 my($self, $database) = @_;
688 $database = $self->{DB_ID} unless $database;
690 $Vend::WriteDatabase{$database} = 1;
692 my $db = database_exists_ref($database);
693 return undef unless defined $db;
696 my @fields = $db->columns();
705 if($self->{OPTIONS}{username_email}) {
706 $ignore{$self->{OPTIONS}{username_email_field} || 'email'} = 1;
709 for(values %{$self->{LOCATION}}) {
713 if($self->{OPTIONS}{force_lower}) {
714 @fields = map { lc $_ } @fields;
719 $self->{PRESENT}->{$_} = 1;
725 $self->{DB_FIELDS} = \@final;
729 # Sets location map, returns old value
731 my ($self, $location, $field) = @_;
732 if(! defined $field) {
733 return $self->{LOCATION}->{$location};
736 my $old = $self->{LOCATION}->{$field};
737 $self->{LOCATION}->{$location} = $field;
743 my($self, @fields) = @_;
745 @fields = @{ $self->{DB_FIELDS} } unless @fields;
751 if($self->{OPTIONS}->{constant}) {
752 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{constant} ;
754 my ($k, $v) = split /=/, $_;
760 if($self->{OPTIONS}->{scratch}) {
761 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{scratch} ;
763 my ($k, $v) = split /=/, $_;
769 if($self->{OPTIONS}->{session_hash}) {
770 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{session_hash} ;
772 my ($k, $v) = split /=/, $_;
774 $session_hash{$k} = $v;
779 if(my $s = $scratch{$_}) {
780 if (exists $Vend::Cfg->{ScratchDefault}->{$s}) {
781 $::Scratch->{$s} = $Vend::Cfg->{ScratchDefault}->{$s};
784 delete $::Scratch->{$s};
787 elsif($constant{$_}) {
788 delete $Vend::Session->{constant}{$constant{$_}};
790 elsif($session_hash{$_}) {
791 delete $Vend::Session->{$session_hash{$_}};
794 if (exists $Vend::Cfg->{ValuesDefault}->{$_}) {
795 $::Values->{$_} = $Vend::Cfg->{ValuesDefault}->{$_};
798 delete $::Values->{$_};
800 delete $CGI::values{$_};
808 my($self, $valref, $scratchref) = @_;
810 $valref = $::Values unless ref($valref);
811 $scratchref = $::Scratch unless ref($scratchref);
812 my $constref = $Vend::Session->{constant} ||= {};
814 my @fields = @{ $self->{DB_FIELDS} };
816 if($self->{OPTIONS}{username_email}) {
817 push @fields, $self->{OPTIONS}{username_email_field} || 'email';
821 or die errmsg("No user database found.");
823 unless ( $db->record_exists($self->{USERNAME}) ) {
824 $self->{ERROR} = errmsg("username %s does not exist.", $self->{USERNAME});
833 for(values %{$self->{LOCATION}}) {
838 if($self->{OUTBOARD}) {
839 %outboard = split /[\s=,]+/, $self->{OUTBOARD};
840 push @fields, keys %outboard;
843 if($self->{OPTIONS}->{constant}) {
844 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{constant} ;
846 my ($k, $v) = split /=/, $_;
850 #::logDebug("constant ones: " . join " ", @s);
853 if($self->{OPTIONS}->{session_hash}) {
854 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{session_hash} ;
856 my ($k, $v) = split /=/, $_;
858 $session_hash{$k} = $v;
860 #::logDebug("session_hash ones: " . join " ", @s);
863 if($self->{OPTIONS}->{scratch}) {
864 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{scratch} ;
866 my ($k, $v) = split /=/, $_;
870 #::logDebug("scratch ones: " . join " ", @s);
874 my $row = $db->row_hash($self->{USERNAME});
875 my $outkey = $self->{LOCATION}->{OUTBOARD_KEY}
876 ? $row->{$self->{LOCATION}->{OUTBOARD_KEY}}
879 if(my $ef = $self->{OPTIONS}->{extra_fields}) {
880 my @s = grep /\w/, split /[\s,]+/, $ef;
881 my $field = $self->{LOCATION}{PREFERENCES};
882 my $loc = $self->{OPTIONS}{extra_selector} || 'default';
883 my $hash = get_option_hash($row->{$field});
884 if($hash and $hash = $hash->{$loc} and ref($hash) eq 'HASH') {
887 $::Scratch->{$_} = $hash->{$_};
890 $::Values->{$_} = $hash->{$_};
898 $self->{PRESENT}->{$_} = 1;
903 my ($t, $c, $k) = split /:+/, $outboard{$_};
904 $val = ::tag_data($t, ($c || $_), $outkey, { foreign => $k });
911 if($k = $scratch{$_}) {
912 $scratchref->{$k} = $val;
915 elsif($k = $constant{$_}) {
916 $constref->{$k} = $val;
919 elsif($k = $session_hash{$_}) {
920 $Vend::Session->{$k} = string_to_ref($val) || {};
923 $valref->{$_} = $val;
928 foreach $area (qw!SHIPPING BILLING PREFERENCES CARTS!) {
929 my $f = $self->{LOCATION}->{$area};
930 if ($self->{PRESENT}->{$f}) {
931 my $s = $self->get_hash($area);
932 die errmsg("Bad structure in %s: %s", $f, $@) if $@;
933 $::Values->{$f} = join "\n", sort keys %$s;
941 my($self, $valref, $scratchref) = @_;
943 $valref = $::Values unless ref($valref);
944 $scratchref = $::Scratch unless ref($scratchref);
946 my $user = $self->{USERNAME};
948 my @fields = @{$self->{DB_FIELDS}};
950 my $db = $self->{DB};
952 unless ( $db->record_exists($self->{USERNAME}) ) {
953 $self->{ERROR} = errmsg("username %s does not exist.", $self->{USERNAME});
961 if ($self->{OPTIONS}{read_only}) {
962 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{read_only} ;
963 $read_only{$_} = 1 for @s;
966 if($self->{OPTIONS}->{scratch}) {
967 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{scratch} ;
969 my ($k, $v) = split /=/, $_;
975 if($self->{OPTIONS}->{constant}) {
976 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{constant} ;
978 my ($k, $v) = split /=/, $_;
984 if($self->{OPTIONS}->{session_hash}) {
985 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{session_hash} ;
987 my ($k, $v) = split /=/, $_;
989 $session_hash{$k} = $v;
995 if($self->{OUTBOARD}) {
996 %outboard = split /[\s=,]+/, $self->{OUTBOARD};
997 push @fields, keys %outboard;
1007 if(my $ef = $self->{OPTIONS}->{extra_fields}) {
1008 my $row = $db->row_hash($user);
1009 my @s = grep /\w/, split /[\s,]+/, $ef;
1010 my $field = $self->{LOCATION}{PREFERENCES};
1011 my $loc = $self->{OPTIONS}{extra_selector} || 'default';
1012 my $hash = get_option_hash( $row->{$field} ) || {};
1014 my $subhash = $hash->{$loc} ||= {};
1016 $subhash->{$_} = $scratch{$_} ? $scratchref->{$_} : $valref->{$_};
1019 push @extra, $field;
1020 push @extra, uneval_it($hash);
1024 #::logDebug("set_values saving $_ as $valref->{$_}\n");
1027 if ($read_only{$_}) {
1028 # Pull from get_values only; never write through set_values
1031 if ($k = $scratch{$_}) {
1032 $val = $scratchref->{$k}
1033 if defined $scratchref->{$k};
1035 elsif ($constant{$_}) {
1036 # we never store constants
1039 elsif ($k = $session_hash{$_}) {
1040 $val = uneval_it($Vend::Session->{$k});
1043 $val = $valref->{$_}
1044 if defined $valref->{$_};
1047 next if ! defined $val;
1050 my ($t, $c, $k) = split /:+/, $outboard{$_};
1051 ::tag_data($t, ($c || $_), $self->{USERNAME}, { value => $val, foreign => $k });
1053 elsif ($db->test_column($_)) {
1059 "cannot set unknown userdb field %s to: %s",
1069 if($dfield = $self->{OPTIONS}{updated_date_iso}) {
1070 if($self->{OPTIONS}{updated_date_gmtime}) {
1071 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%SZ', gmtime());
1073 elsif($self->{OPTIONS}{updated_date_showzone}) {
1074 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%S %z', localtime());
1077 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime());
1080 elsif($dfield = $self->{OPTIONS}{updated_date_epoch}) {
1084 if($dfield and $dstring) {
1085 if($db->test_column($dfield)) {
1086 push @bfields, $dfield;
1087 push @bvals, $dstring;
1090 my $msg = errmsg("updated field %s doesn't exist", $dfield);
1091 Vend::Tags->warnings($msg);
1096 push @bfields, shift @extra;
1097 push @bvals, shift @extra;
1100 #::logDebug("bfields=" . ::uneval(\@bfields));
1101 #::logDebug("bvals=" . ::uneval(\@bvals));
1103 $db->set_slice($user, \@bfields, \@bvals);
1108 my $msg = errmsg("error saving values in userdb: %s", $@);
1109 $self->{ERROR} = $msg;
1114 # Changes made to support Accounting Interface.
1116 if(my $l = $Vend::Cfg->{Accounting}) {
1119 while ($indexvar <= (scalar @bfields)) {
1120 $hashvar{ $bfields[$indexvar] } = $bvals[$indexvar];
1124 my $class = $l->{Class};
1131 "Failed to save customer data with accounting system %s: %s",
1136 my $returnval = $obj->save_customer_data($user, \%hashvar);
1144 my $ref = $self->set_hash('BILLING', @B_FIELDS );
1150 my $ref = $self->set_hash('SHIPPING', @S_FIELDS );
1154 sub set_preferences {
1156 my $ref = $self->set_hash('PREFERENCES', @P_FIELDS );
1162 my $ref = $self->get_hash('SHIPPING', @S_FIELDS );
1168 my $ref = $self->get_hash('BILLING', @B_FIELDS );
1172 sub get_preferences {
1174 my $ref = $self->get_hash('PREFERENCES', @P_FIELDS );
1178 sub get_shipping_names {
1180 my $ref = $self->get_hash('SHIPPING');
1181 return undef unless ref $ref;
1182 $::Values->{$self->{LOCATION}{SHIPPING}} = join "\n", sort keys %$ref;
1183 return $::Values->{$self->{LOCATION}{SHIPPING}} if $self->{OPTIONS}{show};
1187 sub get_shipping_hashref {
1189 my $ref = $self->get_hash('SHIPPING');
1190 return $ref if ref($ref) eq 'HASH';
1194 sub get_billing_names {
1196 my $ref = $self->get_hash('BILLING');
1197 return undef unless ref $ref;
1198 $::Values->{$self->{LOCATION}{BILLING}} = join "\n", sort keys %$ref;
1199 return $::Values->{$self->{LOCATION}{BILLING}} if $self->{OPTIONS}{show};
1203 sub get_billing_hashref {
1205 my $ref = $self->get_hash('BILLING');
1206 return $ref if ref($ref) eq 'HASH';
1210 sub get_preferences_names {
1212 my $ref = $self->get_hash('PREFERENCES');
1213 return undef unless ref $ref;
1214 $::Values->{$self->{LOCATION}{PREFERENCES}} = join "\n", sort keys %$ref;
1215 return $::Values->{$self->{LOCATION}{PREFERENCES}} if $self->{OPTIONS}{show};
1219 sub get_cart_names {
1221 my $ref = $self->get_hash('CARTS');
1222 return undef unless ref $ref;
1223 $::Values->{$self->{LOCATION}{CARTS}} = join "\n", sort keys %$ref;
1224 return $::Values->{$self->{LOCATION}{CARTS}} if $self->{OPTIONS}{show};
1228 sub delete_billing {
1230 $self->delete_nickname('BILLING', @B_FIELDS );
1236 $self->delete_nickname('CARTS', $self->{NICKNAME});
1240 sub delete_shipping {
1242 $self->delete_nickname('SHIPPING', @S_FIELDS );
1246 sub delete_preferences {
1248 $self->delete_nickname('PREFERENCES', @P_FIELDS );
1252 sub delete_nickname {
1253 my($self, $name, @fields) = @_;
1255 die errmsg("no fields?") unless @fields;
1256 die errmsg("no name?") unless $name;
1258 $self->get_hash($name) unless ref $self->{$name};
1260 my $nick_field = shift @fields;
1261 my $nick = $self->{NICKNAME} || $::Values->{$nick_field};
1263 delete $self->{$name}{$nick};
1265 my $field_name = $self->{LOCATION}->{$name};
1266 unless($self->{PRESENT}->{$field_name}) {
1267 $self->{ERROR} = errmsg('%s field not present to set %s', $field_name, $name);
1271 my $s = uneval_it($self->{$name});
1273 $self->{DB}->set_field( $self->{USERNAME}, $field_name, $s);
1275 return ($s, $self->{$name});
1279 my($self, $name, @fields) = @_;
1281 die errmsg("no fields?") unless @fields;
1282 die errmsg("no name?") unless $name;
1284 $self->get_hash($name) unless ref $self->{$name};
1286 my $nick_field = shift @fields;
1287 my $nick = $self->{NICKNAME} || $::Values->{$nick_field};
1288 $nick =~ s/^[\0\s]+//;
1289 $nick =~ s/[\0\s]+.*//;
1290 $::Values->{$nick_field} = $nick;
1291 $CGI::values{$nick_field} = $nick if $self->{CGI};
1293 die errmsg("no nickname?") unless $nick;
1295 $self->{$name}{$nick} = {} unless $self->{OPTIONS}{keep}
1296 and defined $self->{$name}{$nick};
1299 $self->{$name}{$nick}{$_} = $::Values->{$_}
1300 if defined $::Values->{$_};
1303 my $field_name = $self->{LOCATION}->{$name};
1304 unless($self->{PRESENT}->{$field_name}) {
1305 $self->{ERROR} = errmsg('%s field not present to set %s', $field_name, $name);
1309 my $s = uneval_it($self->{$name});
1311 $self->{DB}->set_field( $self->{USERNAME}, $field_name, $s);
1313 return ($s, $self->{$name});
1317 my($self, $name, @fields) = @_;
1319 my $field_name = $self->{LOCATION}->{$name};
1323 die errmsg("no name?") unless $name;
1324 die errmsg("%s field not present to get %s", $field_name, $name) . "\n"
1325 unless $self->{PRESENT}->{$field_name};
1327 $s = $self->{DB}->field( $self->{USERNAME}, $field_name);
1330 $self->{$name} = string_to_ref($s);
1331 die errmsg("Bad structure in %s: %s", $field_name, $@) if $@;
1334 $self->{$name} = {};
1337 die errmsg("eval failed?") . "\n" unless ref $self->{$name};
1341 $self->{ERROR} = $@;
1345 return $self->{$name} unless @fields;
1348 my $nick_field = shift @fields;
1349 $nick = $self->{NICKNAME} || $::Values->{$nick_field};
1350 $nick =~ s/^[\0\s]+//;
1351 $nick =~ s/[\0\s]+.*//;
1352 $::Values->{$nick_field} = $nick;
1353 $CGI::values{$nick_field} = $nick if $self->{CGI};
1354 die errmsg("no nickname?") unless $nick;
1358 $self->{ERROR} = $@;
1362 $self->{$name}->{$nick} = {} unless defined $self->{$name}{$nick};
1365 delete $::Values->{$_};
1366 $::Values->{$_} = $self->{$name}{$nick}{$_}
1367 if defined $self->{$name}{$nick}{$_};
1368 next unless $self->{CGI};
1369 $CGI::values{$_} = $::Values->{$_};
1371 ::update_user() if $self->{CGI};
1372 return $self->{$name}{$nick};
1379 Using set_enclair() allows logging of enclair password to separate
1380 database table. Designed to allow administration personnel to look
1381 at passwords, without allowing access to web-connected systems. Or
1382 perhaps more properly, to check prior MD5-encrypted password values
1383 for repeat passwords.
1385 Designed to log to an insert-only handle on a table, with a database
1388 create table enclair (
1389 username varchar(32),
1390 password varchar(32),
1391 update_date timestamp
1394 Then a program on a secure behind-firewall no-select write-only
1395 database can access the table, logged via request and username.
1399 UserDB default enclair_db some_table
1401 You can set the following, which have the defaults shown in the
1402 setting. You can also insert %M, which is the MD5 of the password, or
1403 %D which is a datetime localtime value in the form YYYYmmddHHMMSS.
1405 #UserDB default enclair_key_field username
1406 #UserDB default enclair_field password
1407 #UserDB default enclair_query_template "INSERT INTO %t (%U,%P) values (%u,%p)"
1409 String substitutions:
1411 %u value of username
1412 %p value of password
1413 %U field of username
1414 %P field of password
1415 %t enclair table name
1416 %D datetime value of form YYYYmmddHHMMSS
1417 %M MD5 hashed value of password
1425 if( my $tab = $self->{OPTIONS}{enclair_db} ) {
1427 my $dbh = dbref($tab)->dbh();
1428 my $field = $self->{OPTIONS}{enclair_field} || 'password';
1429 my $key = $self->{OPTIONS}{enclair_key_field} || 'username';
1430 my $datetime = POSIX::strftime('%Y%m%d%H%M%S', localtime());
1431 my $md5 = generate_key($self->{PASSWORD});
1432 my $q = $self->{OPTIONS}{enclair_query_template} || "INSERT INTO %t (%U,%P) values (%u,%p)";
1433 $q =~ s/\%M/$dbh->quote($md5)/eg;
1434 $q =~ s/\%D/$dbh->quote($datetime)/eg;
1437 $q =~ s/\%P/$field/g;
1438 $q =~ s/\%u/$dbh->quote($self->{USERNAME})/eg;
1439 $q =~ s/\%p/$dbh->quote($self->{PASSWORD})/eg;
1443 $self->log_either("Failed to set enclair password for $self->{USERNAME}: $@");
1456 my ($user_data, $pw);
1458 # Show this generic error message on login page to avoid
1459 # helping would-be intruders
1460 my $stock_error = errmsg("Invalid user name or password.");
1464 $self = new Vend::UserDB %options;
1467 if($Vend::Cfg->{CookieLogin}) {
1468 $self->{USERNAME} = Vend::Util::read_cookie('MV_USERNAME')
1469 if ! $self->{USERNAME};
1470 $self->{PASSWORD} = Vend::Util::read_cookie('MV_PASSWORD')
1471 if ! $self->{PASSWORD};
1474 if ($self->{VALIDCHARS} !~ / /) {
1475 # If space isn't a valid character in usernames,
1476 # be nice and strip leading and trailing whitespace.
1477 $self->{USERNAME} =~ s/^\s+//;
1478 $self->{USERNAME} =~ s/\s+$//;
1481 if ($self->{OPTIONS}{ignore_case}) {
1482 $self->{PASSWORD} = lc $self->{PASSWORD};
1483 $self->{USERNAME} = lc $self->{USERNAME};
1486 # We specifically check for login attempts with group names to see if
1487 # anyone is trying to exploit a former vulnerability in the demo catalog.
1488 if ($self->{USERNAME} =~ /^:/) {
1489 $self->log_either(errmsg("Denied attempted login with group name '%s'",
1490 $self->{USERNAME}));
1491 die $stock_error, "\n";
1494 # Username must be long enough
1495 if (length($self->{USERNAME}) < $self->{USERMINLEN}) {
1496 $self->log_either(errmsg("Denied attempted login for user name '%s'; must have at least %s characters",
1497 $self->{USERNAME}, $self->{USERMINLEN}));
1498 die $stock_error, "\n";
1501 # Username must contain only valid characters
1502 if ($self->{USERNAME} !~ m{^$self->{VALIDCHARS}+$}) {
1503 $self->log_either(errmsg("Denied attempted login for user name '%s' with illegal characters",
1504 $self->{USERNAME}));
1505 die $stock_error, "\n";
1508 # Fail if password is too short
1509 if (length($self->{PASSWORD}) < $self->{PASSMINLEN}) {
1510 $self->log_either(errmsg("Denied attempted login with user name '%s' and password less than %s characters",
1511 $self->{USERNAME}, $self->{PASSMINLEN}));
1512 die $stock_error, "\n";
1515 my $udb = $self->{DB};
1516 my $foreign = $self->{OPTIONS}{indirect_login};
1519 my $uname = ($self->{PASSED_USERNAME} ||= $self->{USERNAME});
1520 my $ufield = $self->{LOCATION}{USERNAME};
1521 $uname = $udb->quote($uname);
1522 my $q = "select $ufield from $self->{DB_ID} where $foreign = $uname";
1523 #::logDebug("indirect login query: $q");
1524 my $ary = $udb->query($q)
1526 my $msg = errmsg( "Database access error for query: %s", $q);
1531 $self->log_either(errmsg(
1532 @$ary ? "Denied attempted login with ambiguous (indirect from %s) user name %s" : "Denied attempted login with nonexistent (indirect from %s) user name %s",
1537 die $stock_error, "\n";
1539 $self->{USERNAME} = $ary->[0][0];
1542 # If not superuser, an entry must exist in access database
1543 unless ($Vend::superuser) {
1544 unless ($udb->record_exists($self->{USERNAME})) {
1545 $self->log_either(errmsg("Denied attempted login with nonexistent user name '%s'",
1546 $self->{USERNAME}));
1547 die $stock_error, "\n";
1549 unless ($user_data = $udb->row_hash($self->{USERNAME})) {
1550 $self->log_either(errmsg("Login denied after failed fetch of user data for user '%s'",
1551 $self->{USERNAME}));
1552 die $stock_error, "\n";
1554 my $db_pass = $user_data->{ $self->{LOCATION}{PASSWORD} };
1556 $self->log_either(errmsg("Refusing to use blank password from '%s' database for user '%s'", $self->{DB_ID}, $self->{USERNAME}));
1557 die $stock_error, "\n";
1559 $pw = $self->{PASSWORD};
1561 if ( $self->{CRYPT} && $self->{OPTIONS}{promote} ) {
1562 my ($cur_method) = grep { $self->{OPTIONS}{ $_ } } keys %enc_subs;
1563 $cur_method ||= 'default';
1565 my $stored_by = $enc_id{ length($db_pass) };
1568 $cur_method ne $stored_by
1570 $db_pass eq $enc_subs{$stored_by}->($self, $pw, $db_pass)
1573 my $newpass = $enc_subs{$cur_method}->($self, $pw, $db_pass);
1574 my $db_newpass = eval {
1575 $self->{DB}->set_field(
1577 $self->{LOCATION}{PASSWORD},
1582 if ($db_newpass ne $newpass) {
1583 # Usually, an error in the update will cause $db_newpass to be set to a
1584 # useful error string. The usefulness is dependent on DB store itself, though.
1585 my $err_msg = qq{Could not update database "%s" field "%s" with promoted password due to error:\n}
1587 . qq{Check that field "%s" is at least %s characters wide.\n};
1588 $err_msg = ::errmsg(
1591 $self->{LOCATION}{PASSWORD},
1593 $self->{LOCATION}{PASSWORD},
1596 ::logError($err_msg);
1599 $db_pass = $newpass;
1603 if ($self->{CRYPT}) {
1604 $self->{PASSWORD} = $self->do_crypt($pw, $db_pass);
1607 $db_pass = lc $db_pass if $self->{OPTIONS}{ignore_case};
1609 #::logDebug(errmsg("crypt: %s", $self->{CRYPT}));
1610 #::logDebug(errmsg("ignore_case: %s", $self->{OPTIONS}{ignore_case}));
1611 #::logDebug(errmsg("given password: %s", $self->{PASSWORD}));
1612 #::logDebug(errmsg("stored password: %s", $db_pass));
1613 unless ($self->{PASSWORD} eq $db_pass) {
1614 $self->log_either(errmsg("Denied attempted login by user '%s' with incorrect password",
1615 $self->{USERNAME}));
1616 die $stock_error, "\n";
1618 $self->log_either(errmsg("Successful login by user '%s'", $self->{USERNAME}));
1621 if($self->{PRESENT}->{ $self->{LOCATION}{EXPIRATION} } ) {
1624 $cmp = POSIX::strftime("%Y%m%d%H%M", localtime($now))
1625 unless $self->{OPTIONS}->{unix_time};
1626 my $exp = $udb->field(
1628 $self->{LOCATION}{EXPIRATION},
1630 die errmsg("Expiration date not set.") . "\n"
1631 if ! $exp and $self->{EMPTY_EXPIRE_FATAL};
1632 if($exp and $exp < $cmp) {
1633 die errmsg("Expired %s.", $exp) . "\n";
1637 if($self->{PRESENT}->{ $self->{LOCATION}{MERGED_USER} } ) {
1638 my $old = $self->{USERNAME};
1639 my $new = $udb->field(
1641 $self->{LOCATION}{MERGED_USER},
1644 $self->{USERNAME} = $new;
1645 my $msg = errmsg('%s logged in as user %s, merged.', $old, $new);
1646 Vend::Tags->warnings($msg);
1647 $self->log_either($msg);
1651 if($self->{PRESENT}->{ $self->{LOCATION}{GROUPS} } ) {
1653 = $Vend::Session->{groups}
1656 $self->{LOCATION}{GROUPS},
1660 username_cookies($self->{PASSED_USERNAME} || $self->{USERNAME}, $pw, $self->{OPTIONS}{secure_cookies})
1661 if $Vend::Cfg->{CookieLogin};
1663 if ($self->{LOCATION}{LAST} ne 'none') {
1666 unless($self->{OPTIONS}{null_time}) {
1667 $login_time = $self->{OPTIONS}{iso_time}
1668 ? POSIX::strftime("%Y-%m-%d %H:%M:%S", localtime($now))
1672 $udb->set_field( $self->{USERNAME},
1673 $self->{LOCATION}{LAST},
1678 my $msg = errmsg("Failed to record timestamp in UserDB: %s", $@);
1683 $self->log('login') if $options{'log'};
1685 $self->get_values() unless $self->{OPTIONS}{no_get};
1692 $self->{ERROR} = $@;
1695 logError( "Vend::UserDB error: %s\n", $@ );
1703 unless $self->{LOCATION}{PRICING}
1704 and $pprof = $user_data->{ $self->{LOCATION}{PRICING} };
1706 Vend::Interpolate::tag_profile(
1708 { tag => $self->{OPTIONS}{profile} },
1712 $Vend::login_table = $Vend::Session->{login_table} = $self->{DB_ID};
1713 $Vend::username = $Vend::Session->{username} = $self->{USERNAME};
1714 $Vend::Session->{logged_in} = 1;
1716 if (my $macros = $self->{OPTIONS}{postlogin_action}) {
1718 Vend::Dispatch::run_macro $macros;
1721 logError("UserDB postlogin_action execution error: %s\n", $@);
1729 for(qw/ mv_password mv_verify mv_password_old /) {
1730 delete $CGI::values{$_};
1731 delete $::Values->{$_};
1736 my $self = shift or return undef;
1739 my $opt = $self->{OPTIONS};
1741 if( is_yes($opt->{clear}) ) {
1742 $self->clear_values();
1745 Vend::Interpolate::tag_profile("", { restore => 1 });
1758 delete $Vend::Session->{$_};
1759 undef ${"Vend::$_"};
1762 delete $CGI::values{mv_username};
1763 delete $::Values->{mv_username};
1764 $self->log('logout') if $opt->{log};
1765 $self->{MESSAGE} = errmsg('Logged out.');
1766 if ($opt->{clear_cookie}) {
1767 my @cookies = split /[\s,\0]+/, $opt->{clear_cookie};
1770 Vend::Util::set_cookie($_, '', $exp);
1773 if ($opt->{clear_session}) {
1774 Vend::Session::init_session();
1781 my ($self, $original_self);
1788 if ($self->{OPTIONS}{ignore_case}) {
1789 $self->{USERNAME} = lc $self->{USERNAME};
1790 $self->{OLDPASS} = lc $self->{OLDPASS};
1791 $self->{PASSWORD} = lc $self->{PASSWORD};
1792 $self->{VERIFY} = lc $self->{VERIFY};
1796 # Create copies so that ignore_case doesn't lc the originals.
1797 my $vend_username = $Vend::username;
1798 my $cgi_mv_username = $CGI::values{mv_username};
1799 if ($self->{OPTIONS}{ignore_case}) {
1800 $vend_username = lc $vend_username;
1801 $cgi_mv_username = lc $cgi_mv_username
1802 if defined $cgi_mv_username;
1805 # Database operations still use the mixed-case original.
1806 my $super = $Vend::superuser || (
1808 $self->{DB}->field($Vend::username, $self->{LOCATION}{SUPER})
1811 if ($self->{USERNAME} ne $vend_username or
1812 defined $cgi_mv_username and
1813 $self->{USERNAME} ne $cgi_mv_username
1816 if ($cgi_mv_username and
1817 $cgi_mv_username ne $self->{USERNAME}) {
1818 $original_self = $self;
1819 $options{username} = $cgi_mv_username;
1823 errmsg("Unprivileged user '%s' attempted to change password of user '%s'",
1824 $vend_username, $self->{USERNAME}) if $options{log};
1825 die errmsg("You are not allowed to change another user's password.");
1830 $self = new Vend::UserDB %options;
1833 die errmsg("Bad object.") unless defined $self;
1835 die errmsg("'%s' not a user.", $self->{USERNAME}) . "\n"
1836 unless $self->{DB}->record_exists($self->{USERNAME});
1838 unless ($super and $self->{USERNAME} ne $Vend::username) {
1839 my $db_pass = $self->{DB}->field($self->{USERNAME}, $self->{LOCATION}{PASSWORD});
1840 if ($self->{CRYPT}) {
1841 $self->{OLDPASS} = $self->do_crypt($self->{OLDPASS}, $db_pass);
1843 die errmsg("Must have old password.") . "\n"
1844 if $self->{OLDPASS} ne $db_pass;
1847 die errmsg("Must enter at least %s characters for password.",
1848 $self->{PASSMINLEN}) . "\n"
1849 if length($self->{PASSWORD}) < $self->{PASSMINLEN};
1850 die errmsg("Password and check value don't match.") . "\n"
1851 unless $self->{PASSWORD} eq $self->{VERIFY};
1853 $self->{OPTIONS}{enclair_db} and $self->set_enclair();
1855 if ( $self->{CRYPT} ) {
1856 $self->{PASSWORD} = $self->do_crypt(
1858 Vend::Util::random_string(2),
1862 my $pass = $self->{DB}->set_field(
1864 $self->{LOCATION}{PASSWORD},
1867 die errmsg("Database access error.") . "\n" unless defined $pass;
1868 $self->log(errmsg('change password')) if $options{'log'};
1873 $self = $original_self if $original_self;
1877 $self->{ERROR} = $@;
1878 $self->log(errmsg('change password failed')) if $options{'log'};
1881 logError( "Vend::UserDB error: %s", $@ );
1889 sub assign_username {
1891 my $file = shift || $self->{OPTIONS}{counter};
1892 my $start = $self->{OPTIONS}{username} || 'U00000';
1893 $file = './etc/username.counter' if ! $file;
1895 my $o = { start => $start, sql => $self->{OPTIONS}{sql_counter} };
1899 if(my $l = $Vend::Cfg->{Accounting}) {
1901 my $class = $l->{Class};
1903 my $assign = defined $l->{assign_username} ? $l->{assign_username} : 1;
1906 #::logDebug("Accounting class is $class");
1911 #::logDebug("Accounting object is $obj");
1915 "Failed to assign new customer number with accounting system %s",
1919 $custno = $obj->assign_customer_number();
1921 #::logDebug("assigned new customer number $custno");
1924 return $custno || Vend::Interpolate::tag_counter($file, $o);
1938 $self = new Vend::UserDB %options;
1941 delete $Vend::Session->{auto_created_user};
1943 die errmsg("Bad object.") . "\n" unless defined $self;
1945 die errmsg("Already logged in. Log out first.") . "\n"
1946 if $Vend::Session->{logged_in} and ! $options{no_login};
1947 die errmsg("Sorry, reserved user name.") . "\n"
1948 if $self->{OPTIONS}{username_mask}
1949 and $self->{USERNAME} =~ m!$self->{OPTIONS}{username_mask}!;
1950 die errmsg("Sorry, user name must be an email address.") . "\n"
1951 if $self->{OPTIONS}{username_email}
1952 and $self->{USERNAME} !~ m!^[[:alnum:]]([.]?([[:alnum:]._-]+)*)?@([[:alnum:]\-_]+\.)+[a-zA-Z]{2,4}$!;
1953 die errmsg("Must enter at least %s characters for password.",
1954 $self->{PASSMINLEN}) . "\n"
1955 if length($self->{PASSWORD}) < $self->{PASSMINLEN};
1956 die errmsg("Password and check value don't match.") . "\n"
1957 unless $self->{PASSWORD} eq $self->{VERIFY};
1959 if ($self->{OPTIONS}{ignore_case}) {
1960 $self->{PASSWORD} = lc $self->{PASSWORD};
1961 $self->{USERNAME} = lc $self->{USERNAME};
1964 my $pw = $self->{PASSWORD};
1965 if($self->{CRYPT}) {
1967 $pw = $self->do_crypt($pw, Vend::Util::random_string(2));
1971 my $udb = $self->{DB};
1973 if($self->{OPTIONS}{assign_username}) {
1974 $self->{PASSED_USERNAME} = $self->{USERNAME};
1975 $self->{USERNAME} = $self->assign_username();
1976 $self->{USERNAME} = lc $self->{USERNAME}
1977 if $self->{OPTIONS}{ignore_case};
1979 # plain error message without user-supplied username
1980 # to avoid XSS exploit (RT #306)
1981 die errmsg("Username contains illegal characters.") . "\n"
1982 if $self->{USERNAME} !~ m{^$self->{VALIDCHARS}+$};
1983 die errmsg("Must have at least %s characters in username.",
1984 $self->{USERMINLEN}) . "\n"
1985 if length($self->{USERNAME}) < $self->{USERMINLEN};
1987 if($self->{OPTIONS}{captcha}) {
1988 my $status = Vend::Tags->captcha( { function => 'check' });
1989 die errmsg("Must input captcha code correctly.") . "\n"
1993 # Here we put the username in a non-primary key field, checking
1995 my $foreign = $self->{OPTIONS}{indirect_login};
1997 my $uname = ($self->{PASSED_USERNAME} ||= $self->{USERNAME});
1998 $uname = $udb->quote($uname);
1999 my $q = "select $foreign from $self->{DB_ID} where $foreign = $uname";
2000 my $ary = $udb->query($q)
2002 my $msg = errmsg( "Database access error for query: %s", $q);
2007 my $msg = errmsg( "Username already exists (indirect).");
2012 if ($udb->record_exists($self->{USERNAME})) {
2013 die errmsg("Username already exists.") . "\n";
2020 $self->{PASSED_USERNAME},
2022 or die errmsg("Database access error.");
2025 $self->{OPTIONS}{enclair_db} and $self->set_enclair();
2027 my $pass = $udb->set_field(
2029 $self->{LOCATION}{PASSWORD},
2033 die errmsg("Database access error.") . "\n" unless defined $pass;
2035 if($self->{OPTIONS}{username_email}) {
2036 my $field_name = $self->{OPTIONS}{username_email_field} || 'email';
2037 $::Values->{$field_name} ||= $self->{USERNAME};
2043 or die errmsg("Database access error: %s", $udb->errstr) . "\n";
2048 if($dfield = $self->{OPTIONS}{created_date_iso}) {
2049 if($self->{OPTIONS}{created_date_gmtime}) {
2050 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%SZ', gmtime());
2052 elsif($self->{OPTIONS}{created_date_showzone}) {
2053 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%S %z', localtime());
2056 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime());
2059 elsif($dfield = $self->{OPTIONS}{created_date_epoch}) {
2063 if($dfield and $dstring) {
2070 my $msg = errmsg('Failed to set new account creation date: %s', $udb->errstr);
2071 Vend::Tags->warnings($msg);
2075 if($options{no_login}) {
2076 $Vend::Session->{auto_created_user} = $self->{USERNAME};
2079 $self->set_values() unless $self->{OPTIONS}{no_set};
2080 $self->{USERNAME} = $foreign if $foreign;
2081 username_cookies($self->{USERNAME}, $pw, $self->{OPTIONS}{secure_cookies})
2082 if $Vend::Cfg->{CookieLogin};
2084 $self->log('new account') if $options{'log'};
2087 "Cannot log in after new account creation: %s",
2097 $self->{ERROR} = $@;
2100 logError( "Vend::UserDB error: %s\n", $@ );
2108 sub username_cookies {
2109 my ($user, $pw, $secure) = @_;
2111 $CGI::values{mv_cookie_password} or
2112 $CGI::values{mv_cookie_username} or
2113 Vend::Util::read_cookie('MV_PASSWORD') or
2114 Vend::Util::read_cookie('MV_USERNAME');
2115 $::Instance->{Cookies} = [] unless defined $::Instance->{Cookies};
2116 my $exp = time() + $Vend::Cfg->{SaveExpire};
2117 $secure ||= $CGI::secure;
2118 push @{$::Instance->{Cookies}},
2119 ['MV_USERNAME', $user, $exp];
2121 $CGI::values{mv_cookie_password} or
2122 Vend::Util::read_cookie('MV_PASSWORD');
2123 push @{$::Instance->{Cookies}},
2124 ['MV_PASSWORD', $pw, $exp, undef, undef, $secure];
2129 my($self, %options) = @_;
2131 my $from = $self->{NICKNAME};
2134 my $opt = $self->{OPTIONS};
2136 if ($opt->{target}) {
2137 $to = ($::Carts->{$opt->{target}} ||= []);
2143 #::logDebug ("to=$to nick=$opt->{target} from=$from cart=" . ::uneval_it($from));
2145 my $field_name = $self->{LOCATION}->{CARTS};
2149 die errmsg("no from cart name?") unless $from;
2150 die errmsg("%s field not present to get %s", $field_name, $from) . "\n"
2151 unless $self->{PRESENT}->{$field_name};
2153 my $s = $self->{DB}->field( $self->{USERNAME}, $field_name);
2155 die errmsg("no saved carts.") . "\n" unless $s;
2157 my @carts = split /\0/, $from;
2158 my $d = string_to_ref($s);
2159 #::logDebug ("saved carts=" . ::uneval_it($d));
2161 die errmsg("eval failed?") unless ref $d;
2164 die errmsg("source cart '%s' does not exist.", $from) . "\n" unless ref $d->{$_};
2165 push @$cart, @{$d->{$_}};
2171 $self->{ERROR} = $@;
2174 #::logDebug ("to=$to nick=$opt->{target} from=$from cart=" . ::uneval_it($cart));
2177 $to = [] unless ref $to;
2184 next unless $master = $_->{mv_mi};
2186 $max = $master if $master > $max;
2195 next unless $master = $_->{mv_mi};
2196 next unless $used{$master};
2199 $alias{$master} = $max++;
2200 $_->{mv_mi} = $alias{$master};
2203 $_->{mv_mi} = $alias{$master};
2216 my($self, %options) = @_;
2219 my $to = $self->{NICKNAME};
2221 my $opt = $self->{OPTIONS};
2223 if ($opt->{source}) {
2224 $from = $::Carts->{$opt->{source}} || [];
2227 $from = $Vend::Items;
2230 my $field_name = $self->{LOCATION}->{CARTS};
2234 die errmsg("no to cart name?") . "\n" unless $to;
2235 die errmsg('%s field not present to set %s', $field_name, $from) . "\n"
2236 unless $self->{PRESENT}->{$field_name};
2238 $d = string_to_ref( $self->{DB}->field( $self->{USERNAME}, $field_name) );
2242 die errmsg("eval failed?") unless ref $d;
2245 $d->{$to} = [] unless ref $d->{$to};
2246 push(@{$d->{$to}}, @{$from});
2258 $self->{ERROR} = $@;
2262 $self->{DB}->set_field( $self->{USERNAME}, $field_name, $s);
2267 my $function = shift;
2276 %options = ($opt, @_);
2282 my $module = $Vend::Cfg->{UserControl} ? 'Vend::UserControl' : 'Vend::UserDB';
2284 if($function eq 'login') {
2285 $Vend::Session->{logged_in} = 0;
2286 delete $Vend::Session->{username};
2287 delete $Vend::Session->{groups};
2288 undef $Vend::username;
2289 undef $Vend::groups;
2291 $user = $module->new(%options);
2292 unless (defined $user) {
2293 $Vend::Session->{failure} = errmsg("Unable to access user database.");
2296 if ($status = $user->login(%options) ) {
2297 if( $Vend::ReadOnlyCfg->{AdminUserDB}{$user->{PROFILE}} ) {
2303 elsif($function eq 'new_account') {
2304 $user = $module->new(%options);
2305 unless (defined $user) {
2306 $Vend::Session->{failure} = errmsg("Unable to access user database.");
2309 $status = $user->new_account(%options);
2310 if($status and ! $options{no_login}) {
2311 $Vend::Session->{logged_in} = 1;
2312 $Vend::Session->{username} = $user->{USERNAME};
2315 elsif($function eq 'logout') {
2316 $user = $module->new(%options)
2318 $Vend::Session->{failure} = errmsg("Unable to create user object.");
2323 elsif (! $Vend::Session->{logged_in}) {
2324 $Vend::Session->{failure} = errmsg("Not logged in.");
2327 elsif($function eq 'save') {
2328 $user = $module->new(%options);
2329 unless (defined $user) {
2330 $Vend::Session->{failure} = errmsg("Unable to access user database.");
2333 $status = $user->set_values();
2335 elsif($function eq 'load') {
2336 $user = $module->new(%options);
2337 unless (defined $user) {
2338 $Vend::Session->{failure} = errmsg("Unable to access user database.");
2341 $status = $user->get_values();
2344 $user = $module->new(%options);
2345 unless (defined $user) {
2346 $Vend::Session->{failure} = errmsg("Unable to access user database.");
2350 $status = $user->$function(%options);
2352 $user->{ERROR} = $@ if $@;
2355 if(defined $status) {
2356 delete $Vend::Session->{failure};
2357 $Vend::Session->{success} = $user->{MESSAGE};
2358 if($options{show_message}) {
2359 $status = $user->{MESSAGE};
2363 $Vend::Session->{failure} = $user->{ERROR};
2364 if($options{show_message}) {
2365 $status = $user->{ERROR};
2368 return $status unless $options{hide};
2373 my ($self, $password, $salt) = @_;
2374 my $sub = $self->{ENCSUB};
2376 for (grep { $self->{OPTIONS}{$_} } keys %enc_subs) {
2377 $sub = $enc_subs{$_};
2380 $self->{ENCSUB} = $sub ||= $enc_subs{default};
2382 return $sub->($self, $password, $salt);