1#!/usr/bin/env python
2# coding: utf-8
3
4# getent.py - program for querying nslcd
5#
6# Copyright (C) 2013-2019 Arthur de Jong
7#
8# This library is free software; you can redistribute it and/or
9# modify it under the terms of the GNU Lesser General Public
10# License as published by the Free Software Foundation; either
11# version 2.1 of the License, or (at your option) any later version.
12#
13# This library is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16# Lesser General Public License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public
19# License along with this library; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21# 02110-1301 USA
22
23import argparse
24import re
25import socket
26import struct
27import sys
28
29import constants
30from cmdline import VersionAction
31from nslcd import NslcdClient
32
33
34epilog = '''
35supported databases:
36  aliases, ethers, group, group.bymember, hosts, hostsv4, hostsv6,
37  netgroup, netgroup.norec, networks, networksv4, networksv6, passwd,
38  protocols, rpc, services, shadow
39
40Report bugs to <%s>.
41'''.strip() % constants.PACKAGE_BUGREPORT
42
43# set up command line parser
44parser = argparse.ArgumentParser(
45    formatter_class=argparse.RawDescriptionHelpFormatter,
46    description='Query information in %s via nslcd.' %
47    constants.MODULE_NAME.upper(),
48    epilog=epilog)
49parser.add_argument('-V', '--version', action=VersionAction)
50parser.add_argument('database', metavar='DATABASE',
51                    help='any database supported by nslcd')
52parser.add_argument('keys', metavar='KEY', nargs='*',
53                    help='filter returned database values by key')
54
55
56def write_aliases(con):
57    while con.get_response() == constants.NSLCD_RESULT_BEGIN:
58        print('%-16s%s' % (
59            con.read_string() + ': ',
60            ', '.join(con.read_stringlist())))
61
62
63def getent_aliases(database, keys=None):
64    if not keys:
65        write_aliases(NslcdClient(constants.NSLCD_ACTION_ALIAS_ALL))
66        return
67    for key in keys:
68        con = NslcdClient(constants.NSLCD_ACTION_ALIAS_BYNAME)
69        con.write_string(key)
70        write_aliases(con)
71
72
73def write_ethers(con):
74    while con.get_response() == constants.NSLCD_RESULT_BEGIN:
75        name = con.read_string()
76        ether = con.read_ether()
77        print('%s %s' % (ether, name))
78
79
80def getent_ethers(database, keys=None):
81    if not keys:
82        write_ethers(NslcdClient(constants.NSLCD_ACTION_ETHER_ALL))
83        return
84    for key in keys:
85        if re.match('^[0-9a-fA-F]{1,2}(:[0-9a-fA-F]{1,2}){5}$', key):
86            con = NslcdClient(constants.NSLCD_ACTION_ETHER_BYETHER)
87            con.write_ether(key)
88        else:
89            con = NslcdClient(constants.NSLCD_ACTION_ETHER_BYNAME)
90            con.write_string(key)
91        write_ethers(con)
92
93
94def write_group(con):
95    while con.get_response() == constants.NSLCD_RESULT_BEGIN:
96        print('%s:%s:%d:%s' % (
97            con.read_string(),
98            con.read_string(),
99            con.read_int32(),
100            ','.join(con.read_stringlist())))
101
102
103def getent_group(database, keys=None):
104    if not keys:
105        write_group(NslcdClient(constants.NSLCD_ACTION_GROUP_ALL))
106        return
107    for key in keys:
108        if database == 'group.bymember':
109            con = NslcdClient(constants.NSLCD_ACTION_GROUP_BYMEMBER)
110            con.write_string(key)
111        elif re.match(r'^\d+$', key):
112            con = NslcdClient(constants.NSLCD_ACTION_GROUP_BYGID)
113            con.write_int32(int(key))
114        else:
115            con = NslcdClient(constants.NSLCD_ACTION_GROUP_BYNAME)
116            con.write_string(key)
117        write_group(con)
118
119
120def _get_ipv4(value):
121    try:
122        return socket.inet_pton(socket.AF_INET, value)
123    except socket.error:
124        return None
125
126
127def _get_ipv6(value):
128    try:
129        return socket.inet_pton(socket.AF_INET6, value)
130    except socket.error:
131        return None
132
133
134def _get_af(database):
135    if database.endswith('v4'):
136        return socket.AF_INET
137    elif database.endswith('v6'):
138        return socket.AF_INET6
139    else:
140        return None
141
142
143def write_hosts(con, db_af):
144    while con.get_response() == constants.NSLCD_RESULT_BEGIN:
145        names = ' '.join([con.read_string()] + con.read_stringlist())
146        for af, address in con.read_addresslist():
147            if db_af in (af, None):
148                print('%-15s %s' % (address, names))
149
150
151def getent_hosts(database, keys=None):
152    db_af = _get_af(database)
153    if not keys:
154        write_hosts(NslcdClient(constants.NSLCD_ACTION_HOST_ALL), db_af)
155        return
156    for key in keys:
157        ipv4_addr = _get_ipv4(key)
158        ipv6_addr = _get_ipv6(key)
159        if ipv4_addr and db_af in (socket.AF_INET, None):
160            con = NslcdClient(constants.NSLCD_ACTION_HOST_BYADDR)
161            con.write_address(socket.AF_INET, ipv4_addr)
162        elif ipv6_addr and db_af in (socket.AF_INET6, None):
163            con = NslcdClient(constants.NSLCD_ACTION_HOST_BYADDR)
164            con.write_address(socket.AF_INET6, ipv6_addr)
165        else:
166            con = NslcdClient(constants.NSLCD_ACTION_HOST_BYNAME)
167            con.write_string(key)
168        write_hosts(con, db_af)
169
170
171def _read_netgroup(con):
172    """Read netgroup name, members and triples from stream."""
173    name = con.read_string()
174    members = []
175    tripples = []
176    while True:
177        member_type = con.read_int32()
178        if member_type == constants.NSLCD_NETGROUP_TYPE_NETGROUP:
179            members.append(con.read_string())
180        elif member_type == constants.NSLCD_NETGROUP_TYPE_TRIPLE:
181            tripples.append((
182                con.read_string(), con.read_string(), con.read_string()))
183        else:
184            break
185    return name, members, tripples
186
187
188def _get_getgroups(con, recurse, netgroups=None):
189    if netgroups is None:
190        netgroups = {}
191    while con.get_response() == constants.NSLCD_RESULT_BEGIN:
192        name, members, tripples = _read_netgroup(con)
193        if not recurse:
194            yield (name, members, tripples)
195        else:
196            netgroups[name] = None
197            for netgroup in members:
198                if netgroup not in netgroups:
199                    con2 = NslcdClient(constants.NSLCD_ACTION_NETGROUP_BYNAME)
200                    con2.write_string(netgroup)
201                    all(_get_getgroups(con2, recurse, netgroups))
202                if netgroups.get(netgroup, None) is not None:
203                    tripples += netgroups[netgroup][1]
204            netgroups[name] = (members, tripples)
205            yield (name, [], tripples)
206
207
208def write_netgroup(con, recurse):
209    for name, members, tripples in _get_getgroups(con, recurse):
210        print('%-15s %s' % (name, ' '.join(
211            members +
212            ['(%s, %s, %s)' % (host, user, domain)
213             for host, user, domain in tripples])))
214
215
216def getent_netgroup(database, keys=None):
217    recurse = database == 'netgroup'
218    if not keys:
219        write_netgroup(
220            NslcdClient(constants.NSLCD_ACTION_NETGROUP_ALL), recurse)
221        return
222    for key in keys:
223        con = NslcdClient(constants.NSLCD_ACTION_NETGROUP_BYNAME)
224        con.write_string(key)
225        write_netgroup(con, recurse)
226
227
228def write_networks(con, db_af):
229    while con.get_response() == constants.NSLCD_RESULT_BEGIN:
230        names = ' '.join([con.read_string()] + con.read_stringlist())
231        for af, address in con.read_addresslist():
232            if db_af in (af, None):
233                print('%-22s %s' % (names, address))
234
235
236def getent_networks(database, keys=None):
237    db_af = _get_af(database)
238    if not keys:
239        write_networks(NslcdClient(constants.NSLCD_ACTION_NETWORK_ALL), db_af)
240        return
241    for key in keys:
242        ipv4_addr = _get_ipv4(key)
243        ipv6_addr = _get_ipv6(key)
244        if ipv4_addr and db_af in (socket.AF_INET, None):
245            con = NslcdClient(constants.NSLCD_ACTION_NETWORK_BYADDR)
246            con.write_address(socket.AF_INET, ipv4_addr)
247        elif ipv6_addr and db_af in (socket.AF_INET6, None):
248            con = NslcdClient(constants.NSLCD_ACTION_NETWORK_BYADDR)
249            con.write_address(socket.AF_INET6, ipv6_addr)
250        else:
251            con = NslcdClient(constants.NSLCD_ACTION_NETWORK_BYNAME)
252            con.write_string(key)
253        write_networks(con, db_af)
254
255
256def write_passwd(con):
257    while con.get_response() == constants.NSLCD_RESULT_BEGIN:
258        print('%s:%s:%d:%d:%s:%s:%s' % (
259            con.read_string(),
260            con.read_string(),
261            con.read_int32(),
262            con.read_int32(),
263            con.read_string(),
264            con.read_string(),
265            con.read_string()))
266
267
268def getent_passwd(database, keys=None):
269    if not keys:
270        write_passwd(NslcdClient(constants.NSLCD_ACTION_PASSWD_ALL))
271        return
272    for key in keys:
273        if re.match(r'^\d+$', key):
274            con = NslcdClient(constants.NSLCD_ACTION_PASSWD_BYUID)
275            con.write_int32(int(key))
276        else:
277            con = NslcdClient(constants.NSLCD_ACTION_PASSWD_BYNAME)
278            con.write_string(key)
279        write_passwd(con)
280
281
282def write_protocols(con):
283    while con.get_response() == constants.NSLCD_RESULT_BEGIN:
284        name = con.read_string()
285        aliases = con.read_stringlist()
286        number = con.read_int32()
287        print('%-21s %d %s' % (name, number, ' '.join(aliases)))
288
289
290def getent_protocols(database, keys=None):
291    if not keys:
292        write_protocols(NslcdClient(constants.NSLCD_ACTION_PROTOCOL_ALL))
293        return
294    for key in keys:
295        if re.match(r'^\d+$', key):
296            con = NslcdClient(constants.NSLCD_ACTION_PROTOCOL_BYNUMBER)
297            con.write_int32(int(key))
298        else:
299            con = NslcdClient(constants.NSLCD_ACTION_PROTOCOL_BYNAME)
300            con.write_string(key)
301        write_protocols(con)
302
303
304def write_rpc(con):
305    while con.get_response() == constants.NSLCD_RESULT_BEGIN:
306        name = con.read_string()
307        aliases = con.read_stringlist()
308        number = con.read_int32()
309        print('%-15s %d  %s' % (name, number, ' '.join(aliases)))
310
311
312def getent_rpc(database, keys=None):
313    if not keys:
314        write_rpc(NslcdClient(constants.NSLCD_ACTION_RPC_ALL))
315        return
316    for key in keys:
317        if re.match(r'^\d+$', key):
318            con = NslcdClient(constants.NSLCD_ACTION_RPC_BYNUMBER)
319            con.write_int32(int(key))
320        else:
321            con = NslcdClient(constants.NSLCD_ACTION_RPC_BYNAME)
322            con.write_string(key)
323        write_rpc(con)
324
325
326def write_services(con):
327    while con.get_response() == constants.NSLCD_RESULT_BEGIN:
328        name = con.read_string()
329        aliases = con.read_stringlist()
330        number = con.read_int32()
331        protocol = con.read_string()
332        print('%-21s %d/%s %s' % (name, number, protocol, ' '.join(aliases)))
333
334
335def getent_services(database, keys=None):
336    if not keys:
337        write_services(NslcdClient(constants.NSLCD_ACTION_SERVICE_ALL))
338        return
339    for key in keys:
340        value = key
341        protocol = ''
342        if '/' in value:
343            value, protocol = value.split('/', 1)
344        if re.match(r'^\d+$', value):
345            con = NslcdClient(constants.NSLCD_ACTION_SERVICE_BYNUMBER)
346            con.write_int32(int(value))
347            con.write_string(protocol)
348        else:
349            con = NslcdClient(constants.NSLCD_ACTION_SERVICE_BYNAME)
350            con.write_string(value)
351            con.write_string(protocol)
352        write_services(con)
353
354
355def _shadow_value2str(number):
356    return str(number) if number != -1 else ''
357
358
359def write_shadow(con):
360    while con.get_response() == constants.NSLCD_RESULT_BEGIN:
361        print('%s:%s:%s:%s:%s:%s:%s:%s:%s' % (
362            con.read_string(),
363            con.read_string(),
364            _shadow_value2str(con.read_int32()),
365            _shadow_value2str(con.read_int32()),
366            _shadow_value2str(con.read_int32()),
367            _shadow_value2str(con.read_int32()),
368            _shadow_value2str(con.read_int32()),
369            _shadow_value2str(con.read_int32()),
370            _shadow_value2str(con.read_int32())))
371
372
373def getent_shadow(database, keys=None):
374    if not keys:
375        write_shadow(NslcdClient(constants.NSLCD_ACTION_SHADOW_ALL))
376        return
377    for key in keys:
378        con = NslcdClient(constants.NSLCD_ACTION_SHADOW_BYNAME)
379        con.write_string(key)
380        write_shadow(con)
381
382
383def main():  # noqa: C901
384    args = parser.parse_args()
385    try:
386        if args.database == 'aliases':
387            getent_aliases(args.database, args.keys)
388        elif args.database == 'ethers':
389            getent_ethers(args.database, args.keys)
390        elif args.database in ('group', 'group.bymember'):
391            getent_group(args.database, args.keys)
392        elif args.database in ('hosts', 'hostsv4', 'hostsv6'):
393            getent_hosts(args.database, args.keys)
394        elif args.database in ('netgroup', 'netgroup.norec'):
395            getent_netgroup(args.database, args.keys)
396        elif args.database in ('networks', 'networksv4', 'networksv6'):
397            getent_networks(args.database, args.keys)
398        elif args.database == 'passwd':
399            getent_passwd(args.database, args.keys)
400        elif args.database == 'protocols':
401            getent_protocols(args.database, args.keys)
402        elif args.database == 'rpc':
403            getent_rpc(args.database, args.keys)
404        elif args.database == 'services':
405            getent_services(args.database, args.keys)
406        elif args.database == 'shadow':
407            getent_shadow(args.database, args.keys)
408        else:
409            parser.error('Unknown database: %s' % args.database)
410    except struct.error:
411        print('Problem communicating with nslcd')
412        sys.exit(1)
413
414
415if __name__ == '__main__':
416    main()
417