I am currently preparing a machine for a web hosting service, and I decided to use MySQL to store all our users (since the rest of our services use it already). For that, I am using libnss-mysql and pam-mysql. However, even though most of the setup is functioning, I am facing a problem when trying to change a user's password with passwd.

At the moment, it is possible to create a user (INSERT INTO) and log in as this user using su. The machine does not prompt for a password, and access to the user's shell is directly given. However, once I'm logged as this user, passwd ends with :

$ passwd myuser
passwd: Authentication token manipulation error
passwd: password unchanged

According to the MySQL logs, a query is made when passwd is called, therefore the connection with MySQL isn't a problem. Besides, when I try calling passwd with an unexisting user, I get an appropriate passwd: user 'doesnotexist' does not exist. passwd does find a user, but cannot modify its information. The auth.log log file says :

pam_unix(passwd:chauthtok): user "myuser" does not exist in /etc/passwd
pam_mysql - option verbose is set to "1"
pam_mysql - pam_sm_chauthtok() called.
pam_mysql - pam_mysql_open_db() called.
pam_mysql - pam_mysql_open_db() returning 0.
pam_mysql - pam_sm_chauthtok() returning 0.
pam_mysql - pam_mysql_release_ctx() called.
pam_mysql - pam_mysql_destroy_ctx() called.
pam_mysql - pam_mysql_close_db() called.

When calling passwd -Sa to get the status of all accounts, the myuser account does appear. Besides, getent passwd and getent shadow both return a valid entry for myuser.

$ passwd -Sa
...
messagebus L 06/28/2014 0 99999 7 -1
mysql L 06/28/2014 0 99999 7 -1
myuser P 01/01/1970 0 99999 7 -1

$ getent passwd myuser
myuser:x:5001:5000:First Last:/home/members/myuser:/bin/bash

$ getent shadow myuser
myuser:$6$...:0:0:99999:7:::0

However, when requesting ageing information about myuser :

$ chage -l myuser
chage: user 'myuser' does not exist in /etc/passwd

All in all :

  • su does find the user, and performs a password-less login in all cases (if I am root, otherwise I am prompted for a password, and get the token error).
  • chage cannot find the user ; it seems to search /etc/passwd instead of the DB.
  • passwd does find the user, but returns a token error when trying to edit it.

Here are some configuration samples :

/etc/pam.d/common-account

account sufficient          pam_unix.so 
account required            pam_mysql.so config_file=/etc/pam-mysql.conf

/etc/pam.d/common-auth

auth    sufficient          pam_unix.so nullok_secure
auth    required            pam_mysql.so config_file=/etc/pam-mysql.conf

/etc/pam.d/common-session

session sufficient          pam_unix.so 
session required            pwam_mysql.so config_file=/etc/pam-mysql.conf

/etc/pam.d/common-passwd

password    sufficient      pam_unix.so obscure sha512
password    required        pam_mysql.so config_file=/etc/pam-mysql.conf

/etc/libnss-mysql.cfg

getpwnam    SELECT username,'x',(5000+id),5000,CONCAT(firstname, lastname),CONCAT('/home/members/', username),'/bin/bash' \
            FROM users \
            WHERE username='%1$s' \
            LIMIT 1
getpwuid    SELECT username,'x',(5000+id),5000,CONCAT(firstname, lastname),CONCAT('/home/members/', username),'/bin/bash' \
            FROM users \
            WHERE (5000+id)='%1$u' \
            LIMIT 1
getspnam    SELECT username,password,0,'0','99999','7','-1','-1','0' \
            FROM users \
            WHERE username='%1$s' \
            LIMIT 1
getpwent    SELECT username,'x',(5000+id),5000,CONCAT(firstname, lastname),CONCAT('/home/members/', username),'/bin/bash' \
            FROM users
getspent    SELECT username,password,0,'0','99999','7','-1','-1','0' \
            FROM users
getgrnam    SELECT '%1$s','members',5000
getgrgid    SELECT name,password,'%1$u'
getgrent    SELECT 'members','members',5000
memsbygid   SELECT username \
            FROM users
gidsbymem   SELECT 5000
  • My user IDs start at 1 in the DB, but must start at 5000 for the system (5000+id).
  • /bin/bash for everyone.
  • The $HOME directory for myuser is /home/members/myuser.
  • Everyone belongs to the members group, the GID of which is 5000.

/etc/pam-mysql.conf

users.host             = localhost
users.database         = mydatabase
users.db_user          = root
users.db_passwd        = root_password
users.table            = users
users.user_column      = username
users.password_column  = password
users.password_crypt   = 1

/etc/nsswitch.conf

passwd:         compat mysql
group:          compat
shadow:         compat mysql

What I tried :

  • Setting a password (SHA512 hash, $6$...) in the DB for myuser.
  • Deleting the password first (passwd -d) : tells me myuser cannot be found in /etc/passwd. Same happens if I try to lock the account or force its password expiration.
  • Calling passwd from the root account, no changes.
  • Logging from a terminal on the machine (instead of su) : I get the authentication token manipulation error, and get sent back to the login prompt. Same when I try to connect with SSH (plus the password expiration warnings which are logical).

To me, it seems like the user management part (account/session ?) is correctly linked to MySQL, yet the password management part seems to rely partially on /etc/passwd. Have I missed something in the configuration?

up vote 2 down vote accepted

Okay, I found the solution for that one. First : I would strongly recommend anyone getting into this to study PAM modules basics. This really helps understanding the whole thing. Now, let's have a look at my common-* files. They all have the same structure :

facility    required    pam_unix.so [...]
facility    sufficient  pam_mysql.so [...]

Now, after reading more about PAM modules, this looks ridiculous to me. Here is some documentation about the PAM control flags :

required : if the module succeeds, the rest of the chain is executed, and the request is granted unless some other module fails. If the module fails, the rest of the chain is also executed, but the request is ultimately denied.

sufficient : if the module succeeds and no earlier module in the chain has failed, the chain is immediately terminated and the request is granted. If the module fails, the module is ignored and the rest of the chain is executed.

Basically, my configuration makes PAM behaves in the following way : the UNIX authentication through /etc/passwd and /etc/shadow must succeed in all cases. The MySQL lookup will also be performed, yet if the UNIX authentication failed, its result won't be taken into account.

With this setup, the MySQL authentication mechanism is rendered useless in all situations. A proper configuration would be :

facility    sufficient  pam_mysql.so [...]
facility    required    pam_unix.so [...]

Which tells : The MySQL authentication mechanism is sufficient. If it succeeds, no further mechanisms are to be tested. If it fails however, the UNIX authentication must succeed. Since I'll have more MySQL users than UNIX users, it is more logical to authenticate against MySQL first.

It is also important to note the difference in the roles of PAM and NSS. With my setup, the authentication was perfectly functional since the NSS configuration was correct. NSS handles basic UNIX authentication, but not account/session management, nor service-specific (SSH/FTP/...) connections. All these are handled by PAM. This separation is the reason why it was possible for root to connect as a MySQL user : the NSS did find the entry, and since root does not need to authenticate against anything to take a user's identity, PAM modules were never called.

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.