#!/usr/bin/python

#    cups2lprng.py - Cups Backend to transfer print jobs to LPRng
#    Copyright 2003, 2006 NC State University
#    Written by Jack Neely <jjneely@ncsu.edu>
#
#    SDG
#
#    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., 675 Mass Ave, Cambridge, MA 02139, USA.

# Cups Exit Codes:
# 1 and 4: Place queue in error state and halt future printing
#
# 0 (CUPS_BACKEND_OK)
# 1 (CUPS_BACKEND_FAILED)
# 2 (CUPS_BACKEND_AUTH_REQUIRED)
# 3 (CUPS_BACKEND_HOLD)
# 4 (CUPS_BACKEND_STOP)
# 5 (CUPS_BACKEND_CANCEL)

import os
import sys
import string
import pwd
import tempfile
import shutil

version = "0.0.2"
lpr = "/usr/local/bin/lpr"
pcap = "/afs/bp/contrib/lprng/etc/printcap"

def getRealmPrinter():
    file = "/etc/rc.conf.d/HostPrinter"
    if not os.access(file, os.R_OK):
        return None

    printer = open(file).read().strip()
    if printer == "lp":
        return None
    if printer == "":
        return None

    # Return exactly what's in the HostPrinter file
    # if this is a realm printer there should be no @print.ncsu.edu
    # but allow for that case to print to weird printers.
    # Although having @ in this file will break CLI printing with Cups
    return printer

def parseURI(uri):
    "Parse the URI and return printer[@server]"

    tuple = string.split(uri, "/")
    server = tuple[2]
    printer = tuple[3]

    if printer == "ncsurealmdefault":
        return getRealmPrinter()
    else:
        #return "%s@%s" % (printer, server)
        return printer

def parseOptions(options):
    """Returns a dict of options passed to this backend.  The string passed
       in is of the format name=value separated by spaces.  This will be
       transformed into a dict where name is the key that the value is
       referenced by."""

    ret = {}
    list = string.split(options)
    for pair in list:
        (name, value) = string.split(pair, "=")
        ret[name] = value

    return ret


def fileCompare(file1, file2):
    "Compare files by last modification time."

    time1 = os.stat(file1).st_mtime
    time2 = os.stat(file2).st_mtime

    if time1 == time2:
        return 0
    if time1 < time2:
        return -1
    return 1

def setupTokens():
    # God is this evil, grab the user's tokens
    os.system("/usr/bin/aklog bp.ncsu.edu")
    os.system("/usr/bin/aklog eos.ncsu.edu")
    os.system("/usr/bin/aklog unity.ncsu.edu")

def setUpAuth(auth):
    """Set up LPRng authentication.  Mainly a catch for kerberos as we
       no longer know for sure where the credential cache file is, so
       let's find it."""
    
    #os.environ["AUTH"] = auth
    if auth == "kerberos":
        # Find the credential cache (Kerberos 5)
        basefile = "/tmp/krb5cc_%s" % os.getuid()
        if os.access(basefile, os.R_OK):
            os.environ["KRB5CCNAME"] = "FILE:%s" % basefile
            return
        
        # This is kind of a hack...find the krb file that looks right
        ls = os.listdir("/tmp")
        caches = []
        for file in ls:
            abs = os.path.join("/tmp", file)
            if abs[0:len(basefile)] == basefile:
                caches.append(abs)

        # Sort by last modification time
        if len(caches) == 0:
            sys.stderr.write("WARNING: Can't find kerberos cache file.\n")
            return
        caches.sort(fileCompare)
        caches.reverse()
        os.environ["KRB5CCNAME"] = "FILE:%s" % caches[0]
    
def main():

    global version
    
    # Report device discovery
    if len(sys.argv) <= 1:
        print "network ncsu \"Unknown\" \"Cups to LPRng Gateway %s\""% version
        sys.exit(0)
    
    # Read in data from Cups
    if len(sys.argv) == 7:
        file = sys.argv[6]
    elif len(sys.argv) == 6:
        file = "-"
    else:
        sys.stderr.write("ERROR: Not enough cammand arguments.\n")
        sys.exit(1)
    
    user = sys.argv[2]
    copies = sys.argv[4]
    jobname = sys.argv[3]
    options_string = sys.argv[5]
    URI = os.environ["DEVICE_URI"]

    # Get user's information
    userInfo = pwd.getpwnam(user)

    # Arg...only lp or root can read the spool file
    try:
        if file != "-":
            os.environ["TMPDIR"] = "/tmp"
            tmpfile = tempfile.mktemp()
            shutil.copyfile(file, tmpfile)
            os.chown(tmpfile, userInfo[2], userInfo[3])
            os.chmod(tmpfile, 0600)
            file = tmpfile
    except Exception, e:
        sys.stderr.write("An IO error occured in the NCSU Cups driver.\n")
        sys.stderr.write("Error: %s\n" % str(e))
        sys.exit(5)

    # the local printcap Cups creates is for compatibility and interfears
    # with running LPRng commands.  We nuke it.
    if os.access("/etc/printcap", os.F_OK):
        os.unlink("/etc/printcap")

    # Okay, Bye Bye root!!
    os.setgid(userInfo[3])
    os.setuid(userInfo[2])

    # Setup Kerberos and AFS Authentication
    setUpAuth("kerberos")
    setupTokens()
    os.environ['USER'] = userInfo[0]
    os.environ['PATH'] = "/usr/local/bin:/usr/bin:/bin"

    # We should now be the user that submitted this job and hopefully
    # have access to very cool things...like kerberos tickets

    if not os.access(lpr, os.X_OK):
        sys.stderr.write("ERROR: %s was not found.\n" % lpr)
        sys.exit(1)

    printer = parseURI(URI)
    if printer == None:
        sys.stderr.write("Error: No printer in /etc/rc.conf.d/HostPrinter")
        sys.exit(5)

    cmd = "%s -P%s -J '%s' -K %s" % (lpr, printer, jobname, copies)

    # Work with options...this can be extended as much as needed
    #opt = parseOptions(options_string)
    # Ignore this for now, the printcap file should provide what we want
    #if opt.has_key("auth") and opt["auth"] != "no":
    #    # Set up LPRng authentication...like kerberos
    #    setUpAuth(opt["auth"])
    #    cmd = "%s -A" % cmd

    if file == "-":
        fd = sys.stdin
    else:
        fd = open(file)

    # Write file or stdin to the lpr command.
    # Attempting to not read in all data at once to save memory
    try:
        pipe = os.popen(cmd, "w")
        line = fd.readline()
        while line != "":
            pipe.write(line)
            line = fd.readline()
        pipe.close()
    except Exception, e:
        sys.stderr.write("Error writing to pipe to lpr command.")
        sys.exit(5)
        
    if file != "-":
        try:
            fd.close()
            os.unlink(file)
        except Exception, e:
            pass
    

if __name__ == "__main__":
    main()

