Repository: ansible/ansible

Issue:
user module "failed to update users password: invalid literal for int() with base 10: 'NONE'"
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and devel branch are affected too.
Always add information AFTER of these html comments.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Bug Report

##### COMPONENT NAME
<!---
Insert below this comment the name of the module, plugin, task or feature.
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path
-->
user
##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
$ ansible --version
ansible 2.4.2.0 (stable-2.4 4433544eb0) last updated 2017/11/13 16:50:30 (GMT +200)
  python version = 2.7.5 (default, Aug  2 2016, 04:20:16) [GCC 4.8.5 20150623 (Red Hat 4.8.5-4)]
```

##### CONFIGURATION
<!---
If using Ansible 2.4 or above, paste the results of "ansible-config dump --only-changed"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
N/A
##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->
I'm running Ansible from:
`Red Hat Enterprise Linux Server release 7.3 (Maipo)`
managing OS:
`Oracle Solaris 10 1/13 s10x_u11wos_24a X86`
##### SUMMARY
<!--- Explain the problem briefly -->
While trying to crate new user on Solaris 10, Ansible crashes while it is trying to set up the new password for user. The problem occurs because Ansible is not checking what kind of values was taken from `/etc/default/passwd` on Solaris system, when it is trying to get default values of password's aging:
```python
1300     def get_password_defaults(self):
1301         # Read password aging defaults
1302         try:
1303             minweeks = ''
1304             maxweeks = ''
1305             warnweeks = ''
1306             for line in open("/etc/default/passwd", 'r'):
1307                 line = line.strip()
1308                 if (line.startswith('#') or line == ''):
1309                     continue
1310                 key, value = line.split('=')
1311                 if key == "MINWEEKS":
1312                     minweeks = value.rstrip('\n')
1313                 elif key == "MAXWEEKS":
1314                     maxweeks = value.rstrip('\n')
1315                 elif key == "WARNWEEKS":
1316                     warnweeks = value.rstrip('\n')
1317         except Exception:
1318             err = get_exception()
1319             self.module.fail_json(msg="failed to read /etc/default/passwd: %s" % str(err))
1320 
1321         return (minweeks, maxweeks, warnweeks)
```
In my system those 3 parameters are set up to `NONE` value:
```
root# cat /etc/default/passwd
...
MINWEEKS=NONE
MAXWEEKS=NONE
WARNWEEKS=NONE
...
```
In this case when functions:
```python
1331     def create_user(self): 
```
```python
1406     def modify_user_usermod(self):
```
are trying to use `int()` function with `NONE` values, python is throwing exception `invalid literal for int() with base 10: 'NONE'` and Anisble do not change passwords on newly created account.
```python
1380                 minweeks, maxweeks, warnweeks = self.get_password_defaults()
1381                 try:
1382                     lines = []
1383                     for line in open(self.SHADOWFILE, 'rb').readlines():
...
1389                         fields[1] = self.password
1390                         fields[2] = str(int(time.time() // 86400))
1391                         if minweeks:
1392                             fields[3] = str(int(minweeks) * 7)
1393                         if maxweeks:
1394                             fields[4] = str(int(maxweeks) * 7)
1395                         if warnweeks:
1396                             fields[5] = str(int(warnweeks) * 7)
...
```
When I replace `fields[3-5]` with empty strings the passwords was set without any further problems:
```python
1380                 minweeks, maxweeks, warnweeks = self.get_password_defaults()
1381                 try:
1382                     lines = []
1383                     for line in open(self.SHADOWFILE, 'rb').readlines():
...
1389                         fields[1] = self.password
1390                         fields[2] = str(int(time.time() // 86400))
1391                         if minweeks:
1392                             fields[3] = ""
1393                         if maxweeks:
1394                             fields[4] = ""
1395                         if warnweeks:
1396                             fields[5] = ""
...
```
Probably it will be good to add some kind of `if` statement to check these variables' values, before they will be use further.
##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
---
- hosts: linux
  become: yes
  become_method: su
  tasks:
    - name: create test user
      user:
        name: tuser
        password: 123456
        groups: "unknown"
        generate_ssh_key: yes
        update_password: on_create
        #remove: yes
        #state: absent
...
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
The `tuser` will be created with all relevant parameters set up.
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
Ansible is not able to set up `tuser` password. The `tuser` account is created but it's state is "not active":
```
root# cat /etc/shadow | grep user
tuser:UP:::::::
```
> "UP - this account has not yet been activated by the administrator and cannot be used." - from Oracle documentation
<!--- Paste verbatim command output between quotes below -->
```
$ ansible-playbook -i test_files/test_inventory/ --ask-vault-pass test_files/test_solaris_user.yml -vvv
ansible-playbook 2.4.2.0 (stable-2.4 4433544eb0) last updated 2017/11/13 16:50:30 (GMT +200)
  python version = 2.7.5 (default, Aug  2 2016, 04:20:16) [GCC 4.8.5 20150623 (Red Hat 4.8.5-4)]

Vault password: 

PLAYBOOK: test_solaris_user.yml **************************************************************************************************************************************************************************
1 plays in test_files/test_solaris_user.yml

PLAY [linux] *********************************************************************************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************************************************************************
Using module file /home/user/ansible/lib/ansible/modules/system/setup.py

ok: [solaris_test]
META: ran handlers

TASK [create test user] **********************************************************************************************************************************************************************************
The full traceback is:
  File "/tmp/ansible_KKC_c9/ansible_module_user.py", line 1392, in create_user
    fields[3] = str(int(minweeks) * 7)

fatal: [solaris_test]: FAILED! => {
    "changed": false, 
    "invocation": {
        "module_args": {
            "append": false, 
            "comment": null, 
            "createhome": true, 
            "expires": null, 
            "force": false, 
            "generate_ssh_key": true, 
            "group": null, 
            "groups": [
                "unknown"
            ], 
            "home": null, 
            "local": null, 
            "login_class": null, 
            "move_home": false, 
            "name": "tuser", 
            "non_unique": false, 
            "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "remove": false, 
            "seuser": null, 
            "shell": null, 
            "skeleton": null, 
            "ssh_key_bits": 0, 
            "ssh_key_comment": "ansible-generated on js9-a", 
            "ssh_key_file": null, 
            "ssh_key_passphrase": null, 
            "ssh_key_type": "rsa", 
            "state": "present", 
            "system": false, 
            "uid": null, 
            "update_password": "on_create"
        }
    }, 
    "msg": "failed to update users password: invalid literal for int() with base 10: 'NONE'"
}

PLAY RECAP ***********************************************************************************************************************************************************************************************
solaris_test               : ok=1    changed=0    unreachable=0    failed=1   
```



Discussion:
Files identified in the description:
* [lib/ansible/modules/system/user.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/system/user.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @sfromm
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/system/user.py b/lib/ansible/modules/system/user.py
--- a/lib/ansible/modules/system/user.py
+++ b/lib/ansible/modules/system/user.py
@@ -1442,11 +1442,23 @@ def create_user(self):
                         fields[1] = self.password
                         fields[2] = str(int(time.time() // 86400))
                         if minweeks:
-                            fields[3] = str(int(minweeks) * 7)
+                            try:
+                                fields[3] = str(int(minweeks) * 7)
+                            except ValueError:
+                                # mirror solaris, which allows for any value in this field, and ignores anything that is not an int.
+                                pass
                         if maxweeks:
-                            fields[4] = str(int(maxweeks) * 7)
+                            try:
+                                fields[4] = str(int(maxweeks) * 7)
+                            except ValueError:
+                                # mirror solaris, which allows for any value in this field, and ignores anything that is not an int.
+                                pass
                         if warnweeks:
-                            fields[5] = str(int(warnweeks) * 7)
+                            try:
+                                fields[5] = str(int(warnweeks) * 7)
+                            except ValueError:
+                                # mirror solaris, which allows for any value in this field, and ignores anything that is not an int.
+                                pass
                         line = ':'.join(fields)
                         lines.append('%s\n' % line)
                     open(self.SHADOWFILE, 'w+').writelines(lines)


Test patch: