Install OpenVPN for Android

  • Transfer the ‘‘.ovpn’’ file you built for that user from ~/easy-rsa/keys (on the machine where you built your keys – in my case that is my Macbook at ~/Packages/easy-rsa/keys).
    • Don’t copy the .crt or the .csr or the .key files – just the .ovpn. easy-rsa bundled those other files into the .ovpn for your convenience.
    • This is hard to do on an Android, if you’ve never transferred a file before.
    • Here are instructions for using a Windows PC to transfer the file. (Of course, you’ll have to first put the .ovpn file somewhere on your Windows PC!)
      • Connect your phone to your PC with a USB cable. If this is the first time you’ve done so, you will see an installation message on your PC. Wait for the driver install to complete.
      • On your phone, pull down the notification/settings. You’ll see a “USB for charging” notification. Tap it.
      • Choose “Transfer files (MTP)”
      • On your computer, you might have a new Windows Explorer window. If not, open Windows Explorer and click on the new “removable disk” icon for your cell phone. On my PC it shows up as a little cell icon labeled “XT1095”, but unless you have the same model of phone, your label will be different.
      • You should see “Internal storage” inside that Explorer window. Open “Internal storage”. Then, open the “Download” folder inside it.
      • Copy the .ovpn file from your PC to the Download file on your phone using Windows Explorer.
      • Delete the .ovpn file from your PC. If you used a USB drive to transfer the .ovpn file, delete it there too. You really want to keep this file confidential.

The rest of these instructions are done on your phone unless otherwise stated:

  • Open “Play Store” on your Android phone.
  • Search for “OpenVPN Connect”.
  • Install it.
  • Open it.
  • From the menu (the three dots stacked vertically, in the upper-right corner), choose “Import Profile from SD card” (even if it isn’t on an SD card).
  • Navigate to your Download folder, tap the .ovpn file, and press Select. You should see (on a very busy page) “Profile successfully imported”.
  • Using your PC, delete the .ovpn file from your phone’s Download folder. You don’t want to leave extra copies of this file sitting around.
  • Press Connect. It will tell you it is about to connect. Press OK. You should see “OpenVPN: Connected”.
  • Press Disconnect.
  • From the menu, choose Settings and enable Battery Saver and also Notifications.
  • To the right of “OpenVPN Profile”, you should see a Notepad icon. Tap it.
  • Choose Rename Profile. Rename it to “my-home-vpn-1”.
  • Tap the Notepad icon again and choose “
  • Select “Create Connect Shortcut”
  • Give it a name of your choosing. I like “My Home VPN”. Tap “Create Shortcut”.
  • Return to your phone’s launcher screen (typically by pressing the circle icon at the bottom of your screen).
  • Unplug your phone from your computer.

In normal use, here’s how you will run OpenVPN:

  • Tap the “My Home VPN” icon (or whatever you decided to call it) on your phone’s launch screen.
  • When you want to disconnect, tap the icon again and tap “Disconnect”.
  • That’s it!

If you want to confirm that you’re really using VPN:

  • Install PingTools Network Utilities from Play Store. (You want the one by “StreamSoft”.)
  • While you’re on VPN, confirm that you can ping the IP address of your home router, and do a Traceroute to google.com and confirm it goes via your 10.16.x.x VPN subnet.

Return to Surf Safe at Starbucks

TagLibrary.scptd

I don’t know where this came from, but it is helpful in dealing with tags on Mac OS X.

use framework "Foundation"

-- Must be stored in ~/Library/Script Libraries as a .scptd file
-- Sample use from a script:
-- 
-- use theLib : script "TagLibrary"
-- use scripting additions
-- set fileName to "/Users/kevin/Temp/image-and-text.pdf"
-- theLib's setTags:{"tag1", "tag2"} forPath:(fileName)
-- theLib's addTags:{"tag3", "tag4"} forPath:(fileName)
-- set s to theLib's returnTagsFor:(fileName)
-- repeat with x in s
-- 	display dialog x
-- end repeat

on returnTagsFor:posixPath -- get the tags
    set aURL to current application's |NSURL|'s fileURLWithPath:posixPath -- make URL
    set {theResult, theTags} to aURL's getResourceValue:(reference) forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
    if theTags = missing value then return {} -- because when there are none, it returns missing value
    return theTags as list
end returnTagsFor:

on setTags:tagList forPath:posixPath -- set the tags, replacing any existing
    set aURL to current application's |NSURL|'s fileURLWithPath:posixPath -- make URL
    aURL's setResourceValue:tagList forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
end setTags:forPath:

on addTags:tagList forPath:posixPath -- add to existing tags
    set aURL to current application's |NSURL|'s fileURLWithPath:posixPath -- make URL
    -- get existing tags
    set {theResult, theTags} to aURL's getResourceValue:(reference) forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
    if theTags ≠ missing value then -- add new tags
        set tagList to (theTags as list) & tagList
        set tagList to (current application's NSOrderedSet's orderedSetWithArray:tagList)'s allObjects() -- delete any duplicates
    end if
    aURL's setResourceValue:tagList forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
end addTags:forPath:

Export Everything From Evernote

I’m leaving Evernote. Here’s a script I wrote to export everything from Evernote.

-- Evernote-Export-All: Export everything from Evernote.
-- Copyright (c) 2017 by Kevin P. Kleinfelter

-- **********************************************************
-- Assumptions:
--   * If you have notes with PDF attachments, they have only one PDF attachment and no body. (Error message if not true.)
--      If this isn't the case, edit your notes to make it the case.  ENML editor is helpful.
--      You could print the note body to PDF, then combine the body-PDF with the attached PDF into a 
--      new single PDF, but many of my notes-with-PDF had a body with a blank line.  (You might
--      think you could delete the blank line with Evernote, but EN adds a bunch of markup
--      which it does not remove when you delete the blank line, so use ENML editor.)
--      For notes where you really want the note body followed by PDF attachment(s),
--      print the note to PDF, which will produce multiple PDFs, and then manually combine
--      the PDFs into  a single PDF.
--   * Must copy your TagLibrary.scptd source file (which was in my ~/Documents/code/AppleScripts folder, 
--      as of December 2016) to "~/Library/Script Libraries".  You may have to mkdir "Script Libraries"
--
-- Learnings/disappointments:
--   * AppleScript does not have access to Evernote Stacks.  "Use the full API" says Support.
-- **********************************************************

-- **********************************************************
use tagLib : script "TagLibrary"
use scripting additions

-- ************************************************************

-- Dev and Debug options
set limitToNotebook to ""
set scanOnly to false -- don't write anything.  Just report metrics and problems.
set ensureDataQuality to true
-- ************************************************************

set exportFolder to "/Users/kevin/Temp/Evernote-export"
set totalNoteCount to 0
set onePdfCount to 0 -- Notes with a single PDF attached.
set oneImageCount to 0 -- Notes with a single image attached and no body.
set htmlCount to 0 -- Notes exported as HTML
set simpleCount to 0 -- Notes exported as single file
set newline to "
"

-- **********************************************************


-- **********************************************************
-- **********************************************************
set kount to 0
display dialog "Have you cleared the old export folder and are you showing log messages?"

with timeout of (1000 * 60) seconds
    if ensureDataQuality then
        fixBadNoteNames()
        findDuplicateNoteNames()
    else
        display dialog "Re-enable data quality checks"
    end if

    tell application id "com.evernote.evernote"
    
        if notebook named "temp_export_notebook" exists then delete notebook "temp_export_notebook"
        create notebook "temp_export_notebook" with type local only
    
        if my limitToNotebook = "" then
            set allNotebooks to every notebook
        else
            display dialog "limited to notebook " & limitToNotebook
            set allNotebooks to notebook named limitToNotebook
        end if
    
        repeat with currentNoteBook in allNotebooks
            set notebookName to (the name of currentNoteBook)
            log "0, NOTEBOOK, " & notebookName
        
            set allNotes to every note in notebook notebookName
            repeat with currentNote in allNotes
                set totalNoteCount to (totalNoteCount + 1)
                set theHtml to HTML content of currentNote
                set noteTitle to (the title of currentNote)
            
                if (count of attachments of currentNote) = 0 then
                    my exportSingleFile(currentNote, notebookName)
                else if (count of attachments of currentNote) = 1 then
                    set myMime to mime of first attachment of currentNote
                
                    if myMime = "application/pdf" then
                        if (my countSubstring(theHtml, "</div>")) > 1 then
                            log "Notes with PDF attachments must have no body. Violation:" & noteTitle
                            display dialog theHtml
                            my die("Notes with PDF attachments must have no body.  Violation:" & noteTitle)
                        end if
                    
                        my exportToPdf(currentNote, noteTitle, notebookName)
                    else if (my isSingleImageOnly(myMime, theHtml)) then
                        my exportToImage(currentNote, noteTitle, notebookName, my mimeToFileType(myMime))
                    else
                        my exportToHtml(currentNote, notebookName)
                    end if
                else -- Multiple attachments
                    repeat with theAttachment in (attachments of currentNote)
                        if mime of theAttachment = "application/pdf" then
                            my die("Not implemented: Multiple attachments with a PDF. " & noteTitle)
                        end if
                    end repeat
                
                    my exportToHtml(currentNote, notebookName)
                end if
            end repeat -- repeat with currentNoteBook in allNotebooks
        end repeat
        if notebook named "temp_export_notebook" exists then delete notebook "temp_export_notebook"
    end tell
    my die("Successfully completed")
end timeout


-- Sample of image-only note:
-- <div id="en-note"><img src="?hash=12345" id="en-media:image/png:12345:none:none" class="en-media"/></div>
on isSingleImageOnly(myMime, theHtml)
    if myMime is not equal to "image/png" and myMime is not equal to "image/jpeg" then return false

    set prefix to "<div id=\"en-note\"><img src=\"?hash="

    if theHtml starts with prefix or theHtml starts with (my newline & prefix) then
        -- continue
    else
        return false
    end if

    if (countSubstring(theHtml, "</div>")) ≠ 1 then return false

    set s to RemoveFromString(theHtml, "<div id=\"en-note\"><img src=\"?hash=")
    set s to RemoveFromString(s, "</div>")

    if (countSubstring(s, "<") > 0) then return false
    if (countSubstring(s, ">") > 1) then return false

    if (my countSubstring(theHtml, "en-media:image/png")) = 1 then return true
    if (my countSubstring(theHtml, "en-media:image/jpeg")) = 1 then return true

    return false
end isSingleImageOnly


on findDuplicateNoteNames()
    do shell script "rm -f /tmp/evernote-titles.txt"
    set foundDuplicates to false
    tell application "Evernote"
        set EVNotebooks to every notebook
        repeat with EVnotebook in EVNotebooks
            log "0, message, Finding duplicate titles in " & name of EVnotebook
            set allNotes to {}
            set allNotes to every note in EVnotebook
            repeat with aNote in allNotes
                set aTitle to title of aNote
                set aTitle to my filenameSafe(aTitle) -- comment
                do shell script "echo '" & aTitle & "' >> /tmp/evernote-titles.txt"
            end repeat
        end repeat
    end tell

    do shell script "sort /tmp/evernote-titles.txt | uniq -d > /tmp/evernote-duplicates.txt"
    set s to do shell script "cat /tmp/evernote-duplicates.txt"
    if length of s > 4 then
        with timeout of 30000 seconds -- wait 500 minutes
            display dialog "Duplicate note names found.  Please fix. " & s
        end timeout
    end if
end findDuplicateNoteNames



on fixBadNoteNames()
    tell application "Evernote"
        set EVNotebooks to every notebook
        repeat with EVnotebook in EVNotebooks
            log "0, message, Fixing note titles in " & name of EVnotebook
            set allNotes to {}
            set allNotes to every note in EVnotebook
            repeat with aNote in allNotes
                set originalTitle to title of aNote
                set newTitle to my filenameSafe(originalTitle)
                if newTitle ≠ originalTitle then
                    --	display dialog "Renaming '" & originalTitle & "' to '" & newTitle & "'"
                    set title of aNote to newTitle
                end if
            end repeat
        end repeat
    end tell
end fixBadNoteNames


-- **********************************************************
-- **********************************************************
on mkDir(pathName)
    do shell script "mkdir -p '" & pathName & "'"
end mkDir


-- **********************************************************
-- From https://geert.vanderkelen.org/2010/splitting-as-string-and-joining-a-list-using-applescript/
-- **********************************************************
to joinList(aList, delimiter)
    set retVal to ""
    set prevDelimiter to AppleScript's text item delimiters
    set AppleScript's text item delimiters to delimiter
    set retVal to aList as string
    set AppleScript's text item delimiters to prevDelimiter
    return retVal
end joinList


-- **********************************************************
-- I tried asking Evernote for the Note's HTML and writing that to a file, but I found some notes would contain 
-- a byte with hex CA that Evernote rendered as a space but HTML viewers and file editors showed that byte as an E with an accent.
-- Telling Evernote to *export* the note did not have that problem.  However, exporting the note
-- puts the HTML into a folder, and I'd prefer that simple HTML notes not go into a folder which will contain only
-- the one file.
-- 
-- Unfortunately, the note exports without the note TITLE when you tell Evernote to export it.
-- That note title renders very visibly in Evernote so I think of it as part of the note.
-- Adding a title to the output was easy when I wrote the HTML directly, because Evernote gave me 
-- the HTML for the BODY of the page (without the <html> or the <body> tags) so I could just prepend the title.
-- When I ask Evernote to do the export, I need to add the title before exporting.
--
-- OK, Evernote got really slow when I started creating temp notes to add the title.  I'm going to export the note
-- and then "sed" (or similar) the html to add a title.
--
-- I'm going to convert these from HTML to RTF.  I can get away with this because they have no attachments/images.
-- By converting them to RTF, if I double-click on the resultant file, it will open with an editor, and if it is a
-- hand-created note (as much of these are), it is probably some kind of list or brainstorming document that I want
-- to edit as often as to view.
--
-- Nope.  RTF is a bad idea.  The conversion to RTF loses column-widths, which some web site captured data
-- uses.  Best to stick with HTML and I'll manually convert files I want to routinely edit.  
-- (Or I can add an HTML editor to the open-with list.)
-- **********************************************************
on exportSingleFile(currentNote, notebookName)
    set my simpleCount to ((my simpleCount) + 1)
    if my scanOnly then return

    set sanitizedNotebookName to my filenameSafe(notebookName)
    set htmlFolderPath to my exportFolder & "/" & sanitizedNotebookName
    mkDir(htmlFolderPath)
    set noteAsList to {}

    tell application id "com.evernote.evernote"
        set noteTitle to title of currentNote
        set safeNoteTitle to my filenameSafe(noteTitle)
        set exportFilename to htmlFolderPath & "/" & safeNoteTitle & ".html"
    end tell
    set beginning of noteAsList to currentNote

    if not my scanOnly then
        log my totalNoteCount & " Single:" & exportFilename
        tell application id "com.evernote.evernote" to export noteAsList to "/tmp/tmp_evernote_export" format HTML with tags
    
        do shell script "mv /tmp/tmp_evernote_export/* '/tmp/tmp_evernote_export/" & safeNoteTitle & ".html'"
    
    
        -- ****** Add the note title to the note ******
        set perlCmd to "perl -pi -e 's/<(body.*?)>/<\\1>"
        set perlCmd to perlCmd & "<div style=\"font-size:16pt;line-height: 140%;font-family:Helvetica Neue, Helvetica, Arial, sans-serif\">"
        set perlCmd to perlCmd & safeNoteTitle
        set perlCmd to perlCmd & "<\\/div><br>"
        set perlCmd to perlCmd & "/' " & "'/tmp/tmp_evernote_export/" & safeNoteTitle & ".html'"
    
        set perlCmd to perlCmd & " 2>>/tmp/tmp_evernote_perl.err"
        do shell script perlCmd
    
    
        -- ***** Apply a default body font ------
    
        set perlCmd to "perl -pi -e 's/(<\\/head><body)/\\1"
        set perlCmd to perlCmd & " style=\"font-weight:380;font-size:10.5pt;line-height: 130%;font-family:Helvetica Neue, Helvetica, Arial, sans-serif\""
        set perlCmd to perlCmd & "/' " & "'/tmp/tmp_evernote_export/" & safeNoteTitle & ".html'"
    
        set perlCmd to perlCmd & " 2>>/tmp/tmp_evernote_perl.err"
        do shell script perlCmd
    
    
        do shell script "mv /tmp/tmp_evernote_export/*.html '" & exportFilename & "'"
    
        applyAttributesToFile(currentNote, exportFilename)
    
        -- do shell script "textutil -convert rtf '/tmp/tmp_evernote_export/" & my filenameSafe(noteTitle) & ".html' -o '" & exportFilename & "'"
    
    end if

end exportSingleFile


-- Update the file metadata with metadata from Evernote.
on applyAttributesToFile(currentNote, exportFilename)
    if my scanOnly then return

    -- Tags
    tell application "Evernote"
        set noteTags to the tags of currentNote
        if (count of (tags of currentNote)) > 0 then
            set tagStrings to my tagsToStrings(noteTags)
            tagLib's setTags:tagStrings forPath:(exportFilename)
        end if
    end tell

    -- Creation Date and Modification Date
    tell application "Evernote" to set cdate to creation date of currentNote
    tell application "Evernote" to set mdate to modification date of currentNote

    set s_cdate to "\"" & timestampReformat(cdate) & "\""
    set s_mdate to "\"" & timestampReformat(mdate) & "\""

    do shell script "SetFile -d " & s_cdate & " \"" & exportFilename & "\""
    do shell script "SetFile -m " & s_mdate & " \"" & exportFilename & "\""

end applyAttributesToFile

on tagsToStrings(noteTags)
    set ret to {}
    repeat with t in noteTags
        copy (name of t) to the end of the ret
    end repeat
    return ret
end tagsToStrings

-- **********************************************************
-- **********************************************************
on exportToImage(currentNote, noteTitle, notebookName, fileType)
    set my oneImageCount to ((my oneImageCount) + 1)

    if my scanOnly then return

    tell application id "com.evernote.evernote" to set theAttachment to first attachment of currentNote
    set sanitizedNotebookName to my filenameSafe(notebookName)
    set imageFolderPath to my exportFolder & "/" & sanitizedNotebookName
    mkDir(imageFolderPath)

    set exportFilename to imageFolderPath & "/" & my filenameSafe(noteTitle) & fileType
    try
        do shell script "rm '" & exportFilename & "'"
    end try
    tell application id "com.evernote.evernote"
        write theAttachment to exportFilename
    end tell
    applyAttributesToFile(currentNote, exportFilename)

    log my totalNoteCount & "Image:" & exportFilename

end exportToImage

-- **********************************************************
-- **********************************************************
on exportToPdf(currentNote, noteTitle, notebookName)
    set my onePdfCount to ((my onePdfCount) + 1)
    if my scanOnly then return

    tell application id "com.evernote.evernote" to set theAttachment to first attachment of currentNote
    set sanitizedNotebookName to my filenameSafe(notebookName)
    set pdfFolderPath to my exportFolder & "/" & sanitizedNotebookName
    mkDir(pdfFolderPath)

    set exportFilename to pdfFolderPath & "/" & my filenameSafe(noteTitle) & ".pdf"
    set exportFilename to replaceString(exportFilename, ".pdf.pdf", ".pdf")
    set exportFilename to replaceString(exportFilename, ".PDF.pdf", ".pdf")
    set exportFilename to replaceString(exportFilename, ".pdf.PDF", ".pdf")
    set exportFilename to replaceString(exportFilename, ".PDF.PDF", ".pdf")
    try
        do shell script "rm '" & exportFilename & "'"
    end try
    tell application id "com.evernote.evernote"
        write theAttachment to exportFilename
    end tell
    applyAttributesToFile(currentNote, exportFilename)

    log my totalNoteCount & "PDF:" & exportFilename

end exportToPdf



-- **********************************************************
-- **********************************************************
on exportToHtml(currentNote, notebookName)
    if my scanOnly then return
    set noteAsList to {}
    set beginning of noteAsList to currentNote
    set sanitizedNotebookName to my filenameSafe(notebookName)
    set htmlFolderPath to my exportFolder & "/" & sanitizedNotebookName
    mkDir(htmlFolderPath)

    set my htmlCount to ((my htmlCount) + 1)
    tell application id "com.evernote.evernote"
        set exportFilename to htmlFolderPath & "/" & my filenameSafe(title of currentNote)
    
        -- Do something with note with multiple attachments
        -- Looks like export as HTML is the best option. This will create a
        -- folder named to match the note tile, containing a single HTML file
        -- named to match the note title, with a sub-folder containing
        -- all of the attachments (which are almost certain to be images).
        if not my scanOnly then
            log my totalNoteCount & "HTML:" & exportFilename
            export noteAsList to exportFilename format HTML with tags
        end if
    end tell
    applyAttributesToFile(currentNote, getHtmlFileIn(exportFilename)) -- exportFilename is really the folder where the html got written.

end exportToHtml

on getHtmlFileIn(folderName)
    set ret to folderName
    set fld to POSIX file folderName
    tell application "Finder" to set folder_list to items of folder fld
    repeat with f in folder_list
        if name of f ends with ".html" then
            set ret to folderName & "/" & name of f
        end if
    end repeat
    return ret
end getHtmlFileIn


-- **********************************************************
-- Ensure that the name is safe for a file or folder name
-- **********************************************************
on filenameSafe(fileSystemName)
    set tempName to fileSystemName
    set tempName to replaceString(tempName, "\\", "")
    set tempName to replaceString(tempName, "/", "-")
    set tempName to replaceString(tempName, "&", "+")
    set tempName to replaceString(tempName, "|", "-")
    set tempName to replaceString(tempName, ":", "-")
    set tempName to replaceString(tempName, "*", "_")

    set tempName to RemoveListFromString(tempName, {"'", "|", "&", "\"", ">", "<", "?", "$"}) -- do NOT replace \ because I ADD it in front of stuff before this line
    return tempName
end filenameSafe



-- **********************************************************
-- **********************************************************
on die(theMessage)
    log "-1, Message, " & theMessage
    log "-1, Simple note count, " & my simpleCount
    log "-1, PDF-only note count, " & my onePdfCount
    log "-1, HTML-export count, " & my htmlCount
    log "-1, Image-only count, " & my oneImageCount
    log "-1, Total note count:" & my totalNoteCount

    with timeout of (1000 * 60) seconds
        display dialog theMessage
    end timeout
    if not my scanOnly then
        error number -128
    end if
end die



-- **********************************************************
-- Example: replaceString("Hello hello", "hello", "Bye") produces "Hello Bye"
-- From http://applescript.bratis-lover.net/library/string/
-- **********************************************************
on replaceString(theText, oldString, newString)
    local ASTID, theText, oldString, newString, lst
    set ASTID to AppleScript's text item delimiters
    try
        considering case
            set AppleScript's text item delimiters to oldString
            set lst to every text item of theText
            set AppleScript's text item delimiters to newString
            set theText to lst as string
        end considering
        set AppleScript's text item delimiters to ASTID
        return theText
    on error eMsg number eNum
        set AppleScript's text item delimiters to ASTID
        error "Can't replaceString: " & eMsg number eNum
    end try
end replaceString


-- **********************************************************
-- From http://applescript.bratis-lover.net/library/string/
-- **********************************************************
on RemoveFromString(theText, CharOrString)
    local ASTID, theText, CharOrString, lst
    set ASTID to AppleScript's text item delimiters
    try
        considering case
            if theText does not contain CharOrString then ¬
                return theText
            set AppleScript's text item delimiters to CharOrString
            set lst to theText's text items
        end considering
        set AppleScript's text item delimiters to ASTID
        return lst as text
    on error eMsg number eNum
        set AppleScript's text item delimiters to ASTID
        error "Can't RemoveFromString: " & eMsg number eNum
    end try
end RemoveFromString



-- **********************************************************
-- Remove items from a string.  e.g. RemoveListFromString("Hello hello2", {"hello2"})
-- From http://applescript.bratis-lover.net/library/string/
-- **********************************************************
on RemoveListFromString(theText, listOfCharsOrStrings)
    local ASTID, theText, CharOrString, lst
    try
        script k
            property l : listOfCharsOrStrings
        end script
        set len to count k's l
        repeat with i from 1 to len
            set cur_ to k's l's item i
            set theText to my RemoveFromString(theText, cur_)
        end repeat
        return theText
    on error eMsg number eNum
        error "Can't RemoveListFromString: " & eMsg number eNum
    end try
end RemoveListFromString



-- **********************************************************
-- How many times does theSubstring appear in theText?
-- From http://applescript.bratis-lover.net/library/string/
-- **********************************************************
on countSubstring(theText, theSubstring)
    local ASTID, theText, theSubstring, i
    set ASTID to AppleScript's text item delimiters
    try
        set AppleScript's text item delimiters to theSubstring
        set i to (count theText's text items) - 1
        set AppleScript's text item delimiters to ASTID
        return i
    on error eMsg number eNum
        set AppleScript's text item delimiters to ASTID
        error "Can't countSubstring: " & eMsg number eNum
    end try
end countSubstring

on mimeToFileType(myMime)
    if myMime = "image/png" then
        return ".png"
    else if myMime = "image/jpeg" then
        return ".jpg"
    else
        display dialog "Unexpected mime type: " & myMime
    end if
end mimeToFileType


-- Need to get a string with "mm/dd/yyyy hh:mm:ss PM"
on timestampReformat(t)
    tell (t) to get ("" & (its month as integer) & "/" & its day as text) & "/" & its year as text
    set theDate to result & " " & time string of t
    return theDate
end timestampReformat

Why I Don't Use Tasker

Tasker is a nifty app for Android. (There’s nothing like it for iPhone/iOS because of sandbox limitations.) It allows all sorts of “when this happens do that”. e.g. When I’m connected to WiFi but it is not my home WiFi, start VPN.

It is complicated to learn, but wonderfully powerful.

I don’t use it. When I was using it, about once or twice a week, my phone would spontaneously reboot. I wouldn’t be using it at the time. It was just sitting in my pocket. Then it would vibrate and I’d pull it out and see that it was restarting.

The only tasks I had at the time were:

  • Chirp when connecting/disconnecting from power.
  • (Disabled) Start VPN when I’m on a foreign VPN.

I don’t have patience for spontaneous reboots right now. To really debug this, one would have to:

  • Install Tasker and let it sit for a couple of weeks with no tasks. If no reboots, continue.
  • Install a task to chirp when connecting to power. If no reboots, continue.
  • Install a task to chirp when disconnecting power. If no reboots, continue.
  • Install other tasks, one at a time, monitoring for 2 weeks for reboots after each one.

Certainly do-able. Not a priority right now.

Practice Revoking a VPN Certificate

Sooner or later, someone is going to lose a laptop or a cell phone and you’ll need to revoke his/her certificate so that the thief can’t use your VPN. When you put a .ovpn file on an client, be certain that client has a good password (e.g. a good screen-lock PIN on your cell phone or a strong Windows password on your Windows PC - with a short timeout on the lock-when-idle.)

On the machine where you built your keys:

  • cd $EASY_RSA
  • Edit openssl*.cnf and change “default_crl_days= 30” to “default_crl_days= 14600” (40 years). If you don’t do this, your CRL will expire in a month and if your CRL expires, the server will refuse ALL logins.

      easy-rsa
      ./revoke-full user-key01-bogus
    

It will respond with “error 23 at 0 depth lookup:certificate revoked”. That’s what you want, but it is phrased confusingly.

If your keys are built on your Pi, execute:

mv ./keys/crl.pem /etc/openvpn/
sudo service openvpn restart

If your keys are built on your Mac:

  • On the Mac, execute:

      scp ./keys/crl.pem pi@raspi:crl.pem
    
  • On the Pi, execute:

      sudo mv ~/crl.pem /etc/openvpn/
      sudo service openvpn restart
    
  • Add the following to /etc/openvpn/server1.conf. (You couldn’t add it until you created a .pem file, or else OpenVPN will throw an error on startup.)

      crl-verify /etc/openvpn/crl.pem
    

And confirm that your client can no longer connect. (Because you revoked access.) Check /var/log/openvpn.log and confirm that you see “CRL CHECK FAILED” in it.

Test again by:

  • Import user-key02-bogus.ovpn into your OpenVPN client
  • Confirm that you can connect with bogus key 2
  • Confirm that you still cannot connect with bogus key 1
  • Revoke bogus key 2
  • Confirm that neither bogus key 1 or 2 can connect.

Note: If your CRL expires, just re-revoke any revoked certificate (or a new one). Revoking any certificate, even an expired one, re-generates the CRL.


Back to Surf Safe at Starbucks

Open Your Firewall on Your Pi VPN

You can’t connect to a VPN on your Pi if your firewall blocks access. Your Pi came with a built-in firewall. We need to open the necessary ports on your Pi.

  • Create a script named /etc/openvpn/firewall-rules.sh. Make it contain this:

      #!/bin/sh
      iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
      iptables -t nat -A POSTROUTING -s 10.16.0.0/24 -o eth0 -j MASQUERADE
      echo "firewall-rules.sh executed" > /tmp/firewall-rules.sh.log
    
  • You don’t really need that echo statement, but if your VPN doesn’t work, you’ll want to check the timestamp on /tmp/firewall-rules.sh.log to see if the script got executed.

  • Set proper permissions:

      sudo chmod 744 /etc/openvpn/firewall-rules.sh
      sudo chown root /etc/openvpn/firewall-rules.sh
    
  • As root, edit /etc/network/interfaces and put this line BEFORE “iface eth0…”. Do not indent it.

    auto eth0

  • As root, edit /etc/network/interfaces, and append this line after the “iface eth0…” line, indented 4 spaces:

      pre-up /etc/openvpn/firewall-rules.sh
    
  • Allow packet forwarding by editing /etc/sysctl.conf and un-commenting this line:

      #net.ipv4.ip_forward=1
    
  • Commit the packet forwarding changes by running:

      sudo sysctl -p
    
  • Start your server with the command below. I think this also causes it to auto-start at boot. Note that “server1” must match up with the “server1.conf” file in /etc/openvpn:

      sudo systemctl start openvpn@server1.service
    
  • Reboot your Pi.


Return to Surf Safe at Starbucks

Build a UDP VPN Config File from TCP Config

I’d like to have a UDP instance too. As I’ve mentioned, in theory, running a VPN over UDP should work better than running it over TCP. When you have TCP layered over TCP, if you run into network latency, you can have both layers retransmitting, and the upper layer can exacerbate the latency of the lower layer with the extra traffic.

Note: On my Pi, UDP performs MUCH worse than TCP. I haven’t figured out why.

cd /etc/openvpn
sudo cp server1.conf server2.conf

Edit server2.conf and:

  • Change: “daemon openvpn-tcp” to “daemon openvpn-udp”
  • Change: “proto tcp-server” to “proto udp” (NOT udp-server)
  • Change: “port 443” to “port 1194”
  • Change: “server 10.16.0.0 255.255.255.0” to “server 10.8.0.0 255.255.255.0”
  • Change: “log /var/log/openvpn.log” to “log /var/log/openvpn2.log”

While you’re at it, edit server1.conf and change the log from openvpn.log to openvpn1.log.

Run this:

sudo systemctl start openvpn@server1.service
sudo systemctl start openvpn@server2.service
sudo rm /var/log/openvpn.log

Update the firewall/forwarding rules on your telco-router to pass UDP on port 1194 to your Pi.

Locally, make a copy of user-key1.ovpn as user-key1-udp.ovpn.

  • Change: “proto tcp” to “proto udp”
  • Change: “remote your-ip 443” to “remote your-ip 1194”
  • Import it into your OpenVPN client.
  • Connect.
  • Debug.

Back to Surf Safe at Starbucks

Use a Raspberry Pi for a VPN Server

OK, coach. I think I’d like to run my own VPN, but I’m on the economy plan. I can’t afford to buy a new computer.

The Raspberry Pi is nominally $35. That will buy you a multi-core CPU, a gig of RAM, an Ethernet port, etc. What it doesn’t include is

  • Disk
  • Power supply
  • Keyboard
  • Monitor
  • Case

You don’t really need a keyboard or monitor for your VPN server, you’ll use a Micro SD card for the disk, and the Pi uses a micro-USB power supply. You might be able to use your cell phone charger for the power supply, but the Pi needs good power, so splurge $10 and get a really good one.

The latest Pi, an SD Card, a case and a power supply are available in a kit for about $50.


Return to Surf Safe at Starbucks

Grant Only Dad Access to the Home LAN

  • Summary: Per-user config file, Dad on a dedicated IP, firewall to filter except that IP

Maybe you want to access your home LAN while out, but you don’t want your kid’s phone to access your LAN. If he loses that phone and it isn’t passworded, someone could (theoretically) find the phone, pull the certificate off, access your VPN, and then access files on your home LAN.

In this case, you’d like most users’ traffic to be permitted to travel through the router on its way to the internet, but not to travel to your LAN; you want the same for traffic from your laptop, but you also want that traffic to be able to reach your LAN devices. Maybe your kid needs to work on his homework on his PC when you’re at Grandma’s, and you want to remote control his PC from your laptop using VNC.

I’m going to describe how to do this on an ASUS router running OpenVPN with ‘Merlin’ firmware. Merlin has some special capabilities which make this easier. Doing this on a Pi is similar, but some of the interfaces will have different names.

Let’s begin by blocking access to your LAN for all VPN users. Executing this command on your server will block access to the LAN:

  • iptables –insert FORWARD –in-interface tun21 –out-interface br0 -s 10.0.0.0/8 -d 192.168.0.0/16 -j DROP
    • iptables is the command to add a firewall rule.
    • ”–insert FORWARD” says to add a rule to the packet forwarding table at the beginning of the table.
    • ”–in-interface tun21” says if a packet comes in on the tun21 interface. If you have a second OpenVPN instance, it will be tun22. (tun1x is used for the VPN client.)
    • ”–out-interface br0” says if a packet is destined for the br0 interface. br0 refers to the Ethernet LAN jacks and the WiFi, combined.
    • -s and -d specify the source and destination subnets.
    • “-j DROP” says to drop any frames which match this rule.
  • To make this permanent, add the iptables command to a file on the router named /jffs/scripts/firewall-start.
    • You need to use the event ‘firewall-start’ and not ‘openvpn-event’, or else you can wind up with many, many copies of your rule in the table, if the VPN server gets stopped and started between reboots. *You need the first line in the file to be “#!/bin/sh”.
  • “chmod ugo+x” the file and it will get executed when the VPN server starts (or stops).
  • See this for more info on event scripts.
  • Enable user scripts via the router at: Administration » System » Enable JFFS custom scripts and configs

When you ran build-key, you ran a command line like:

build-key "user-key-3"

The name you supplied becomes the X509 Common Name (CN) in your key. In this case, the common name will be “user-key03”. Conveniently, when you look in ~/Packages/easy-rsa/keys_xxxxx, you’ll see a file with a name like ‘user-key-3.crt’. The file name of your certificate (less the “.crt”) matches the CN inside the file.

On the router:

mkdir /jffs/configs/openvpn/ccd1/
mkdir /jffs/configs/openvpn/ccd2/
  • Create a file on the router in the ccd1 directory named “user-key-3” (or whatever Dad’s CN is). In this file put something like:

    ifconfig-push 10.8.0.50 255.255.255.0

  • That will assign Dad’s laptop the IP address 10.8.0.50. When a client connects, OpenVPN matches the common name against files in the per-client config directory. If it finds a match, it loads that file as an extension of the OpenVPN file for the current client. Our command pushes the IP address to the client.
  • If no matching file is found, OpenVPN will try to use a default file called “DEFAULT”, so you have a way to specify settings for users who do NOT match a per-client config.
    • Note: We’re going to block access to all users and then grant access to Dad. An alternate approach would be to grant access to all users, and then block most users via a DEFAULT per-client config. I think it is safer to block by default, rather than by ‘DEFAULT’, in case something goes wrong with name matching or per-client config - you want the broken state to deny access, for security.
  • On the router, enable the “Manage Client-Specific Options” option under OpenVPN Server, and press Apply.
    • IMPORTANT: You must do this after you create the config in ccd1. When OpenVPN service is (re)started, Merlin copies from /jffs/configs/openvpn/ccd1 to /etc/openvpn/server1/ccd. jffs files are preserved across reboots. /etc/openvpn is where OpenVPN expects to find files, but it is on a RAM disk, so it vanishes after a reboot.
  • You might read that you can go to your server configuration file (/etc/openvpn/server1.conf) and find the line similar to the line below, and comment it out, to block access:
    • push “route 192.168.1.0 255.255.255.0”
    • (Remember: If your LAN isn’t 192.168.1.x, substitute your LAN’s address in the above.)
  • It won’t work. In the first place, you clients can get to your LAN with or without that route. (I’m assuming that you enabled “Direct clients to redirect Internet traffic”, which sends all client to the rouder.) In the second place, and more importantly, any route you can push (or not push), your users can defeat via the “route” command of their operating system.
  • Important principle: Locking people out is done with your firewall, not routes.

Now edit /jffs/scripts/firewall-start and make it look like this: iptables –insert FORWARD –in-interface tun21 –out-interface br0 -s 10.0.0.0/8 -d 192.168.0.0/16 -j DROP iptables –insert FORWARD –in-interface tun21 –out-interface br0 -s 10.8.0.50 -d 192.168.0.0/16 -j ACCEPT

(You need the lines in that sequence because each one adds a rule to the top of the file, and you need your ACCEPT to override your DROP.)

  • 1st line = If a frame comes in on the VPN, from a 10.x.x.x address, destined for your LAN, drop it.
  • 2nd line = If a frame comes in on the VPN, from Dad’s special address, destined for your LAN, accept it. (This rule will be at the top of the table, so it takes precedence.)

At this point, you should restart your router and confirm that these rules are in place via: “iptables -L FORWARD -v”. Then test it with Dad’s certificate and with someone else’s certificate.

You really ought to ensure that no one else can get Dad’s address. To do that, the end you need to accomplish is:

  • Remove “server 10.8.0.0 255.255.255.0” from your VPN configuration file and replace it with:
    • ifconfig 10.8.0.1 255.255.255.0
    • ipconfig-pool 10.8.0.3 10.8.0.49 255.255.255.0
  • You have to remove the “server” line because it expands (internally) to include an “ifconfig” plus an “ifconfig-pool”

I can think of one way to accomplish this:

It looks like you can assign static IP with an ‘ifconfig-pool-persist’ option, instead of per-client config (if the only per-client config you’re doing is to set static IP). However, I’ve read of people having trouble getting it to work and, as I read things, the server can update this file, which would be a bad idea for your VPN security if it surprises you with an update.


Return to Safe Surfing at Starbucks