def authenticate(self, username, password, save=None): # Retrieve the user object so we can query it further # TODO: test that the user.last_login field gets updated with this call user = auth.authenticate(username=username, password=password) # AUTH_INVALID_PASSWORD: if user is None, the authentication has failed # for some reason other than the user not existing if user != None: log(1, "Authentication success: LOCAL user '%s' logged in." % username) return AuthenticatorResult(code=AuthenticatorResult.AUTH_SUCCESS) # Auth has failed if we've got this far. Find out why. # Ensure user exists try: self.user = User.objects.get(username=username) except User.DoesNotExist: log(1, "Authentication failed: username '%s' not found." % username) return AuthenticatorResult(code=AuthenticatorResult.AUTH_USER_NOT_FOUND) # AUTH_USER_DISABLED: The user is disabled if not self.user.is_active: log(1, "Authentication failed: RADIUS user '%s' is disabled." % username) return AuthenticatorResult(code=AuthenticatorResult.AUTH_USER_DISABLED) # AUTH_USER_EXPIRED: The User is expired if self.user.userprofile.is_expired(): log(1, "Authentication failed: RADIUS user '%s' has expired." % username) return AuthenticatorResult(code=AuthenticatorResult.AUTH_USER_EXPIRED) log(1, "Authentication failed: invalid password for LOCAL user '%s'." % username) return AuthenticatorResult(code=AuthenticatorResult.AUTH_INVALID_PASSWORD)
def get_server_type(self, redetect=False): """This determines what type of LDAP server we're dealing with""" log(3, "Detecting LDAP server type") if self.auth_server.ldap_type != None and redetect != True: return self.auth_server.ldap_type self.connect() results = self.ldap_conn.search_s("", ldap.SCOPE_BASE, "objectClass=*", ["vendorversion", "objectClass", \ "isGlobalCatalogReady", "vendorname"]) log(6, "LDAP query probe result: %s" % str(results)) if len(results) <= 0 or len(results[0]) < 2: log(3, "Generic LDAP server detected") return "GenericLDAP" entry = results[0][1] if "isGlobalCatalogReady" in entry and \ entry["isGlobalCatalogReady"] == ["TRUE"]: log(3, "Active Directory LDAP server detected") return "ActiveDirectory" log(3, "No specific type of LDAP server found, using GenericLDAP") return "GenericLDAP"
def __getattr__(self, attr): if not attr.startswith("map_"): raise AttributeError() attr = attr.replace("map_", "") log(6, "Getting attribute: %s" % attr) value = None if attr in self.COPY_ATTRS: # this is a list of all the LDAP attributes that could potentially # be used to populate the fcombine attribute in user/userprofile dir_attrs = self.COPY_ATTRS[attr] # we search for the first non-blank/non-null attribute and use # that for dir_attr in dir_attrs: if dir_attr in self.entry_attrs: value = self.entry_attrs[dir_attr][0] # see if we need to parse the value at all parse_fn_name = "parse_%s" % dir_attr.lower() if hasattr(self, parse_fn_name) == True: value = getattr(self, parse_fn_name)(value) if value != None and len(value) != 0: log(6, ("Found LDAP attribute, mapping %s to %s," " value= %s") % (dir_attr, attr, value)) break else: raise AttributeError() return lambda: value
def run(self): log(1, "Now listening for requests on FcombineDaemon XML RPC server") try: self.server.serve_forever() except KeyboardInterrupt: log(1, "FcombineDaemon stopping")
def authenticate(self, username, password): #XXX: check expiry, check disabled, if we want try: user_entry = self.get_ldap_entry(username) except MultipleUsersException, ex: log(2, "LDAP authentication failure: %s" % ex) return AuthenticatorResult(AuthenticatorResult.AUTH_MULTIPLE_USERS)
def run(self): while True: for dir_server in DirectoryServer.objects.all(): log(6, "processing dir_server: %s" % dir_server) # calculate time sice last run # compare to specified run frequency value. If we should run: # get a directory server import handler via a factory # spawn the import handler in a new thread time.sleep(600)
def __init__(self): self.home_source = xsftp.common.constants.HOMEDIR_SOURCE self.server_root_mount_point = xsftp.common.constants.SERVER_DIR self.mounter_factory = MounterFactory() self.mounted_servers = {} # self.mounted_bind_mounts = {} self.init_xml_rpc() log(1, "AutoMountManager started")
def to_user(self, entry): """Creates a basic Fcombine UserProfile object based on credentials""" log(3, "RADIUS add user, entry=%s" % str(entry)) userprofile = UserProfile() userprofile.user = User() userprofile.user.username = entry return userprofile
def __init__(self): Thread.__init__(self) log(1, "Initializing FcombineDaemon XML RPC server") key_file = "/etc/pki/tls/private/fcombine_xmlrpc.key" cert_file = "/etc/pki/tls/certs/fcombine_xmlrpc.crt" ca_file = "/etc/pki/tls/certs/fcombine_xmlrpc.crt" self.server = SecureXMLRPCServer(("localhost", 9999), key_file, cert_file, ca_file) log(1, "Done initializing FcombineDaemon XML RPC server")
def check_path(self, path): """This is triggered whenever we suspect that we might need to do something in reaction to a user action/system call such as readdir(), open(), etc. This gives us a chance to do things like populate the /home directory with users or do bind mounts.""" log(6, "check_path") # the path given to us resembles that of an absolute path, relative # to /home # for example, if our fuse moudle is mounted at /home, then the path # we'll get is /. It's like a chroot. parts = self.explode(os.path.normpath(path)) # get rid of leading / parts.pop(0) # check to see if we've got enough path components to be in # a bind mount point i.e. /home/${user}/xsftp/${server}/...... if len(parts) >= 3: username = parts.pop(0) bmp = parts.pop(0) server_name = parts.pop(0) log(3, "Checking: username = %s, bmp = %s, server_name = %s" % \ (username, bmp, server_name)) # check to see if it's a bind mount point if bmp == self.BMP_DIR: server = None try: # mount the server if neccessary server = self.mount_server(server_name) except Exception, ex: log(1, "Exception while mounting server: %s, error = %s" % \ (server_name, str(ex))) return try: # let's mount the mofo! self.mount_bmp(username, server) except Exception, ex: log(1, "Exception while bind mounting server: %s for user: \ %s, error: %s" % (server_name, username, str(ex))) # see if we should unmount the server straight away # due to no users if len(server.bind_mounts) == 0: log(3, "Unmounting %s from %s due to zero users" % \ (server.name, server.mount_point)) self.unmount_server(server.mount_point) return
def create_ramdisk(self, mountpoint, size): # ensure serverdir exists if not os.path.exists(mountpoint): os.makedirs(mountpoint) # see if it's already mounted if mountutil.is_mounted(mountpoint) == True: log(2, "%s was already mounted, unmounting") mountutil.unmount(mountpoint) # create the ramdisk and mount it at SERVERDIR mount_cmd = ["mount", "-t", "tmpfs", "-o", \ "size=" + str(size) + "M", "tmpfs", mountpoint] popenutil.quick_popen(mount_cmd)
def mount_bmp(self, username, server): #_name, server_mount_point): log(6, "mount_bmp") bind_mount_point = os.path.join(self.home_source, \ username, self.BMP_DIR, server.server_name) if mountutil.is_mounted(bind_mount_point) == True: log(3, "BMP already mounted: %s" % bind_mount_point) return cmd = ["mount", "-o", "bind", server.mount_point, \ bind_mount_point] popenutil.quick_popen(cmd) server.add_bind_mount(username, bind_mount_point)
def pam_authenticate(self, username, password): """This is called to authenticate a user via PAM""" log(6, "pam_authenticate") # get the user object from our DB try: result = self.authenticate_user(username, password) if result == AuthenticatorResult.AUTH_SUCCESS: return self.FCOMBINE_AUTH_SUCCESS else: return self.FCOMBINE_AUTH_FAILED except User.DoesNotExist: log(2, 'Authentication failed for user "%s": User Not Found' % \ username) return self.FCOMBINE_DAEMON_ERR
def explode(self, path): """Explode a path into all its little giblets. _path_ should ideally be normalized first""" log(6, "explode") gibs = [] head = path while True: if head == "/": gibs.insert(0, head) break head, tail = os.path.split(head) gibs.insert(0, tail) return gibs
def get_ldap_entry(self, username): """Return the entry from the LDAP server. Returns None if the user is not found""" # check the username is valid if self.is_valid_username(username) == False: log(5, "Invalid username") return None log(6, "Attempting to connect to server: %s" % self.get_uri()) try: self.connect() except ldap.INVALID_CREDENTIALS: log(2, "Service account bind failed for dn: %s" % bind_dn) raise DirectoryQueryException("Invalid LDAP bind credentials") except exceptions.ServerDownException: raise DirectoryQueryException("LDAP server down") # now search for the user filter_template = Template(self.auth_server.filter) filter = filter_template.substitute(username=username) log(5, "Searching, using filter: %s" % filter) results = self.ldap_conn.search_s(self.auth_server.base_dn, \ ldap.SCOPE_SUBTREE, filter) #, ["dn"]) # filter out referrals results = self.filter_results(results) if len(results) == 0: log(2, "LDAP Auth: User not found: %s" % username) return None if len(results) > 1: raise MultipleUsersException( "LDAP Auth: Multiple users found: %s" % username) user_entry = results[0] return user_entry
def statfs(self): log(7, "statfs") """ Should return an object with statvfs attributes (f_bsize, f_frsize...). Eg., the return value of os.statvfs() is such a thing (since py 2.2). If you are not reusing an existing statvfs object, start with fuse.StatVFS(), and define the attributes. To provide usable information (ie., you want sensible df(1) output, you are suggested to specify the following attributes: - f_bsize - preferred size of file blocks, in bytes - f_frsize - fundamental size of file blcoks, in bytes [if you have no idea, use the same as blocksize] - f_blocks - total number of blocks in the filesystem - f_bfree - number of free blocks - f_files - total number of file inodes - f_ffree - nunber of free file inodes """ return os.statvfs(".")
def mount_server(self, server_name): log(6, "mount_server") # check to see if the server is already mounted by checking the dict if server_name in self.mounted_servers: # we're already SLAM mounted, so nothing more to do return # We're not mounted, so we need to get the server object from the daemon server = None try: server = self.xml_rpc_server.get_server_by_name(server_name) except XMLRPCClient.dex("xsftp.common.models.Server.DoesNotExist"): log(2, "Cannot mount server %s, don't know about it!" % (server_name)) return server.mount_point = os.path.join( \ self.server_root_mount_point, server.name) if os.path.exists(server.mount_point) == False: os.makedirs(server.mount_point) if mountutil.is_mounted(server.mount_point) == True: log(3, "Server already mounted: %s" % server.mount_point) else: # determine the type of mounter needed mounter_class = self.mounter_factory.get_mounter_class( \ server.type) mounter = mounter_class() mounter.mount(server) self.mounted_servers[server_name] = server return server
def to_user(self, result): '''Creates and returns a Fcombine UserProfile object based on the user's attributes stored within the LDAP server''' log(3, "LDAP add user, entry=%s" % str(entry)) entry = result.entry userprofile = UserProfile() userprofile.user = User() entry_attrs = entry[1] server_type = self.get_server_type() mapper = self.get_mapper(server_type)(entry_attrs) for attr in self.USER_PROFILE_ATTRS: value = getattr(mapper, "map_%s" % attr)() log(6, "Setting %s to %s" % (attr, value)) setattr(userprofile, attr, value) for attr in self.USER_ATTRS: value = getattr(mapper, "map_%s" % attr)() log(6, "Setting %s to %s" % (attr, value)) setattr(userprofile.user, attr, value) return userprofile
def connect(self): """Connect and bind to the LDAP server""" # TODO log(1, "IMPLEMENT CA CERT VERIFICATION AND ACCEPT FIRST CERT") if self.auth_server.verify_cert == False: ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) # bind as the service account self.ldap_conn = ldap.initialize(self.get_uri()) self.ldap_conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3) self.ldap_conn.set_option(ldap.OPT_REFERRALS, 0) bind_dn = self.auth_server.bind_dn try: self.ldap_conn.simple_bind_s(bind_dn, \ self.auth_server.bind_password) log(3, "Successful service account bind to LDAP server: %s" % \ self.get_uri()) except ldap.SERVER_DOWN: log(1, "LDAP server %s:%d down" % (self.auth_server.hostname, \ self.auth_server.port)) raise exceptions.ServerDownException
def fsdestroy(self): """The fuse filesystem is being destroyed. This means we need to unmount everything""" log(6, "fsdestroy") try: self.unmount_all() log(1, "Finished unmounting all objects") except Exception, ex: log(1, "Exception while unmounting all: %s" % str(ex))
def unmount_all(self): # unmount all servers and their bind mounts # make a copy of the list to ensure we're not # mutating the items we're iterating over. # python 3.0 safe for server_name, server in list(self.mounted_servers.items()): log(2, "Unmounting all mountpoints for server %s" % \ server.name) # unmount bind mounts for username, bind_mount_point in list(server.bind_mounts.items()): log(2, "Unmounting bind mount %s for server %s" % (bind_mount_point, server.name)) self.unmount_bmp(bind_mount_point) del server.bind_mounts[username] # unmount the server itself log(2, "Unmounting server %s" % server.name) self.unmount_server(server) del self.mounted_servers[server_name]
def fsdestroy(self): log(7, "fsdestroy") self.demand_mounter.fsdestroy()
def __init__(self): Thread.__init__(self) log(1, "Initialized FcombineDaemon Directory Server Importer")
# self.mounted_servers[server_name].last_activity = # datetime.datetime.now() def fsdestroy(self): """The fuse filesystem is being destroyed. This means we need to unmount everything""" log(6, "fsdestroy") try: self.unmount_all() log(1, "Finished unmounting all objects") except Exception, ex: log(1, "Exception while unmounting all: %s" % str(ex)) log(0, "AutoMounterManager stopped") def unmount_all(self): # unmount all servers and their bind mounts # make a copy of the list to ensure we're not # mutating the items we're iterating over. # python 3.0 safe for server_name, server in list(self.mounted_servers.items()): log(2, "Unmounting all mountpoints for server %s" % \ server.name) # unmount bind mounts for username, bind_mount_point in list(server.bind_mounts.items()): log(2, "Unmounting bind mount %s for server %s" % (bind_mount_point, server.name))
def utime(self, path, times): log(7, "utime") self.update_activity(path) os.utime("." + path, times)
def access(self, path, mode): log(7, "access") if not os.access("." + path, mode): return -EACCES
return AuthenticatorResult(AuthenticatorResult.AUTH_MULTIPLE_USERS) except DirectoryQueryException, ex: log(2, "LDAP authentication failure: %s" % ex) return AuthenticatorResult( code=AuthenticatorResult.AUTH_FAIL) if user_entry == None: return AuthenticatorResult( code=AuthenticatorResult.AUTH_USER_NOT_FOUND) user_dn = user_entry[0] # rebind as the user try: self.ldap_conn.simple_bind_s(user_dn, password) log(2, "LDAP Authentication succeeded, user dn=%s" % user_dn) result = AuthenticatorResult() result.code = AuthenticatorResult.AUTH_SUCCESS result.entry = user_entry return result except ldap.INVALID_CREDENTIALS: log(2, "LDAP invalid password user=%s" % username) return AuthenticatorResult( code=AuthenticatorResult.AUTH_INVALID_PASSWORD) def lookup(self, username, auth_result): if auth_result != None: entry = auth_result.entry else: entry = self.get_ldap_entry(username)
def authenticate(self, username, password): log(6, "authenticate") server = self.auth_server.radius_server port = self.auth_server.radius_authport secret = self.auth_server.radius_secret # Attempt to autenticate against the configured RADIUS server try: radius_dictionary = Dictionary(constants.RADIUS_DICTIONARY) log(6, ("RADIUS module connecting to server: " \ "%s, authport: %s") % (server, port)) srv = Client(server=server, authport=port, \ secret=secret.encode("ascii"), \ dict=radius_dictionary) req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest) req["User-Name"] = str(username) req["User-Password"] = req.PwCrypt(password.encode('ascii')) req["NAS-Identifier"] = "fcombine" log(6, "Senging RADIUS Access Request to server") reply = srv.SendPacket(req) log(6, "Received reply from RADIUS server, reply code was: %s" % \ reply.code) if reply.code == pyrad.packet.AccessAccept: log(1, "Authentication success: RADIUS user '%s' logged in." % \ username) result = AuthenticatorResult() result.code = AuthenticatorResult.AUTH_SUCCESS result.entry = username return result elif reply.code == pyrad.packet.AccessReject: log(1, ("Authentication failed: Access rejected for " \ "RADIUS user '%s'.") % username) # This may not necessarily be caused by an invalid password, so # we are forced to return a general AUTH_FAIL here return AuthenticatorResult( code=AuthenticatorResult.AUTH_FAIL) else: log(1, ("Authentication failed: Access request failed for" " RADIUS user '%s'. RADIUS packet code was: %s") \ % reply.code) return AuthenticatorResult( code=AuthenticatorResult.AUTH_FAIL) except pyrad.client.Timeout: log(1, "Radius server %s:%d down" % (server, port)) return AuthenticatorResult( code=AuthenticatorResult.AUTH_FAIL) except Exception, e: log(1, ("Authentication failed: Error authenticating RADIUS " " user '%s'. Error Type: %s, Message: %s") % \ (username, type(e), e.message or None)) stacktrace(3) return AuthenticatorResult( code=AuthenticatorResult.AUTH_FAIL)
def authenticate_user(self, username, password): # This function will first try to authenticate the user. If it's # successful, we will query the associated directory server to # pull down user details. We don't care if the directory server # returns nothing as it's just pretty user details etc. # first see if we've already seen the user, that is, they are in # the Fcombine database try: user = User.objects.get(username=username) authenticator = self.authenticators[user.userprofile.auth_server.id] log(6, "User found in the database, authenticator = %d" % \ (user.userprofile.auth_server.id)) result = authenticator.authenticate(username, password) # TODO: do we need to synchronize the user/userprofile objects # here? return result.code except User.DoesNotExist: pass # if we're here, it's possible the user does exist, but is not in the # database yet, ie, they are in the process of being imported into the # database which may be a slow operation as it is serialised. # authenticate the user against each server until we're successful with # one, or none. We handle potential duplication of usernames across # different directory servers at the import phase in that we raise an # error if an admin tries to import a user whos username already exists # on the Fcombine. result_queue = Queue() auth_threads = [] for auth_server_id, authenticator in self.authenticators.items(): log(6, "Thread count = %d" % len(auth_threads)) # TODO: make sure this doesn't cause memory problems if a server # goes down. We should probably track if a server is having # problems so we can rate limit authentication auth_thread = AuthenticatorThread(authenticator, auth_server_id, \ result_queue, username, password) auth_threads.append(auth_thread) auth_thread.start() failed = False user = User() user_profile = UserProfile() for i in xrange(len(auth_threads)): result = result_queue.get() log(6, "Got result from queue") if result.is_successful(): # insert the user into our database now. # look up the auth server and directory server for this user auth_server = AuthServer.objects.get(id=result.auth_server_id) dir_server_id = auth_server.directory_server.id directory_queryer = self.directory_queryers[dir_server_id] # look up the user in the directory #user_profile = result.authenticator.to_user(result.entry) try: user_profile = directory_queryer.lookup(result) # save the user profile user_profile.auth_server = auth_server user_profile.user.save() user_profile.save() except DirectoryQueryException, ex: log(2, "DirectoryQueryException: %s" % str(ex)) return result.code if result.is_failure() == False: failed = True
def fsinit(self): log(7, "fsinit") os.chdir(self.home_source)