salix/modules/account/back/models/samba-config.js

172 lines
5.6 KiB
JavaScript

const ldap = require('../util/ldapjs-extra');
const execFile = require('child_process').execFile;
/**
* Summary of userAccountControl flags:
* https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
*/
const UserAccountControlFlags = {
ACCOUNTDISABLE: 2
};
module.exports = Self => {
const shouldSync = process.env.NODE_ENV !== 'test';
Self.getLinker = async function() {
return await Self.findOne({
fields: [
'host',
'adDomain',
'adController',
'adUser',
'adPassword',
'userDn',
'verifyCert'
]
});
};
Object.assign(Self.prototype, {
async init() {
const baseDn = this.adDomain
.split('.')
.map(part => `dc=${part}`)
.join(',');
const bindDn = `cn=${this.adUser},cn=Users,${baseDn}`;
const adClient = ldap.createClient({
url: `ldaps://${this.adController}:636`,
tlsOptions: {rejectUnauthorized: this.verifyCert}
});
adClient.on('error', () => {});
await adClient.bind(bindDn, this.adPassword);
Object.assign(this, {
adClient,
fullUsersDn: `${this.userDn},${baseDn}`,
bindDn
});
},
async deinit() {
await this.adClient.unbind();
},
async sambaTool(command, args = []) {
let authArgs = [
'--URL', `ldaps://${this.adController}`,
'--simple-bind-dn', this.bindDn,
'--password', this.adPassword
];
if (!this.verifyCert)
authArgs.push('--option', 'tls verify peer = no_check');
const allArgs = [command].concat(
args, authArgs
);
if (!shouldSync) return;
return await new Promise((resolve, reject) => {
execFile('samba-tool', allArgs, (err, stdout, stderr) => {
if (err)
reject(err);
else
resolve({stdout, stderr});
});
});
},
async getAdUser(userName) {
const sambaUser = await this.adClient.searchOne(this.fullUsersDn, {
scope: 'sub',
attributes: [
'dn',
'userAccountControl',
'uidNumber',
'accountExpires',
'mail'
],
filter: `(&(objectClass=user)(sAMAccountName=${userName}))`
});
if (sambaUser) {
for (const intProp of ['uidNumber', 'userAccountControl']) {
if (sambaUser[intProp] != null)
sambaUser[intProp] = parseInt(sambaUser[intProp]);
}
}
return sambaUser;
},
async syncUser(userName, info, password) {
let sambaUser = await this.getAdUser(userName);
let entry;
if (info.hasAccount) {
if (!sambaUser) {
await this.sambaTool('user', [
'create', userName,
'--userou', this.userDn,
'--random-password'
]);
sambaUser = await this.getAdUser(userName);
}
if (password) {
await this.sambaTool('user', [
'setpassword', userName,
'--newpassword', password
]);
}
entry = {
userAccountControl: sambaUser.userAccountControl
& ~UserAccountControlFlags.ACCOUNTDISABLE,
uidNumber: info.uidNumber,
accountExpires: 0,
mail: info.corporateMail
};
} else if (sambaUser) {
entry = {
userAccountControl: sambaUser.userAccountControl
| UserAccountControlFlags.ACCOUNTDISABLE
};
// eslint-disable-next-line no-console
console.log(` -> User '${userName}' disabled on Samba`);
}
if (sambaUser && entry) {
const changes = [];
for (const prop in entry) {
if (sambaUser[prop] == entry[prop]) continue;
changes.push(new ldap.Change({
operation: 'replace',
modification: {
[prop]: entry[prop]
}
}));
}
if (changes.length && shouldSync)
await this.adClient.modify(sambaUser.dn, changes);
}
},
/**
* Gets Samba enabled users.
*
* @param {Set} usersToSync
*/
async getUsers(usersToSync) {
const LDAP_MATCHING_RULE_BIT_AND = '1.2.840.113556.1.4.803';
const filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}`
+ `:=${UserAccountControlFlags.ACCOUNTDISABLE})`;
const opts = {
scope: 'sub',
attributes: ['sAMAccountName'],
filter: `(&(objectClass=user)(${filter}))`
};
await this.adClient.searchForeach(this.fullUsersDn, opts,
o => usersToSync.add(o.sAMAccountName));
}
});
};