#!/usr/bin/env python

# Add a user to to the uid/maintainer/fingerprint table and 
# add his key to the GPGKeyring
# Copyright (C) 2004  Joerg Jaspert <joerg@ganneff.de>
# $Id$

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

################################################################################

# <elmo> wow, sounds like it'll be a big step up.. configuring dak on a
#        new machine even scares me :)

################################################################################

# You don't want to read this script if you know python.
# I know what I say. I dont know python and I wrote it. So go and read some other stuff.

import commands, pg, re, sys, time, os;
import apt_pkg;
import db_access, katie, dak_logging, utils;

################################################################################

Cnf = None;
projectB = None;
Logger = None;
Katie = None;
Subst = None;

re_gpg_fingerprint = re.compile(r"^fpr:+(.*):$", re.MULTILINE);
# The next one is dirty
re_user_address = re.compile(r"^pub:.*<(.*)@.*>.*$", re.MULTILINE);
re_user_mails = re.compile(r"^(pub|uid):[^rdin].*<(.*@.*)>.*$", re.MULTILINE);
re_user_name = re.compile(r"^pub:.*:(.*)<.*$", re.MULTILINE);

################################################################################

def usage(exit_code=0):
    print """Usage: uma [OPTION]...
Adds a new user to the dak databases and keyrings

    -k, --key                keyid of the User
    -u, --user               userid of the User
    -c, --create             create a system account for the user
    -h, --help               show this help and exit."""
    sys.exit(exit_code)

################################################################################
# Stolen from userdir-ldap
# Compute a random password using /dev/urandom.
def GenPass():   
   # Generate a 10 character random string
   SaltVals = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/.";
   Rand = open("/dev/urandom");
   Password = "";
   for i in range(0,15):
      Password = Password + SaltVals[ord(Rand.read(1)[0]) % len(SaltVals)];
   return Password;

# Compute the MD5 crypted version of the given password
def HashPass(Password):
   import crypt;
   # Hash it telling glibc to use the MD5 algorithm - if you dont have
   # glibc then just change Salt = "$1$" to Salt = "";
   SaltVals = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/.";
   Salt  = "$1$";
   Rand = open("/dev/urandom");
   for x in range(0,10):
      Salt = Salt + SaltVals[ord(Rand.read(1)[0]) % len(SaltVals)];
   Pass = crypt.crypt(Password,Salt);
   if len(Pass) < 14:
      raise "Password Error", "MD5 password hashing failed, not changing the password!";
   return Pass;

################################################################################

def createMail(login, passwd, keyid, keyring):
    import GnuPGInterface;
    
    message= """

Additionally there is now an account on dak.ganneff.de created for you. You can login with ssh and
play with the tools that constitute the archive software, like madison for example. Note that this
service is provided for you to learn to work with the archive system tools, so please don't try to
kill it with some stupid script kiddie action.

""";
    message+= "\nYour password for the login %s is: %s\n" % (login, passwd);

    gnupg = GnuPGInterface.GnuPG();
    gnupg.options.armor = 1;
    gnupg.options.meta_interactive = 0;
    gnupg.options.extra_args.append("--no-default-keyring");
    gnupg.options.extra_args.append("--always-trust");
    gnupg.options.extra_args.append("--no-secmem-warning");
    gnupg.options.extra_args.append("--keyring=%s" % keyring);
    gnupg.options.recipients = [keyid];
    proc = gnupg.run(['--encrypt'], create_fhs=['stdin', 'stdout']);
    proc.handles['stdin'].write(message);
    proc.handles['stdin'].close();
    output = proc.handles['stdout'].read();
    proc.handles['stdout'].close();
    proc.wait();
    return output;

################################################################################

def main():
    global Cnf, projectB;

    Cnf = utils.get_conf()

    Arguments = [('h',"help","Uma::Options::Help"),
                 ('c',"create","Uma::Options::Create"),
                 ('k',"key","Uma::Options::Key", "HasArg"),
                 ('u',"user","Uma::Options::User", "HasArg"),
                 ];

    for i in [ "help", "create" ]:
	if not Cnf.has_key("Uma::Options::%s" % (i)):
	    Cnf["Uma::Options::%s" % (i)] = "";

    apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv);

    Options = Cnf.SubTree("Uma::Options");
    if Options["help"]:
        usage();

    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]));
    db_access.init(Cnf, projectB);

# Ignore the PGP keyring for download of new keys. Ignore errors, if key is missing it will
# barf with the next commands.
    cmd = "gpg --no-secmem-warning --no-default-keyring --keyring=%s --recv-keys %s" \
           % (Cnf["Dinstall::GPGKeyring"], Cnf["Uma::Options::Key"]);
    (result, output) = commands.getstatusoutput(cmd);

    cmd = "gpg --with-colons --no-secmem-warning --no-auto-check-trustdb --no-default-keyring --keyring=%s --keyring=%s --with-fingerprint --list-key %s" \
           % (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"],
              Cnf["Uma::Options::Key"]);
    (result, output) = commands.getstatusoutput(cmd);
    m = re_gpg_fingerprint.search(output);
    if not m:
        utils.fubar("0x%s: No fingerprint found in gpg output but it returned 0?\n%s" \
					% (Cnf["Uma::Options::Key"], utils.prefix_multi_line_string(output, \
																				" [GPG output:] ")));
    primary_key = m.group(1);
    primary_key = primary_key.replace(" ","");

    uid = ""
    if Cnf.has_key("Uma::Options::User") and Cnf["Uma::Options::User"]:
        uid = Cnf["Uma::Options::User"]
    else:
        u = re_user_address.search(output);
        if not u:
            print output
            utils.fubar("0x%s: No userid found in gpg output but it returned 0?\n%s" \
                        % (Cnf["Uma::Options::Key"], utils.prefix_multi_line_string(output, " [GPG output:] ")));
        uid = u.group(1);
    n = re_user_name.search(output);
    name = n.group(1);

# Look for all email addresses on the key.
    emails=[];
    for line in output.split('\n'):
        e = re_user_mails.search(line);
        if not e:
            continue;
        emails.append(e.group(2));


    print "0x%s -> %s <%s> -> %s -> %s" % (Cnf["Uma::Options::Key"], name, emails[0], uid, primary_key);

    prompt = "Add user %s with above data (y/N) ? " % (uid);
    yn = utils.our_raw_input(prompt).lower();

    if yn == "y":
# Create an account for the user?
          summary = "";
          if Cnf.FindB("Uma::CreateAccount") or Cnf["Uma::Options::Create"]:
              password = GenPass();
              pwcrypt = HashPass(password);
              if Cnf.has_key("Uma::GID"):
                  cmd = "sudo /usr/sbin/useradd -g users -m -p '%s' -c '%s' -G %s %s" \
                         % (pwcrypt, name, Cnf["Uma::GID"], uid);
              else:
                  cmd = "sudo /usr/sbin/useradd -g users -m -p '%s' -c '%s' %s" \
                         % (pwcrypt, name, uid);
              (result, output) = commands.getstatusoutput(cmd);
              if (result != 0):
                   utils.fubar("Invocation of '%s' failed:\n%s\n" % (cmd, output), result);
              try:
                  summary+=createMail(uid, password, Cnf["Uma::Options::Key"], Cnf["Dinstall::GPGKeyring"]);
              except:
                  summary="";
                  utils.warn("Could not prepare password information for mail, not sending password.");

# Now add user to the database.
          projectB.query("BEGIN WORK");
          uid_id = db_access.get_or_set_uid_id(uid);
          projectB.query('CREATE USER "%s"' % (uid));
          projectB.query("COMMIT WORK");
# The following two are kicked out in rhona, so we don't set them. kelly adds
# them as soon as she installs a package with unknown ones, so no problems to expect here.
# Just leave the comment in, to not think about "Why the hell aren't they added" in
# a year, if we ever touch uma again.
#          maint_id = db_access.get_or_set_maintainer_id(name);
#          projectB.query("INSERT INTO fingerprint (fingerprint, uid) VALUES ('%s', '%s')" % (primary_key, uid_id));

# Lets add user to the email-whitelist file if its configured.
          if Cnf.has_key("Dinstall::MailWhiteList") and Cnf["Dinstall::MailWhiteList"] != "":
              file = utils.open_file(Cnf["Dinstall::MailWhiteList"], "a");
              for mail in emails:
                  file.write(mail+'\n');
              file.close();
          
          print "Added:\nUid:\t %s (ID: %s)\nMaint:\t %s\nFP:\t %s" % (uid, uid_id, \
	             name, primary_key);

# Should we send mail to the newly added user?
          if Cnf.FindB("Uma::SendEmail"):
              mail = name + "<" + emails[0] +">";
              Katie = katie.Katie(Cnf);
              Subst = Katie.Subst;
              Subst["__NEW_MAINTAINER__"] = mail;
              Subst["__UID__"] = uid;
              Subst["__KEYID__"] = Cnf["Uma::Options::Key"];
              Subst["__PRIMARY_KEY__"] = primary_key;
              Subst["__FROM_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
              Subst["__HOSTNAME__"] = Cnf["Dinstall::MyHost"];
              Subst["__SUMMARY__"] = summary;
              new_add_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/uma.added");
              utils.send_mail(new_add_message);

    else:
          uid = None;


#######################################################################################

if __name__ == '__main__':
    main()

