# /etc/wallet/wallet.conf -- Wallet system configuration.  -*- perl -*-
# $Id$
#
# Configuration for the wallet system as used at Stanford University.
# Interesting features to note are loading the database password from an
# external file and full implementations of a naming policy check and default
# ACL rules.
#
# Written by Russ Allbery <rra@stanford.edu>
# Copyright 2007, 2008 Board of Trustees, Leland Stanford Jr. University
#
# See LICENSE for licensing terms.

use Wallet::ACL;
use Wallet::Database;

$DB_DRIVER = 'mysql';
$DB_NAME   = 'wallet';
$DB_HOST   = 'localhost';
$DB_USER   = 'wallet';

# Read the MySQL password from a separate file so that we don't have to commit
# it to the Puppet repository.
open (PASS, '<', '/etc/wallet/mysql-password')
    or die "cannot open /etc/wallet/mysql-password: $!\n";
$DB_PASSWORD = <PASS>;
close PASS;
chomp $DB_PASSWORD;

$KEYTAB_FILE         = '/etc/wallet/keytab';
$KEYTAB_FLAGS        = '-clearpolicy';
$KEYTAB_HOST         = 'krb5-admin.stanford.edu';
$KEYTAB_PRINCIPAL    = 'service/wallet@stanford.edu';
$KEYTAB_REALM        = 'stanford.edu';
$KEYTAB_TMP          = '/var/lib/wallet';

$KEYTAB_REMCTL_CACHE = '/var/lib/wallet/krb5cc_wallet';
$KEYTAB_REMCTL_HOST  = 'kerberos1.stanford.edu';

$KEYTAB_AFS_ADMIN    = 'service.wallet@IR.STANFORD.EDU';
$KEYTAB_AFS_DESTROY  = 1;
$KEYTAB_AFS_REALM    = 'IR.STANFORD.EDU';
$KEYTAB_AFS_SRVTAB   = '/etc/wallet/srvtab';

$NETDB_REALM         = 'stanford.edu';
$NETDB_REMCTL_CACHE  = '/var/lib/wallet/krb5cc_wallet';
$NETDB_REMCTL_HOST   = 'netdb-node-roles-rc.stanford.edu';

# Work around a bug in Net::Remctl.
$NETDB_REMCTL_PRINCIPAL = 'host/netdb-node-roles-rc.stanford.edu';

# Retrieve an existing ACL and check whether it contains a netdb-root member.
# This is used to check if a default ACL is already present with a netdb-root
# member so that we can return a default owner that matches.  We only ever
# increase the ACL from netdb to netdb-root, never degrade it, so this doesn't
# pose a security problem.
#
# On any failure, just return an empty ACL to use the default.
sub acl_has_netdb_root {
    my ($name) = @_;
    my $dbh = eval { Wallet::Database->connect };
    return unless ($dbh and not $@);
    my $acl = eval { Wallet::ACL->new ($name, $dbh) };
    return unless ($acl and not $@);
    for my $line ($acl->list) {
        return 1 if $line->[0] eq 'netdb-root';
    }
    return;
}

# The default owner of a host should be the host keytab and the NetDB ACL for
# that host, with one twist.  If the creator of a new node is using a root
# instance, we want to require everyone managing that node be using root
# instances by default (this will do the right thing for Unix Systems hosts).
sub default_owner {
    my ($type, $name) = @_;
    my %allowed = map { $_ => 1 }
        qw(HTTP afpserver cifs ftp host ident imap ldap lpr nfs pop sieve smtp
           uniengd webauth xmpp);
    my $realm = 'stanford.edu';
    return unless $type eq 'keytab';
    return unless $name =~ m,/,;
    my ($service, $instance) = split ('/', $name, 2);
    return unless $allowed{$service};
    my $acl_name = "host/$instance";
    my @acl;
    if ($ENV{REMOTE_USER} =~ m,/root, or acl_has_netdb_root ($acl_name)) {
        @acl = ([ 'netdb-root', $instance ],
                [ 'krb5', "host/$instance\@$realm" ]);
    } else {
        @acl = ([ 'netdb', $instance ],
                [ 'krb5', "host/$instance\@$realm" ]);
    }
    return ($acl_name, @acl);
}

# Enforce a naming policy.  Host-based keytabs must have fully-qualified
# hostnames, limit the acceptable characters for service/* keytabs, and
# enforce our naming constraints on */cgi principals.
#
# Also use this function to require that Unix systems staff always do implicit
# object creation using a */root instance.
sub verify_name {
    my ($type, $name, $user) = @_;
    my %host = map { $_ => 1 }
        qw(HTTP afpserver cifs ftp host ident imap ldap lpr nfs pop sieve smtp
           uniengd webauth xmpp);
    my %staff;
    if (open (STAFF, '<', '/etc/remctl/acl/systems')) {
        local $_;
        while (<STAFF>) {
            s/^\s+//;
            s/\s+$//;
            next if m,/root\@,;
            $staff{$_} = 1;
        }
        close STAFF;
    }

    # Check for a staff member not using their root instance.
    if ($staff{$user}) {
        return 'use a */root instance for wallet object creation';
    }

    # Check keytab naming conventions.
    if ($type eq 'keytab') {
        if ($name !~ m,^[a-zA-Z0-9_-]+/[a-z0-9.-]+$,) {
            return "invalid princial name $name";
        }
        my ($principal, $instance)
            = ($name =~ m,^([a-zA-Z0-9_-]+)/([a-z0-9.-]+)$,);
        unless (defined ($principal) && defined ($instance)) {
            return "invalid principal name $name";
        }
        if ($host{$principal}) {
            if ($instance !~ /^[a-z0-9-]+\.[a-z0-9.-]+$/) {
                return "host name $instance is not fully qualified";
            }
        } elsif ($principal eq 'service') {
            if ($instance !~ /^[a-z0-9-]+$/) {
                return "invalid service principal name $name";
            }
        } elsif ($instance eq 'cgi') {
            if ($principal !~ /^[a-z][a-z0-9]{1,7}$/
                and $principal !~ /^(class|dept|group)-[a-z0-9_-]+$/) {
                return "invalid CGI principal name $name";
            }
        }
    }

    # Success.
    return;
}

1;