├── digvip.py ├── everyday-admin.py ├── grepCHD.py ├── grepPW.py ├── known_hosts2hashcat.sh └── list_group_members.sh /digvip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2015 Sebastien MACKE 4 | # 5 | # This program is free software; you can redistribute it and/or modify it under 6 | # the terms of the GNU General Public License version 2, as published by the 7 | # Free Software Foundation 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 12 | # details (http://www.gnu.org/licenses/gpl.txt). 13 | 14 | import re 15 | import logging 16 | from sys import argv, exit 17 | 18 | formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)7s - %(message)s', datefmt='%H:%M:%S') 19 | handler = logging.StreamHandler() 20 | handler.setLevel(logging.DEBUG) 21 | handler.setFormatter(formatter) 22 | logger = logging.getLogger('digvip') 23 | #logger.setLevel(logging.DEBUG) # uncomment to enable debug messages 24 | logger.addHandler(handler) 25 | 26 | if len(argv) not in (2, 3): 27 | print('''Usage: 28 | C:\\>sysinternals\psloglist.exe -s -i 4624,4768,4769,528 -accepteula security > security.csv 29 | $ digvip.py security.csv [vips.txt]''') 30 | exit(2) 31 | 32 | csv = argv[1] 33 | 34 | vips = [] 35 | if len(argv) == 3: 36 | with open(argv[2]) as f: 37 | for l in f: 38 | vips.append(l.strip().lower()) 39 | 40 | logfmt = '%10s %11s %15s %-40s %s' 41 | 42 | print("[+] Parsing: '%s'" % csv) 43 | print("[+] Looking for: %s" % ", ".join(vips)) 44 | print(logfmt % ('Date', 'Time', 'Source IP', 'Domain\\account', 'Extra Info')) 45 | 46 | found = [] 47 | for line in open(csv): 48 | line = line.rstrip() 49 | i = line.find(',"') 50 | if i < 0: 51 | logger.debug('Unmatching line: %s' % line) 52 | continue 53 | 54 | hdr = line[:i] 55 | msg = line[i+2:-1] 56 | _, _, _ , _, _, event_time, event_id, _ = hdr.split(',') 57 | 58 | logger.debug('%s %s - %s' % (event_time, event_id, msg)) 59 | 60 | if event_id == '4624': # Account was successfully logged on 61 | exp = 'Account Name:\s+(?P\S+)\s+Account Domain:\s+(?P\S+)\s+Logon ID:\s+\S+\s+Logon GUID:.+Source Network Address:\s+(?P\d+\.\d+\.\d+\.\d+)\s+Source Port:.+Logon Process:\s+(?P\S+)\s+Authentication Package:\s+(?P\S+)\s+Transited Services:' 62 | 63 | elif event_id == '4768': # Kerberos authentication ticket (TGT) was requested 64 | exp = 'Account Name:\s+(?P\S+)\s+Supplied Realm Name:\s+(?P\S+)\s+User ID:.+Client Address:\s+(?P\d+\.\d+\.\d+\.\d+)\s+Client Port:' 65 | 66 | elif event_id == '4769': # Kerberos service ticket was requested 67 | exp = 'Account Name:\s+(?P\S+)\s+Account Domain:\s+(?P\S+)\s+Logon GUID:.+Client Address:\s+(?P\d+\.\d+\.\d+\.\d+)\s+Client Port:' 68 | 69 | elif event_id == '528': # Successful Logon 70 | exp = 'User Name:\s+(?P\S+)\s+Domain:\s+(?P\S+)\s+.+Workstation Name:\s+(?P\S+)\s+.+Caller Domain:\s+(?P\S+)\s+.+Source Network Address:\s+(?P\d+\.\d+\.\d+\.\d+)\s+' 71 | 72 | m = re.search(exp, msg) 73 | if not m: 74 | logger.debug('non-matching event') 75 | continue 76 | 77 | items = {'key': {}, 'extra': []} 78 | for k, v in m.groupdict().iteritems(): 79 | if k in ['acctname', 'acctdomain', 'cltaddr']: 80 | items['key'][k] = v.lower() 81 | else: 82 | items['extra'].append(v) 83 | 84 | key = ':'.join(v for _,v in items['key'].iteritems()) 85 | was_found = False 86 | 87 | for vip in vips: 88 | if vip in items['key']['acctname']: 89 | if key not in found: # comment out if you want to display all records, otherwise will only display most recent logon from a specific client IP 90 | found.append(key) 91 | was_found = True 92 | 93 | if was_found or not vips: 94 | date, time = event_time.split(' ', 1) 95 | print(logfmt % (date, time, items['key']['cltaddr'], '%s\\%s' % (items['key']['acctdomain'].upper(), items['key']['acctname']), ' '.join(items['extra']))) 96 | -------------------------------------------------------------------------------- /everyday-admin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2015 Sebastien MACKE 4 | # 5 | # This program is free software; you can redistribute it and/or modify it under 6 | # the terms of the GNU General Public License version 2, as published by the 7 | # Free Software Foundation 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 12 | # details (http://www.gnu.org/licenses/gpl.txt). 13 | 14 | from sys import argv 15 | from os.path import basename 16 | from collections import defaultdict 17 | 18 | def usage(): 19 | print '''Usage: %s [hashcat.pot john.pot ...]' 20 | Example: 21 | $ python everyday-admin.py domain.pw admins.pw hashcat.pot ~/.john/john.pot 22 | * Sharing f4dcffd5f469f9ed4474a1fa98b6fe8d (Bonjour!23) 23 | fr.xyz.internal\\amichel-adm (admin) 24 | fr.xyz.internal\\amichel 25 | 26 | * Sharing 3512cd8f085641a814b4424f088071f0 (Sacrebl3u) 27 | fr.xyz.internal\\pmartin-adm (admin) 28 | fr.xyz.internal\\pmartin 29 | 30 | * Sharing 8d85d77e0acc7257b5f60b46ed4a1cc4 (n/a) 31 | uk.xyz.internal\\jsmith-adm (admin) 32 | uk.xyz.internal\\jsmith 33 | ...''' % basename(argv[0]) 34 | exit(2) 35 | 36 | if len(argv) < 3: 37 | usage() 38 | 39 | domain_file = argv[1] 40 | admins_file = argv[2] 41 | pot_files = argv[3:] 42 | 43 | def load_users(filepath): 44 | users = defaultdict(list) 45 | 46 | for line in open(filepath): 47 | 48 | username, _, _, nt, _ = line.split(':', 4) 49 | username, nt = username.lower(), nt.lower() 50 | 51 | if '\\' in username: 52 | domain, username = username.split('\\', 1) 53 | else: 54 | domain = '' 55 | 56 | users[username].append((domain, nt)) 57 | 58 | return users 59 | 60 | def lookup_plain(nt_hash): 61 | for f in pot_files: 62 | for line in open(f): 63 | line = line.rstrip() 64 | if nt_hash in line: 65 | pw = line.split('%s:' % nt_hash, 1)[-1] 66 | return pw 67 | 68 | domain = load_users(domain_file) # domain.pw 69 | admins = load_users(admins_file) # admins.pw 70 | 71 | share_nt = defaultdict(list) 72 | for user_name, user_info in domain.iteritems(): 73 | 74 | if user_name in admins: 75 | continue 76 | 77 | for user_domain, user_nt in user_info: 78 | if user_nt == '31d6cfe0d16ae931b73c59d7e0c089c0': # blank password 79 | continue 80 | 81 | for admin_name, admin_info in admins.iteritems(): 82 | for admin_domain, admin_nt in admin_info: 83 | if user_nt == admin_nt: 84 | share_nt[admin_nt].append(('%s\\%s' % (user_domain, user_name) if user_domain else user_name, '%s\\%s' % (admin_domain, admin_name) if admin_domain else admin_name)) 85 | 86 | 87 | for nt_hash, sharers in sorted(share_nt.iteritems()): 88 | 89 | print '* Sharing %s (%s)' % (nt_hash, lookup_plain(nt_hash) or 'n/a') 90 | print '\n'.join(set('%s (admin)' % admin for _, admin in sharers)) 91 | print '\n'.join(set(user for user, _ in sharers)) 92 | print 93 | 94 | -------------------------------------------------------------------------------- /grepCHD.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2015 Sebastien MACKE 4 | # 5 | # This program is free software; you can redistribute it and/or modify it under 6 | # the terms of the GNU General Public License version 2, as published by the 7 | # Free Software Foundation 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 12 | # details (http://www.gnu.org/licenses/gpl.txt). 13 | 14 | import logging 15 | 16 | from sys import argv, stdin, exc_info, exit 17 | from platform import system 18 | import traceback 19 | import re 20 | import os 21 | 22 | fmt = logging.Formatter('%(message)s') 23 | 24 | sh = logging.StreamHandler() 25 | sh.setFormatter(fmt) 26 | sh.setLevel(logging.INFO) 27 | 28 | fh = logging.FileHandler('grepCHD.log') 29 | fh.setFormatter(fmt) 30 | fh.setLevel(logging.DEBUG) 31 | 32 | logging.getLogger().setLevel(logging.DEBUG) 33 | 34 | logging.getLogger().addHandler(sh) 35 | logging.getLogger().addHandler(fh) 36 | 37 | # Luhn checking stolen from http://stackoverflow.com/q/21079439 38 | def luhn_checksum(card_number): 39 | def digits_of(n): 40 | return [int(d) for d in str(n)] 41 | digits = digits_of(card_number) 42 | odd_digits = digits[-1::-2] 43 | even_digits = digits[-2::-2] 44 | checksum = 0 45 | checksum += sum(odd_digits) 46 | for d in even_digits: 47 | checksum += sum(digits_of(d*2)) 48 | return checksum % 10 49 | 50 | def is_luhn_valid(card_number): 51 | return luhn_checksum(card_number) == 0 52 | 53 | def walk_tree(toppath): 54 | tp = os.path.normpath(toppath) 55 | for root, dirnames, filenames in os.walk(tp): 56 | for fn in filenames: 57 | fp = os.path.join(root, fn) 58 | yield fp 59 | for dn in dirnames: 60 | walk_tree(dn) 61 | 62 | def color_context(ctx, cc): 63 | if not on_windows: 64 | return re.sub(cc, '\033[1m\033[31m'+cc+'\033[0m', ctx) 65 | else: 66 | return ctx 67 | 68 | pan_all = '[3-6][0-9]{3}.?[0-9]{4}.?[0-9]{4}.?[0-9]{1,4}' # 13-16 digits 69 | 70 | # https://en.wikipedia.org/wiki/Bank_card_number 71 | pan_re = [ 72 | ('Visa', '4[0-9]{3}[0-9]{4}[0-9]{4}[0-9]{1,4}'), # 13-16 digits 73 | ('MasterCard', '5[1-5][0-9]{2}[0-9]{4}[0-9]{4}[0-9]{4}'), # 16 digits 74 | ('Amex', '3[47][0-9]{2}[0-9]{6}[0-9]{5}'), # 15 digits 75 | ('Diners', '30[0-59][0-9]{11}'), # 14 digits 76 | ('Diners', '3[689][0-9]{13}'), 77 | ('Discover', '6011[0-9]{11}'), # 16 digits 78 | ('Discover', '622[0-9]{13}'), 79 | ('Discover', '64[4-9][0-9]{13}'), 80 | ('Discover', '65[0-9]{14}'), 81 | ('Maestro', '6[0-9][0-9][0-9]{9,16}'), # overlaps with Discover 82 | ('Maestro', '5[06][0-9][0-9]{9,16}'), # 12-19 digits 83 | ('Interpayment', '636[0-9]{13,16}'), # 16-19 digits 84 | ('JCB', '35[2-8][0-9]{13}'), # 16 digits 85 | ] 86 | 87 | track_re = [ 88 | ('Track 1', 'B__CCNUM__\^(.+?)\^(\d{4})\d{3}'), 89 | ('Track 2', '__CCNUM__[D=](\d{4})'), 90 | ] 91 | 92 | def find_cc(filepath): 93 | 94 | if isinstance(filepath, basestring): 95 | fd, fname = open(filepath), filepath 96 | else: 97 | fd, fname = filepath, 'stdin' 98 | 99 | while True: 100 | line = fd.readline(1000000) 101 | if len(line) == 0: 102 | break 103 | 104 | line = line.rstrip() 105 | 106 | cc_all = re.findall('%s' % pan_all, line) 107 | if not cc_all: 108 | continue 109 | 110 | for cc in cc_all: 111 | 112 | if re.search(r'[^\x20-\x7F]', cc): 113 | continue 114 | 115 | cc_num = re.sub("\D", "", cc) 116 | 117 | if not is_luhn_valid(cc_num): 118 | continue 119 | 120 | if cc_num == '4111111111111111': 121 | continue 122 | 123 | for cc_type, cc_re in pan_re: 124 | if re.match(cc_re, cc_num): 125 | break 126 | else: 127 | continue 128 | 129 | for ttype, tre in track_re: 130 | 131 | track_match = re.search(tre.replace('__CCNUM__', cc_num), line, re.I) 132 | if track_match: 133 | if ttype == 'Track 1': 134 | track_infos = '%s %s' % (track_match.group(2), track_match.group(1).strip()) 135 | else: 136 | track_infos = '%s' % track_match.group(1) 137 | break 138 | 139 | if track_match: 140 | cc_num= '%s %s' % (cc_num, track_infos.strip()) 141 | 142 | line = re.sub(r'[\x00-\x1F]+', '', line) 143 | line = re.sub(r'[\x7F-\xFF]+', '.', line) 144 | line = re.sub(r'[\x09\x20]+', ' ', line) 145 | 146 | cc_ctx = re.search('(.{0,50}%s.{0,50})' % cc, line).group(1).strip() 147 | cc_ctx = color_context(cc_ctx, cc) 148 | 149 | logging.info('%s: %s %s - %s' % (fname, cc_type, cc_num, cc_ctx)) 150 | 151 | line = line[line.index(cc) + len(cc):] 152 | 153 | def search_file(filepath): 154 | try: 155 | find_cc(filepath) 156 | except KeyboardInterrupt: 157 | exit(0) 158 | except: 159 | traceback.print_exc() 160 | 161 | on_windows = 'Win' in system() 162 | 163 | if len(argv) == 1: 164 | search_file(stdin) 165 | else: 166 | for arg in argv[1:]: 167 | if os.path.isdir(arg): 168 | for f in walk_tree(arg): 169 | if os.path.isfile(f): 170 | search_file(f) 171 | 172 | elif os.path.isfile(arg): 173 | search_file(arg) 174 | 175 | # vim: ts=2 sw=2 sts=2 et fdm=marker bg=dark 176 | -------------------------------------------------------------------------------- /grepPW.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from sys import argv 4 | import re 5 | import os 6 | 7 | IGNORE=['cd', 'ls', 'rm', 'mkdir', 'vi', 'view', 'vim', 8 | 'tar', 'gunzip', 'ps', 'cp', 'mv', 'head', 'tail', 'less', 'more', 'ln', '/bin/rm', 9 | 'find', 'diff', 'cat', 'gzcat', 10 | 'grep'] 11 | 12 | def color(s, color): 13 | colors = { 14 | 'red': '\033[91m', 15 | 'blue': '\033[94m', 16 | 'green': '\033[92m', 17 | 'end': '\033[0m', 18 | } 19 | return '%s%s%s' % (colors[color], s, colors['end']) 20 | 21 | def green(s): 22 | return color(s, 'green') 23 | 24 | def red(s): 25 | return color(s, 'red') 26 | 27 | def blue(s): 28 | return color(s, 'blue') 29 | 30 | def walk_tree(toppath): 31 | tp = os.path.normpath(toppath) 32 | for root, dirnames, filenames in os.walk(tp): 33 | for fn in filenames: 34 | fp = os.path.join(root, fn) 35 | yield fp 36 | for dn in dirnames: 37 | walk_tree(dn) 38 | 39 | def search_file(filepath): 40 | for l in open(filepath): 41 | l = l.strip() 42 | 43 | parts = re.split('\s', l) 44 | if not parts: 45 | continue 46 | 47 | #if parts[0] in IGNORE: 48 | # continue 49 | 50 | for w in parts: 51 | 52 | if re.subn('[^\x20-\x7F]', '', w)[1] > 0: 53 | continue 54 | 55 | if not (len(w) >= 8 and len(w) <= 12): 56 | #if not (len(w) == 10): 57 | continue 58 | 59 | if not re.search('[a-z]', w): 60 | continue 61 | 62 | if not re.match('[a-zA-Z0-9]', w): 63 | continue 64 | 65 | if not (re.search('[a-zA-Z]', w) and re.search('[0-9]', w)): #and re.search('[^a-zA-Z0-9]', w)): 66 | continue 67 | 68 | #if re.search('[^a-zA-Z0-9]', w): 69 | # continue 70 | 71 | i = l.find(w) 72 | if re.search('[^\x20-\x7F]', l): 73 | l = '%r' % l 74 | #if re.search('[^\x20-\x7F]', w): 75 | # w = '%r' % w 76 | ctx = '...' + l[i-200:i] + red(w) + l[i+len(w):i+100] + '...' 77 | 78 | if w not in candidates: 79 | candidates[w] = 0 80 | lines[w] = [] 81 | files[w] = [] 82 | 83 | candidates[w] += 1 84 | if ctx not in lines[w]: 85 | lines[w].append(ctx) 86 | files[w].append(filepath) 87 | 88 | def display_results(): 89 | for w, c in candidates.iteritems(): 90 | if c > 10: 91 | continue 92 | 93 | for i, (l, f) in enumerate(zip(lines[w], files[w])): 94 | print '%s -> %s' % (f, l) 95 | print 96 | 97 | SKIP_EXTENSIONS = ['pdf', 'docx', 'zip'] 98 | #ONLY_EXTENSIONS = ['log', 'xml', 'adm', 'ini' ,'txt', 'cmd', 'bat', 'vbs', 'csv'] 99 | ONLY_EXTENSIONS = ['msg', 'eml', 'log', 'xml', 'adm', 'ini' ,'txt', 'cmd', 'bat', 'vbs', 'csv'] 100 | 101 | if __name__ == '__main__': 102 | candidates = {} 103 | lines = {} 104 | files = {} 105 | 106 | if len(argv) == 1: 107 | search_file(stdin) 108 | else: 109 | for arg in argv[1:]: 110 | if os.path.isdir(arg): 111 | for f in walk_tree(arg): 112 | if os.path.isfile(f): 113 | ext = os.path.splitext(f)[1][1:].lower() 114 | if ext in SKIP_EXTENSIONS: 115 | continue 116 | if ext not in ONLY_EXTENSIONS: 117 | continue 118 | search_file(f) 119 | 120 | elif os.path.isfile(arg): 121 | search_file(arg) 122 | display_results() 123 | -------------------------------------------------------------------------------- /known_hosts2hashcat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (C) 2015 Sebastien MACKE 4 | # 5 | # This program is free software; you can redistribute it and/or modify it under 6 | # the terms of the GNU General Public License version 2, as published by the 7 | # Free Software Foundation 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 12 | # details (http://www.gnu.org/licenses/gpl.txt). 13 | 14 | # A simple Bash script to help format and run Hashcat against hashed known_hosts. 15 | 16 | Usage=' 17 | $ cat > /tmp/known_hosts <> "$outfile" 73 | done < "$1" 74 | 75 | rm -f "$leftfile" "$rightfile" 76 | echo "[+] Creating $leftfile, $rightfile" 77 | for a in {0..255}; do 78 | for b in {0..255}; do 79 | echo "$a.$b." >> "$leftfile" 80 | echo "$a.$b" >> "$rightfile" 81 | 82 | # use this in case 192.168.1.1 was stored as 192.168.001.001 83 | #printf "%03d.%03d.\n" $a $b >> "$leftfile" 84 | #printf "%03d.%03d\n" $a $b >> "$rightfile" 85 | 86 | # use this in case destination port was not 22/tcp 87 | #for c in {1..1024}; do 88 | # echo "[$a.$b." >> "$leftfile" 89 | # echo "$a.$b]:$c" >> "$rightfile" 90 | #done 91 | done 92 | done 93 | 94 | rm -f "$namesfile" 95 | echo "[+] Creating $namesfile" 96 | cat > "$namesfile" <<'EOF' 97 | localhost 98 | github.com 99 | bitbucket.com 100 | EOF 101 | 102 | echo "[+] Done, now run: 103 | oclHashcat -a 1 -m 160 --hex-salt $outfile $leftfile $rightfile 104 | oclHashcat -m 160 --hex-salt $outfile $namesfile" 105 | -------------------------------------------------------------------------------- /list_group_members.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (C) 2015 Sebastien MACKE 4 | # 5 | # This program is free software; you can redistribute it and/or modify it under 6 | # the terms of the GNU General Public License version 2, as published by the 7 | # Free Software Foundation 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 12 | # details (http://www.gnu.org/licenses/gpl.txt). 13 | 14 | # This script will recursively list members of the given target domain group(s). 15 | 16 | DOMAINS=() 17 | 18 | usage() 19 | { 20 | echo "Usage: 21 | $(basename $0) -d' ' ... 22 | $(basename $0) -d'CORPDOM 10.0.0.100 jdoe%Password1' '.\\Administrators' 23 | $(basename $0) -d'CORPDOM 10.0.0.100 jdoe%Password1' -d'OTHERDOM 10.2.0.100 CORPDOM\\jdoe%Password1' 'CORPDOM\\Domain Admins' 'CORPDOM\\Enterprise Admins' ..." 24 | exit 2 25 | } 26 | 27 | while getopts d: opt ; do 28 | case $opt in 29 | d) DOMAINS+=("$OPTARG") 30 | ;; 31 | *) usage 32 | esac 33 | done 34 | shift "$((OPTIND-1))" 35 | 36 | if (( $# < 1)); then 37 | usage 38 | fi 39 | 40 | net_members() 41 | { 42 | local dc creds 43 | for i in "${DOMAINS[@]}"; do 44 | domain=$(echo $i | cut -d' ' -f1) 45 | if [[ $1 == $domain ]]; then 46 | dc=$(echo $i | cut -d' ' -f2) 47 | creds=$(echo $i | cut -d' ' -f3-) 48 | break 49 | fi 50 | done 51 | if [[ -z $dc ]]; then # fallback if the group's domain name is not found in DOMAINS 52 | dc=$(echo "${DOMAINS[0]}" | cut -d' ' -f2) 53 | creds=$(echo "${DOMAINS[0]}" | cut -d' ' -f3-) 54 | fi 55 | 56 | net -l -U "$creds" -S "$dc" rpc group members "$2" 57 | } 58 | 59 | print_members() 60 | { 61 | echo "$indent$1 (group)" 62 | 63 | local indent="$2 " 64 | local d=$(echo "$1" | cut -d'\' -f1) 65 | local n=$(echo "$1" | cut -d'\' -f2-) 66 | 67 | while read -r line; do 68 | 69 | sid=$(echo "$line" | cut -d' ' -f1) 70 | domain=$(echo "$line" | cut -d' ' -f2- | cut -d '\' -f1) 71 | temp=$(echo "$line" | cut -d' ' -f2- | cut -d '\' -f2-) 72 | name=$(echo "$temp" | sed -ne 's,^\(.*\) [0-9]$,\1,p') 73 | use=$(echo "$temp" | sed -e 's,^.* ,,') 74 | 75 | #echo "debug: $name ($domain)" 76 | 77 | if [[ $use == "1" ]]; then 78 | echo "$indent$domain\\$name" 79 | else 80 | print_members "$domain\\$name" "$indent" 81 | fi 82 | 83 | done < <(net_members "$d" "$n") 84 | 85 | } 86 | 87 | for grp in "$@"; do 88 | print_members "$grp" 89 | echo 90 | done 91 | --------------------------------------------------------------------------------