const ldap = require('ldapjs'); const fs = require('fs'); const csv = require('csv-parser'); const { Parser } = require('json2csv'); const iconv = require('iconv-lite'); // 追加 const LDAP_URL = 'ldap://192.168.4.225'; const BASE_DN = 'dc=ldap,dc=next-hd,dc=co,dc=jp'; // ルートに変更 const now = new Date(); const pad = (n) => n.toString().padStart(2, '0'); const timestamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`; const EXPORT_CSV = `ldap_users_export_${timestamp}.csv`; const IMPORT_CSV = 'ldap_users_import.csv'; const BIND_DN = 'uid=root,cn=users,dc=ldap,dc=next-hd,dc=co,dc=jp'; // 管理者DN const BIND_PASSWORD = 'il7DXhKA'; // 管理者パスワード // 管理者権限でLDAP接続 function getLdapClientAndBind(callback) { const client = ldap.createClient({ url: LDAP_URL }); client.bind(BIND_DN, BIND_PASSWORD, err => { callback(client, err); }); } // LDAPからデータを取得してCSV出力 function exportLdapToCsv() { getLdapClientAndBind((client, err) => { if (err) { console.error('LDAP bind error:', err); client.unbind(); return; } const entries = []; // filterでcn=users配下のユーザーのみ取得 client.search(BASE_DN, { scope: 'sub', filter: 'objectClass=person', attributes: [] }, (err, res) => { if (err) { console.error('LDAP search error:', err); client.unbind(); return; } res.on('searchEntry', entry => { const obj = {}; if (Array.isArray(entry.attributes)) { entry.attributes.forEach(attr => { obj[attr.type] = Array.isArray(attr.vals) ? attr.vals.join(',') : attr.vals; }); entries.push(obj); } }); res.on('end', () => { if (entries.length === 0) { console.log('エントリが見つかりませんでした。'); } else { console.log(`取得したエントリ数: ${entries.length}`); console.log('エントリの例:', entries[100]); // 属性値が配列の場合は文字列化 const normalized = entries.map(obj => { const n = {}; if (obj && typeof obj === 'object') { Object.keys(obj).forEach(k => { if (Array.isArray(obj[k])) { n[k] = obj[k].join(','); // カンマ区切りで連結 } else { n[k] = obj[k]; } }); } return n; }); // 取得できた属性名を自動で抽出 const allFields = Array.from( normalized.reduce((set, obj) => { if (obj && typeof obj === 'object') { Object.keys(obj).forEach(k => set.add(k)); } return set; }, new Set()) ); const parser = new Parser({ fields: allFields }); const csvData = parser.parse(normalized); // Shift-JISに変換して保存 const sjisData = iconv.encode(csvData, 'Shift_JIS'); fs.writeFileSync(EXPORT_CSV, sjisData); console.log('LDAPデータをShift-JISでCSVにエクスポートしました:', EXPORT_CSV); } client.unbind(); }); }); }); } // CSVを読み込んでLDAPを更新 function importCsvToLdap() { const updates = []; fs.createReadStream(IMPORT_CSV) .pipe(csv()) .on('data', (row) => { updates.push(row); }) .on('end', () => { getLdapClientAndBind((client, err) => { if (err) { console.error('LDAP bind error:', err); client.unbind(); return; } let completed = 0; updates.forEach(update => { const uidNumber = update.uidNumber; if (uidNumber) { // uidNumberがある場合は検索して更新 client.search(BASE_DN, { scope: 'sub', filter: `(uidNumber=${uidNumber})`, attributes: ['dn'] }, (err, res) => { if (err) { console.error('LDAP search error:', err); completed++; if (completed === updates.length) client.unbind(); return; } let found = false; res.on('searchEntry', entry => { found = true; const dn = entry.objectName || entry.dn || (entry.object && entry.object.dn); if (!dn) { console.error('dnが取得できませんでした:', entry); completed++; if (completed === updates.length) client.unbind(); return; } // dnとobjectClass以外の属性を更新 const changes = []; Object.keys(update).forEach(attr => { if (attr !== 'dn' && attr !== 'objectClass' && update[attr]) { changes.push(new ldap.Change({ operation: 'replace', modification: { [attr]: update[attr] } })); } }); if (changes.length > 0) { client.modify(dn, changes, err => { if (err) { console.error(`LDAP modify error for ${dn}:`, err); } else { console.log(`LDAPエントリを更新しました: ${dn}`); } completed++; if (completed === updates.length) client.unbind(); }); } else { completed++; if (completed === updates.length) client.unbind(); } }); res.on('end', () => { if (!found) { // uidNumberはあるが該当エントリがない場合は新規登録 addLdapEntry(client, update, () => { completed++; if (completed === updates.length) client.unbind(); }); } }); }); } else { // uidNumberがない場合は新規登録 addLdapEntry(client, update, () => { completed++; if (completed === updates.length) client.unbind(); }); } }); }); }); } // 新規登録用ヘルパー関数 function addLdapEntry(client, entry, callback) { // 必要な属性を設定 const dn = entry.dn || `uid=${entry.uid},cn=users,${BASE_DN}`; const objectClass = entry.objectClass ? entry.objectClass.split(',') : [ 'top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount' ]; const attrs = { ...entry, objectClass }; delete attrs.dn; client.add(dn, attrs, err => { if (err) { console.error(`LDAPエントリ新規登録失敗: ${dn}`, err); } else { console.log(`LDAPエントリ新規登録: ${dn}`); } if (callback) callback(); }); } // 指定したuidNumberのエントリーを削除する関数 function deleteEntriesByUidNumbers(uidNumbers) { if (!Array.isArray(uidNumbers) || uidNumbers.length === 0) { console.log('uidNumberの配列を指定してください。'); return; } getLdapClientAndBind((client, err) => { if (err) { console.error('LDAP bind error:', err); client.unbind(); return; } // 各uidNumberごとに削除処理 let processed = 0; uidNumbers.forEach(uidNumber => { // uidNumberでエントリーを検索 client.search(BASE_DN, { scope: 'sub', filter: `(uidNumber=${uidNumber})`, attributes: ['dn'] }, (err, res) => { if (err) { console.error('LDAP search error:', err); processed++; if (processed === uidNumbers.length) client.unbind(); return; } let found = false; res.on('searchEntry', entry => { found = true; const dn = entry.objectName || entry.dn || (entry.object && entry.object.dn); if (dn) { client.del(dn, err => { if (err) { console.error(`削除失敗: ${dn}`, err); } else { console.log(`削除成功: ${dn}`); } processed++; if (processed === uidNumbers.length) client.unbind(); }); } else { console.error('dnが取得できませんでした:', entry); processed++; if (processed === uidNumbers.length) client.unbind(); } }); res.on('end', () => { if (!found) { console.log(`uidNumber=${uidNumber} のエントリーが見つかりませんでした。`); processed++; if (processed === uidNumbers.length) client.unbind(); } }); }); }); }); } // コマンドライン引数で処理を切り替え const mode = process.argv[2]; if (mode === 'export') { exportLdapToCsv(); } else if (mode === 'import') { importCsvToLdap(); } else if (mode === 'delete-uids') { // 例: node ldapUpdate.js delete-uids 1000003,1000004 const uidNumbers = (process.argv[3] || '').split(',').map(s => s.trim()).filter(Boolean); deleteEntriesByUidNumbers(uidNumbers); } else { console.log('使い方:'); console.log(' node ldapUpdate.js export # cn=users配下のLDAPデータをCSVエクスポート'); console.log(' node ldapUpdate.js import # CSVからLDAPを更新'); console.log(' node ldapUpdate.js delete-uids 1000003,1000004 # 指定uidNumberのエントリーを削除'); }