summaryrefslogtreecommitdiffstats
path: root/lib/modules
diff options
context:
space:
mode:
Diffstat (limited to 'lib/modules')
-rw-r--r--lib/modules/fetch_cmd.py56
-rw-r--r--lib/modules/mysql_user2491
-rw-r--r--lib/modules/openldap82
-rw-r--r--lib/modules/postmap8
-rw-r--r--lib/modules/postmulti8
5 files changed, 105 insertions, 540 deletions
diff --git a/lib/modules/fetch_cmd.py b/lib/modules/fetch_cmd.py
new file mode 100644
index 0000000..ca3e817
--- /dev/null
+++ b/lib/modules/fetch_cmd.py
@@ -0,0 +1,56 @@
+#!/usr/bin/python3
+
+# Fetch the output of a remote command
+# Copyright (c) 2016 Guilhem Moulin <guilhem@fripost.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# import module snippets
+from ansible.module_utils.basic import *
+
+def main():
+ module = AnsibleModule(
+ argument_spec = dict(
+ cmd = dict( default=None ),
+ stdin = dict( default=None ),
+ dest = dict( default=None ),
+ ),
+ supports_check_mode=False
+ )
+
+ params = module.params
+ cmd = params['cmd']
+ stdin = params['stdin']
+ dest = params['dest']
+
+ if cmd is None or dest is None:
+ return dict(failed=True, msg="cmd and dest are required")
+
+ changed = False
+ try:
+ if stdin is not None:
+ stdin = open(stdin, 'r')
+
+ with open(dest, 'w') as stdout:
+ subprocess.check_call(cmd.split(), stdin=stdin, stdout=stdout)
+ if stdin is not None:
+ stdin.close()
+
+ except KeyError as e:
+ module.fail_json(msg=str(e))
+
+ module.exit_json(changed=changed)
+
+main()
diff --git a/lib/modules/mysql_user2 b/lib/modules/mysql_user2
deleted file mode 100644
index d10e3e0..0000000
--- a/lib/modules/mysql_user2
+++ /dev/null
@@ -1,491 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2012, Mark Theunissen <mark.theunissen@gmail.com>
-# Sponsored by Four Kitchens http://fourkitchens.com.
-#
-# This file is part of Ansible
-#
-# Ansible is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Ansible is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-
-DOCUMENTATION = '''
----
-module: mysql_user2
-short_description: Adds or removes a user from a MySQL database.
-description:
- - Adds or removes a user from a MySQL database.
-version_added: "0.6"
-options:
- name:
- description:
- - name of the user (role) to add or remove
- required: true
- default: null
- password:
- description:
- - set the user's password
- required: false
- default: null
- host:
- description:
- - the 'host' part of the MySQL username
- required: false
- default: localhost
- login_user:
- description:
- - The username used to authenticate with
- required: false
- default: null
- login_password:
- description:
- - The password used to authenticate with
- required: false
- default: null
- login_host:
- description:
- - Host running the database
- required: false
- default: localhost
- login_port:
- description:
- - Port of the MySQL server
- required: false
- default: 3306
- version_added: '1.4'
- login_unix_socket:
- description:
- - The path to a Unix domain socket for local connections
- required: false
- default: null
- priv:
- description:
- - "MySQL privileges string in the format: C(db.table:priv1,priv2)"
- required: false
- default: null
- append_privs:
- description:
- - Append the privileges defined by priv to the existing ones for this
- user instead of overwriting existing ones.
- required: false
- choices: [ "yes", "no" ]
- default: "no"
- version_added: "1.4"
- state:
- description:
- - Whether the user should exist. When C(absent), removes
- the user.
- required: false
- default: present
- choices: [ "present", "absent" ]
- check_implicit_admin:
- description:
- - Check if mysql allows login as root/nopassword before trying supplied credentials.
- required: false
- default: false
- version_added: "1.3"
-notes:
- - Requires the MySQLdb Python package on the remote host. For Ubuntu, this
- is as easy as apt-get install python-mysqldb.
- - Both C(login_password) and C(login_username) are required when you are
- passing credentials. If none are present, the module will attempt to read
- the credentials from C(~/.my.cnf), and finally fall back to using the MySQL
- default login of 'root' with no password.
- - "MySQL server installs with default login_user of 'root' and no password. To secure this user
- as part of an idempotent playbook, you must create at least two tasks: the first must change the root user's password,
- without providing any login_user/login_password details. The second must drop a ~/.my.cnf file containing
- the new root credentials. Subsequent runs of the playbook will then succeed by reading the new credentials from
- the file."
-
-requirements: [ "ConfigParser", "MySQLdb" ]
-author: Mark Theunissen
-'''
-
-EXAMPLES = """
-# Create database user with name 'bob' and password '12345' with all database privileges
-- mysql_user: name=bob password=12345 priv=*.*:ALL state=present
-
-# Ensure no user named 'sally' exists, also passing in the auth credentials.
-- mysql_user: login_user=root login_password=123456 name=sally state=absent
-
-# Example privileges string format
-mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanotherdb.*:ALL
-
-# Example using login_unix_socket to connect to server
-- mysql_user: name=root password=abc123 login_unix_socket=/var/run/mysqld/mysqld.sock
-
-# Example .my.cnf file for setting the root password
-# Note: don't use quotes around the password, because the mysql_user module
-# will include them in the password but the mysql client will not
-
-[client]
-user=root
-password=n<_665{vS43y
-"""
-
-import ConfigParser
-import getpass
-import tempfile
-try:
- import MySQLdb
-except ImportError:
- mysqldb_found = False
-else:
- mysqldb_found = True
-
-# ===========================================
-# MySQL module specific support methods.
-#
-
-def user_exists(cursor, user, host):
- cursor.execute("SELECT count(*) FROM user WHERE user = %s AND host = %s", (user,host))
- count = cursor.fetchone()
- return count[0] > 0
-
-def load_plugin(cursor, plugin):
- cursor.execute("SELECT count(*) FROM information_schema.plugins WHERE name = %s", plugin)
- count = cursor.fetchone()
- if count[0] == 0:
- so = "%s.so" % plugin
- cursor.execute("INSTALL PLUGIN %s SONAME %s", (plugin, so))
-
-def user_add(cursor, user, host, password, new_priv, auth_plugin):
- if password is None:
- # Automatically loaded on first first use.
- load_plugin(cursor, auth_plugin)
- cursor.execute("CREATE USER %s@%s IDENTIFIED WITH %s", (user,host,auth_plugin))
- else:
- cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user,host,password))
- if new_priv is not None:
- for db_table, priv in new_priv.iteritems():
- privileges_grant(cursor, user,host,db_table,priv)
- return True
-
-def user_mod(cursor, user, host, password, new_priv, append_privs, auth_plugin):
- changed = False
- grant_option = False
-
- # Handle plugin.
- if auth_plugin is not None:
- cursor.execute("SELECT plugin FROM user WHERE user = %s AND host = %s", (user,host))
- if cursor.fetchone()[0] != auth_plugin:
- # Sadly there is no proper way to updade the authentication plugin:
- # http://bugs.mysql.com/bug.php?id=67449
- cursor.execute( "UPDATE user SET plugin = %s, password = '' WHERE user = %s AND host = %s"
- , (auth_plugin,user,host))
- cursor.execute("FLUSH PRIVILEGES")
- changed = True
-
- # Handle passwords.
- if password is not None:
- cursor.execute("SELECT password FROM user WHERE user = %s AND host = %s", (user,host))
- current_pass_hash = cursor.fetchone()
- cursor.execute("SELECT PASSWORD(%s)", (password,))
- new_pass_hash = cursor.fetchone()
- if current_pass_hash[0] != new_pass_hash[0]:
- cursor.execute("SET PASSWORD FOR %s@%s = PASSWORD(%s)", (user,host,password))
- changed = True
-
- # Handle privileges.
- if new_priv is not None:
- curr_priv = privileges_get(cursor, user,host)
-
- # If the user has privileges on a db.table that doesn't appear at all in
- # the new specification, then revoke all privileges on it.
- for db_table, priv in curr_priv.iteritems():
- # If the user has the GRANT OPTION on a db.table, revoke it first.
- if "GRANT" in priv:
- grant_option = True
- if db_table not in new_priv:
- if user != "root" and "PROXY" not in priv and not append_privs:
- privileges_revoke(cursor, user,host,db_table,grant_option)
- changed = True
-
- # If the user doesn't currently have any privileges on a db.table, then
- # we can perform a straight grant operation.
- for db_table, priv in new_priv.iteritems():
- if db_table not in curr_priv:
- privileges_grant(cursor, user,host,db_table,priv)
- changed = True
-
- # If the db.table specification exists in both the user's current privileges
- # and in the new privileges, then we need to see if there's a difference.
- db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys())
- for db_table in db_table_intersect:
- priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table])
- if (len(priv_diff) > 0):
- privileges_revoke(cursor, user,host,db_table,grant_option)
- privileges_grant(cursor, user,host,db_table,new_priv[db_table])
- changed = True
-
- return changed
-
-def user_delete(cursor, user, host):
- cursor.execute("DROP USER %s@%s", (user,host))
- return True
-
-def privileges_get(cursor, user,host):
- """ MySQL doesn't have a better method of getting privileges aside from the
- SHOW GRANTS query syntax, which requires us to then parse the returned string.
- Here's an example of the string that is returned from MySQL:
-
- GRANT USAGE ON *.* TO 'user'@'localhost' IDENTIFIED BY 'pass';
-
- This function makes the query and returns a dictionary containing the results.
- The dictionary format is the same as that returned by privileges_unpack() below.
- """
- output = {}
- cursor.execute("SHOW GRANTS FOR %s@%s", (user,host))
- grants = cursor.fetchall()
-
- def pick(x):
- if x == 'ALL PRIVILEGES':
- return 'ALL'
- else:
- return x
-
- for grant in grants:
- res = re.match("GRANT (.+) ON (.+) TO '.+'@'.+'( IDENTIFIED BY PASSWORD '.+')? ?(.*)", grant[0])
- if res is None:
- module.fail_json(msg="unable to parse the MySQL grant string")
- privileges = res.group(1).split(", ")
- privileges = [ pick(x) for x in privileges]
- if "WITH GRANT OPTION" in res.group(4):
- privileges.append('GRANT')
- db = res.group(2)
- output[db] = privileges
- return output
-
-def privileges_unpack(priv):
- """ Take a privileges string, typically passed as a parameter, and unserialize
- it into a dictionary, the same format as privileges_get() above. We have this
- custom format to avoid using YAML/JSON strings inside YAML playbooks. Example
- of a privileges string:
-
- mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanother.*:ALL
-
- The privilege USAGE stands for no privileges, so we add that in on *.* if it's
- not specified in the string, as MySQL will always provide this by default.
- """
- output = {}
- for item in priv.split('/'):
- pieces = item.split(':')
- if pieces[0].find('.') != -1:
- pieces[0] = pieces[0].split('.')
- for idx, piece in enumerate(pieces):
- if pieces[0][idx] != "*":
- pieces[0][idx] = "`" + pieces[0][idx] + "`"
- pieces[0] = '.'.join(pieces[0])
-
- output[pieces[0]] = [ g.strip() for g in pieces[1].upper().split(',') ]
-
- if '*.*' not in output:
- output['*.*'] = ['USAGE']
-
- return output
-
-def privileges_revoke(cursor, user,host,db_table,grant_option):
- if grant_option:
- query = "REVOKE GRANT OPTION ON %s FROM '%s'@'%s'" % (db_table,user,host)
- cursor.execute(query)
- query = "REVOKE ALL PRIVILEGES ON %s FROM '%s'@'%s'" % (db_table,user,host)
- cursor.execute(query)
-
-def privileges_grant(cursor, user,host,db_table,priv):
-
- priv_string = ",".join(filter(lambda x: x != 'GRANT', priv))
- query = "GRANT %s ON %s TO '%s'@'%s'" % (priv_string,db_table,user,host)
- if 'GRANT' in priv:
- query = query + " WITH GRANT OPTION"
- cursor.execute(query)
-
-
-def strip_quotes(s):
- """ Remove surrounding single or double quotes
-
- >>> print strip_quotes('hello')
- hello
- >>> print strip_quotes('"hello"')
- hello
- >>> print strip_quotes("'hello'")
- hello
- >>> print strip_quotes("'hello")
- 'hello
-
- """
- single_quote = "'"
- double_quote = '"'
-
- if s.startswith(single_quote) and s.endswith(single_quote):
- s = s.strip(single_quote)
- elif s.startswith(double_quote) and s.endswith(double_quote):
- s = s.strip(double_quote)
- return s
-
-
-def config_get(config, section, option):
- """ Calls ConfigParser.get and strips quotes
-
- See: http://dev.mysql.com/doc/refman/5.0/en/option-files.html
- """
- return strip_quotes(config.get(section, option))
-
-
-def _safe_cnf_load(config, path):
-
- data = {'user':'', 'password':''}
-
- # read in user/pass
- f = open(path, 'r')
- for line in f.readlines():
- line = line.strip()
- if line.startswith('user='):
- data['user'] = line.split('=', 1)[1].strip()
- if line.startswith('password=') or line.startswith('pass='):
- data['password'] = line.split('=', 1)[1].strip()
- f.close()
-
- # write out a new cnf file with only user/pass
- fh, newpath = tempfile.mkstemp(prefix=path + '.')
- f = open(newpath, 'wb')
- f.write('[client]\n')
- f.write('user=%s\n' % data['user'])
- f.write('password=%s\n' % data['password'])
- f.close()
-
- config.readfp(open(newpath))
- os.remove(newpath)
- return config
-
-def load_mycnf():
- config = ConfigParser.RawConfigParser()
- mycnf = os.path.expanduser('~/.my.cnf')
- if not os.path.exists(mycnf):
- return False
- try:
- config.readfp(open(mycnf))
- except (IOError):
- return False
- except:
- config = _safe_cnf_load(config, mycnf)
-
- # We support two forms of passwords in .my.cnf, both pass= and password=,
- # as these are both supported by MySQL.
- try:
- passwd = config_get(config, 'client', 'password')
- except (ConfigParser.NoOptionError):
- try:
- passwd = config_get(config, 'client', 'pass')
- except (ConfigParser.NoOptionError):
- return False
-
- # If .my.cnf doesn't specify a user, default to user login name
- try:
- user = config_get(config, 'client', 'user')
- except (ConfigParser.NoOptionError):
- user = getpass.getuser()
- creds = dict(user=user,passwd=passwd)
- return creds
-
-def connect(module, login_user, login_password):
- if module.params["login_unix_socket"]:
- db_connection = MySQLdb.connect(host=module.params["login_host"], unix_socket=module.params["login_unix_socket"], user=login_user, passwd=login_password, db="mysql")
- else:
- db_connection = MySQLdb.connect(host=module.params["login_host"], port=int(module.params["login_port"]), user=login_user, passwd=login_password, db="mysql")
- return db_connection.cursor()
-
-# ===========================================
-# Module execution.
-#
-
-def main():
- module = AnsibleModule(
- argument_spec = dict(
- login_user=dict(default=None),
- login_password=dict(default=None),
- login_host=dict(default="localhost"),
- login_port=dict(default="3306"),
- login_unix_socket=dict(default=None),
- user=dict(required=True, aliases=['name']),
- password=dict(default=None),
- host=dict(default="localhost"),
- state=dict(default="present", choices=["absent", "present"]),
- priv=dict(default=None),
- append_privs=dict(type="bool", default="no"),
- check_implicit_admin=dict(default=False),
- auth_plugin=dict(default=None)
- )
- )
- user = module.params["user"]
- password = module.params["password"]
- host = module.params["host"]
- state = module.params["state"]
- priv = module.params["priv"]
- check_implicit_admin = module.params['check_implicit_admin']
- append_privs = module.boolean(module.params["append_privs"])
- auth_plugin = module.params['auth_plugin']
-
- if not mysqldb_found:
- module.fail_json(msg="the python mysqldb module is required")
-
- if priv is not None:
- try:
- priv = privileges_unpack(priv)
- except:
- module.fail_json(msg="invalid privileges string")
-
- # Either the caller passes both a username and password with which to connect to
- # mysql, or they pass neither and allow this module to read the credentials from
- # ~/.my.cnf.
- login_password = module.params["login_password"]
- login_user = module.params["login_user"]
- if login_user is None and login_password is None:
- mycnf_creds = load_mycnf()
- if mycnf_creds is False:
- login_user = "root"
- login_password = ""
- else:
- login_user = mycnf_creds["user"]
- login_password = mycnf_creds["passwd"]
- elif login_password is None or login_user is None:
- module.fail_json(msg="when supplying login arguments, both login_user and login_password must be provided")
-
- cursor = None
- try:
- if check_implicit_admin:
- try:
- cursor = connect(module, 'root', '')
- except:
- pass
-
- if not cursor:
- cursor = connect(module, login_user, login_password)
- except Exception, e:
- module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or ~/.my.cnf has the credentials")
-
- if state == "present":
- if user_exists(cursor, user, host):
- changed = user_mod(cursor, user, host, password, priv, append_privs, auth_plugin)
- else:
- if (password is None and auth_plugin is None) or (password is not None and auth_plugin is not None):
- module.fail_json(msg="password xor auth_plugin is required when adding a user")
- changed = user_add(cursor, user, host, password, priv, auth_plugin)
- elif state == "absent":
- if user_exists(cursor, user, host):
- changed = user_delete(cursor, user, host)
- else:
- changed = False
- module.exit_json(changed=changed, user=user)
-
-# this is magic, see lib/ansible/module_common.py
-#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
-main()
diff --git a/lib/modules/openldap b/lib/modules/openldap
index 5178033..c09e791 100644
--- a/lib/modules/openldap
+++ b/lib/modules/openldap
@@ -1,21 +1,21 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# Manage OpenLDAP databases
# Copyright (c) 2013 Guilhem Moulin <guilhem@fripost.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import ldap, ldap.sasl
from ldap.filter import filter_format
from ldap.dn import dn2str,explode_dn,str2dn
@@ -32,162 +32,162 @@ import tempfile, atexit
# explicit indices to what we find in the LDIF.
indexedAttributes = frozenset([
'olcAttributeTypes',
'olcObjectClasses',
'olcAccess',
'olcSyncrepl',
'olcOverlay',
'olcLimits',
'olcAuthzRegexp',
'olcDbConfig',
])
# Another hack. Configuration entries sometimes pollutes the DNs with
# indices, thus it's not possible to directly use them as base.
# Instead, we use their parent as a base, and search for the *unique*
# match with the same ObjectClass and the matching extra attributes.
# ('%s' in the attribute value is replaced with the value of the source
# entry.)
indexedDN = {
- 'olcSchemaConfig': [('cn', '{*}%s')],
- 'olcMdbConfig': [('olcDbDirectory', '%s' )],
- 'olcOverlayConfig': [('olcOverlay', '%s' )],
- 'olcMonitorConfig': [],
+ b'olcSchemaConfig': [('cn', '{*}%s')],
+ b'olcMdbConfig': [('olcDbDirectory', '%s' )],
+ b'olcOverlayConfig': [('olcOverlay', '%s' )],
+ b'olcMonitorConfig': [],
}
# Allow for flexible ACLs for user using SASL's EXTERNAL mechanism.
# "username=postfix,cn=peercred,cn=external,cn=auth" is replaced by
# "gidNumber=106+uidNumber=102,cn=peercred,cn=external,cn=auth" where
# 102 is postfix's UID and 106 its primary GID.
# (Regular expressions are not allowed.)
-sasl_ext_re = re.compile( r"""(?P<start>\sby\s+dn(?:\.exact)?)=
- (?P<quote>['\"]?)username=(?P<user>[a-z][-a-z0-9_]*),
+sasl_ext_re = re.compile( b"""(?P<start>\sby\s+dn(?:\.exact)?)=
+ (?P<quote>['\"]?)username=(?P<user>_?[a-z][-a-z0-9_]*),
(?P<end>cn=peercred,cn=external,cn=auth)
(?P=quote)\s"""
, re.VERBOSE )
-multispaces = re.compile( r"\s+" )
+multispaces = re.compile( b"\s+" )
pwd_dict = {}
def acl_sasl_ext(m):
- u = m.group('user')
+ u = m.group('user').decode("utf-8")
if u not in pwd_dict.keys():
pwd_dict[u] = pwd.getpwnam(u)
- return '%s="gidNumber=%d+uidNumber=%d,%s" ' % ( m.group('start')
- , pwd_dict[u].pw_gid
- , pwd_dict[u].pw_uid
- , m.group('end')
- )
+ return b'%s="gidNumber=%d+uidNumber=%d,%s" ' % ( m.group('start')
+ , pwd_dict[u].pw_gid
+ , pwd_dict[u].pw_uid
+ , m.group('end')
+ )
# Run the given callback on each DN seen. If its return value is not
# None, update the changed variable.
class LDIFCallback(LDIFParser):
def __init__(self, module, input, callback):
LDIFParser.__init__(self,input)
self.callback = callback
self.changed = False
def handle(self,dn,entry):
b = self.callback(dn,entry)
if b is not None:
self.changed |= b
# Check if the given dn is already present in the directory.
# Returns None if doesn't exist, and give the dn,entry otherwise
def flexibleSearch(module, l, dn, entry):
idxClasses = set(entry['objectClass']).intersection(indexedDN.keys())
if not idxClasses:
base = dn
scope = ldap.SCOPE_BASE
f = 'objectClass=*'
else:
# Search on the parent instead, and try to use a precise filter
dn = str2dn(dn)
h,t,_ = dn.pop(0)[0]
base = dn2str(dn)
scope = ldap.SCOPE_ONELEVEL
f = []
for c in idxClasses:
- f.append ( filter_format('objectClass=%s', [c]) )
+ f.append ( filter_format('objectClass=%s', [c.decode("utf-8")]) )
for a,v in indexedDN[c]:
if a == h:
v2 = t
elif a not in entry.keys() or len(entry[a]) > 1:
module.fail_json(msg="Multiple values found! This is a bug. Please report.")
else:
- v2 = entry[a][0]
+ v2 = entry[a][0].decode("utf-8")
f.append ( filter_format(a+'='+v, [v2]) )
if len(f) == 1:
f = f[0]
else:
f = '(&(' + ')('.join(f) + '))'
r = l.search_s( base, scope, filterstr=f )
if len(r) > 1:
module.fail_json(msg="Multiple results found! This is a bug. Please report.")
elif r:
return r.pop()
# Add or modify (only the attributes that differ from those in the
# directory) the entry for that DN.
# l must be an LDAPObject, and should provide an open connection to the
# directory with disclose/search/write access.
def processEntry(module, l, dn, entry):
changed = False
for x in indexedAttributes.intersection(entry.keys()):
# remove useless extra spaces in ACLs etc
- entry[x] = map( partial(multispaces.sub, ' '), entry[x] )
+ entry[x] = list(map( partial(multispaces.sub, b' '), entry[x] ))
r = flexibleSearch( module, l, dn, entry )
if r is None:
changed = True
if module.check_mode:
module.exit_json(changed=changed, msg="add DN %s" % dn)
if 'olcAccess' in entry.keys():
# replace "username=...,cn=peercred,cn=external,cn=auth"
# by a DN with proper gidNumber and uidNumber
- entry['olcAccess'] = map ( partial(sasl_ext_re.sub, acl_sasl_ext)
- , entry['olcAccess'] )
+ entry['olcAccess'] = list(map ( partial(sasl_ext_re.sub, acl_sasl_ext)
+ , entry['olcAccess'] ))
l.add_s( dn, addModlist(entry) )
else:
d,e = r
fst = str2dn(dn).pop(0)[0][0]
diff = []
- for a,v in e.iteritems():
+ for a,v in e.items():
if a not in entry.keys():
if a != fst:
# delete all values except for the first attribute,
# which is implicit
diff.append(( ldap.MOD_DELETE, a, None ))
elif a in indexedAttributes:
if a == 'olcAccess':
# replace "username=...,cn=peercred,cn=external,cn=auth"
# by a DN with proper gidNumber and uidNumber
- entry[a] = map ( partial(sasl_ext_re.sub, acl_sasl_ext)
- , entry[a] )
+ entry[a] = list(map ( partial(sasl_ext_re.sub, acl_sasl_ext)
+ , entry[a] ))
# add explicit indices in the entry from the LDIF
- entry[a] = map( (lambda x: '{%d}%s' % x)
- , zip(range(len(entry[a])),entry[a]) )
+ entry[a] = list(map( (lambda x: b'{%d}%s' % x)
+ , zip(range(len(entry[a])),entry[a])))
if v != entry[a]:
diff.append(( ldap.MOD_REPLACE, a, entry[a] ))
elif v != entry[a]:
# for non-indexed attribute, we update values in the
# symmetric difference only
s1 = set(v)
s2 = set(entry[a])
if s1.isdisjoint(s2):
# replace the former values with the new ones
diff.append(( ldap.MOD_REPLACE, a, entry[a] ))
else:
x = list(s1.difference(s2))
if x:
diff.append(( ldap.MOD_DELETE, a, x ))
y = list(s2.difference(s1))
if y:
diff.append(( ldap.MOD_ADD, a, y ))
# add attributes that weren't in e
for a in set(entry).difference(e.keys()):
@@ -214,65 +214,65 @@ def loadModule(module, l, name):
module.exit_json(changed=changed, msg="add module %s" % name)
l.modify_s( 'cn=module{0},cn=config'
, [(ldap.MOD_ADD, 'olcModuleLoad', name)] )
return changed
# Find the database associated with a given attribute (eg,
# olcDbDirectory or olcSuffix).
def getDN_DB(module, l, a, v, attrlist=['']):
f = filter_format( '(&(objectClass=olcDatabaseConfig)('+a+'=%s))', [v] )
return l.search_s( 'cn=config'
, ldap.SCOPE_ONELEVEL
, filterstr = f
, attrlist = attrlist )
# Convert a *.schema file into *.ldif format. The algorithm can be found
# in /etc/ldap/schema/openldap.ldif .
def slapd_to_ldif(src, name):
- s = open( src, 'r' )
+ s = open( src, 'rb' )
d = tempfile.NamedTemporaryFile(delete=False)
atexit.register(lambda: os.unlink( d.name ))
- d.write('dn: cn=%s,cn=schema,cn=config\n' % name)
- d.write('objectClass: olcSchemaConfig\n')
+ d.write(b'dn: cn=%s,cn=schema,cn=config\n' % name.encode("utf-8"))
+ d.write(b'objectClass: olcSchemaConfig\n')
- re1 = re.compile( r'^objectIdentifier\s(.*)', re.I )
- re2 = re.compile( r'^objectClass\s(.*)', re.I )
- re3 = re.compile( r'^attributeType\s(.*)', re.I )
- reSp = re.compile( r'^\s+' )
+ re1 = re.compile( b'^objectIdentifier\s(.*)', re.I )
+ re2 = re.compile( b'^objectClass\s(.*)', re.I )
+ re3 = re.compile( b'^attributeType\s(.*)', re.I )
+ reSp = re.compile( b'^\s+' )
for line in s.readlines():
- if line == '\n':
- line = '#\n'
+ if line == b'\n':
+ line = b'#\n'
m1 = re1.match(line)
m2 = re2.match(line)
m3 = re3.match(line)
if m1 is not None:
- line = 'olcObjectIdentifier: %s' % m1.group(1)
+ line = b'olcObjectIdentifier: %s' % m1.group(1)
elif m2 is not None:
- line = 'olcObjectClasses: %s' % m2.group(1)
+ line = b'olcObjectClasses: %s' % m2.group(1)
elif m3 is not None:
- line = 'olcAttributeTypes: %s' % m3.group(1)
+ line = b'olcAttributeTypes: %s' % m3.group(1)
- d.write( reSp.sub(line, ' ') )
+ d.write( reSp.sub(line, b' ') )
s.close()
d.close()
return d.name
def main():
module = AnsibleModule(
argument_spec = dict(
target = dict( default=None ),
module = dict( default=None ),
suffix = dict( default=None ),
format = dict( default="ldif", choices=["ldif","slapd.conf"] ),
name = dict( default=None ),
local = dict( default="no", choices=["no","file","template"] ),
delete = dict( default=None ),
),
supports_check_mode=True
)
@@ -286,41 +286,41 @@ def main():
delete = params['delete']
changed = False
try:
if delete is not None:
if name is None:
module.fail_json(msg="missing name")
l = ldap.initialize( 'ldapi://' )
l.sasl_interactive_bind_s('', ldap.sasl.external())
if delete == 'entry':
filterStr = '(objectClass=*)'
else:
filterStr = [ '(%s=*)' % x for x in delete.split(',') ]
if len(filterStr) > 1:
filterStr = '(|' + ''.join(filterStr) + ')'
else:
filterStr = filterStr[0]
try:
r = l.search_s( name, ldap.SCOPE_BASE, filterStr, attrsonly=1 )
- except ldap.LDAPError, ldap.NO_SUCH_OBJECT:
+ except (ldap.LDAPError, ldap.NO_SUCH_OBJECT):
r = None
if r:
changed = True
if module.check_mode:
module.exit_json(changed=changed)
if delete == 'entry':
l.delete_s(r[0][0])
else:
attrlist = list(set(r[0][1].keys()) & set(delete.split(',')))
l.modify_s(r[0][0], [ (ldap.MOD_DELETE, x, None) for x in attrlist ])
l.unbind_s()
else:
if form == 'slapd.conf':
if name is None:
module.fail_json(msg="missing name")
target = slapd_to_ldif(target, name)
if target is None and mod is None:
@@ -335,40 +335,40 @@ def main():
changed |= loadModule (module, l, '%s.la' % mod)
if target is None and suffix is None:
l.unbind_s()
module.exit_json(changed=changed)
if target is None or suffix is None:
module.fail_json(msg="missing target or suffix")
r = getDN_DB(module, l, 'olcSuffix', suffix)
if not r:
module.fail_json(msg="No database found for suffix %s" % suffix)
elif len(r) > 1:
module.fail_json(msg="Multiple results found! This is a bug. Please report.")
else:
d = 'olcOverlay=%s,%s' % (mod, r.pop()[0])
callback = lambda _,e: processEntry(module,l,d,e)
parser = LDIFCallback( module, open(target, 'r'), callback )
parser.parse()
changed = parser.changed
l.unbind_s()
- except subprocess.CalledProcessError, e:
+ except subprocess.CalledProcessError as e:
module.fail_json(rv=e.returncode, msg=e.output.rstrip())
- except ldap.LDAPError, e:
+ except ldap.LDAPError as e:
e = e.args[0]
if 'info' in e.keys():
msg = e['info']
elif 'desc' in e.keys():
msg = e['desc']
else:
msg = str(e)
module.fail_json(msg=msg)
- except KeyError, e:
+ except KeyError as e:
module.fail_json(msg=str(e))
module.exit_json(changed=changed)
# import module snippets
from ansible.module_utils.basic import *
main()
diff --git a/lib/modules/postmap b/lib/modules/postmap
index 7080b25..ce09018 100644
--- a/lib/modules/postmap
+++ b/lib/modules/postmap
@@ -1,21 +1,21 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# Create or update postfix's alias and lookup tables
# Copyright (c) 2013 Guilhem Moulin <guilhem@fripost.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
try:
import selinux
HAVE_SELINUX=True
@@ -25,85 +25,85 @@ except ImportError:
# Look up for the file suffix corresponding to 'db'. If 'db' is unset,
# pick the default_detabase_type of the given instance instead.
def file_suffix(instance, db):
if not db:
if instance:
cmd = [ os.path.join(os.sep, 'usr', 'sbin', 'postmulti')
, '-x'
, '-i', instance
, '--'
]
else:
cmd = []
cmd.extend([ os.path.join(os.sep, 'usr', 'sbin', 'postconf')
, '-h', 'default_database_type' ])
null = open (os.devnull, 'wb')
db = subprocess.check_output(cmd, stderr=null).rstrip()
null.closed
# See postmap(1) and postalias(1)
- suffixes = { 'btree': 'db', 'cdb': 'cdb', 'hash': 'db' }
+ suffixes = { 'btree': 'db', 'cdb': 'cdb', 'hash': 'db', 'lmdb': 'lmdb' }
return suffixes[db]
# Compile the given (alias/lookup) table
def compile(cmd, instance, db, src):
cmd = [ os.path.join(os.sep, 'usr', 'sbin', cmd) ]
if instance:
config = os.path.join(os.sep, 'etc', 'postfix-%s' % instance)
cmd.extend([ '-c', config ])
if db:
src = "%s:%s" % (db,src)
cmd.append(src)
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
def main():
module = AnsibleModule(
argument_spec = dict(
src = dict( required=True ),
- db = dict( choices=['btree','cdb','hash'] ),
+ db = dict( choices=['btree','cdb','hash','lmdb'] ),
cmd = dict( choices=['postmap','postalias'], default='postmap' ),
instance = dict( required=False )
),
add_file_common_args=True,
supports_check_mode=True
)
params = module.params
src = params['src']
db = params['db']
cmd = params['cmd']
instance = params['instance']
if os.path.isabs(src):
src = src
else:
module.fail_json(msg="absolute paths are required")
if not os.path.exists(src):
module.fail_json(src=src, msg="no such file")
try:
dst = "%s.%s" % (src, file_suffix(instance, db))
params['dest'] = dst
file_args = module.load_file_common_arguments(params)
changed = False
msg = None
if not os.path.exists(dst) or os.path.getmtime(dst) <= os.path.getmtime(src):
changed = True
if not module.check_mode:
msg = compile( cmd, instance, db, src)
- except subprocess.CalledProcessError, e:
+ except subprocess.CalledProcessError as e:
module.fail_json(rv=e.returncode, msg=e.output.rstrip())
changed = module.set_file_attributes_if_different(file_args, changed)
module.exit_json(changed=changed, msg=msg)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()
diff --git a/lib/modules/postmulti b/lib/modules/postmulti
index d6ecb09..e6f58e3 100644
--- a/lib/modules/postmulti
+++ b/lib/modules/postmulti
@@ -1,103 +1,103 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# Create and manage postfix instances.
# Copyright (c) 2013 Guilhem Moulin <guilhem@fripost.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Look up postfix configuration variable
def postconf(k, instance=None):
if instance:
cmd = [ os.path.join(os.sep, 'usr', 'sbin', 'postmulti')
, '-x'
, '-i', instance
, '--'
]
else:
cmd = []
cmd.extend([ os.path.join(os.sep, 'usr', 'sbin', 'postconf')
, '-h', k ])
- return subprocess.check_output(cmd, stderr=subprocess.STDOUT).rstrip()
+ return subprocess.check_output(cmd, stderr=subprocess.STDOUT).rstrip().decode("utf-8")
# To destroy an existing instance:
# postmulti -e disable -i mx
# postmulti -e destroy -i mx
def main():
module = AnsibleModule(
argument_spec = dict(
instance = dict( required=True ),
group = dict( required=False )
),
supports_check_mode=True
)
params = module.params
instance = params['instance']
group = params['group']
changed=False
try:
enable = postconf('multi_instance_enable')
wrapper = postconf('multi_instance_wrapper')
- if enable != "yes" or not wrapper:
+ if enable != "yes" or wrapper == "":
# Initiate postmulti
changed = True
if module.check_mode:
module.exit_json(changed=changed, msg="init postmulti")
cmd = [ os.path.join(os.sep, 'usr', 'sbin', 'postmulti') ]
cmd.extend([ '-e', 'init' ])
subprocess.check_output(cmd, stderr=subprocess.STDOUT).rstrip()
instances = postconf('multi_instance_directories').split()
if os.path.join(os.sep, 'etc', 'postfix-%s' % instance) not in instances:
changed = True
# Create the instance
if module.check_mode:
module.exit_json(changed=changed, msg="create postmulti")
cmd = [ os.path.join(os.sep, 'usr', 'sbin', 'postmulti') ]
cmd.extend([ '-e', 'create' ])
if group:
cmd.extend([ '-G', group ])
cmd.extend([ '-I', 'postfix-%s' % instance ])
subprocess.check_output(cmd, stderr=subprocess.STDOUT).rstrip()
elif group != postconf('multi_instance_group', instance):
changed = True
# Assign a new group, or remove the existing group
if module.check_mode:
module.exit_json(changed=changed, msg="assign group")
cmd = [ os.path.join(os.sep, 'usr', 'sbin', 'postmulti') ]
cmd.extend([ '-e', 'assign', '-i', 'postfix-%s' % instance ])
if group:
cmd.extend([ '-G', group ])
else:
cmd.extend([ '-G', '-' ])
subprocess.check_output(cmd, stderr=subprocess.STDOUT).rstrip()
module.exit_json(changed=changed)
- except subprocess.CalledProcessError, e:
+ except subprocess.CalledProcessError as e:
module.fail_json(rv=e.returncode, msg=e.output.rstrip())
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()