I’ve been rewriting a script I use that lets people update their linux and samba user passwords at the same time. Our server uses samba and openldap to behave as a primary domain controller for our windows computers and a single sign-on for a few other services. It’s been working great for years and way back when we started using this, I wrote a perl script to take care of keeping the two passwords in sync. The original problem was that if you use the regular linux passwd command, the linux password will change, but the samba one will not. The samba password uses an nthash. In a given user’s ldap account, there is the userPassword field which holds the linux password and the sambaNTPassword field which holds the samba password. For a user to be able to use any of the web services we run on the server and login to any of our windows computers with the same password, these two fields need to contain the same password.

The solution however many years ago, was a perl script. I’m not much of a perl programmer, but I was able to hack something together that worked for years. However, each time that I upgraded the system, I’d have to install a few perl modules and it was always really difficult. If I were better at perl, this may not have been an issue, but it seemed to get more difficult each time I had to do it. It got to the point where I had a server with an older os on it where the script worked fine. I would direct people to log in to that server just to run the script to set the passwords. Thus, I finally decided that I should do something about this. These days, I do much more ruby on rails programming (though I’m still very much a beginner), so I figured this would be a good project to write in ruby. I found the net-ldap gem and was on my way.

The good news is that it didn’t take me too long to set something up. I had a simple script that would let a user change their own password. However, the biggest use that my old perl script got was me using it to set an initial password for new accounts. Or, for me to reset a user’s password after they forgot what it was. So I knew the next step was to write something that would let me (as the root user) change another user’s password. This is my giant roadblock. I’m not an expert, but I’ve read the net-ldap docs and even looked at the code a bit. As far as I can tell, it does not support binding with one user’s authentication and then updating a different user’s attributes. Now, since I’ve been doing this for years with my perl script, I’m completely certain that I have my server set up properly. The issue has to be in the net-ldap gem. In fact, I’ve used the get_operation_result a lot and am pretty sure this is what’s happening.

Here is the main method where I update a user’s settings. I’ve edited it to make it as simple as possible and I’ve put in a bunch of print statements to see what’s happening.

def change_password(logged_in_user, user_to_change, linux, samba)
    puts "USER TO CHANGE: #{user_to_change}"
    settings = user_settings(logged_in_user)
    conn = Net::LDAP.new(settings)
    conn.bind
    puts "CHANGE_PASSWORD_BIND:  #{conn.get_operation_result.code}"
    puts "CHANGE_PASSWORD_BIND:  #{conn.get_operation_result.message}"
    conn.replace_attribute user_settings(user_to_change)[:auth][:username], :userPassword, linux
    puts "CHANGE_PASSWORD: #{conn.get_operation_result.code}"
    puts "CHANGE_PASSWORD:  #{conn.get_operation_result.message}"
    conn.replace_attribute user_settings(user_to_change)[:auth][:username], :sambaNTPassword, samba
    puts "CHANGE_PASSWORD: #{conn.get_operation_result.code}"
    puts "CHANGE_PASSWORD:  #{conn.get_operation_result.message}"
  end

For a regular user changing their own password, the logged_in_user and user_to_change will be the same. But when I’m root trying to change someone else’s password, this is not the case. Here’s a sample of the output for when I try to update a user named arthur.

USER TO CHANGE: arthur
CHANGE_PASSWORD_BIND:  0
CHANGE_PASSWORD_BIND:  Success
CHANGE_PASSWORD: 50
CHANGE_PASSWORD:  Insufficient Access Rights
CHANGE_PASSWORD: 50
CHANGE_PASSWORD:  Insufficient Access Rights

As soon as I try to run one of the replace_attribute commands, it tries to bind with that user’s credentials. That will fail because I’ve collected the root user’s password to use for authentication, not the user’s password. As far as I can tell, this is by design. From the ruby-ldap docs:

User code does not need to call bind directly. It will be called implicitly by the library whenever you invoke an LDAP operation, such as search or add.

So, any bind I do doesn’t matter because it’s going to automatically bind with the replace_attribute call. And this call is going to use the dn for the user that I’m trying to fix, not the root user’s dn.

I’ve spent all day at work and a good chunk of this evening trying to figure this out and I just think it’s not designed to work as I’m expecting. So, what to do. Sometime next week, I think instead of using the net-ldap methods, I’ll just run a system command that will run with the root binding. I think it’s kind of funny that I tried to do this in ruby using the correct commands and I’m going back to how I did it in perl where I just run system commands with backtics. Anyway, if I ever get this working, I’m sure I’ll post how I ended up doing it.