const ldap = require('../../util/ldapjs-extra'); const nthash = require('smbhash').nthash; const ssh = require('node-ssh'); const crypto = require('crypto'); module.exports = Self => { Self.remoteMethod('sync', { description: 'Synchronizes the user with the other user databases', accepts: [ { arg: 'userName', type: 'string', description: 'The user name', required: true }, { arg: 'password', type: 'string', description: 'The password' } ], http: { path: `/sync`, verb: 'PATCH' } }); Self.sync = async function(userName, password) { let $ = Self.app.models; let user = await $.Account.findOne({ fields: ['id', 'sync'], where: {name: userName} }); if (user && user.sync) return; let accountConfig; let mailConfig; let extraParams; let hasAccount = false; if (user) { accountConfig = await $.AccountConfig.findOne({ fields: ['homedir', 'shell', 'idBase'] }); mailConfig = await $.MailConfig.findOne({ fields: ['domain'] }); user = await $.Account.findById(user.id, { fields: [ 'id', 'nickname', 'email', 'lang', 'roleFk', 'sync', 'active', 'created', 'updated' ], where: {name: userName}, include: { relation: 'roles', scope: { include: { relation: 'inherits', scope: { fields: ['name'] } } } } }); extraParams = { corporateMail: `${userName}@${mailConfig.domain}`, uidNumber: accountConfig.idBase + user.id }; hasAccount = user.active && await $.UserAccount.exists(user.id); } if (user) { let bcryptPassword = $.User.hashPassword(password); await $.Account.upsertWithWhere({id: user.id}, {bcryptPassword}); await $.user.destroyById(user.id); if (hasAccount) { await $.user.upsert({ id: user.id, username: userName, password: bcryptPassword, email: user.email, created: user.created, updated: user.updated }); } } // SIP if (hasAccount) { await Self.rawSql('CALL pbx.sip_setPassword(?, ?)', [user.id, password] ); } // LDAP let ldapConfig = await $.LdapConfig.findOne({ fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn'] }); if (ldapConfig) { let ldapClient = ldap.createClient({ url: `ldap://${ldapConfig.host}:389` }); let ldapPassword = Buffer .from(ldapConfig.password, 'base64') .toString('ascii'); await ldapClient.bind(ldapConfig.rdn, ldapPassword); let err; try { // Deletes user try { let dn = `uid=${userName},${ldapConfig.baseDn}`; await ldapClient.del(dn); } catch (e) { if (e.name !== 'NoSuchObjectError') throw e; } // Removes user from groups let opts = { scope: 'sub', attributes: ['dn'], filter: `&(memberUid=${userName})(objectClass=posixGroup)` }; res = await ldapClient.search(ldapConfig.groupDn, opts); let oldGroups = []; await new Promise((resolve, reject) => { res.on('error', reject); res.on('searchEntry', e => oldGroups.push(e.object)); res.on('end', resolve); }); let reqs = []; for (oldGroup of oldGroups) { let change = new ldap.Change({ operation: 'delete', modification: {memberUid: userName} }); reqs.push(ldapClient.modify(oldGroup.dn, change)); } await Promise.all(reqs); if (hasAccount) { // Recreates user let nameArgs = user.nickname.split(' '); let sshaPassword = crypto .createHash('sha1') .update(password) .digest('base64'); let dn = `uid=${userName},${ldapConfig.baseDn}`; let newEntry = { uid: userName, objectClass: [ 'inetOrgPerson', 'posixAccount', 'sambaSamAccount' ], cn: user.nickname || userName, displayName: user.nickname, givenName: nameArgs[0], sn: nameArgs[1] || 'Empty', mail: extraParams.corporateMail, userPassword: `{SSHA}${sshaPassword}`, preferredLanguage: user.lang, homeDirectory: `${accountConfig.homedir}/${userName}`, loginShell: accountConfig.shell, uidNumber: extraParams.uidNumber, gidNumber: accountConfig.idBase + user.roleFk, sambaSID: '-', sambaNTPassword: nthash(password) }; await ldapClient.add(dn, newEntry); // Adds user to groups let reqs = []; for (let role of user.roles()) { let change = new ldap.Change({ operation: 'add', modification: {memberUid: userName} }); let roleName = role.inherits().name; let dn = `cn=${roleName},${ldapConfig.groupDn}`; reqs.push(ldapClient.modify(dn, change)); } await Promise.all(reqs); } } catch (e) { err = e; } // FIXME: Cannot disconnect, hangs on undind() call // await ldapClient.unbind(); if (err) throw err; } // Samba let sambaConfig = await $.SambaConfig.findOne({ fields: ['host', 'sshUser', 'sshPass'] }); if (sambaConfig) { let sshPassword = Buffer .from(sambaConfig.sshPass, 'base64') .toString('ascii'); let sshClient = new ssh.NodeSSH(); await sshClient.connect({ host: sambaConfig.host, username: sambaConfig.sshUser, password: sshPassword }); let commands; if (hasAccount) { commands = [ `samba-tool user create "${userName}" ` + `--uid-number=${extraParams.uidNumber} ` + `--mail-address="${extraParams.corporateMail}" ` + `--random-password`, `samba-tool user setexpiry "${userName}" ` + `--noexpiry`, `samba-tool user setpassword "${userName}" ` + `--newpassword="${password}"`, `mkhomedir_helper "${userName}" 0027` ]; } else { commands = [ `samba-tool user delete "${userName}"` ]; } for (let command of commands) await sshClient.execCommand(command); await sshClient.dispose(); } // Mark as synchronized // if (user) // await $.Account.upsertWithWhere({id: user.id}, {sync: true}); }; };