├── .gitignore ├── NetworkManager ├── autologin.d │ └── wifi_in_de_trein └── dispatcher.d │ ├── autologin │ └── synergy ├── README.rst ├── check_trace ├── cisco-crypt.py ├── clientbucket_to_git ├── fake_time.c ├── gateway_identify.py ├── git-ssh ├── gnome-keyring-dump ├── hilight.py ├── launchpadduser ├── mutt-keyring ├── mutt-ldapsearch ├── nagios_uptime_report.pl ├── nma.pl ├── otpindicator ├── p2000 ├── pcat.c ├── progressbar.py ├── rainbow ├── reset_passwords ├── run-single-cron ├── singleton.py ├── slack-emoji ├── slack-send ├── slack-tail ├── stealenv.py ├── subsetsum.py ├── suid_script_wrapper.c ├── wag.c └── wallpaper.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .coverage 3 | *,cover 4 | *.so 5 | *.orig 6 | -------------------------------------------------------------------------------- /NetworkManager/autologin.d/wifi_in_de_trein: -------------------------------------------------------------------------------- 1 | #!/usr/bin/phantomjs 2 | /* 3 | * Log in to the public wifi in the dutch trains 4 | * 5 | * Open a url that will return a 404 response, then follow the following state 6 | * diagram: 7 | * 8 | * - If the URL fails to load, exit. This is either because we hit the 404 and 9 | * are thus already logged in, or because the wifi thing is overloaded again 10 | * and it makes no sense to continue 11 | * - If the URL does load, try to find the loginpage. If found, log in 12 | * - If a popup window opens, it must be the confirmation window. We have 13 | * succeeded and exit. 14 | */ 15 | 16 | "use strict"; 17 | 18 | console.log("WiFi in de trein autologin starting..."); 19 | 20 | function say(page, message) { 21 | console.log("Page " + page.id + " " + message); 22 | } 23 | 24 | var pagecount = 0; 25 | function decorate(page) { 26 | page.id = ++pagecount; 27 | page.onConsoleMessage = function(message) { 28 | say(page, "window console: " + message); 29 | } 30 | page.onPageCreated = function(newpage) { 31 | decorate(newpage); 32 | say(newpage, "created"); 33 | } 34 | page.onLoadFinished = function(status) { 35 | say(page, "finished loading (" + status + "): " + page.url); 36 | if(page.id == 2) { 37 | say(page, "Confirmation page loaded. Great success!"); 38 | phantom.exit(); 39 | }; 40 | if(status != "success") 41 | phantom.exit(); 42 | page.evaluate(function() { 43 | console.log("Searching for login page"); 44 | try { $; } catch(err) { return; } 45 | var button = $(".button"); 46 | var check = $("input:checkbox"); 47 | if(check) { 48 | console.log("Found login page, attempting to log in"); 49 | check.attr('checked', true); 50 | button.trigger('click'); 51 | } 52 | }); 53 | } 54 | return page; 55 | } 56 | decorate(require('webpage').create()).open('http://www.google.com/intentional_404'); 57 | 58 | /* 59 | * vim:syntax=javascript 60 | */ 61 | -------------------------------------------------------------------------------- /NetworkManager/dispatcher.d/autologin: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Whenever a connection comes up, search for an autologin script in 4 | # ../autologin.d and execute it if found. 5 | # 6 | # Scripts must be named after a sanitized version of the connection name. The 7 | # sanitization is that the name must be downcased, and all characters other 8 | # than ascii letters, numbers and underscore must be replaced with an 9 | # underscore. 10 | # 11 | # e.g. 'WiFi in de trein' becomes wifi_in_de_trein 12 | 13 | test "$2" != up && exit 0 14 | 15 | ( 16 | dev="$1" 17 | conn="$(nmcli device show "$dev" | sed -ne 's/GENERAL.CONNECTION: *//p')" 18 | connf="$(echo "$conn" | tr A-Z a-z | sed -e 's/[^a-z0-9_]/_/g')" 19 | connp="$(dirname "$(dirname "$(readlink -f "$0")")")/autologin.d/$connf" 20 | if [ -f "$connp" ] && [ -x "$connp" ]; then 21 | exec $connp 22 | fi 23 | ) 2>&1 | logger --id=$$ --tag=nm-dispatch-autologin 24 | -------------------------------------------------------------------------------- /NetworkManager/dispatcher.d/synergy: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Autostart synergy when I'm connected to the right network (next to my 4 | # desktop) 5 | # 6 | # Hardcodes the user, network, and IP address of the synergy server 'cause I'm 7 | # a lazy bugger. 8 | 9 | ( 10 | device=$1 11 | state=$2 12 | devtype="$(nmcli -t -f GENERAL.TYPE device show "$device")" 13 | devtype=${devtype#*:} 14 | case $devtype,$state,$IP4_ADDRESS_0 in 15 | ethernet,down,*) 16 | sudo -u dennis killall synergyc 17 | ;; 18 | ethernet,up,10.155.73.*) 19 | sudo -u dennis DISPLAY=:0 synergyc -d WARNING 10.155.73.50 20 | ;; 21 | esac 22 | ) 2>&1 | logger --id=$$ --tag=nm-dispatch-synergy 23 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Collection of hacks 2 | =================== 3 | 4 | This is a collection of small hacks I wrote over the years. 5 | 6 | ansi.py 7 | ------- 8 | ANSI terminal colors demo. Glorified readme, reusable as lib. 9 | 10 | check_trace 11 | ----------- 12 | Nagios plugin to resolve a check whether a hostname is fully resolvable, from 13 | the root nameservers down to the authoritative nameserver for the domain. On 14 | more than one occasion, SIDN broke the .nl zone, making this check useful. 15 | 16 | clientbucket_to_git 17 | -------------------- 18 | Converts a puppet clientbucket to a git repository for easy inspection of 19 | server changes over time. 20 | 21 | cisco-crypt.py 22 | -------------- 23 | Python module and standalone script for encrypting/decrypting cisco-style 24 | encrypted passwords in .pcf files for the vpn client. 25 | 26 | fake_time.c 27 | ----------- 28 | Make your system think differently about time. 29 | 30 | gateway_identify.py 31 | ------------------- 32 | xchat script that prints the IP address/hostname of users connecting from a 33 | known gateway on freenode. 34 | 35 | git-ssh 36 | ------- 37 | Easy ssh access to remote git hosts and repos. 38 | 39 | gnome-keyring-dump 40 | ------------------ 41 | Dump the contents of the gnome keyring 42 | 43 | hilight.py 44 | ---------- 45 | xchat script that copies all hilighting messages to a separate channel-like 46 | window. 47 | 48 | launchpadduser 49 | -------------- 50 | create local accounts based on launchpad accounts. 51 | 52 | mutt-ldapsearch 53 | --------------- 54 | Search e-mail addresses in ldap from mutt 55 | 56 | mutt-keyring 57 | ------------ 58 | Make mutt use gnome-keyring for passwords 59 | 60 | nagios_uptime_report.pl 61 | ----------------------- 62 | A management pacifier, mail them uptime graphs monthly to keep them happy (or 63 | sad, if you didn't do your job properly). 64 | 65 | NetworkManager/dispatcher.d/autologin 66 | ------------------------------------- 67 | Automatically log in to wireless networks that have a captive portal. This 68 | script doesn't actually login, but dispatches to actual login scripts. 69 | 70 | NetworkManager/dispatcher.d/synergy 71 | ----------------------------------- 72 | Autostart synergy when connected to the right network 73 | 74 | NetworkManager/autologin.d/wifi_in_de_trein 75 | ------------------------------------------- 76 | Automatically log in to the wifi in dutch trains. 77 | 78 | nma.pl 79 | ------ 80 | Send irssi notifications to your android phone when screen is detached. 81 | 82 | otpindicator 83 | ------------ 84 | An appindicator that can display a totp token 85 | 86 | p2000 87 | ----- 88 | Script to monitor and alert on p2000 (dutch emergency services) messages. 89 | 90 | pcat.c 91 | ------ 92 | A cat that works on fifos (created with mkfifo). 93 | 94 | progressbar.py 95 | -------------- 96 | Python library for creating smooth progressbars on the terminal using unicode 97 | characters for sub-character precision. 98 | 99 | rainbow 100 | ------- 101 | Colorize output with a rainbow pattern. For those boring days. 102 | 103 | reset_passwords 104 | --------------- 105 | Boot from a live cd and run this to reset all passwords it finds. 106 | 107 | run-single-cron 108 | --------------- 109 | Runs a single job from a crontab exactly as cron would do it 110 | 111 | singleton.py 112 | ------------ 113 | Pure-python subclassable singleton class that uses __new__ instead of the 114 | usual __init__+impl trick (borg pattern), so they are real singletons. 115 | 116 | slack-send & slack-tail 117 | ----------------------- 118 | Tools to send and receive slack messages on the command line. 119 | 120 | stealenv.py 121 | ----------- 122 | Steal (well, output) a process in a variety of formats, usable by shells and 123 | other languages. 124 | 125 | subsetsum.py 126 | ------------ 127 | Find a subset of a set of integers with a given sum. Useful for "for €50 in 128 | receipts, you get a free something". 129 | 130 | suid_script_wrapper.c 131 | --------------------- 132 | When sudo is not available, you can use this to let people run a script as 133 | another user. 134 | 135 | wag.c 136 | ----- 137 | Poor-mans file watcher. Whenever a file changes, execute an application. Made 138 | for systems where inotify does not exist. 139 | 140 | wallpaper.py 141 | ------------ 142 | Random wallpaper grabber & changer. Supports only interfacelift.com for now. 143 | -------------------------------------------------------------------------------- /check_trace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # 3 | # check_trace plugin for nagios 4 | # 5 | # This plugins checks whether a hostname resolves to the address you expect 6 | # it to. It uses dig +trace to check the entire path, from the root 7 | # nameservers to your domain 8 | # 9 | # See check_trace -h for usage information 10 | # (c)2008 Dennis Kaarsemaker - Licensed under GNU GPL v3 or later 11 | 12 | use strict; 13 | use warnings; 14 | 15 | sub print_help (); 16 | sub _usage (); 17 | sub in ($$); 18 | 19 | use Getopt::Long; 20 | 21 | use lib '/usr/lib/nagios/plugins'; 22 | use lib '/usr/lib64/nagios/plugins'; 23 | use utils qw(%ERRORS $TIMEOUT &print_revision &usage); 24 | 25 | our ($progname, $progversion); 26 | my ($opt_h, $opt_A, $opt_V); 27 | my ($verbose, $host, $address, @addresses, @found_addresses, $cnames); 28 | 29 | $progname = "check_trace"; 30 | $progversion = "1.1"; 31 | $cnames = 5; 32 | 33 | 34 | # Parse options 35 | Getopt::Long::Configure('bundling'); 36 | GetOptions( 37 | "V" => \$opt_V, "version" => \$opt_V, 38 | "h" => \$opt_h, "help" => \$opt_h, 39 | "H=s" => \$host, "hostname" => \$host, 40 | "a=s" => \$address, "address" => \$address, 41 | "A=s" => \$opt_A, "addresses" => \$opt_A, 42 | "v" => \$verbose, "verbose" => \$verbose, 43 | "c" => \$cnames, "cnames" => \$cnames, 44 | ); 45 | 46 | if ($opt_h) { print_help(); exit $ERRORS{"OK"}; } 47 | if ($opt_V) { print_revision($progname, $progversion); exit $ERRORS{"OK"}; } 48 | @addresses = (); 49 | @addresses = split(/,/, $opt_A) if(defined($opt_A)); 50 | push(@addresses, $address); 51 | 52 | usage(_usage) unless $host; 53 | usage(_usage) unless $address; 54 | 55 | # Trace the hostname 56 | my $orig_host = $host; 57 | $host .= '.' unless $host =~ /\.$/; 58 | my @ns_trace = (); 59 | my $cname_count = 0; 60 | 61 | while($cname_count <= $cnames) { 62 | my $cname = undef; 63 | print "*** Searching $host\n" if $verbose; 64 | open(DIG, '-|', "dig +time=$TIMEOUT +trace $host"); 65 | while() { 66 | print if $verbose; 67 | # ;; Received 427 bytes from 192.33.4.12#53(C.ROOT-SERVERS.NET) in 24 ms 68 | if(/^;; Received.*?\((.*?)\)./) { 69 | push(@ns_trace, $1); 70 | } 71 | # foo.example.com. 86400 IN CNAME bar.example.com. 72 | if(/$host\s+\d+\s+IN\s+CNAME\s+(.*)/) { 73 | $cname = sprintf('%s CNAME %s', $host, $1); 74 | $host = $1; 75 | } 76 | # bar.example.com. 300 IN A 127.0.0.1 77 | if(/$host\s+\d+\s+IN\s+A\s+(.*)/) { 78 | push(@found_addresses, $1); 79 | } 80 | } 81 | last if not defined($cname); 82 | last if @found_addresses; 83 | push(@ns_trace, $cname); 84 | $cname_count++; 85 | } 86 | 87 | # So, did it work? 88 | if($cname_count == $cnames) { 89 | print "Need to follow too many cnames (more than $cnames) to resolve address"; 90 | exit($ERRORS{"CRITICAL"}); 91 | } 92 | 93 | my @errors; 94 | foreach my $address (@addresses) { 95 | if(!in(\@found_addresses, $address)) { 96 | push(@errors,"$orig_host did not resolve to $address"); 97 | } 98 | } 99 | foreach my $address (@found_addresses) { 100 | if(!in(\@addresses, $address)) { 101 | push(@errors, "$orig_host resolved to $address"); 102 | } 103 | } 104 | if(@errors) { 105 | print join(', ', @errors); 106 | exit $ERRORS{"CRITICAL"}; 107 | } 108 | print "$orig_host resolved to ".join(', ', @addresses)." via ".join(', ', @ns_trace); 109 | exit $ERRORS{"OK"}; 110 | 111 | sub print_help() { 112 | print_revision($progname, $progversion); 113 | print "\n" . 114 | _usage . 115 | "\n" . 116 | "-H|--hostname hostname to resolve\n" . 117 | "-a|--address address it resolves to\n" . 118 | "-A|--addresses extra addresses it can resolve to (round robin)\n" . 119 | "-c|--cnames max. number of CNAME's to follow\n" . 120 | "\n" . 121 | "The plugin will use dig +trace to trace your hostname. It will\n" . 122 | "allso follow CNAME's unless the A answer came in baliwick\n"; 123 | } 124 | 125 | sub _usage() { 126 | return "Usage:\n$progname -H hostname -a address [-A extra_addresses] -v\n" . 127 | "$progname -h|--help\n" . 128 | "$progname -V|--version\n" 129 | } 130 | 131 | sub in($$) { 132 | my $array_ref = shift; 133 | my $test = shift; 134 | 135 | foreach my $element (@{$array_ref}) { 136 | if ($test eq $element) { 137 | return 1; 138 | } 139 | } 140 | return 0; 141 | } 142 | -------------------------------------------------------------------------------- /cisco-crypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Cisco password encryption/decryption functions. These can be used to 4 | # encrypt/decrypt passwords found in Cisco VPN .pcf files (and maybe other 5 | # places too) 6 | # 7 | # Usable as a python module and as a standalone tool. Usage for the tool: 8 | # ./cisco-crypt.py -d 9 | # ./cisco-crypt.py -e [key] 10 | # 11 | # Dependencies: pycrypto 12 | # 13 | # (c) 2010 Dennis Kaarsemaker - Dedicated to the public domain 14 | 15 | import base64 16 | try: 17 | from hashlib import sha1 18 | except ImportError: 19 | from sha1 import new as sha1 20 | import random 21 | import struct 22 | import Crypto.Cipher.DES3 as DES3 23 | 24 | def decrypt_password(data): 25 | """Given the hexdump of an obfuscated password, this function will return 26 | the real password""" 27 | 28 | # Decode incoming string, it's a hexdump of a bytestring 29 | data = base64.b16decode(data) 30 | 31 | # Length check 32 | if len(data) < 48: 33 | raise ValueError("Invalid encoded data, too short") 34 | 35 | # The stored data consists of three parts 36 | key = data[:20] 37 | sha = data[20:40] 38 | password = data[40:] 39 | 40 | # Check whether the sha1 is correct 41 | if sha != sha1(password).digest(): 42 | raise RuntimeError("Invalid encoded data, sha1 mismatch") 43 | 44 | # The IV is the first 8 bytes of the non-mangled key 45 | iv = key[:8] 46 | 47 | # Construct the real, obfuscated, key 48 | p1 = key[:19] + struct.pack('B',1 + struct.unpack('B', key[19])[0]) 49 | p2 = key[:19] + struct.pack('B',3 + struct.unpack('B', key[19])[0]) 50 | key_ = sha1(p1).digest() + sha1(p2).digest()[:4] 51 | 52 | # Encryption is 3DES, CBC 53 | decrypted = DES3.new(key_, DES3.MODE_CBC, iv).decrypt(password) 54 | 55 | # Remove padding 56 | padlen = struct.unpack('B', decrypted[-1])[0] 57 | decrypted = decrypted[:-padlen] 58 | 59 | return decrypted 60 | 61 | def encrypt_password(password, key=None): 62 | """This function encrypts and obfuscates the given password for use in 63 | Cisco VPN .pcf files. The key will be randomly generated if not given""" 64 | 65 | # Pad password for 3DES+CBC 66 | padlen = 8 - (len(password) % 8) 67 | password += padlen * struct.pack('B', padlen) 68 | 69 | # Create 20-byte random key, if needed 70 | if not key: 71 | key = sha1(str(random.getrandbits(8*20))).digest() 72 | elif not isinstance(key, str) or len(key) != 20: 73 | raise ValueError("Invalid key") 74 | 75 | # The IV is the first 8 bytes of the key 76 | iv = key[:8] 77 | 78 | # Construct the real, obfuscated, key 79 | p1 = key[:19] + struct.pack('B',1 + struct.unpack('B', key[19])[0]) 80 | p2 = key[:19] + struct.pack('B',3 + struct.unpack('B', key[19])[0]) 81 | key_ = sha1(p1).digest() + sha1(p2).digest()[:4] 82 | 83 | # Encryprion is 3DES, CBC 84 | encrypted = DES3.new(key_, DES3.MODE_CBC, iv).encrypt(password) 85 | 86 | # Hexdump 87 | data = key + sha1(encrypted).digest() + encrypted 88 | return base64.b16encode(data) 89 | 90 | if __name__ == "__main__": 91 | import optparse 92 | import sys 93 | usage = "\n%prog -d \n%prog -e [key]" 94 | parser = optparse.OptionParser(usage=usage) 95 | parser.add_option('-d', '--decrypt', dest="do_decrypt", 96 | action="store_true", default=False, help="Decrypt a password") 97 | parser.add_option('-e', '--encrypt', dest="do_encrypt", 98 | action="store_true", default=False, help="Encrypt a password") 99 | 100 | options, args = parser.parse_args() 101 | if not (options.do_decrypt ^ options.do_encrypt): 102 | parser.print_help() 103 | sys.exit(1) 104 | 105 | if (options.do_decrypt and len(args) != 1) or \ 106 | (options.do_encrypt and len(args) not in (1,2)): 107 | parser.print_help() 108 | sys.exit(1) 109 | 110 | try: 111 | if options.do_decrypt: 112 | print decrypt_password(args[0]) 113 | elif options.do_encrypt: 114 | print encrypt_password(*args) 115 | except Exception, e: 116 | print str(e) 117 | sys.exit(1) 118 | -------------------------------------------------------------------------------- /clientbucket_to_git: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Puppet nicely stores old versions of files in the clientbucket. The 4 | # clientbucket is a pain to search through though, especially if you want to 5 | # see changes over time. 6 | # 7 | # So let's convert the client bucket to a git repository! 8 | # 9 | # This script does *not* work in an incremental way, you will need to 10 | # regenerate the entire repository every time. 11 | # 12 | # Usage: 13 | # 14 | # clientbucket_to_git [ -b bucket ... ] [ -r repo] 15 | # 16 | # Default buckets: 17 | # - /var/puppet/clientbuckt 18 | # - /var/lib/puppet/clientbucket 19 | # 20 | # Default repo path: /tmp/clientbucket 21 | 22 | import os 23 | import stat 24 | import subprocess 25 | import optparse 26 | 27 | default_buckets = ['/var/puppet/clientbucket', '/var/lib/puppet/clientbucket'] 28 | default_repo = '/tmp/clientbucket' 29 | 30 | def buckets_to_git(paths, repo): 31 | # First we read all path info from the buckets 32 | files = [] 33 | for path in paths: 34 | for dirpath, _, filenames in os.walk(path): 35 | if 'paths' in filenames: 36 | cp = os.path.join(dirpath, 'contents') 37 | with open(os.path.join(dirpath, 'paths')) as fd: 38 | for name in fd.read().rstrip().split('\n'): 39 | if not name.startswith('/'): 40 | name = '/' + name 41 | files.append(File(name, os.stat(cp)[stat.ST_MTIME], cp)) 42 | files.sort(key=lambda x: x.mtime) 43 | 44 | # Create the initial commit: first mention of all files 45 | seen = {} 46 | repo = Repo(repo) 47 | for file in list(files): 48 | if file.path not in seen: 49 | seen[file.path] = file.mtime 50 | repo.add_file(file.path, file.path_to_content) 51 | files.remove(file) 52 | else: 53 | # Correct the mtimes. The commit time of a file in the clientbucket 54 | # is when the previous puppetrun that changed this file ran 55 | file.mtime, seen[file.path] = seen[file.path] , file.mtime 56 | repo.commit(files[0].mtime, """Initial commit 57 | 58 | This commit contains the first occurence in the clientbucket of all files. This 59 | is not necessarily the same as the "virgin" stat of this machine, but will make 60 | all diffs look accurate and not add any new files later on. 61 | 62 | The reason for this is that it is impossible to determine when a file was added 63 | to the repository, as that does not create an entry in the clientbucket. 64 | """) 65 | 66 | # Now add the deletions and final states 67 | for path in seen: 68 | if not os.path.exists(path): 69 | files.append(File(path, seen[path], None)) 70 | else: 71 | files.append(File(path, seen[path], path)) 72 | files.sort(key=lambda x: x.mtime) 73 | 74 | # Now we add files one by one, committing every 5 minutes to group by puppet run 75 | last_mtime = files[0].mtime 76 | for file in files: 77 | if file.mtime > last_mtime + 300: 78 | repo.commit(last_mtime, "Puppet run at approximately %s" % str(last_mtime)) 79 | last_mtime = file.mtime 80 | repo.add_file(file.path, file.path_to_content) 81 | repo.commit(last_mtime, "Puppet run at approximately %s" % str(last_mtime)) 82 | 83 | class File(object): 84 | def __init__(self, path, mtime, path_to_content): 85 | self.path, self.mtime, self.path_to_content = path, mtime, path_to_content 86 | 87 | class Repo(object): 88 | def __init__(self, path): 89 | self.path = path 90 | os.mkdir(path) 91 | subprocess.call(['git', 'init'], preexec_fn=lambda: os.chdir(self.path)) 92 | 93 | def add_file(self, path, content_path): 94 | if path.startswith('/'): 95 | path = path[1:] 96 | if path.startswith('/'): 97 | raise ValueError("Invalid path: %s" % path) 98 | if not os.path.exists(os.path.join(self.path, os.path.dirname(path))): 99 | os.makedirs(os.path.join(self.path, os.path.dirname(path))) 100 | if not content_path: 101 | subprocess.call(['git', 'rm', '-q', path], preexec_fn=lambda: os.chdir(self.path)) 102 | else: 103 | with open(content_path) as rfd: 104 | with open(os.path.join(self.path, path), 'w') as wfd: 105 | wfd.write(rfd.read()) 106 | subprocess.call(['git', 'add', path], preexec_fn=lambda: os.chdir(self.path)) 107 | 108 | def commit(self, mtime, msg): 109 | subprocess.Popen(['git', 'commit', '-q', '--date', str(mtime), '-F-'], preexec_fn=lambda: os.chdir(self.path), stdin=subprocess.PIPE).communicate(msg) 110 | 111 | if __name__ == '__main__': 112 | p = optparse.OptionParser() 113 | p.usage = "%prog [ -b bucket ... ] [ -r repo]" 114 | p.add_option("-b", "--bucket", dest="buckets", default=default_buckets, metavar="PATH", 115 | help="Bucket to read (default: %s)" % ", ".join(default_buckets)) 116 | p.add_option("-r", "--repository", dest="repo", default=default_repo, metavar="PATH", 117 | help="Path to the git repository (default: %s)" % default_repo) 118 | opts, args = p.parse_args() 119 | 120 | buckets_to_git(opts.buckets, opts.repo) 121 | -------------------------------------------------------------------------------- /fake_time.c: -------------------------------------------------------------------------------- 1 | /* 2 | * fake_time.c -- Report fake date/time 3 | * 4 | * All calls to get the current date/time on linux come down to calling 5 | * gettimeofday, clock_gettime, time, or ftime in the C library. This cfile 6 | * can be compiled into a shared library that overrides these four calls 7 | * via LD_PRELOAD. This way you can make your app think differently about 8 | * time, which is useful for testing and fuzzing. 9 | * 10 | * To compile the library: 11 | * 12 | * gcc -Wall -fPIE -shared -o fake_time.so fake_time.c 13 | * 14 | * To use the library, set the following environment variables: 15 | * 16 | * export LD_PRELOAD=/path/to/fake_time.so 17 | * export FAKE_TIME=1281228785 18 | * 19 | * All calls to retrieve the current time will then report the value of 20 | * FAKE_TIME (which is interpreted as seconds since epoch). The Default 21 | * value for FAKE_TIME is 1234567890 or Fri Feb 13 23:31:30 UTC 2009 22 | * 23 | * You can also change the value that will be returned by simply calling 24 | * setenv from your code to update the FAKE_TIME variable. 25 | * 26 | * Beware: any processes spawned by your code will inherit the fake_time 27 | * behaviour unless you clean the environment variable LD_PRELOAD before 28 | * calling execve or one of the many wrappers around it 29 | * 30 | * (c)2010 Dennis Kaarsemaker Dedicated to the 31 | * public domain 32 | */ 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #undef gettimeofday 39 | #undef clock_gettime 40 | #undef time 41 | #undef ftime 42 | 43 | #define DEFAULT_FAKE_TIME (1234567890) 44 | #define FAKE_TIME_VAR "FAKE_TIME" 45 | 46 | static long get_time() { 47 | int fake_time; 48 | char *env_time = getenv(FAKE_TIME_VAR); 49 | char *err = NULL; 50 | if(!env_time) 51 | return DEFAULT_FAKE_TIME; 52 | fake_time = strtol(env_time, &err, 10); 53 | if(err && *err) 54 | return DEFAULT_FAKE_TIME; 55 | return fake_time; 56 | } 57 | 58 | int gettimeofday(struct timeval *tv, struct timezone *tz) { 59 | if(tv == NULL) 60 | return -1; 61 | tv->tv_sec = get_time(); 62 | return 0; 63 | } 64 | 65 | int clock_gettime(clockid_t clk_id, struct timespec *tp) { 66 | if(tp == NULL) 67 | return -1; 68 | tp->tv_sec = get_time(); 69 | return 0; 70 | } 71 | 72 | time_t time(time_t *t) { 73 | int t_ = get_time(); 74 | if(t) 75 | *t = t_; 76 | return t_; 77 | } 78 | 79 | int ftime(struct timeb *tp) { 80 | tp->time = get_time(); 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /gateway_identify.py: -------------------------------------------------------------------------------- 1 | import dns.exception 2 | import dns.resolver 3 | import dns.reversename 4 | import re 5 | import socket 6 | import struct 7 | import xchat 8 | 9 | __module_name__ = "gateway_identify" 10 | __module_description__ = "Print the IP of users joining from gateways" 11 | __module_version__ = "1.0" 12 | 13 | hex_ip_regex = re.compile(r'^~?[0-9a-f]{8}$') 14 | def make_ip(s): 15 | return socket.inet_ntoa(struct.pack('!I',eval('0x'+s))) 16 | 17 | def identify_gateway(word, word_eol, userdata): 18 | id = word[0][1:] 19 | nick, ident, host = re.split('[@!]', id) 20 | if not (hex_ip_regex.match(ident) and host.startswith('gateway/')): 21 | return 22 | channel = word[-1][1:] 23 | ip = make_ip(ident[-8:]) 24 | try: 25 | host = dns.resolver.query(dns.reversename.from_address(ip), 'PTR').response.answer[0][0].to_text()[:-1] 26 | except (dns.resolver.NXDOMAIN, dns.exception.Timeout): 27 | host = 'unknown hostname' 28 | xchat.find_context(channel=channel).emit_print('Server Notice', "%s is coming from %s (%s)" % (nick, ip, host)) 29 | xchat.hook_server('JOIN', identify_gateway, priority=xchat.PRI_LOWEST) 30 | -------------------------------------------------------------------------------- /git-ssh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # SSH to a remote, and possibly run a command inside the remote repo 4 | # 5 | # Examples: 6 | # 7 | # Open a shell in the remote repository: 8 | # 9 | # $ git ssh origin 10 | # 11 | # Run gc remotely 12 | # 13 | # $ git ssh origin git gc 14 | # 15 | # Or just any random command, maybe du to check a repo's size: 16 | # 17 | # $ git ssh origin du -shc . 18 | # 19 | # (c) 2015 Dennis Kaarsemaker 20 | # Based on an idea of Chern Jie Lim (https://github.com/chernjie/git-ssh) 21 | 22 | USAGE="[ssh options] [command]" 23 | . $(git --exec-path)/git-sh-setup 24 | 25 | declare -a ssh_opts 26 | declare -a ssh_args 27 | 28 | # Parse an option string like -vvt and return whether the next argument to 'git 29 | # ssh' is an argument to an ssh option, e.g. when using 'git ssh -vvl root 30 | # origin', this returns true for -vvl, signaling the option checker to skip the 31 | # next argument. 32 | expect_more() { 33 | opt="${1:1}" 34 | while [ -n "$opt" ]; do 35 | cur="${opt:0:1}" 36 | opt="${opt:1}" 37 | [[ "$cur" =~ [tT] ]] && seen_tty=yes 38 | if [[ "$cur" =~ [bcDeFIiLlmOopRSWw] ]]; then 39 | test -z "$opt" 40 | return 41 | fi 42 | done 43 | return 1 44 | } 45 | 46 | while [ $# -gt 0 ]; do 47 | case "$1" in 48 | -*) 49 | # SSH option. Store and skip. 50 | expect_more "$1" && { ssh_opts[${#ssh_opts[@]}]="$1"; shift; } 51 | ssh_opts[${#ssh_opts[@]}]="$1"; shift 52 | ;; 53 | *) 54 | # First non-option is the remote 55 | if [ -z "$remote" ]; then 56 | remote="$1"; 57 | shift 58 | else 59 | # When more non-options appear, all other arguments are part of the 60 | # command to execute. 61 | while [ $# -gt 0 ]; do ssh_args[${#ssh_args[@]}]="$1"; shift; done 62 | fi 63 | ;; 64 | esac 65 | done 66 | 67 | # A remote is madatory. Find and parse the URl for the remote 68 | test -z "$remote" && usage 69 | remote_url=$(git config remote."$remote".url) 70 | case "$remote_url" in 71 | '') 72 | die "Could not find url for $remote" 73 | ;; 74 | file://*|https://*|https://*|git://*) 75 | die "Protocol not supported: ${remote_url%%://}" 76 | ;; 77 | esac 78 | 79 | case "$remote_url" in 80 | git+ssh://*|ssh://*) 81 | url="${remote_url#*://}" 82 | host="${url%%/*}" 83 | path="${url#*/}" 84 | ;; 85 | *) 86 | host="${remote_url%%:*}" 87 | path="${remote_url#*:}" 88 | ;; 89 | esac 90 | 91 | # If we don't specify a command, we need a shell. Started in a bit of a special 92 | # way as we need to cd first. 93 | if [ ${#ssh_args[@]} -eq 0 ]; then 94 | if [ "$seen_tty" != yes ]; then 95 | ssh_opts[${#ssh_opts[@]}]="-t" 96 | fi 97 | ssh_args[0]="${SHELL:-/bin/bash}" 98 | ssh_args[1]="-l" 99 | fi 100 | 101 | # And now we can actually run the ssh command safely, quoting things wither 102 | # ourselves or using git rev-parse --sq-quote 103 | exec ssh "$host" "${ssh_opts[@]}" "cd \"$path\" && $(git rev-parse --sq-quote "${ssh_args[@]}")" 104 | -------------------------------------------------------------------------------- /gnome-keyring-dump: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import json 4 | import secretstorage 5 | import sys 6 | 7 | bus = secretstorage.dbus_init() 8 | collection = secretstorage.get_default_collection(bus) 9 | all = [] 10 | for secret in collection.get_all_items(): 11 | all.append({ 12 | 'label': secret.get_label(), 13 | 'attrs': secret.get_attributes(), 14 | 'secret': secret.get_secret().decode('us-ascii'), 15 | }) 16 | json.dump(all, sys.stdout, sort_keys=True, indent=4) 17 | -------------------------------------------------------------------------------- /hilight.py: -------------------------------------------------------------------------------- 1 | # Sends highlights as a /query to yourself so they are all available in one tab 2 | # (c)2010 Dennis Kaarsemaker 3 | # Licence: MIT 4 | 5 | import fnmatch 6 | import string 7 | import xchat 8 | 9 | __module_name__ = "hilight" 10 | __module_version__ = "0.2" 11 | __module_description__ = "Log highlighting messages to a special tab" 12 | 13 | rfc2812_tolower = string.maketrans('[]\\~','{}|^') 14 | irc_lower = lambda txt: txt.lower().translate(rfc2812_tolower) 15 | hilight_query_name = u'Hilights' 16 | 17 | def on_msg(word, word_eol, userdata): 18 | sender=word[0][1:] 19 | recipient=word[2] 20 | message=word_eol[3][1:] 21 | if not is_highlight(sender, recipient, message): 22 | return xchat.EAT_NONE 23 | ctx = xchat.find_context(server=xchat.get_info('server'),channel=hilight_query_name) 24 | if not ctx: 25 | # Open a query if it isn't there yet 26 | xchat.command('query -nofocus %s' % hilight_query_name) 27 | ctx = xchat.find_context(server=xchat.get_info('server'),channel=hilight_query_name) 28 | if message[0] == message[-1] and message[0] == '\x01': 29 | # CTCP. Only honor CTCP action aka /me 30 | if message[1:7].lower() != 'action': 31 | return xchat.EAT_NONE 32 | ctx.emit_print('Channel Action Hilight', '%s/%s' % (sender[:sender.find('!')], recipient), message[8:-1], '') 33 | else: 34 | ctx.emit_print('Channel Msg Hilight', '%s/%s' % (sender[:sender.find('!')], recipient), message, '') 35 | return xchat.EAT_NONE 36 | xchat.hook_server("PRIVMSG", on_msg) 37 | 38 | def is_highlight(sender, recipient, message): 39 | """Are we being highlighted?""" 40 | message = irc_lower(message) 41 | sender = irc_lower(sender) 42 | # Only highlight channels 43 | if not recipient[0] in '#&@': 44 | return False 45 | # Nicks to never highlight 46 | nnh = irc_lower(xchat.get_prefs('irc_no_hilight') or '') 47 | if match_word(sender[:sender.find('!')], nnh.split(',')): 48 | return False 49 | # Nicks to always highlight 50 | nth = irc_lower(xchat.get_prefs('irc_nick_hilight') or '') 51 | if match_word(sender[:sender.find('!')], nth.split(',')): 52 | return True 53 | # Words to highlight on, masks allowed 54 | wth = [irc_lower(xchat.get_prefs('irc_nick%d' % x) or '') for x in (1,2,3)] + [irc_lower(xchat.get_info('nick'))] 55 | wth += irc_lower(xchat.get_prefs('irc_extra_hilight') or '').split(', ') 56 | for w in wth: 57 | w = w.strip() 58 | if w and w in message: 59 | return True 60 | return False 61 | 62 | def match_word(needle, haystack): 63 | # Evil. Use fnmatch. 64 | haystack = [x.strip() for x in haystack if x.strip()] 65 | return bool(fnmatch.filter(haystack, needle)) 66 | -------------------------------------------------------------------------------- /launchpadduser: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Create a local user based on a launchpad account. The useracount will be 4 | # created with a locked password and an ssh authorized_keys file with all their 5 | # keys on launchpad. 6 | 7 | # Abuse the docstring so running this with /bin/sh will do the right thing 8 | """exec" "/usr/bin/python" "$0""" 9 | __doc__ = "Create a local user based on a launchpad account" 10 | __all__ = ["create_user"] 11 | 12 | import os, pwd, subprocess, urllib2, xml.dom.minidom 13 | 14 | lp = "https://launchpad.net/~" 15 | 16 | def verify_exists(lpuser): 17 | try: 18 | urllib2.urlopen(lp + lpuser) 19 | return True 20 | except: 21 | return False 22 | 23 | def get_realname(lpuser): 24 | rdf = urllib2.urlopen(lp + lpuser + "/+rdf").read() 25 | tree = xml.dom.minidom.parseString(rdf) 26 | return tree.getElementsByTagName("foaf:name")[0].firstChild.nodeValue 27 | 28 | def create_user(local_name, launchpad_name=None): 29 | if not launchpad_name: 30 | launchpad_name = local_name 31 | 32 | # Sanity checking 33 | try: 34 | pwd.getpwnam(local_name) 35 | except: 36 | pass 37 | else: 38 | raise LookupError("%s already exists on your local system" % local_name) 39 | if not verify_exists(launchpad_name): 40 | raise LookupError("%s does not have a launchpad account" % launchpad_name) 41 | try: 42 | sshkey = urllib2.urlopen(lp + launchpad_name + "/+sshkeys").read() 43 | except: 44 | raise LookupError("%s has not yet uploaded his/her SSH key to launchpad" % launchpad_name) 45 | 46 | # Add the user 47 | realname = get_realname(launchpad_name) 48 | if subprocess.call(["/usr/sbin/adduser", 49 | "--gecos", realname, 50 | "--disabled-password", 51 | local_name]): 52 | raise RuntimeError("Could not create user %s" % local_name) 53 | user = pwd.getpwnam(local_name) 54 | os.mkdir(os.path.join(user.pw_dir, ".ssh"), 0700) 55 | os.chown(os.path.join(user.pw_dir, ".ssh"), user.pw_uid, user.pw_gid) 56 | fd = os.open(os.path.join(user.pw_dir, ".ssh", "authorized_keys2"), os.O_WRONLY | os.O_CREAT, 0600) 57 | os.write(fd, sshkey) 58 | os.close(fd) 59 | os.chown(os.path.join(user.pw_dir, ".ssh", "authorized_keys2"), user.pw_uid, user.pw_gid) 60 | 61 | if __name__ == '__main__': 62 | import optparse, sys 63 | 64 | parser = optparse.OptionParser(usage="%prog [options] name\nCreate a local account based on a launchpad.net account") 65 | parser.add_option('-l', '--launchpad-name', 66 | dest="launchpad_name", 67 | default=None, 68 | help="Use this launchpad account", 69 | metavar="NAME") 70 | opts, args = parser.parse_args() 71 | 72 | if len(args) != 1: 73 | parser.print_help() 74 | sys.exit(1) 75 | 76 | if os.geteuid() != 0: 77 | print >>sys.stderr, "Only root can add users to the system" 78 | sys.exit(1) 79 | 80 | local_name = args[0] 81 | launchpad_name = opts.launchpad_name or local_name 82 | try: 83 | create_user(local_name, launchpad_name) 84 | except LookupError, e: 85 | print >>sys.stderr, e.message 86 | sys.exit(1) 87 | -------------------------------------------------------------------------------- /mutt-keyring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # Fetch passwords for imap/smtp accounts from gnome-keyring 4 | # Adding passwords to gnome-keyring is up to yourself. Passwords added 5 | # by evolution will be picked up, which may help. 6 | # 7 | # Usage in .muttrc: 8 | # source "~/bin/mutt-keyring $folder $smtp_url|" 9 | 10 | import configparser 11 | import glob 12 | import os 13 | import secretstorage 14 | import sys 15 | import urllib.parse 16 | 17 | def main(): 18 | urls = [parse_url(x) for x in sys.argv[1:]] 19 | bus = secretstorage.dbus_init() 20 | collection = secretstorage.get_default_collection(bus) 21 | root = os.path.join(os.path.expanduser('~'), '.config', 'evolution', 'sources') 22 | 23 | for source in glob.glob(os.path.join(root, '*.source')): 24 | uid = os.path.splitext(os.path.basename(source))[0] 25 | proto, user, host, parent = read_source(source) 26 | if (proto, user, host) in urls: 27 | secretd = { 28 | 'xdg:schema': 'org.gnome.Evolution.Data.Source', 29 | 'e-source-uid': uid, 30 | } 31 | secrets = list(collection.search_items(secretd)) 32 | if not secrets and parent: 33 | secretd['e-source-uid'] = parent 34 | secrets = list(collection.search_items(secretd)) 35 | if secrets: 36 | print("set %s_pass='%s'" % (proto, secrets[0].get_secret().decode('utf-8'))) 37 | 38 | def read_source(file): 39 | source = configparser.ConfigParser() 40 | source.read(file) 41 | user = host = proto = None 42 | parent = source['Data Source']['parent'] 43 | if 'Authentication' in source: 44 | user = source['Authentication']['user'] 45 | host = source['Authentication']['host'] 46 | for key in ('Mail Account', 'Mail Transport'): 47 | if key in source: 48 | proto = source[key]['backendname'] 49 | if proto == 'imapx': 50 | proto = 'imap' 51 | return (proto, user, host, parent) 52 | 53 | def parse_url(url): 54 | url = urllib.parse.urlsplit(url) 55 | scheme = 'imap' if url.scheme.startswith('imap') else 'smtp' 56 | username = url.username 57 | if ';' in username: 58 | username = username[:username.find(';')] 59 | return (scheme, username, url.hostname) 60 | 61 | if __name__ == '__main__': 62 | main() 63 | -------------------------------------------------------------------------------- /mutt-ldapsearch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | LDAP_HOST = 'ldap://ldap.server.com' 4 | LDAP_BASE = 'ou=People,dc=server,dc=com' 5 | 6 | import ldap 7 | import re 8 | import sys 9 | 10 | search_string = ' '.join(sys.argv[1:]) 11 | if not re.match(r'^[a-zA-Z .*]+$', search_string): 12 | print "Invalid search string" 13 | sys.exit(1) 14 | search_string = "(|%s)" % ''.join(['(%s=*%s*)' % (key, search_string) for key in ('sn', 'mail', 'givenName', 'cn')]) 15 | 16 | server = ldap.initialize(LDAP_HOST) 17 | try: 18 | server.simple_bind_s() 19 | except ldap.SERVER_DOWN: 20 | sys.exit(0) 21 | entries = server.search_s(LDAP_BASE, ldap.SCOPE_ONELEVEL, search_string) 22 | 23 | entries = [x for x in entries if 'mail' in x[1]] 24 | print "%d entries found" % len(entries) 25 | for e in entries: 26 | print "%s\t%s\tFrom LDAP" % (e[1]['mail'][0], e[1]['cn'][0]) 27 | -------------------------------------------------------------------------------- /nagios_uptime_report.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # nagios_uptime_report.pl (aka nagios2mgmt.pl) 3 | # 4 | # Mail uptime graphs for a host to a set of addresses. Useful to 5 | # keep curious managers at a distance. 6 | # 7 | # 1) Run this script from cron on 01:00 on the first of every month. 8 | # 2) Make management receive this mail 9 | # 3) ??? 10 | # 4) Profit 11 | 12 | use strict; 13 | use warnings; 14 | use POSIX qw(mktime); 15 | use MIME::Lite; 16 | use LWP::Simple; 17 | 18 | # "config" 19 | my $from = '"Nagios uptime graphs" '; 20 | my $subject = 'Nagios uptime graphs for %d/%d', 21 | my @recipients = ('me@example.com', 22 | 'you@example.com'); 23 | 24 | my $nagios_host = 'http://user:pass@nagios.example.com'; 25 | my @hosts = ('www.example.com','test.example.com','mail.example.com'); 26 | 27 | # Here's where to get the graphs 28 | my $baseurl = "https://$nagios_host/nagios/cgi-bin/trends.cgi?createimage" . 29 | '&assumeinitialstates=yes&assumestatesduringnotrunning=yes&initialassumedhoststate=0&' . 30 | 'initialassumedservicestate=0&assumestateretention=yes&includesoftstates=no' . 31 | '&host=%s&backtrack=4&zoom=4&t1=%d&t2=%d'; 32 | 33 | # Which month to graph 34 | # - Previous month if no month given 35 | # - Allow overrides in argv, ./$0 year month 36 | my $year = $ARGV[0]; 37 | my $month = $ARGV[1]; 38 | my ($cursec, $curmin, $curhour, $curmday, $curmon, $curyear, $curwday, $curyday, $curisdst) = gmtime(time); 39 | $year = $curyear + 1900 if not defined $year; 40 | $month = $curmon if not defined $month; 41 | my $t1 = mktime(0, 0, 0, 1, $month-1, $year - 1900, 0, 0, 0); 42 | my $t2 = mktime(0, 0, 0, 1, $month, $year - 1900, 0, 0, 0); 43 | 44 | # Construct mail 45 | my $msg = MIME::Lite->new( 46 | From => $from, 47 | To => join(',', @recipients), 48 | Subject => sprintf($subject, $year, $month), 49 | Type => 'multipart/related', 50 | ); 51 | $msg->attach( 52 | Type => 'text/html', 53 | Data => '' . join('
', map(sprintf('', $_), @hosts)) . '' 54 | ); 55 | 56 | # Fetch images and attach 57 | foreach my $host (@hosts) { 58 | my $url = sprintf $baseurl, $host, $t1, $t2; 59 | my $img = get($url); 60 | $msg->attach( 61 | Type => 'image/png', 62 | Data => $img, 63 | Id => "$host.png", 64 | ); 65 | } 66 | 67 | # Send! 68 | $msg->send(); 69 | -------------------------------------------------------------------------------- /nma.pl: -------------------------------------------------------------------------------- 1 | # nma.pl - Send notifications to your android phone when screen is detached 2 | # (c) 2013 Dennis Kaarsemaker 3 | # Bugs can be filed at github.com/seveas/hacks 4 | # 5 | # Prep work: 6 | # - Get an account on notifymyandroid.com 7 | # - Buy a premium account (cheap, one time purchase) if you want to receive 8 | # more than 5 notifications per day 9 | # - Install the app on your android device(s) 10 | # - Generate an API key 11 | # 12 | # Usage: 13 | # - /load nma.pl 14 | # - /set nma_api_key your_key_goes_here 15 | # - /save 16 | # 17 | # When that is all done, all private messages and all public messages that 18 | # contain your nickname will be sent to your android device(s). 19 | 20 | use strict; 21 | use warnings; 22 | 23 | use Irssi; 24 | use LWP::UserAgent; 25 | use POSIX qw(access X_OK); 26 | 27 | use vars qw($VERSION %IRSSI $screen_socket); 28 | 29 | $VERSION = "1.0"; 30 | %IRSSI = ( 31 | authors => "Dennis Kaarsemaker", 32 | contact => "dennis\@kaarsemaker.net", 33 | name => "nma", 34 | description => "Send a notification to android devices via notifymyandroid", 35 | license => "GPLv3", 36 | url => "https://github.com/seveas/nma.pl" 37 | ); 38 | 39 | check_screen(); 40 | check_tmux(); 41 | Irssi::settings_add_str("nma", "nma_api_key", ""); 42 | Irssi::signal_add_last('message private', 'private'); 43 | Irssi::signal_add_last('message public', 'public'); 44 | 45 | # Code inspired by screen_away.pl 46 | sub check_screen { 47 | return unless defined($ENV{STY}); 48 | 49 | my $socket = `LC_ALL="C" screen -ls`; 50 | return if $socket =~ /^No Sockets found/s; 51 | 52 | $socket =~ s/^.+\d+ Sockets? in (.*?)\/?\.\n.+$/$1/s; 53 | $socket .= "/" . $ENV{STY}; 54 | $screen_socket = $socket; 55 | } 56 | sub check_tmux { 57 | return unless defined($ENV{TMUX}); 58 | $screen_socket = $ENV{TMUX}; 59 | $screen_socket =~ s/(,\d+)+$//; 60 | } 61 | 62 | sub screen_detached { $screen_socket && !access($screen_socket, X_OK) } 63 | 64 | sub private { 65 | my ($server, $msg, $nick, $address) = @_; 66 | return unless screen_detached; 67 | send_message($server->{tag}, "Private message from $nick", $msg); 68 | } 69 | 70 | sub public { 71 | my ($server, $msg, $nick, $address, $target) = @_; 72 | return unless screen_detached; 73 | return unless $msg =~ /\Q$server->{nick}\E/i; 74 | send_message($server->{tag}, "Message from $nick in $target", $msg); 75 | } 76 | 77 | sub send_message { 78 | my ($server, $title, $message) = @_; 79 | 80 | my $key = Irssi::settings_get_str("nma_api_key"); 81 | return unless $key; 82 | 83 | my $ua = LWP::UserAgent->new(); 84 | $ua->env_proxy(); 85 | $ua->agent("irssi/$Irssi::VERSION"); 86 | 87 | my $resp = $ua->post("https://nma.usk.bz/publicapi/notify", { 88 | apikey => $key, 89 | application => "IRC: $server", 90 | priority => 0, 91 | event => $title, 92 | description => $message}); 93 | } 94 | -------------------------------------------------------------------------------- /otpindicator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # Quick hack to have a totp token in the unity status bar. Pass the totp key as 4 | # commandline argument. Only does 6 digit, 30 second totp tokens. 5 | 6 | import dbus.mainloop.glib 7 | import gi 8 | gi.require_version('GObject', '2.0') 9 | gi.require_version('Gtk', '3.0') 10 | gi.require_version('AppIndicator3', '0.1') 11 | from gi.repository import GObject as gobject 12 | from gi.repository import Gtk as gtk 13 | from gi.repository import Gdk as gdk 14 | from gi.repository import AppIndicator3 as appindicator 15 | import NetworkManager 16 | import pyotp 17 | import signal 18 | import sys 19 | import time 20 | 21 | signal.signal(signal.SIGINT, signal.SIG_DFL) 22 | APPINDICATOR_ID = 'totp' 23 | 24 | step=30 25 | icons = { 26 | 100: 'battery-full-symbolic', 27 | 60: 'battery-good-symbolic', 28 | 40: 'battery-low-symbolic', 29 | 20: 'battery-caution-symbolic', 30 | 10: 'battery-empty-symbolic', 31 | } 32 | 33 | def main(): 34 | token = sys.argv[1] 35 | vpn = None 36 | if len(sys.argv) > 2: 37 | vpn = sys.argv[2] 38 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 39 | 40 | indicator = appindicator.Indicator.new(APPINDICATOR_ID, icons[100], appindicator.IndicatorCategory.SYSTEM_SERVICES) 41 | indicator.set_status(appindicator.IndicatorStatus.ACTIVE) 42 | indicator.set_label('......', '888888') 43 | indicator.connect('scroll-event', lambda ind, *_: gtk.Clipboard.get(gdk.SELECTION_CLIPBOARD).set_text(ind.get_label(), -1)) 44 | set_token(indicator, token) 45 | if vpn: 46 | agent = SecretAgent(vpn, indicator) 47 | 48 | menu = gtk.Menu() 49 | item = gtk.MenuItem('Quit') 50 | item.connect('activate', lambda source: gtk.main_quit()) 51 | menu.append(item) 52 | if vpn: 53 | item = gtk.MenuItem('Activate VPN') 54 | item.connect('activate', lambda source: activate_vpn(vpn)) 55 | menu.append(item) 56 | menu.show_all() 57 | indicator.set_menu(menu) 58 | 59 | gobject.timeout_add(1000 - (time.time() % 1) * 1000, second_sync, indicator, token) 60 | 61 | gtk.main() 62 | 63 | 64 | def second_sync(indicator, token): 65 | gobject.timeout_add(1000, set_token, indicator, token) 66 | set_token(indicator, token) 67 | 68 | def set_token(indicator, token): 69 | otp = pyotp.TOTP(token).now() 70 | pct_remaining = int((step-(time.time() % step))/step * 100) 71 | for icon in sorted(icons): 72 | if icon > pct_remaining: 73 | break 74 | icon = icons[icon] 75 | if indicator.get_icon() != icon: 76 | indicator.set_icon(icon) 77 | if indicator.get_label() != otp: 78 | indicator.set_label(otp, '888888') 79 | return True 80 | 81 | def activate_vpn(vpn): 82 | for connection in NetworkManager.Settings.ListConnections(): 83 | settings = connection.GetSettings()['connection'] 84 | if settings['id'] == vpn: 85 | NetworkManager.NetworkManager.ActivateConnection(connection, "/", "/") 86 | 87 | class SecretAgent(NetworkManager.SecretAgent): 88 | def __init__(self, vpn, indicator): 89 | self.vpn = vpn 90 | self.indicator = indicator 91 | super(SecretAgent, self).__init__('net.seveas.otpindicator') 92 | 93 | def GetSecrets(self, settings, connection, setting_name, hints, flags): 94 | try: 95 | if setting_name != 'vpn' or settings['connection']['id'] != self.vpn: 96 | return {} 97 | return {setting_name: {'secrets': {'password': self.indicator.get_label()}}} 98 | except: 99 | import traceback 100 | traceback.print_exc() 101 | return {} 102 | 103 | if __name__ == "__main__": 104 | main() 105 | -------------------------------------------------------------------------------- /p2000: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Watch p2000 alarms and notify for new alarms 4 | # 5 | # This program displays p2000 pager messages. p2000 is the pager system for the 6 | # dutch emergency services. Messages are downloaded from p2000-online.net every 7 | # so often (default: 60 seconds). This website also has a delay of about 90 8 | # seconds, making the total delay approximately 2.5 minutes. 9 | # 10 | # Requirements: 11 | # - A linux desktop 12 | # - pynotify and gtk to display notifications 13 | # - beautifulsoup for parsing html 14 | # - libcanberra to play a sound when new alarms arrive (optional) 15 | # 16 | # (c)2011 Dennis Kaarsemaker 17 | # License: gpl 3+ 18 | 19 | from BeautifulSoup import BeautifulSoup as soup 20 | import ctypes 21 | import os 22 | import glib 23 | import gtk 24 | import pynotify 25 | import sys 26 | import time 27 | import urllib 28 | 29 | # These are the region names as used by p2000-online.net 30 | regions = ( 31 | "Amsterdam-Amstelland", 32 | "Brabant Noord", 33 | "Brabant ZuidOost", 34 | "Drenthe", 35 | "Flevoland", 36 | "Friesland", 37 | "Gelderland-Midden", 38 | "Gelderland-Zuid", 39 | "Gooi en Vechtstreek", 40 | "Groningen", 41 | "Haaglanden", 42 | "Hollands Midden", 43 | "IJsselland", 44 | "Kennemerland", 45 | "Limburg Noord", 46 | "Limburg Zuid", 47 | "Midden en West Brabant", 48 | "Noord- en Oost Gelderland", 49 | "Noord-Holland Noord", 50 | "Rotterdam Rijnmond", 51 | "Twente", 52 | "Utrecht", 53 | "Zaanstreek-Waterland", 54 | "Zeeland", 55 | "Zuid Holland Zuid", 56 | ) 57 | 58 | def _escape(data): 59 | return data.replace('&','&').replace('<','<').replace('>','>') 60 | 61 | class I(dict): 62 | def __getitem__(self, item): 63 | return 'http://alarmeringen.nl/static/images/icon_%s.png' % dict.__getitem__(self, item) 64 | 65 | class Alert(object): 66 | """Representation of a single message""" 67 | icons = I({ 68 | 'brandweer': 'fire', 69 | 'politie': 'police', 70 | 'ambulance': 'ambu', 71 | 'lifeliner': 'heli', 72 | 'knrm': 'boat', 73 | }) 74 | 75 | def __init__(self, date, time, who, region, text, recipients = None): 76 | self.time = '%s %s' % (date, time) 77 | self.who = who 78 | self.region = region 79 | self.text = text 80 | self.recipients = recipients or [] 81 | 82 | if self.is_lifeliner(): 83 | self.who = 'LifeLiner' 84 | 85 | def is_lifeliner(self): 86 | return 'lifeliner' in (''.join(self.recipients)).lower() 87 | 88 | def show(self, timeout): 89 | """Display a notification for this alarm""" 90 | title = "%s %s" % (self.who, self.region) 91 | text = "%s %s" % (self.time, self.text) 92 | mod = self.__module__ 93 | if mod == '__main__': 94 | path = sys.argv[0] 95 | else: 96 | path = sys.modules[mod].__file__ 97 | path = os.path.abspath(os.path.dirname(path)) 98 | imgl = self.icons[self.who.lower()] 99 | imgc = os.path.join(os.path.expanduser('~'), '.cache', os.path.basename(imgl)) 100 | if not os.path.exists(imgc): 101 | with open(imgc, 'w') as fd: 102 | req = urllib.urlopen(img) 103 | if req.code == 404: 104 | raise IOError 105 | fd.write(req.read()) 106 | 107 | notification = pynotify.Notification(title, _escape(text), 'file://' + imgc) 108 | notification.set_urgency(pynotify.URGENCY_CRITICAL) 109 | caps = pynotify.get_server_caps() 110 | if self.recipients and 'actions' in caps: 111 | notification.add_action("details", "Details", self.details) 112 | # If the notification is deleted, the callback will not be called 113 | _alerts.append(notification) 114 | notification.set_timeout(timeout * 1000) 115 | notification.show() 116 | 117 | def prnt(self): 118 | """Output this alarm on stdout""" 119 | print "%s %s %s %s" % (time.ctime(), self.time, self.who, self.region) 120 | print "%s %s %s" % (time.ctime(), ' ' * len(self.time), self.text) 121 | for r in self.recipients: 122 | print "%s %s %s" % (time.ctime(), ' ' * len(self.time), r) 123 | 124 | def details(self, notification, action): 125 | message = '%s\n%s\n%s' % (self.text, self.time, '\n'.join(self.recipients)) 126 | dialog = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE) 127 | dialog.set_markup(message) 128 | dialog.set_title('%s %s' % (self.who, self.region)) 129 | dialog.connect("response", lambda *args: dialog.destroy()) 130 | dialog.show() 131 | dialog.set_keep_above(True) 132 | _alerts.remove(notification) 133 | 134 | _alerts = [] 135 | 136 | class P2000Interface(object): 137 | def __init__(self, opts): 138 | self.regions = opts.regions 139 | self.verbose = opts.verbose 140 | self.last_alert = None 141 | self.quiet = opts.quiet 142 | 143 | if not self.quiet: 144 | try: 145 | self.canberra = ctypes.CDLL('libcanberra.so.0') 146 | self.ctx = ctypes.c_void_p() 147 | self.canberra.ca_context_create(ctypes.byref(self.ctx)) 148 | except OSError: 149 | # Can't find libcanberra 150 | self.quiet = True 151 | pynotify.init("p2000 meldingen") 152 | 153 | def get_alerts(self): 154 | """Fetch all alerts for these regions""" 155 | url = 'http://www.p2000-online.net/p2000.php?%s&nofilterform=1' 156 | url = url % '&'.join(['%s=1' % x for x in self.regions]) 157 | if self.verbose: 158 | print time.ctime(), url 159 | try: 160 | data = urllib.urlopen(url).read() 161 | except IOError: 162 | if self.verbose: 163 | import traceback 164 | traceback.print_exc() 165 | return [] 166 | 167 | doc = soup(data) 168 | alerts = [] 169 | table = doc.body('table', {'style': 'align:center'})[0] 170 | for tr in table('tr'): 171 | if tr.td.get('class', None) == 'DT': 172 | alerts.append(Alert(*[x.text for x in tr('td')])) 173 | else: 174 | recipient = tr('td')[-1].text 175 | if recipient != ' ': 176 | alerts[-1].recipients.append(recipient) 177 | return alerts 178 | 179 | def alert_iteration(self, first_iteration = False): 180 | global _alerts 181 | _alerts = [] 182 | if self.verbose: 183 | print time.ctime(), "Fetching alerts..." 184 | alerts = self.get_alerts() 185 | if self.verbose: 186 | print time.ctime(), "Received %d alerts" % len(alerts) 187 | play_sound = True 188 | alerts = [x for x in alerts[:5] if x.time > self.last_alert] 189 | alerts.reverse() 190 | timeout = min(10,3*len(alerts)) 191 | if self.verbose: 192 | print "Timeout: %d" % timeout 193 | for alert in alerts: 194 | if play_sound: 195 | self.canberra.ca_context_play(self.ctx, 1, "event.id", "message-new-instant", None) 196 | play_sound = False 197 | if self.verbose: 198 | alert.prnt() 199 | alert.show(timeout) 200 | self.last_alert = alert.time 201 | return not first_iteration 202 | 203 | def main(): 204 | import optparse 205 | 206 | usage = """%prog [options] 207 | 208 | This tool autimatically downloads and displays p2000 alerts for the regions you 209 | specify (Default: Noord-Holland Noord and Flevoland). It can display images and 210 | play sounds too.""" 211 | 212 | p = optparse.OptionParser(usage = usage) 213 | default_regions = ("Flevoland","Noord-HollandNoord") 214 | p.add_option('-a', '--all', dest='all_regions', action='store_true', default=False, 215 | help="Show alerts for all regions") 216 | for r in regions: 217 | opt_r = '--%s' % r.lower().replace('-','').replace(' ','') 218 | p.add_option(opt_r, dest='regions', action='append_const', const=r.replace(' ',''), 219 | help="Show alerts for region %s" % r) 220 | p.add_option('-d', '--delay', dest='delay', type='int', default=60, metavar='SECS', 221 | help="Delay between updates (default: 60)") 222 | p.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, 223 | help="Verbose output") 224 | p.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, 225 | help="Don't play sounds when new alarms arrive") 226 | opts, args = p.parse_args() 227 | if not opts.regions: 228 | opts.regions = default_regions 229 | if opts.all_regions: 230 | opts.regions = [x.replace(' ','') for x in regions] 231 | 232 | ui = P2000Interface(opts) 233 | glib.timeout_add_seconds(0, ui.alert_iteration, True) 234 | glib.timeout_add_seconds(opts.delay, ui.alert_iteration) 235 | gtk.main() 236 | 237 | if __name__ == '__main__': 238 | main() 239 | -------------------------------------------------------------------------------- /pcat.c: -------------------------------------------------------------------------------- 1 | /* pcat - cat for pipes 2 | * 3 | * The 'cat' application is not suitable for pipes as the read side of a pipe 4 | * gets closed when there are no more writers. pcat is a very simple cat-like 5 | * application that keeps the pipe open for writing too, thus ensuring that the 6 | * reading end doesn't get closed. 7 | * 8 | * Compiling is simple: cc -o pcat pcat.c 9 | * 10 | * (c) Dennis Kaarsemaker, released into the public domain. 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | int main(int argc, char *argv[]) { 20 | struct stat sbuf; 21 | int fd1, fd2; 22 | char buf; 23 | 24 | if(argc != 2) { 25 | fprintf(stderr, "Usage: %s name-of-pipe\n", argv[0]); 26 | return 1; 27 | } 28 | if(stat(argv[1], &sbuf)){ 29 | perror("stat failed"); 30 | return 1; 31 | } 32 | if(!S_ISFIFO(sbuf.st_mode)) { 33 | fprintf(stderr, "%s is not a fifo", argv[1]); 34 | return 1; 35 | } 36 | fd1 = open(argv[1], O_WRONLY); 37 | fd2 = open(argv[1], O_RDONLY); 38 | while(read(fd2, &buf, 1)) { 39 | putchar(buf); 40 | } 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /progressbar.py: -------------------------------------------------------------------------------- 1 | # Abuse the power of unicode to give smooth progressbars in the terminal 2 | # 3 | # Unicode defines the codepoints 0x2588 up to 0x258F as SOLID BLOCK, LEFT SEVEN 4 | # EIGHTS BLOCK etc.. This can be used for almost pixel-by-pixel painting of a 5 | # progressbar. The Progressbar class does that for you and can also guess what 6 | # width your progressbar should be. 7 | # 8 | # Usage: 9 | # Progressbar(target=100, start=0, reserve=20, columns=None) 10 | # - target is the final numerical goal of the progressbar 11 | # - start is the progress set at the beginning 12 | # - reserve is the amount of space to reserve for text to the right of the 13 | # progressbar for extra information 14 | # - columns is the total width of progressbar and text. This can usually be 15 | # autodetected, so you can omit this parameter 16 | # 17 | # Progressbars have one function: 18 | # 19 | # Progressbar.set_progress(progress, text="%(progress)s/%(target)s", args=None): 20 | # 21 | # - progress is the numerical progress towards the target. 22 | # - text is the status text that goes to the right of the bar 23 | # - args is a dict of variables that the text needs. The variables progress and 24 | # target are filled in automatically 25 | # 26 | # When the progress is equal to, or higher than the target. The progressbar is 27 | # finalized. A final newline is printed and further calls to set_progress are 28 | # ignored. Until the progressbar is complete, progress can go backwards if you 29 | # want to. See the bottom of this file for a demonstration. 30 | # 31 | # The following attributes can be read, but should not be changed directly: 32 | # 33 | # - Progressbar.columns -- What the progressbar thinks the wodth of your terminal is 34 | # - Progressbar.target -- The target of the progressbar 35 | # - Progressbar.progress -- The progress towards that target 36 | # - Progressbar.complete -- Whether the target has been reached 37 | 38 | from __future__ import division 39 | import fcntl 40 | import termios 41 | import os 42 | import struct 43 | import sys 44 | 45 | class Progressbar(object): 46 | chars = [x.encode('utf-8') for x in u' \u258f\u258e\u258d\u258c\u258b\u258a\u2589\u2588'] 47 | def __init__(self, target=100, start=0, reserve=20, columns=None): 48 | self.reserve = reserve 49 | if columns: 50 | self.columns = columns 51 | elif 'COLUMNS' in os.environ: 52 | self.columns = os.environ['COLUMNS'] 53 | else: 54 | try: 55 | _, self.columns = struct.unpack('hh', fcntl.ioctl(sys.stdin, termios.TIOCGWINSZ, '1234')) 56 | except: 57 | self.columns = 80 58 | 59 | self.columns -= reserve 60 | if self.columns < 10: 61 | raise ValueError("Screen too small for a progressbar") 62 | 63 | self.target = target 64 | self.complete = False 65 | self.psl = 0 66 | self.set_progress(start) 67 | 68 | def set_progress(self, progress, text="%(progress)s/%(target)s", args=None): 69 | if self.complete: 70 | return 71 | self.progress = progress 72 | if progress >= self.target: 73 | self.progress = self.target 74 | self.complete = True 75 | 76 | full = self.progress / self.target * self.columns 77 | args_ = {'progress': self.progress, 'target': self.target} 78 | if args: 79 | args_.update(args) 80 | bar = "\r%s%s%s%s%s" % ( self.chars[-1] * int(full), 81 | ('' if self.complete else self.chars[int((full-int(full)) * 8)]), 82 | self.chars[0] * (self.columns - int(full) -1), 83 | self.chars[1], 84 | text % args_ 85 | ) 86 | psl = len(bar) 87 | bar += ' ' * max(0,self.psl-len(bar)) 88 | bar += '\b' * max(0,self.psl-len(bar)) 89 | self.psl = psl 90 | sys.stdout.write(bar) 91 | if self.complete: 92 | sys.stdout.write('\n') 93 | sys.stdout.flush() 94 | 95 | if __name__ == '__main__': 96 | # Demonstration 97 | import time 98 | step = 0.1 99 | target = 111 100 | 101 | pb = Progressbar(target=target, reserve=30) 102 | while not pb.complete: 103 | pb.set_progress(pb.progress + step, 104 | "%(progress)s/%(target)s (Demo 1)") 105 | time.sleep(0.02) 106 | 107 | pb = Progressbar(target=target, reserve=30) 108 | while pb.progress < target * 0.8: 109 | pb.set_progress(pb.progress + step, 110 | "%(progress)s/%(target)s (Demo 2)") 111 | time.sleep(0.02) 112 | while pb.progress > target * 0.3: 113 | pb.set_progress(pb.progress - step, 114 | "%(progress)s/%(target)s (Demo 2)") 115 | time.sleep(0.02) 116 | while not pb.complete: 117 | pb.set_progress(pb.progress + step, 118 | "%(progress)s/%(target)s (Demo 2)") 119 | time.sleep(0.02) 120 | -------------------------------------------------------------------------------- /rainbow: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # echo -e 'Taste\nthe\nrainbow!' | figlet | cowsay -n | ./rainbow 4 | 5 | import math, random, sys 6 | 7 | def rainbow(freq, i, c): 8 | part = lambda a, b: int(6 * (math.sin(freq * i + a * math.pi/3) * 127 + 128) / 256) * b 9 | return '\033[38;5;%dm%s' % (sum([16, part(0, 36), part(2, 6), part(4, 1)]), c) 10 | 11 | start = random.randint(0,256); i=1 12 | while i: 13 | line = sys.stdin.readline().rstrip() 14 | i = line and i + 1 or 0 15 | print "".join([rainbow(0.3, (start+i+j)/6.0, c) for j,c in enumerate(line)]) + '\033[0m' 16 | -------------------------------------------------------------------------------- /reset_passwords: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Find linux installs and reset passwords 4 | # (c) 2008 Dennis Kaarsemaker 5 | # 6 | # Instructions: 7 | # To recover/reset passwords of an installed linux system, it's easiest to boot 8 | # from a live CD and run this script. To do so, follow this list: 9 | # - Boot from live cd 10 | # - Download this file to the desktop 11 | # - Run this command: bash ~/Desktop/reset_passwords 12 | # - Follow the instructions presented by the script 13 | # 14 | # For each user account it finds, it will prompt you to ask whether you want to 15 | # change the password. 16 | 17 | # Bugs: 18 | # - Doesn't work if /usr is on a separate filesystem 19 | # - Doesn't work for non-lsb distros 20 | 21 | # Warning, disclaimer 22 | if [ "$1" != "--nowarn" ]; then 23 | echo -e "\033[1;31mThis tool will let you reset the passwords of any accounts\033[0m" 24 | echo -e "\033[1;31mit finds on installed linux systems. Use with care!\033[0m" 25 | echo -n "Press enter to continue, or CTRL-C to abort " 26 | read discard 27 | fi 28 | 29 | if [ $UID -ne 0 ]; then 30 | echo -e "\033[0;37mYou are $USER, not root. Attempting to run sudo\033[0m" 31 | exec sudo bash "$0" --nowarn 32 | fi 33 | 34 | # Warn if things are mounted on /mnt 35 | mount | grep -q '/mnt' && { 36 | echo "A partition has been mounted under /mnt, aborting" 37 | echo "To unmount these partitions, run the following commands:" 38 | awk '/mnt/{ print "sudo umount " $2}' < /proc/mounts 39 | exit; 40 | } 41 | 42 | # Find partitions 43 | partitions=$(fdisk -l | sed -n -e 's@^\(/dev/[[:alnum:]]\+\).*@\1@p') 44 | 45 | # Iterate over partitions 46 | for part in $partitions; do 47 | echo -e "\033[0;37mTrying partition $part\033[0m" 48 | 49 | # Mount 50 | mount $part /mnt >/dev/null 2>&1 || { 51 | echo -e "\033[0;37mCouldn't mount $part\033[0m" 52 | continue 53 | } 54 | 55 | # Detect linux 56 | if [ ! -e /mnt/etc/shadow ] || [ ! -e /mnt/usr/bin/lsb_release ]; then 57 | echo "$part doesn't seem to be a linux rootpartition" 58 | umount /mnt 59 | continue 60 | fi 61 | echo -e "\033[4;30mFound the following distribution on $part\033[0m:" 62 | chroot /mnt /usr/bin/lsb_release -i -r -c -d 63 | echo -ne "\033[4;30mReset passwords in this system\033[0m? [y/n] " 64 | read cont 65 | case $cont in 66 | [yY]) 67 | ;; 68 | *) 69 | echo -e "\033[0;37mSkipping $part\033[0m" 70 | umount /mnt 71 | continue 72 | esac 73 | 74 | # Detect users with usable passwords 75 | users=$(chroot /mnt /usr/bin/passwd -S -a | sed -n -e 's/\([^[:space:]]\+\) P.*/\1/p') 76 | 77 | # Iterate over them 78 | for user in $users; do 79 | echo -ne "Reset the password for \033[4;30m$user\033[0m? [y/n] " 80 | read input 81 | case $input in 82 | [yY]) 83 | chroot /mnt /usr/bin/passwd $user 84 | ;; 85 | esac 86 | done 87 | 88 | umount /mnt 89 | done 90 | -------------------------------------------------------------------------------- /run-single-cron: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Runs a single cronjob the way cron runs it 4 | 5 | import docopt 6 | import getpass 7 | from whelk import shell 8 | import sys, os, re 9 | 10 | def main(): 11 | usage = """Run a single cronjob the way crond would run it 12 | 13 | Usage: 14 | run-single-cron [-n] [-u=] [-c=] 15 | 16 | Options: 17 | -h --help Show this help message and exit 18 | -n Noop. Just show what the command is. 19 | -u= Run a cronjob from this users crontab instead of the 20 | current user 21 | -c=']) 30 | 31 | if crontab: 32 | user = 'root' 33 | 34 | if user != curuser and os.geteuid() != 0: 35 | raise RuntimeError("Only root can run other peoples cronjobs") 36 | 37 | env = load_environment(user) 38 | job,input = load_cronjob(env, user, crontab, lineno) 39 | 40 | if user != curuser: 41 | uid, gid = shell.getent('passwd', user).stdout.split(':')[2:4] 42 | os.setgid(int(gid)) 43 | os.setuid(int(uid)) 44 | 45 | def logger(shell, sp, fd, data): 46 | if data is None: 47 | return 48 | fd = sp.stdout.fileno() == fd and 1 or 2 49 | os.write(fd, data) 50 | 51 | print wrap("Environment:", attr.faint) 52 | kl = max([len(x) for x in env]) + 2 53 | for key in sorted(env): 54 | print wrap(" %-*s %s" % (kl, key, env[key]), attr.faint) 55 | if input: 56 | print wrap("Input:\n%s" % input, attr.faint) 57 | if opts['-n']: 58 | print "Not running cronjob: %s" % job 59 | sys.exit(0) 60 | print wrap("Running cronjob: %s" % job, attr.faint) 61 | ret = shell[env['SHELL']]('-c', job, output_callback=logger, env=env, input=input) 62 | print wrap("Exit code: %d" % ret.returncode, attr.faint) 63 | sys.exit(ret.returncode) 64 | 65 | def load_environment(user): 66 | """Load an environment as specified by crontab(5)""" 67 | env = {} 68 | if os.path.exists('/etc/debian_version'): 69 | # Debian special: cron reads environment via pam 70 | import PAM 71 | auth = PAM.pam() 72 | auth.start('cron') 73 | auth.set_item(PAM.PAM_USER, user) 74 | pid = os.getpid() 75 | if pid != os.getpid(): 76 | sys.exit(0) 77 | for item in auth.getenvlist(): 78 | key, val = item.split('=', 1) 79 | env[key] = val 80 | env['PATH'] = '/usr/bin:/bin' 81 | env['HOME'] = shell.getent('passwd', user).stdout.strip().split(':')[-2] 82 | env['USER'] = env['LOGNAME'] = user 83 | env['SHELL'] = '/bin/sh' 84 | return env 85 | 86 | def load_cronjob(env, user, crontab, lineno): 87 | if crontab: 88 | with open(crontab) as fd: 89 | crontab = fd.readlines() 90 | else: 91 | crontab = shell.crontab('-u', user, '-l', stderr=None).stdout.strip().splitlines() 92 | 93 | if len(crontab) < lineno: 94 | raise ValueError("Crontab is only %d lines long" % len(crontab)) 95 | 96 | crontab = crontab[:lineno] 97 | 98 | for line in crontab: 99 | if re.match('^\s*[a-zA-Z][_a-zA-Z0-9]*\s*=', line): 100 | key, val = line.split('=', 1) 101 | if key not in ('USER', 'LOGNAME'): 102 | env[key.strip()] = val.strip() 103 | 104 | job = crontab[-1].strip() 105 | if re.match('^@(reboot|(hour|dai|week|month|annual|year)ly|midnight)\s', job): 106 | return job.split(None, 1)[1].strip() 107 | items = job.split(None, 5) 108 | if len(items) != 6 or job.startswith('#'): 109 | raise ValueError("Not a cronjob: %s" % job) 110 | for item in items[:3]: 111 | if not re.match(r'((?:\d+(?:-\d+)?|\*)(/\d+)?)(,\1)*', item): 112 | raise ValueError("Not a cronjob: %s" % job) 113 | job = items[-1] 114 | input = '' 115 | if re.search(r'(? 1: 60 | print(ansi.cursor.up(args.size-1), end="", flush=True) 61 | print(ansi.cursor.down(args.size-1)) 62 | -------------------------------------------------------------------------------- /slack-send: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # How to get a slack API token 4 | # 5 | # From https://github.com/yuya373/emacs-slack/tree/1f6a40faec0d8d9c9de51c444508d05a3e995ccd#how-to-get-token 6 | # - Using chrome or firefox, open and sign into the slack customization page, e.g. https://my.slack.com/customize 7 | # - Right click anywhere on the page and choose "inspect" from the context menu. This will open the developer tools. 8 | # - Find the console (it's one of the tabs in the developer tools window) 9 | # - At the prompt ("> ") type the following: window.prompt("your api token is: ", TS.boot_data.api_token) 10 | # - Copy the displayed token elsewhere, and close the window. 11 | 12 | import argparse 13 | import os 14 | import sys 15 | 16 | from slack_sdk import WebClient 17 | from slack_sdk.errors import SlackApiError 18 | 19 | client = WebClient(token=os.getenv("SLACK_TOKEN")) 20 | 21 | parser = argparse.ArgumentParser(description="Send messages to slack channels") 22 | parser.add_argument("channel", metavar="CHANNEL", nargs=1) 23 | parser.add_argument("message", metavar="MESSAGE", nargs="*") 24 | parser.add_argument("--split-lines", default=False, action='store_true') 25 | parser.add_argument("--mono", default=False, action='store_true') 26 | args = parser.parse_args() 27 | 28 | def send(msg): 29 | if args.mono: 30 | msg = "```%s```" % msg 31 | try: 32 | response = client.chat_postMessage(channel=args.channel[0], text=msg) 33 | except SlackApiError as e: 34 | print("Got an error: " + e.response['error'], file=sys.stderr) 35 | 36 | if args.message: 37 | send(" ".join(args.message)) 38 | else: 39 | if not args.split_lines: 40 | send(sys.stdin.read()) 41 | else: 42 | for line in sys.stdin: 43 | send(line) 44 | -------------------------------------------------------------------------------- /slack-tail: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # How to get a slack API token 4 | # 5 | # From https://github.com/yuya373/emacs-slack/tree/1f6a40faec0d8d9c9de51c444508d05a3e995ccd#how-to-get-token 6 | # - Using chrome or firefox, open and sign into the slack customization page, e.g. https://my.slack.com/customize 7 | # - Right click anywhere on the page and choose "inspect" from the context menu. This will open the developer tools. 8 | # - Find the console (it's one of the tabs in the developer tools window) 9 | # - At the prompt ("> ") type the following: window.prompt("your api token is: ", TS.boot_data.api_token) 10 | # - Copy the displayed token elsewhere, and close the window. 11 | 12 | from ansi.colour import * 13 | import ansi.cursor 14 | import ansi.osc 15 | import ansi.iterm 16 | import argparse 17 | import atexit 18 | import emoji 19 | import fcntl 20 | import html 21 | import os 22 | from pprint import pprint 23 | import re 24 | import requests 25 | import struct 26 | import sys 27 | import termios 28 | import textwrap 29 | import traceback 30 | import unicodedata 31 | 32 | is_iterm = os.environ['TERM_PROGRAM'] == "iTerm.app" 33 | 34 | width=80 35 | try: 36 | height, width = struct.unpack('hh', fcntl.ioctl(sys.stdin, termios.TIOCGWINSZ, '1234')) 37 | except: 38 | pass 39 | 40 | from slack_sdk.rtm import RTMClient 41 | 42 | parser = argparse.ArgumentParser(description="Send messages to slack channels") 43 | parser.add_argument('--until', metavar='REGEX') 44 | parser.add_argument("channel", metavar="CHANNEL", nargs='?') 45 | parser.add_argument("users", metavar="USER", nargs='*') 46 | parser.add_argument("--debug", default=False, action='store_true') 47 | parser.add_argument("--send", metavar="MESSAGE", default="") 48 | args = parser.parse_args() 49 | 50 | if args.channel and args.channel.startswith('#'): 51 | args.channel = args.channel[1:] 52 | 53 | @RTMClient.run_on(event="hello") 54 | def send_message(rtm_client, web_client, **payload): 55 | if args.send and args.channel: 56 | msg = args.send 57 | try: 58 | response = web_client.chat_postMessage(channel=args.channel, text=msg) 59 | except SlackApiError as e: 60 | print("Got an error: " + e.response['error'], file=sys.stderr) 61 | 62 | @RTMClient.run_on(event="message") 63 | def show_message(rtm_client, web_client, **payload): 64 | try: 65 | show_message_(rtm_client, web_client, **payload) 66 | except Exception as e: 67 | pprint(payload, width=width) 68 | traceback.print_exc() 69 | 70 | def show_message_(rtm_client, web_client, **payload): 71 | data = payload['data'] 72 | if data.get('subtype') in ('message_changed', 'message_replied', 'message_deleted'): 73 | return 74 | team = data.get('team', data.get('user_team', data.get('source_team', ""))) 75 | user = bot = username = display_name = None 76 | channel = get_channel(web_client, data['channel']) 77 | link = 'slack://channel?team=%s&id=%s' % (team, data['channel']) 78 | avatars = {} 79 | if 'bot_id' in data: 80 | bot = get_bot(web_client, data['bot_id']) 81 | if bot: 82 | avatars = bot['icons'] 83 | if 'bot_profile' in data and 'name' in data['bot_profile']: 84 | username = data['bot_profile']['name'] 85 | avatars = data['bot_profile']['icons'] 86 | if 'user' in data: 87 | user = get_user(web_client, data['user']) 88 | if 'profile' in user and 'display_name' in user['profile']: 89 | display_name = user['profile']['display_name'] 90 | if 'real_name' in user: 91 | username = user['real_name'] 92 | elif 'profile' in user and 'real_name' in user['profile']: 93 | username = user['profile']['real_name'] 94 | avatars = user['profile'] 95 | if 'username' in data: 96 | username = data['username'] 97 | if 'icons' in data: 98 | avatars = data['icons'] 99 | if channel['is_im'] or channel['is_mpim']: 100 | channel = "" 101 | else: 102 | channel = channel['name'] 103 | 104 | if args.channel and args.channel != channel: 105 | return 106 | 107 | if username is None: 108 | print("Username not known!") 109 | pprint(payload, width=width) 110 | avatar = None 111 | if avatars: 112 | smallest = 100000 113 | for k in sorted(avatars.keys()): 114 | if k == 'emoji': 115 | avatar = find_emoji(avatars[k][1:-1], custom_emoji=web_client.emoji) 116 | if k.startswith('image_'): 117 | size = k[6:] 118 | if size.isdigit() and int(size) < smallest: 119 | smallest = int(size) 120 | avatar = avatars[k] 121 | 122 | if args.users and (username not in args.users) and (display_name not in args.users): 123 | return 124 | 125 | if data.get('text', ''): 126 | print_(web_client, channel, username, data['text'], args.until, link, avatar) 127 | for a in data.get('attachments', []) + data.get('blocks', []): 128 | if a.get('title', ''): 129 | print_(web_client, channel, username, a['title'] + ' ' + a.get('title_link', ''), args.until, link, avatar) 130 | if a.get('text', '') and isinstance(a['text'], str): 131 | print_(web_client, channel, username, a['text'], args.until, link, avatar) 132 | if a.get('image_url', ''): 133 | msg = a['fallback'][1:-1] 134 | imgt = None 135 | if a['image_bytes'] < 1024 * 1024: 136 | img = requests.get(a['image_url']) 137 | if img.ok: 138 | msg = '' 139 | imgt = ansi.iterm.image(img.content) 140 | print_(web_client, channel, username, msg, args.until, link, avatar) 141 | if imgt: 142 | print(imgt) 143 | for a in data.get('files', []): 144 | msg = "%s: %s" % (a['title'], a['url_private']) 145 | imgt = None 146 | if a['mimetype'].startswith('image/') and a['size'] < 1024 * 1024: 147 | img = requests.get(a['url_private'], headers={'Authorization': 'Bearer %s' % web_client.token}) 148 | if img.ok: 149 | msg = '' 150 | imgt = ansi.iterm.image(img.content) 151 | print_(web_client, channel, username, msg, args.until, link, avatar) 152 | if imgt: 153 | print(imgt) 154 | if args.debug: 155 | pprint(payload, width=width) 156 | 157 | def print_(w, c, u, m, r, l, a): 158 | ou = u 159 | if len(c) > 20: 160 | c = c[:17] + '...' 161 | if len(u) > 20: 162 | u = u[:17] + '...' 163 | 164 | c = "%-20s" % c 165 | u = "%-20s" % u 166 | 167 | if is_iterm: 168 | c = ansi.osc.anchor(l) + c + ansi.osc.anchor("") 169 | 170 | c = fx.faint(c) 171 | u = fx.bright(u) 172 | 173 | indent = 42 174 | if is_iterm: 175 | u = get_avatar(ou, a) + ' ' + u 176 | indent += 3 177 | 178 | links = [] 179 | m = re.sub('<@([^>]+)>', lambda m_: replace_user(w, m_), m) 180 | m = re.sub('<#([^>]+?)(?:\|([^>]+))?>', lambda m_: replace_channel(w, m_), m) 181 | m = re.sub('<(https?://[^| >]+)\|([^>]+)>', lambda m_: replace_url(m_, links), m) 182 | m = re.sub('<(https?://[^| >]+)>', r'\1', m) 183 | m = re.sub(':([^ :]+):', lambda m_: find_emoji(m_.group(1)), m) 184 | m = html.unescape(m) 185 | frags = [] 186 | for line in m.strip().splitlines(): 187 | line = line.strip() 188 | if not line: 189 | continue 190 | frags += textwrap.wrap(line, width-indent, break_on_hyphens=False) 191 | if is_iterm: 192 | frags = [re.sub(':([^ :]+):', lambda m_: find_emoji(m_.group(1), custom_emoji=w.emoji), m) for m in frags] 193 | frags += [fx.faint("[%d] %s") % (pos+1, link) for (pos, link) in enumerate(links)] 194 | if not frags: 195 | frags = [''] 196 | 197 | print("\n%s %s %s" % (c, u, frags[0]),flush=True, end="") 198 | for frag in frags[1:]: 199 | print("\n" + " " * indent + frag, flush=True, end="") 200 | 201 | if r and re.search(r, m): 202 | sys.exit(0) 203 | 204 | def replace_user(w, m): 205 | try: 206 | return '@' + get_user(w, m.group(1))['name'] 207 | except: 208 | return m.group(0) 209 | 210 | def replace_channel(w, m): 211 | if m.group(2): 212 | return '#' + m.group(2) 213 | try: 214 | return '@' + get_channel(w, m.group(1))['name'] 215 | except: 216 | return m.group(0) 217 | 218 | def replace_url(m, links): 219 | link = html.unescape(m.group(1)) 220 | try: 221 | pos = links.index(link)+1 222 | except ValueError: 223 | pos = len(links)+1 224 | links.append(link) 225 | return "%s[%d]" % (m.group(2), pos) 226 | 227 | modifiers = { 228 | 'skin-tone-1': '\U0001F3FB', 229 | 'skin-tone-2': '\U0001F3FC', 230 | 'skin-tone-3': '\U0001F3FD', 231 | 'skin-tone-4': '\U0001F3FE', 232 | 'skin-tone-5': '\U0001F3FF', 233 | } 234 | 235 | def find_emoji(name, custom_emoji={}, fallback=None): 236 | if not fallback: 237 | fallback = ':' + name + ':' 238 | if name in modifiers: 239 | return modifiers[name] 240 | if name in custom_emoji: 241 | val = custom_emoji[name] 242 | if val.startswith('alias:'): 243 | return find_emoji(val[6:], custom_emoji, fallback) 244 | if val.startswith('https://'): 245 | img = requests.get(val) 246 | if not img.ok: 247 | return fallback 248 | val = custom_emoji[name] = ansi.iterm.image(img.content, name=fallback, height=1) 249 | return val 250 | text = ':' + name + ':' 251 | val = emoji.emojize(text, language='alias') 252 | if val != text: 253 | return val 254 | try: 255 | return unicodedata.lookup(name) 256 | except KeyError: 257 | try: 258 | return unicodedata.lookup(name.replace('_', ' ')) 259 | except KeyError: 260 | return fallback 261 | 262 | channels = {} 263 | def get_channel(web_client, channel): 264 | if channel not in channels: 265 | channels[channel] = web_client.conversations_info(channel=channel).data['channel'] 266 | return channels[channel] 267 | 268 | users = {} 269 | def get_user(web_client, user): 270 | if user not in users: 271 | users[user] = web_client.users_info(user=user).data['user'] 272 | return users[user] 273 | 274 | avatars = {} 275 | def get_avatar(name, url): 276 | if url not in avatars: 277 | if url.startswith('https://'): 278 | img = requests.get(url) 279 | if not img.ok: 280 | return None 281 | avatars[url] = ansi.iterm.image(img.content, name=name, height=1) 282 | else: 283 | avatars[url] = url 284 | return avatars[url] 285 | 286 | bots = {} 287 | def get_bot(web_client, bot): 288 | if bot not in bots: 289 | try: 290 | bots[bot] = web_client.bots_info(bot=bot).data['bot'] 291 | except: 292 | bots[bot] = None 293 | return bots[bot] 294 | 295 | if is_iterm: 296 | print(ansi.iterm.badge("Slack"), end='', flush=True) 297 | atexit.register(lambda: print(ansi.iterm.badge(""), end='', flush=True)) 298 | atexit.register(lambda: print(ansi.cursor.show())) 299 | 300 | print("Waiting for slack messages..." + ansi.cursor.hide(), end="", flush=True) 301 | slack_token = os.environ["SLACK_TOKEN"] 302 | rtm_client = RTMClient(token=slack_token) 303 | rtm_client._web_client.emoji = rtm_client._web_client.emoji_list()['emoji'] 304 | rtm_client.start() 305 | -------------------------------------------------------------------------------- /stealenv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Steal env: steal a process' environment 4 | # (c)2011 Dennis Kaarsemaker, dedicated to the public domain 5 | 6 | import os 7 | import sys 8 | 9 | def from_pid(pid, update=False): 10 | env = open(os.path.join('/proc/%d/environ' % pid)).read() 11 | env = dict([x.split('=', 1) for x in env.split('\x00') if x]) 12 | if update: 13 | os.environ.update(env) 14 | return env 15 | 16 | def from_name(name, update=False): 17 | for d in os.listdir('/proc'): 18 | if not d.isdigit(): 19 | continue 20 | try: 21 | exe = os.readlink(os.path.join('/proc', d, 'exe')) 22 | except OSError: 23 | continue 24 | if exe == name: 25 | return from_pid(int(d), update) 26 | else: 27 | print "Process %s not running" % find_exe 28 | 29 | if __name__ == '__main__': 30 | import optparse 31 | usage = """%prog [options] pid 32 | 33 | Output the environment of the process with the specified pid in a variety of 34 | formats, usable by shells and other languages.""" 35 | p = optparse.OptionParser(usage=usage) 36 | p.add_option('-s','--sh', 37 | action='store_true', dest='sh', default=False, 38 | help='Output sh style commands') 39 | p.add_option('-c','--csh', 40 | action='store_true', dest='csh', default=False, 41 | help='Output csh style commands') 42 | p.add_option('-j','--json', 43 | action='store_true', dest='json', default=False, 44 | help='Output json') 45 | p.add_option('-e','--export', 46 | action='store_true', dest='export', default=False, 47 | help='sh/csh command will export variables') 48 | p.add_option('-0', '--null', 49 | action='store_true', dest='zero', default=False, 50 | help='Output null-terminated strings and a trailing null') 51 | 52 | opts, args = p.parse_args() 53 | if len(args) < 1: 54 | p.error('Must specify a pid or application to steal from') 55 | if sum([opts.sh, opts.csh, opts.json, opts.zero, len(args) > 1]) > 1: 56 | p.error('Must specify exactly one output format or specify a command line') 57 | elif sum([opts.sh, opts.csh, opts.json, opts.zero]) == 0: 58 | if os.environ['SHELL'].endswith('csh'): 59 | opts.csh = True 60 | else: 61 | opts.sh = True 62 | 63 | if args[0].isdigit(): 64 | env = from_pid(int(args[0], False)) 65 | else: 66 | env = from_name(args[0], False) 67 | 68 | if len(args) > 1: 69 | os.environ.update(env) 70 | os.execve(args[1], args[1:], os.environ) 71 | 72 | if opts.json: 73 | import simplejson 74 | print simplejson.dumps(env) 75 | exit(0) 76 | 77 | escape = True 78 | if opts.sh: 79 | tmpl = '%s="%s"' 80 | if opts.export: 81 | tmpl = 'export ' + tmpl 82 | elif opts.csh: 83 | tmpl = 'set %s="%s"' 84 | if opts.export: 85 | tmpl = 'setenv %s "%s"' 86 | elif opts.zero: 87 | tmpl = '%s\x00%s\x00' 88 | escape = False 89 | 90 | for key, val in env.items(): 91 | if escape: 92 | val = val.replace('\\','\\\\') 93 | val = val.replace('$','\\$') 94 | val = val.replace('"','\\"') 95 | val = val.replace('`','\\`') 96 | 97 | print tmpl % (key, val) 98 | 99 | if opts.zero: 100 | print '\x00' 101 | -------------------------------------------------------------------------------- /subsetsum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | def subsetsum(data, wanted, sort=True, approximate=False): 4 | sums = [] 5 | for item in sorted(data) if sort else data: 6 | if item > wanted: 7 | continue 8 | for xdata, xsum in sums[:len(sums)]: 9 | if xsum + item > wanted: 10 | continue 11 | if xsum + item == wanted: 12 | return xdata + [item] 13 | sums.append((xdata + [item], xsum + item)) 14 | sums.append(([item], item)) 15 | if not approximate: 16 | raise ValueError("No matching subset found") 17 | cdata, csum = sums[0] 18 | for xdata, xsum in sums[1:]: 19 | if abs(wanted - xsum) < abs(wanted - csum): 20 | cdata, csum = xdata, xsum 21 | return cdata 22 | 23 | if __name__ == '__main__': 24 | import sys 25 | import argparse 26 | p = argparse.ArgumentParser() 27 | p.add_argument('input', metavar='FILE', type=open) 28 | p.add_argument('sum', type=int) 29 | p.add_argument('--sort', action='store_true') 30 | p.add_argument('--approximate', action='store_true') 31 | args = p.parse_args() 32 | data = [int(val) for val in args.input.read().split() if val.strip()] 33 | data = subsetsum(data, args.sum, sort=args.sort, approximate=args.approximate) 34 | if sum(data) != args.sum: 35 | print("No exact match found, closest match: %d" % sum(data)) 36 | print("\n".join([str(x) for x in data])) 37 | 38 | -------------------------------------------------------------------------------- /suid_script_wrapper.c: -------------------------------------------------------------------------------- 1 | /* suid_script_wrapper.c 2 | * 3 | * Wrapper around scripts for chmod +s purposes. Scripts can only be setuid if 4 | * their interpreter is, which most interpreters are not. You can work around 5 | * this limitation with this program. You must hardcode the full path to both 6 | * interpreter and script to prevent security breaches. 7 | * 8 | * An example script is included below. 9 | * 10 | * The first four #define's below configure the program, the rest of the code 11 | * should not be changed. 12 | * 13 | * You should only use this program if sudo is not available for you. 14 | 15 | % cat /tmp/suidtest.py 16 | import os, sys 17 | print "UID: %d GID: %d" % (os.getuid(), os.getgid()) 18 | print "EUID: %d EGID: %d" % (os.geteuid(), os.getegid()) 19 | print "ARGV: %s" % str(sys.argv) 20 | print "ENVP: %s" % str(os.environ) 21 | 22 | * (c)2008 Dennis Kaarsemaker - Dedicated to the public domain 23 | */ 24 | 25 | /* Full path to interpreter */ 26 | #define INTERPRETER_PATH "/usr/bin/python" 27 | /* Full path to script */ 28 | #define SCRIPT_PATH "/tmp/suidtest.py" 29 | /* Pass argv on or not */ 30 | #define TAKE_ARGV 1 31 | /* Paranoid mode: Every involved file should be owned by root */ 32 | #define PARANOID 1 33 | 34 | #define _GNU_SOURCE 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | extern char **environ; 44 | 45 | /* Clear the environment. Only preserve USER/USERNAME/LOGNAME/HOME/LANG/LC_* */ 46 | void clear_environ() { 47 | char **env = environ; 48 | size_t len; 49 | char *key; 50 | while(*env) env++; 51 | do { 52 | env--; 53 | len = strchr(*env, '=') - *env; 54 | key = strndup(*env, len); 55 | /* You can extend this list if you want, but be careful */ 56 | if(strcmp(key, "USER") && 57 | strcmp(key, "USERNAME") && 58 | strcmp(key, "LOGNAME") && 59 | strcmp(key, "HOME") && 60 | strcmp(key, "LANG") && 61 | strncmp(key, "LC_", 3) 62 | ) 63 | unsetenv(key); 64 | free(key); 65 | } while (env != environ); 66 | /* Inject a few safe things */ 67 | setenv("PATH","/bin:/sbin:/usr/bin:/usr/sbin:/usr/games:/usr/X11/bin", 1); 68 | setenv("IFS"," \t\n", 1); 69 | } 70 | 71 | int main(int argc, char **argv) { 72 | int ret = 0; 73 | struct stat buf1, buf2; 74 | char *interpreter_path = INTERPRETER_PATH; 75 | char *script_path = SCRIPT_PATH; 76 | 77 | clear_environ(); 78 | 79 | /* Config errors */ 80 | if(strchr(interpreter_path, '/') != interpreter_path) { 81 | fprintf(stderr, "Path to interpreter not absolute\n"); 82 | ret |= 1; 83 | } 84 | if(strchr(script_path, '/') != script_path) { 85 | fprintf(stderr, "Path to script not absolute\n"); 86 | ret |= 1; 87 | } 88 | 89 | /* Access errors */ 90 | if(stat(argv[0], &buf1) == -1) { 91 | perror("stat on argv[0] failed"); 92 | ret |= 2; 93 | } 94 | if(!(buf1.st_mode & (S_ISGID | S_ISUID))) { 95 | fprintf(stderr, "Wrapper isn't suid/sgid\n"); 96 | ret |= 2; 97 | } 98 | if(access(interpreter_path, X_OK)) { 99 | perror("Cannot execute interpreter " INTERPRETER_PATH); 100 | ret |= 2; 101 | } 102 | if(access(script_path, R_OK)) { 103 | perror("Cannot read script " SCRIPT_PATH); 104 | ret |= 2; 105 | } 106 | 107 | /* Permission/owner errors */ 108 | if(!stat(argv[0], &buf1) && !stat(script_path, &buf2)) { 109 | #if PARANOID 110 | if((buf1.st_uid != 0) || (buf1.st_gid != 0)) { 111 | fprintf(stderr, "%s is not owned by root\n", argv[0]); 112 | ret |= 4; 113 | } 114 | if((buf2.st_uid != 0) || (buf2.st_gid != 0)) { 115 | fprintf(stderr, "%s is not owned by root\n", script_path); 116 | ret |= 4; 117 | } 118 | #else 119 | if((buf1.st_mode & S_ISGID) && (buf1.st_gid != buf2.st_gid)) { 120 | fprintf(stderr, "Mismatch betweeen the gid of %s and %s\n", argv[0], script_path); 121 | ret |= 4; 122 | } 123 | if((buf1.st_mode & S_ISUID) && (buf1.st_uid != buf2.st_uid)) { 124 | fprintf(stderr, "Mismatch betweeen the uid of %s and %s\n", argv[0], script_path); 125 | ret |= 4; 126 | } 127 | #endif 128 | if((buf1.st_mode & S_ISGID) && (buf1.st_mode & S_IWGRP)) { 129 | fprintf(stderr, "%s is world writable\n", argv[0]); 130 | ret |= 4; 131 | } 132 | if((buf1.st_mode & S_ISGID) && (buf2.st_mode & S_IWGRP)) { 133 | fprintf(stderr, "%s is world writable\n", script_path); 134 | ret |= 4; 135 | } 136 | if((buf1.st_mode & S_ISGID) && (buf1.st_mode & (S_IWGRP | S_IWOTH))) { 137 | fprintf(stderr, "%s is world/group writable\n", argv[0]); 138 | ret |= 4; 139 | } 140 | if((buf1.st_mode & S_ISGID) && (buf2.st_mode & (S_IWGRP | S_IWOTH))) { 141 | fprintf(stderr, "%s is world/group writable\n", script_path); 142 | ret |= 4; 143 | } 144 | } 145 | 146 | /* Interpreter check */ 147 | if(!stat(interpreter_path, &buf1)) { 148 | if(buf1.st_uid != 0) { 149 | fprintf(stderr, "Invalid interpreter, not owned by root\n"); 150 | ret |= 8; 151 | } 152 | if(buf1.st_mode & (S_IWGRP | S_IWOTH)) { 153 | fprintf(stderr, "%s is world/group writable\n", interpreter_path); 154 | ret |= 8; 155 | } 156 | } 157 | 158 | if(ret) 159 | return ret | 128; 160 | 161 | #if TAKE_ARGV 162 | char **new_argv = (char**)malloc((argc+2) * sizeof(char*)); 163 | int i = 1; 164 | new_argv[0] = interpreter_path; 165 | new_argv[1] = script_path; 166 | while(i < argc) { 167 | new_argv[i+1] = argv[i]; 168 | i++; 169 | } 170 | new_argv[i+1] = NULL; 171 | #else 172 | char* new_argv[3] = {interpreter_path, script_path, NULL}; 173 | #endif 174 | 175 | execve(interpreter_path, new_argv, environ); 176 | perror("Could not execute wrapped script " INTERPRETER_PATH " " SCRIPT_PATH); 177 | return 16 | 128; 178 | } 179 | -------------------------------------------------------------------------------- /wag.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Wag: watch a file and wag your tail when it changes. 3 | * Usage: wag /path/to/file /path/to/executable [args for executable] 4 | * 5 | * Every time the mtime of the watched file changes, the executable is executed 6 | * with the arguments as given to wag. 7 | * 8 | * Do not use this if you have inotify available on your system, as inotify is 9 | * the proper way of watching files. I wrote this for a linux 2.4 system where 10 | * inotify is not available. 11 | * 12 | * (c)2009 Dennis Kaarsemaker, dedicated to the public domain. 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | void usage(char *prog, int exitcode) { 23 | printf("Usage: %s [args]\n", prog); 24 | puts("The app will be executed whenever the file changes"); 25 | exit(exitcode); 26 | } 27 | 28 | #define perror_quit(msg) do { perror(msg); exit(1); } while(0) 29 | 30 | int main(int argc, char **argv) { 31 | struct stat buf; 32 | char *file; 33 | time_t last_change = 0; 34 | pid_t child; 35 | int status; 36 | 37 | if(argc < 3) 38 | usage(argv[0], 1); 39 | file = argv[1]; 40 | argc -= 2; 41 | argv = &(argv[2]); 42 | 43 | if(access(file, R_OK) || access(argv[0], R_OK|X_OK)) 44 | usage(argv[0], 2); 45 | 46 | while(1) { 47 | if(stat(file, &buf) == -1) 48 | perror_quit("Stat failed"); 49 | 50 | if(buf.st_mtime != last_change) { 51 | last_change = buf.st_mtime; 52 | child = fork(); 53 | if(child == -1) 54 | perror_quit("Fork failed"); 55 | if(child == 0){ 56 | execv(argv[0], argv); 57 | perror_quit("Exec failed"); 58 | } 59 | waitpid(child, &status, 0); 60 | } 61 | sleep(1); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /wallpaper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Grab a random desktop background and set it as background. Requirements: xrandr, gnome, PIL 4 | # 5 | # (c) 2010-2013 Dennis Kaarsemaker 6 | 7 | import cStringIO as stringio 8 | import glob 9 | import os 10 | from PIL import Image 11 | import random 12 | import re 13 | import stat 14 | import stealenv 15 | import sys 16 | import requests 17 | from whelk import shell 18 | 19 | requests.utils.default_user_agent = lambda: 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:20.0) Gecko/20100101 Firefox/20.0' 20 | download_path = '/home/dennis/Pictures/wallpapers' 21 | 22 | class DownloadError(Exception): 23 | pass 24 | 25 | class IfLift(object): 26 | index = "aHR0cDovL2ludGVyZmFjZWxpZnQuY29tL3dhbGxwYXBlci9kb3dubG9hZHMvZGF0ZS9hbnkv==".decode('base64') 27 | download_base = "aHR0cHM6Ly9pbnRlcmZhY2VsaWZ0LmNvbS93YWxscGFwZXIvN3l6NG1hMS8=".decode('base64') 28 | 29 | def random_image(self, resolution): 30 | html = requests.get(self.index).text 31 | pages = max([int(x) for x in re.findall("index(\d+)\.html", html)]) 32 | rand = random.randint(1, int(pages)) 33 | html = requests.get("%sindex%d.html" % (self.index, rand)).text 34 | images = re.findall(']*previews/(.*?)"', html) 35 | name,ext = random.choice(images).rsplit('.', 1) 36 | name = name.rsplit('_', 1)[0] 37 | name = '%s_%s.%s' % (name, resolution, ext) 38 | dpath = os.path.join(download_path, name) 39 | if not os.path.exists(dpath): 40 | img = requests.get(self.download_base + name).content 41 | Image.open(stringio.StringIO(img)) 42 | with open(dpath, 'w') as fd: 43 | fd.write(img) 44 | return dpath 45 | 46 | def main(): 47 | uid = stealenv.from_name('/usr/bin/nautilus') 48 | resolution = re.search(r"current\s+(\d+ x \d+)", shell.xrandr().stdout).group(1).replace(' ', '') 49 | path = None 50 | 51 | if len(sys.argv) == 2: 52 | path = sys.argv[1] 53 | else: 54 | klass = IfLift # In the future there could be more classes 55 | try: 56 | path = klass().random_image(resolution) 57 | except DownloadError: 58 | pictures = glob.glob(os.path.join(download_path, '*_%s.*' % resolution)) 59 | if pictures: 60 | path = random.choice(pictures) 61 | if path: 62 | shell.gsettings("set", "org.gnome.desktop.background", "picture-uri", "file://" + os.path.abspath(path)) 63 | else: 64 | print "No image found" 65 | 66 | if __name__ == '__main__': 67 | main() 68 | --------------------------------------------------------------------------------