--- majordomo.orig Tue Sep 9 22:26:44 1997 +++ majordomo Mon Feb 16 00:46:16 1998 @@ -27,7 +27,7 @@ # Read and execute the .cf file $cf = $ENV{"MAJORDOMO_CF"} || "/etc/majordomo.cf"; -while ($ARGV[0]) { # parse for config file or default list +while ($ARGV[0]) { # parse for config file, default list, or auth if ($ARGV[0] =~ /^-C$/i) { # sendmail v8 clobbers case $cf = $ARGV[1]; shift(@ARGV); @@ -36,6 +36,9 @@ $deflist = $ARGV[1]; shift(@ARGV); shift(@ARGV); + } elsif ($ARGV[0] eq "-a") { # We're at the confirm address + $confirm_mode=1; + shift(@ARGV); } else { die "Unknown argument $ARGV[0]\n"; } @@ -125,7 +128,7 @@ # if somebody has set $reply_to to be our own input address, there's a problem. if (&addr_match($reply_to, $whoami)) { - &abort( "$whoami punting to avoid mail loop.\n"); + &abort( "$whoami not replying to $1 to avoid mail loop.\n"); exit 0; } @@ -160,6 +163,131 @@ select((select(REPLY), $| = 1)[0]); +if($confirm_mode) # search the message for keys +{ + local(*KEYS, *NEW); + local(%keyaddr,%keycmd,%keytime, %keylist, @keys); + local(@a,$c,$t,$l,$k, $key, $msg); + + print STDERR "$0: processing confirm message.\n" if $DEBUG; + + &lopen(KEYS,"","$confirm_keys") || + &abort("Couldn't read key file $confirm_keys: $!"); + + (local($mode, $uid, $gid)=(stat(KEYS))[2,4,5]) || + &abort("Can't stat key file $confirm_keys: $!"); + open(NEW,">$confirm_keys.new") || + &abort("Can't open $confirm_keys.new: $!"); + chmod($mode, "$confirm_keys.new") || + &abort("chmod($mode, \"$confirm_keys.new\"): $!"); + chown($uid, $gid, "$confirm_keys.new") || + &abort("chown($uid, $gid, \"$confirm_keys.new\"): $!"); + + while() + { + # Break line into fields, using all trailing fields for address + ($k,$t,$c,$l,@a)=split(/\t/,$_); + if($t>time) # Is the key still valid + { + $keyaddr{$k}=join("\t",@a); + $keyaddr{$k}=~ s/\n$//; + $keycmd{$k}=$c; + $keytime{$k}=$t; + $keylist{$k}=$l; + + push(@keys,$_); + } + } + + if($hdrs{'subject'} =~ /\b([A-F0-9]{7}-[A-F0-9]{7})\b/i) + { + if($keytime{$1}) + { + $key=$1; + } + } + + $msg.="> Subject: $hdrs{'subject'}\n>\n"; + + if(!$key) + { + while(<>) + { + $msg.="> $_"; + + while(/\b([A-F0-9]{7}-[A-F0-9]{7})\b/ig) # look all over for keys + { + if($keytime{$1}) + { + $key=$1; + last; + } + } + } + } + + while(<>) # eat up remaining input + { + $msg.="> $_"; + } + + if($key) + { + print STDERR "$0: found key $key-> $keycmd{$key} $keylist{$key} $keyaddr{$key}.\n" if $DEBUG; + + $confirmed=1; + if($keycmd{$key} eq "subscribe") + { + print REPLY ">>>> Confirmation for \"subscribe $keylist{$key} $keyaddr{$key}\": $key\n"; + &do_subscribe($keylist{$key},$keyaddr{$key}) + } + elsif($keycmd{$key} eq "unsubscribe") + { + print REPLY ">>>> Confirmation for \"unsubscribe $keylist{$key} $keyaddr{$key}\": $key\n"; + &do_unsubscribe($keylist{$key},$keyaddr{$key}) + } + } + else + { + print REPLY <<"EOM"; +**** Confirmation message: no valid key found or key expired! +**** Only send confirmation messages to $confirm_address, +**** Other list commands should be directed to $whoami. +**** If you have any questions or problems, please contact +**** "$whoami_owner". +**** Here is the message you sent: + +$msg +EOM + ; + print STDERR "$0: No key found.\n" if $DEBUG; + } + + foreach $_ (@keys) + { + if(!$key || !/^$key/) + { + print NEW $_ + ||&abort("Error writing $confirm_keys.new: $!"); + } + } + + close(NEW) || &abort("Error closing $confirm_keys.new: $!"); + + link("$confirm_keys", "$confirm_keys.old") || + &abort("link(\"$confirm_keys\", \"$confirm_keys.old\"): $!"); + unlink("$confirm_keys"); + link("$confirm_keys.new", "$confirm_keys") || + &abort("link(\"$confirm_keys.new\", \"$confirm_keys\"): $!"); + unlink("$confirm_keys.old"); + unlink("$confirm_keys.new"); + + &lclose(KEYS); + + $count=1; + &done(); +} + print STDERR "$0: processing commands in message body.\n" if $DEBUG; # Process the rest of the message as commands @@ -221,7 +349,6 @@ elsif ($cmd eq "help") { &do_help(@parts); } elsif ($cmd eq "get") { &do_get(@parts); } elsif ($cmd eq "index") { &do_index(@parts); } - elsif ($cmd eq "auth") { &do_auth(@parts); } else { &squawk("Command '$cmd' not recognized."); $count--; # if we get to here, it wasn't really a command @@ -266,33 +393,32 @@ local($sub_policy) = $config_opts{$clean_list,"subscribe_policy"}; - # check to see if this is a list with a 'confirm' subscribe policy, - # and check the cookie if so. - # + # check to see if this is a list with a 'confirm' subscribe policy if (! $approved - && (($sub_policy =~ /confirm/) - && (&gen_cookie($sm, $clean_list, $subscriber) ne $auth_info))) + && (($sub_policy =~ /confirm/) && !$confirmed)) { # We want to send the stripped address in the confirmation # message if strip = yes. if (&cf_ck_bool($clean_list,"strip")) { $subscriber = (&ParseAddrs($subscriber))[0]; } - &send_confirm("subscribe", $clean_list, $subscriber); + &send_confirm("subscribe", $clean_list, $subscriber, $reply_to); return 0; } - + # Check to see if this request is approved, or if the list is an # auto-approve list, or if the list is an open list and the - # subscriber is the person making the request + # subscriber is the person making the request, + # or if it's open and they've confirmed it if ($approved || ($sub_policy =~ /auto/i && &check_and_request("subscribe", $clean_list, $subscriber, "check_only")) # I don't think this check is doing the right thing. Chan 95/10/19 || (($sub_policy !~ /closed/ ) - && &addr_match($reply_to, $subscriber, - (&cf_ck_bool($clean_list,"mungedomain") ? 2 : undef))) + && ($confirmed || + &addr_match($reply_to, $subscriber, + (&cf_ck_bool($clean_list,"mungedomain") ? 2 : undef)))) ) { # Either the request is approved, or the list is open and the # subscriber is the requester, so check to see if they're @@ -366,7 +492,8 @@ local($sm) = "unsubscribe"; local($list) = shift; local($clean_list); - + local(*NEW); + if ($list =~ /^\*$/) { &do_unsubscribe_all(@_); return 0; @@ -412,16 +539,35 @@ print STDERR "do_unsubscribe: valid list, valid subscriber.\n" if $DEBUG; + # Check to see if subscribe confirmation is required + # If so, then we will send a confirmation message + + local($sub_policy) = $config_opts{$clean_list,"unsubscribe_policy"}; + + if(! $approved + && (($sub_policy =~ /confirm/) && !$confirmed)) + { + # We want to send the stripped address in the confirmation + # message if strip = yes. + if (&cf_ck_bool($clean_list,"strip")) { + $subscriber = (&ParseAddrs($subscriber))[0]; + } + &send_confirm("unsubscribe", $clean_list, $subscriber, $reply_to); + return 0; + } + # Check to see if this request is approved, or if the subscriber is # the person making the request (even on a closed list, folks can # unsubscribe themselves without the owner's approval). + # If the request has been confirmed and the policy is open, + # we don't need to match if ($approved || ($config_opts{$clean_list,"unsubscribe_policy"} eq "auto" && &check_and_request("unsubscribe", $clean_list, $subscriber, "check_only")) || (($config_opts{$clean_list,"unsubscribe_policy"} ne "closed" ) - && &addr_match($reply_to, $subscriber, - (&cf_ck_bool($clean_list,"mungedomain") ? 2 : undef)))) { + && ($confirmed || &addr_match($reply_to, $subscriber, + (&cf_ck_bool($clean_list,"mungedomain") ? 2 : undef))))) { # Either the request is approved, or the subscriber is the # requester, so drop them from the list &lopen(LIST, "", "$listdir/$clean_list") || @@ -495,21 +641,6 @@ } } -sub do_auth { - # Check to see we've got all the arguments; the address is allowed to - # contain spaces, so since our argument list was split on spaces we - # have to join them back together. - local($auth_info, $cmd, $list, @sub) = @_; - if ( !length($auth_info) - || $cmd ne 'subscribe' # can only authorize subscribes at the moment - ) { - &squawk("auth: needs key"); - return 0; - } - $sub = join(' ',@sub); - &do_subscribe($list, $sub); -} - sub do_approve { # Check to see we've got all the arguments (local($passwd) = shift) || &squawk("approve: needs passwd"); @@ -1669,31 +1800,36 @@ sub send_confirm { local($cmd) = shift; + local($cmdtext); local($list) = &valid_list($listdir, shift); - local($subscriber) = @_; - local($cookie) = &gen_cookie($cmd, $list, $subscriber); + local($subscriber) = shift; + local($requestor) = shift; + local($cookie) = &add_key($cmd, $list, $subscriber); + + $cmdtext="subscribed to" if $cmd eq "subscribe"; + $cmdtext="unsubscribed from" if $cmd eq "unsubscribe"; + &set_mail_from($confirm_address); + local(*AUTH); - &sendmail(AUTH, $subscriber, "Confirmation for $cmd $list"); + &sendmail(AUTH, $subscriber, "Confirm $cmd $list ($cookie)"); print AUTH <<"EOM"; -Someone (possibly you) has requested that your email address be added -to or deleted from the mailing list "$list\@$whereami". +Someone ($reply_to) has requested +that your email address be $cmdtext the mailing list +"$list\@$whereami". -If you really want this action to be taken, please send the following -commands (exactly as shown) back to "$whoami": +If you really want this action to be taken, send a reply to this +message to $confirm_address including the following confirmation key +anywhere in the subject or the body: - auth $cookie $cmd $list $subscriber + KEY: $cookie + +You must reply in two weeks or this request will expire. If you do not want this action to be taken, simply ignore this message and the request will be disregarded. -If your mailer will not allow you to send the entire command as a single -line, you may split it using backslashes, like so: - - auth $cookie $cmd $list \\ - $subscriber - If you have any questions about the policy of the list owner, please contact "$list-approval\@$whereami". @@ -1702,13 +1838,15 @@ $whoami EOM close(AUTH); + + &set_mail_from($whoami); print REPLY <<"EOM"; **** Your request to $whoami: **** **** $cmd $list $subscriber **** -**** must be authenticated. To accomplish this, another request must be +**** must be authenticated. To accomplish this, a confirmation must be **** sent in with an authorization key, which has been sent to: **** $subscriber **** @@ -1953,23 +2091,42 @@ } } -sub gen_cookie { - local($combined) = join('/', $cookie_seed ? $cookie_seed : $homedir, @_); - local($cookie) = 0; - local($i, $carry); - - # Because of backslashing and all of the splitting on whitespace and - # joining that goes on, we need to ignore whitespace. - $combined =~ s/\s//g; - - for ($i = 0; $i < length($combined); $i++) { - $cookie ^= ord(substr($combined, $i)); - $carry = ($cookie >> 28) & 0xf; - $cookie <<= 4; - $cookie |= $carry; +sub add_key +{ + local($cmd, $list, $addr) = @_; + local(*KEYS, $binkey, $asckey, $time); + + if($confirm_randev && -f $confirm_randev) + { + local(*RAND); + open(RAND,"$confirm_randev") || + abort("Couldn't open $confirm_randev: $!"); + + for(1..7) + { + $binkey .= getc RAND; + } + close RAND; } - return (sprintf("%08x", $cookie)); -} + else + { + srand time^$$; + for(1..7) + { + $binkey .= pack("c",rand 256); + } + } + + $asckey = unpack("h14",$binkey); + $asckey =~ s/(.{7})(.{7})/\U$1-$2/; + $time=time+$confirm_expire; + &lopen(KEYS,">>","$confirm_keys") || + &abort("Couldn't append key file $confirm_keys: $!"); + print KEYS "$asckey\t$time\t$cmd\t$list\t$addr\n"; + &lclose(KEYS) || &abort("Error closing $confirm_keys: $!"); + + return $asckey; +}