Repository: ansible/ansible

Issue:
pamd fails when inserting a new rule at the end of a file
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
pamd

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.2.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609]
```

Also affects 2.4:
```
ansible 2.4.0 (devel beed59f303) last updated 2017/08/19 20:29:45 (GMT -400)
  config file = None
  configured module search path = [u'/home/david/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/david/ansible/lib/ansible
  executable location = /home/david/ansible/bin/ansible
  python version = 2.7.13 (default, Jan 19 2017, 14:48:08) [GCC 6.3.0 20170118]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### 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.
-->
Ubuntu 16.04

##### SUMMARY
<!--- Explain the problem briefly -->
In the pamd module, when you try to insert a new rule after the last rule in a file, it crashes with a list index error.

##### 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
---
- name: pamd bug example
  hosts: localhost
  connection: local
  become: yes
  tasks:
  - pamd:
      name: common-session
      type: session
      control: optional
      module_path: pam_systemd.so
      new_type: session
      new_control: optional
      new_module_path: pam_mkhomedir.so
      state: after
...
```

Given the following /etc/pam.d/common-session (the Ubuntu 16.04 default, not including comments):
```
session [default=1]                     pam_permit.so
session requisite                       pam_deny.so
session required                        pam_permit.so
session optional                        pam_umask.so
session required        pam_unix.so
session optional        pam_systemd.so
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
/etc/pam.d/common-session with last line 
```
session optional        pam_mkhomedir.so
```


##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
TASK [pamd] ********************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "msg": "error running changing pamd: list index out of range"}
        to retry, use: --limit @/home/ubuntu/pamd.retry
```



Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/system/pamd.py b/lib/ansible/modules/system/pamd.py
--- a/lib/ansible/modules/system/pamd.py
+++ b/lib/ansible/modules/system/pamd.py
@@ -459,7 +459,10 @@ def insert_after_rule(service, old_rule, new_rule):
         if (old_rule.rule_type == rule.rule_type and
                 old_rule.rule_control == rule.rule_control and
                 old_rule.rule_module_path == rule.rule_module_path):
-            if (new_rule.rule_type != service.rules[index + 1].rule_type or
+            if (index == len(service.rules) - 1):
+                service.rules.insert(len(service.rules), new_rule)
+                changed = True
+            elif (new_rule.rule_type != service.rules[index + 1].rule_type or
                     new_rule.rule_control !=
                     service.rules[index + 1].rule_control or
                     new_rule.rule_module_path !=


Test patch:
diff --git a/test/units/modules/system/test_pamd.py b/test/units/modules/system/test_pamd.py
--- a/test/units/modules/system/test_pamd.py
+++ b/test/units/modules/system/test_pamd.py
@@ -167,6 +167,15 @@ def test_insert_after_rule(self):
         line_to_test += str(new_rule).rstrip()
         self.assertIn(line_to_test, str(self.pamd))
 
+    def test_insert_after_rule_last_rule(self):
+        old_rule = PamdRule.rulefromstring('session   	required	pam_unix.so')
+        new_rule = PamdRule.rulefromstring('session   	required	pam_permit.so arg1 arg2 arg3')
+        insert_after_rule(self.pamd, old_rule, new_rule)
+        line_to_test = str(old_rule).rstrip()
+        line_to_test += '\n'
+        line_to_test += str(new_rule).rstrip()
+        self.assertIn(line_to_test, str(self.pamd))
+
     def test_remove_module_arguments_one(self):
         old_rule = PamdRule.rulefromstring('auth      	sufficient	pam_unix.so nullok try_first_pass')
         new_rule = PamdRule.rulefromstring('auth      	sufficient	pam_unix.so try_first_pass')