Update of /cvsroot/tmda/tmda-cgi
In directory sc8-pr-cvs1:/tmp/cvs-serv13366

Modified Files:
        Authenticate.py Session.py compile tmda-cgi.c 
Log Message:
* Authenticate.py now uses Auth.py for checkpassword and remote authentication.
* Cleaned up file authentication a bit in Authenticate.py
* Session.py now initializes the authentication scheme using the environment set
  up via the new compile programs.  By default, the original file authentication
  proceeds.
 
Note: Delete existing compile.ini before running compile again.
      Original file authentication should not be affected, I hope.

Warning: Checkpassword appears to work, but has not been tested robustly.

Caution: Imap authentication appears to work, but other remote protocols have
  not been tested.


Index: Authenticate.py
===================================================================
RCS file: /cvsroot/tmda/tmda-cgi/Authenticate.py,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- Authenticate.py     29 Mar 2003 22:46:28 -0000      1.1.1.1
+++ Authenticate.py     1 Apr 2003 18:15:58 -0000       1.2
@@ -27,106 +27,139 @@
 import os.path
 import pwd
 import random
+import sys
 
-def ComparePassword(Filename, User, Password):
-  """Checks password against a given filename.
+from TMDA import Auth
+from TMDA import Errors
 
-Returns:
-   1: File read, user found, password authenticated
-   0: File read, user found, login deactivated
-  -1: File read, user found, password wrong
-  -2: File read, user not found
-  -3: File couldn't be read"""
+# For now, output all Auth.py errors to http error log
+Auth.DEBUGSTREAM = sys.stderr
+
+authinit = False
+
+def InitProgramAuth( Program, trueProg = "/usr/bin/true" ):
+  """Initializes the authentication scheme with a checkpw-style program.
+(Implemented by Auth.py)"""
+  global authinit
+  Auth.authtype = 'prog'
+  if not os.access( Program, os.F_OK ):
+    authinit = False
+    raise ValueError, "'%s' does not exist" % Program
+  if not os.access( trueProg, os.F_OK ):
+    authinit = False
+    raise ValueError, "'%s' does not exist", trueProg
+  if not os.access( Program, os.X_OK ):
+    authinit = False
+    raise ValueError, "'%s' is not executable" % Program
+  if not os.access( trueProg, os.X_OK ):
+    authinit = False
+    raise ValueError, "'%s' is not executable", trueProg
+  # Now initialize the authprog with the checkpasswd program and "true"
+  Auth.authprog = "%s %s" % ( Program, trueProg )
+  authinit = True
+
+def InitFileAuth( Filename="/etc/tmda-cgi" ):
+  """Initializes the authentication scheme with a flat file
+(Not implemented by Auth.py yet)"""
+  global authinit
+  Auth.authtype = 'file'
+  if not os.access( Filename, os.F_OK ):
+    authinit = False
+    raise ValueError, "File '%s' does not exist" % Filename
+  Auth.authfile = Filename
+  authinit = True
+
+def InitRemoteAuth( URI ):
+  """Initialaze the authentication scheme with a remote URL
+(Implemented by Auth.py)"""
+  global authinit
+  Auth.authtype = 'remote'
   try:
-    F = open(Filename) 
-  except IOError:
-    return -3
-
-  RetVal = -2
-  while (1):
-    PasswordRecord = F.readline()
-
-    # End of file?
-    if PasswordRecord == "":
-      break
-    Temp = PasswordRecord.strip().split(":")
-
-    # Have we found the correct user record?
-    if Temp[0] == User:
-      if Temp[1] == "":
-        RetVal = 0
-        break
-      
-      Perm = os.stat(Filename)[0] & 07777
-
-      # Is the password in the file encrypted?
-      if (Perm != 0400) and (Perm != 0600):
-        if crypt.crypt(Password, Temp[1][:2]) == Temp[1]:
-          RetVal = 1
-        else:
-          RetVal = -1
-        break
-      else:
-        if Temp[1] == Password:
-          RetVal = 1
-        else:
-          RetVal = -1
-        break
-  F.close()
+    Auth.parse_auth_uri( URI )
+    Auth.init_auth_method()
+    authinit = True
+  except ValueError, err:
+    authinit = False
+    raise Errors.AuthError, "Bad URI: %s" % err.value
+  except ImportError, err:
+    authinit = False
+    raise Errors.AuthError, "URI scheme not supported: %s" % err.value
+
+def Authenticate(User, Password):
+  """Checks password against initialized authentication scheme filename.
+
+ - Returns True or False, depending on authentication.
+
+ - May raise Errors.AuthError if something "funny" happens."""
+  global authinit
+  RetVal = False
+  if authinit:
+    if Auth.authtype == 'prog' or Auth.authtype == 'remote':
+      try:
+        if Auth.authenticate_plain( User, Password ):
+          RetVal = True
+      except Errors.AuthError:
+        pass
+    elif Auth.authtype == 'file':
+      Filename = Auth.authfile
+      # Revert to original code since Auth.py doesn't implement files yet.
+      try:
+        F = open(Filename) 
+      except IOError:
+        raise Errors.AuthError, \
+          "Cannot open file '%s' for reading." % Filename, \
+          "Check file permissions"
+
+      PasswordRecord = F.readline()
+      while PasswordRecord != "":
+
+        # Split about the :
+        Temp = PasswordRecord.strip().split(":")
+
+        # Have we found the correct user record?
+        if Temp[0] == User:
+          if Temp[1] == "":
+            raise Errors.AuthError, \
+              "User %s is denied login (blank password in file)" % Temp[0], \
+              "Blank password in file"
+
+          Perm = os.stat(Filename)[0] & 07777
+
+          # Any file may have encrypted passwords in it.
+          # Even though this is a Bad Idea.
+          if crypt.crypt(Password, Temp[1][:2]) == Temp[1]:
+            RetVal = True
+            break
+          # Only <secret> files may have cleartext passwords in it.
+          if Perm == 0400 or Perm == 0600:
+            if Temp[1] == Password:
+              RetVal = True
+              break
+        PasswordRecord = F.readline()
+      F.close()
+    else:
+      raise Errors.AuthError, \
+        "Authentication mechanism '%s' unknown." % Auth.authtype
+  else:
+    raise Errors.AuthError, "No authentication mechanism initialized."
+  # If we made it this far, we're either returning True or False.
   return RetVal
 
 def CheckPassword(Form):
-  "Checks a password against password files."
-
-  try:
-    # Find the requested home directory
-    os.environ["HOME"] = pwd.getpwnam(Form["user"].value)[5]
+  "Checks a password from a form."
 
-    # Look in same directory as TMDARC file
-    if os.environ.has_key("TMDARC"):
-      # Given location?
-      FN = os.path.join(os.path.split(os.environ["TMDARC"])[0], "tmda-cgi")
-    else:
-      # No given location, try ~/.tmda/tmda-cgi
-      FN = os.path.expanduser("~/.tmda/tmda-cgi")
-  
-    # Login succeed?
-    RetVal = ComparePassword(FN, Form["user"].value, Form["password"].value)
-    if RetVal > 0:
-      return RetVal
-  except KeyError:
-    RetVal = -4
-    FN = "<i>n/a</i>"
-  
-  # Login help?
-  if int(Form["debug"].value):
-    Errors = ["Logins for user %(user)s have been deactivated in file 
<tt>%(file)s</tt>",
-      "Password incorrect for user %(user)s in file <tt>%(file)s</tt>",
-      "User %(user)s was not found in file <tt>%(file)s</tt>",
-      "Could not read file <tt>%(file)s</tt>",
-      "User %(user)s does not exist"]
-    Err = Errors[-RetVal] % {"user": Form["user"].value, "file": FN}
-    Err += "<br>" + CgiUtil.FileDetails("Local password", FN)
-    if RetVal > -2:
-      CgiUtil.TermError("Login failed", "Bad pass / login disabled.", "validate 
password",
-        Err, "Correct entry for %s in file <tt>%s</tt>" % (Form["user"].value, FN))
-  if RetVal > -2:
-    return RetVal
-
-  # Login succeed?
-  FN = "/etc/tmda-cgi"
+  errMsg = "Password incorrect for user %s" % Form["user"].value
+  errHelp = "Reset password or correct file permissions"
   try:
-    RetVal = ComparePassword(FN, Form["user"].value, Form["password"].value)
-  except KeyError:
-    RetVal = -4
-  if RetVal > 0:
-    return RetVal
+    if Authenticate( Form["user"].value, Form["password"].value ):
+      return True
+  except Errors.AuthError, error:
+    errMsg = error.msg
+    if error.help != "":
+      errHelp = error.help
 
-  # Login help?
   if int(Form["debug"].value):
-    Err += "<br>" + Errors[-RetVal] % {"user": Form["user"].value, "file": FN}
-    Err += "<br>" + CgiUtil.FileDetails("Global password", FN)
-    CgiUtil.TermError("Login failed", "Password / password file error.",
-      "validate password", Err, "Reset password or correct file permissions")
-  return RetVal
-  
\ No newline at end of file
+    CgiUtil.TermError("Login failed", "Authentication error",
+      "validate password", errMsg, errHelp)
+  else:
+    return False

Index: Session.py
===================================================================
RCS file: /cvsroot/tmda/tmda-cgi/Session.py,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- Session.py  31 Mar 2003 19:33:51 -0000      1.3
+++ Session.py  1 Apr 2003 18:15:59 -0000       1.4
@@ -31,7 +31,6 @@
 import sys
 import time
 import CgiUtil
-import Authenticate
 
 Rands = random.Random()
 
@@ -155,8 +154,40 @@
       os.environ["TMDARC"] = os.environ["TMDARC"].replace("/~/",
         "/%s/" % Form["user"].value)
 
+    # Initialize the auth mechanism
+    import Authenticate
+    try:
+      if os.environ.has_key( "TMDA_AUTH_TYPE" ):
+        if os.environ["TMDA_AUTH_TYPE"] == "program":
+          Authenticate.InitProgramAuth( os.environ["TMDA_AUTH_ARG"],
+            os.environ["TMDA_AUTH_TRUE"] )
+        elif os.environ["TMDA_AUTH_TYPE"] == "remote":
+          Authenticate.InitRemoteAuth( os.environ["TMDA_AUTH_ARG"] )
+        elif os.environ["TMDA_AUTH_TYPE"] == "file":
+          Authenticate.InitFileAuth( os.environ["TMDA_AUTH_ARG"] )
+      else:
+        # Default to regular flat file.
+        # Order of preference:
+        #   1) $TMDARC/tmda-cgi
+        #   2) $HOME/tmda-cgi
+        #   3) /etc/tmda-cgi
+        if os.environ.has_key("TMDARC"):
+          File = os.path.join(os.path.split(os.environ["TMDARC"])[0],
+                              "tmda-cgi")
+        else:
+          # FIXME: "getpwnam" will not work for virtual users!
+          os.environ["HOME"] = pwd.getpwnam(Form["user"].value)[5]
+          File = os.path.expanduser("~/.tmda/tmda-cgi")
+        if not os.access( File, os.F_OK ):
+          File = "/etc/tmda-cgi"
+
+        Authenticate.InitFileAuth( File )
+    except ValueError, err:
+      CgiUtil.TermError( "Auth Initialization Failed", "ValueError caught", 
+        "init auth type %s" % os.environ["TMDA_AUTH_TYPE"], err, "Fix the code." )
+
     # Validate the new session
-    if Authenticate.CheckPassword(Form) > 0:
+    if Authenticate.CheckPassword(Form):
       self.Vars["User"]  = Form["user"].value
       PasswordRecord     = pwd.getpwnam(self.Vars["User"])
       self.Vars["UID"]   = PasswordRecord[2]

Index: compile
===================================================================
RCS file: /cvsroot/tmda/tmda-cgi/compile,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- compile     31 Mar 2003 02:27:42 -0000      1.2
+++ compile     1 Apr 2003 18:15:59 -0000       1.3
@@ -19,6 +19,11 @@
 # along with TMDA; if not, write to the Free Software Foundation, Inc.,
 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 
+# TODO: Perhaps a check should be put in for the various authentication schemes
+# that the arguments are valid.  This is currently done at runtime within
+# "Authenticate.py", but it may be better to check at least some of the
+# values at compile-time anyway.
+
 """Pending queue manipulation tool.
 
 Usage:  %(Program)s [OPTIONS]
@@ -37,6 +42,10 @@
        Specify a different directory for supplimental display files (icons and
        stylesheet).
 
+    -f <authfile>
+    --file-auth <authfile>
+       Specify a different authentication file than the default.
+
     -h
     --help
        Print this help message and exit.
@@ -53,10 +62,37 @@
     --no-su
        Compile a CGI to run in no-su mode.  Forces option "-m no-su".
 
+    -p <checkpassword>
+    --program-auth <checkpassword>
+     ** Warning *********************************************************
+     * The code used to do this is ALPHA stable, so do not rely on this *
+     * functionality yet.                                               *
+     ********************************************************************
+       Specify checkpassword-style authentication, where "checkpw" is the full
+       path to the desired checkpassword-style program.
+       - Ensure that --true-prog is set correctly for your system as well.  
+
+    -r <uri>
+    --remote-auth <uri>
+     ** Warning *********************************************************
+     * The code used to do this is ALPHA stable, so do not rely on this *
+     * functionality yet.                                               *
+     ********************************************************************
+       Specify remote authentication, where "uri" is the remote authentication
+       mechanism and host in the format:
+         protocol://host.domain.com[:port][/ldap_domain]
+       Currently supported protocols:
+         imap, imaps, apop, pop3, ldap
+
     -t <file>
     --target <file>
        Compile as a file other than ./tmda-cgi.
 
+    -T <true>
+    --true-prog <true>
+       Specify the path to the "true" program (which returns 0)
+       Default is '/usr/bin/true'
+
     -u <user>
     --user <user>
        Overrides the value of CGI_USER found in the configuration files.
@@ -71,6 +107,23 @@
 wide mode.  For the resulting code to run correctly, root will have to chown
 the resulting program.
 
+Authentication is done by default using a file of the format username:password.
+The default location for this file is chosen in the following order:
+
+ 1) File tmda-cgi in the same directory as the config file specified by the '-c'
+    option (See below for discussion of -c) 
+ 2) File tmda-cgi in ~/.tmda/tmda-cgi for the user attempting to log in.
+ 3) File /etc/tmda-cgi
+
+The password in this file should probably not be plaintext (although this is
+allowed in files of mode 400 or 600), but should be generated by the included
+script "genpass.py"
+
+The -f, -p, and -r options are used to change the default authentication.  Be
+warned that the options -p and -r are EXPERIMENTAL at this point.
+
+When specifying -p, ensure that the value of -T is correct for your system.
+
 The target may be specified on the command line with the -t option.
 
 You can specify the location of the supplimental display files (icons and
@@ -118,6 +171,21 @@
   Ask("Path to tmda-cgi Python files", OptD, "Path")
   Ask('User config file location (or "None" for system default)', OptD,
     "Config")
+  Ask("Authentication Type [file, program, remote, default]", OptD, "AuthType")
+  if OptD["AuthType"] == "file":
+    Ask('Authentication File (or "None" for system default)', OptD, "AuthFile")
+    OptD["AuthArg"] = OptD["AuthFile"]
+  elif OptD["AuthType"] == "program":
+    Ask('Authentication (checkpassword-style) program (full path)', OptD,
+      "AuthProg")
+    OptD["AuthArg"] = OptD["AuthProg"]
+    Ask('Full path to "true" program', OptD, "AuthTrue")
+  elif OptD["AuthType"] == "remote":
+    Ask('Authentication URI (protocol://host.domain.com[:port][/dn])', OptD,
+      "AuthURI")
+    OptD["AuthArg"] = OptD["AuthURI"]
+  elif OptD["AuthType"] == "default":
+    OptD["AuthArg"] = "None"
   Ask("Relative or absolute web path from CGI to display directory", OptD,
     "DispDir")
   if OptD["DispDir"][-1] != "/": OptD["DispDir"] += "/"
@@ -132,12 +200,18 @@
 
 # Keep options in one handy dictionary
 OptD = {}
-OptD["Python"]  = sys.executable
-OptD["Target"]  = "tmda-cgi"
-OptD["Base"]    = "../tmda/"
-OptD["Path"]    = "."
-OptD["DispDir"] = "../display/"
-OptD["User"]    = "nobody"
+OptD["Python"]   = sys.executable
+OptD["Target"]   = "tmda-cgi"
+OptD["Base"]     = "../tmda/"
+OptD["Path"]     = "."
+OptD["DispDir"]  = "../display/"
+OptD["User"]     = "nobody"
+OptD["AuthType"] = "default"
+OptD["AuthFile"] = "None"
+OptD["AuthProg"] = "/usr/sbin/checkpassword"
+OptD["AuthURI"]  = "imap://localhost"
+OptD["AuthArg"]  = "None"
+OptD["AuthTrue"] = "/usr/bin/true"
 if os.geteuid():
   OptD["Mode"] = "single-user"
 else:
@@ -154,9 +228,10 @@
   sys.exit(Code)
 
 try:
-  Opts, Args = getopt.getopt(sys.argv[1:], "b:c:d:i:m:nht:u:",
-    ["base-dir=", "config-file=", "display-dir=", "help", "install-prefix=",
-     "mode=", "no-su", "target=", "user="])
+  Opts, Args = getopt.getopt(sys.argv[1:], "b:c:d:f:i:m:nhp:r:t:T:u:",
+    ["base-dir=", "config-file=", "display-dir=", "file-auth=", "help",
+     "install-prefix=", "mode=", "no-su", "program-auth=", "remote-auth=",
+     "target=", "true-prog=", "user="])
 except getopt.error, Msg:
   Usage(1, Msg)
 
@@ -173,10 +248,24 @@
       OptD["DispDir"] = Arg
     else:
       OptD["DispDir"] = Arg + "/"
+  elif Opt in ("-f", "--file-auth"):
+    OptD["AuthType"] = "file"
+    OptD["AuthFile"] = Arg
+    OptD["AuthArg"] = Arg
   elif Opt in ("-i", "--install-prefix"):
     OptD["Path"] = Arg
+  elif Opt in ("-p", "--program-auth"):
+    OptD["AuthType"] = "program"
+    OptD["AuthProg"] = Arg
+    OptD["AuthArg"] = Arg
+  elif Opt in ("-r", "--remote-auth"):
+    OptD["AuthType"] = "remote"
+    OptD["AuthURI"] = Arg
+    OptD["AuthArg"] = Arg
   elif Opt in ("-t", "--target"):
     OptD["Target"] = Arg
+  elif Opt in ("-T", "--true-prog"):
+    OptD["AuthTrue"] = Arg
   elif Opt in ("-m", "--mode"):
     if not Arg in ("system-wide", "single-user", "no-su"):
       Usage(1, "Valid modes are system-wide, single-user, and no-su")
@@ -261,6 +350,14 @@
 """ % string.replace(OptD["Config"], "/~/", "/<user>/")
   F.write("""#define TMDARC "TMDARC=%(Config)s"
 """ % OptD)
+if OptD["AuthArg"] != "None":
+  print "Users will be authenticated using %(AuthType)s %(AuthArg)s" % OptD
+  F.write("""#define AUTH_TYPE "TMDA_AUTH_TYPE=%(AuthType)s"
+#define AUTH_ARG "TMDA_AUTH_ARG=%(AuthArg)s"
+""" % OptD)
+  if OptD["AuthType"] == "program":
+    F.write( """#define AUTH_TRUE "TMDA_AUTH_TRUE=%(AuthTrue)s"
+""" % OptD)
 F.close()
 
 print "Compiling..."
@@ -287,3 +384,23 @@
 compileall.compile_dir(OptD["Path"])
 
 print "Compilation done."
+
+# Unsafe authentication type warnings!
+if OptD["AuthType"] == "remote":
+  print """
+     ** Warning *********************************************************
+     * You have selected "remote" authentication.                       *
+     *   The code used to do this is ALPHA stable, and not every        *
+     * reportedly supported protocol has been tested, so do not rely on *
+     * this functionality yet.                                          *
+     ********************************************************************
+"""
+elif OptD["AuthType"] == "program":
+  print """
+     ** Warning *********************************************************
+     * You have selected "program" authentication.                      *
+     *   Initial testing seems to imply that this authentication method *
+     * works properly, but the code used to do so is ALPHA stable, so   *
+     * not relay on this functionality yet.
+     ********************************************************************
+"""

Index: tmda-cgi.c
===================================================================
RCS file: /cvsroot/tmda/tmda-cgi/tmda-cgi.c,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- tmda-cgi.c  31 Mar 2003 02:24:53 -0000      1.2
+++ tmda-cgi.c  1 Apr 2003 18:15:59 -0000       1.3
@@ -28,6 +28,13 @@
 #ifdef TMDARC
   putenv(TMDARC);
 #endif
+#ifdef AUTH_ARG
+  putenv(AUTH_TYPE);
+  putenv(AUTH_ARG);
+  #ifdef AUTH_TRUE
+  putenv(AUTH_TRUE);
+  #endif
+#endif
   putenv(MODE);
   putenv(USER);
   putenv(DISP_DIR);

_______________________________________
tmda-cvs mailing list
http://tmda.net/lists/listinfo/tmda-cvs

Reply via email to