Article verson
Active: 20.09.2008. 16:35
(* MyProgram © kroko GNU GPL*)
global servvicename
set servvicename to "MyProgram"
if getrun() is servvicename then
-- we need to constantly call AppleScript Runner to activate, because user can
-- focus on other windows during this script exec proces
-- (this is a trimmed script from that I wrote as personalized iTunes script plugin- there calling
-- AppleScript Runner to activate was critical for itunes not to get in front of this window)
tell application "AppleScript Runner"
activate
display dialog servvicename & " is running" with title ¬
servvicename buttons {"Preferences", "Good to know", "Stop"} default button 2 with icon 1
end tell
if the button returned of the result is "Stop" then
setrun(servvicename & "Stopped")
else if button returned of the result is "Preferences" then
setprefs()
end if
else if getrun() is (servvicename & "Stopped") then
tell application "AppleScript Runner"
activate
display dialog servvicename & " is stopped" with title ¬
servvicename buttons {"Preferences", "Good to know", "Start"} default button 2 with icon 1
end tell
if the button returned of the result is "Start" then
setrun(servvicename)
else if button returned of the result is "Preferences" then
setprefs()
end if
else if getrun() is "cannotgetrun" then
tell application "AppleScript Runner"
activate
display dialog "Could't read " & servvicename & " Preferences.
Have You set them up?" with title ¬
servvicename buttons {"Fuck this", "Edit Preferences"} default button 2 with icon 0
end tell
if button returned of the result is "Edit Preferences" then
setprefs()
end if
else
tell application "AppleScript Runner"
activate
display dialog "Unknown error!
Couldn't talk to " & servvicename with title servvicename buttons {"Damn!"} default button 1 with icon 2
end tell
end if
on getrun()
tell application "Keychain Scripting"
try
set myKey to first key of current keychain ¬
whose service is servvicename
return name of myKey
on error
return "cannotgetrun"
end try
end tell
end getrun
on setrun(runstate)
tell application "Keychain Scripting"
try
set myKey to first key of current keychain ¬
whose service is servvicename
set programid to account of myKey
set programuri to comment of myKey
set programpw to password of myKey
delay 1
delete myKey
delay 1
make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
on error
tell application "AppleScript Runner"
activate
display dialog "Error!" with title servvicename buttons {"Damn!"} default button 1 with icon 2
end tell
end try
end tell
end setrun
on setprefs()
tell application "AppleScript Runner"
activate
display dialog "Here You can (re)set " & servvicename & " account data " with title ¬
servvicename buttons {"Exit", "Continue"} default button 2 with icon 1
end tell
if the button returned of the result is "Continue" then
tell application "AppleScript Runner"
activate
display dialog "Enter new ID" with title ¬
servvicename default answer ""
end tell
set programid to (text returned of result)
tell application "AppleScript Runner"
activate
display dialog "Enter new Password" with title ¬
servvicename default answer "" with hidden answer
end tell
set programpw to (text returned of result)
tell application "AppleScript Runner"
activate
display dialog "Enter new URI" with title ¬
servvicename default answer "http://www.foo.com/"
end tell
set programuri to (text returned of result)
tell application "AppleScript Runner"
activate
display dialog "Do You want to start " & servvicename & "?" with title ¬
servvicename buttons {"No", "Yes"} default button 2 with icon 1
end tell
if the button returned of the result is "Yes" then
set runstate to servvicename
else
set runstate to (servvicename & "Stopped")
end if
tell application "Keychain Scripting"
try
set myKey to first key of current keychain ¬
whose service is servvicename
try
delete myKey
delay 0.2
make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
on error
tell application "AppleScript Runner"
activate
display dialog "Error!" with title servvicename buttons {"Damn!"} default button 1 with icon 2
end tell
end try
on error
-- This will exec on "Could't read MyProgram Preferences", because set myKey has already failed once on getrun()
try
make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
end try
end try
end tell
else
quit me
end if
end setprefs
Here is the Perl script that reads values stored in Keychain and uses them in the program (here the usage is simple printout of those values).
#!/usr/bin/perl
# MyProgram © kroko GNU GLP
use strict;
use warnings;
use diagnostics;
use vars qw { $PROGRAMID $PROGRAMPW $RUNSTATE $PROGRAMURI $SERVICENAME };
$PROGRAMID = "";
$PROGRAMPW = "";
$RUNSTATE = "";
$PROGRAMURI = "";
$SERVICENAME = "MyProgram";
if (getuserinfo() == -3) {
print("Exiting ${SERVICENAME}. Key does not exist.\n");
exit; }
elsif (getuserinfo() == -2) {
print("Exiting ${SERVICENAME}. You have set up ID, PASSWORD, URI incorectly; reconfigure!\n");
exit; }
elsif (getuserinfo() == -1) {
print("Keychain info read successfully!\n${SERVICENAME} is stopped.\n");
exit; }
# The program comes below
# Here simply print out all the info stored in Keychain
print "Keychain info read successfully!\n";
print "Program - ${SERVICENAME}, ID - ${PROGRAMID}, URI - ${PROGRAMURI}, PASSWORD - ${PROGRAMPW}, Program running.\n";
exit;
# Get user info from keychain, check it, get the runstate
sub getuserinfo {
my(@alluserdata);
# Read the MyProgram from Keychain Access, redirect all stderr to stdout
open(USERINFO, "security 2>&1 find-generic-password -g -s $SERVICENAME |") || return -3;
@alluserdata = <USERINFO>;
close(USERINFO);
# If MyProgram key does not exsist return -3
if ($alluserdata[0] =~ m/SecKeychainFindGenericPassword/i) {return -3;}
# Read out the needed info
$alluserdata[0] =~ /^password: "(.*)"$/ ;
$PROGRAMPW = $1;
$alluserdata[4] =~ /0x00000007 <blob>="${SERVICENAME}(.*)"/ ;
$RUNSTATE = $1;
$alluserdata[6] =~ /"acct"<blob>="(.*)"/ ;
$PROGRAMID = $1;
$alluserdata[12] =~ /"icmt"<blob>="(.*)"/ ;
$PROGRAMURI = $1;
# Check if the fields follow the format rules rules, else return -2
if ($PROGRAMID !~ /^([A-Za-z0-9\_]*)$/i || $PROGRAMPW !~ /^([A-Za-z0-9\_]*)$/i || $PROGRAMURI !~ /^([A-Za-z0-9\.\/\-\~\:\_]*)$/i) {return -2;}
# Check if the Runstate is running eles return -1
if ($RUNSTATE eq "Stopped" || $RUNSTATE ne "") {return -1;}
return 0;
}
Note that the AppleScript actually doesn't start or stop the Perl script. It just writes the RUNSTATE into Keychain. Gluing the two parts together can be accomplished in different ways. I.e., for me Perl script is a launchd job, thus the solution is to restart the corresponding launchd job after changing MyProgram preferences in Keychain.
Configure com.foo.MyProgram.plist file as follows and put under one of launchd monitored places, I chose ~/Library/LaunchAgents
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.foo.MyProgram</string> <key>ProgramArguments</key> <array> <string>/path/to/perl/file/remember/to/chmod 755/it</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist>And the modified AppleScript simply restarts the launchd job - meaning it will restart Perl script to read the new preferences.
(* MyProgram © kroko GNU GPL*)
global servvicename
set servvicename to "MyProgram"
if getrun() is servvicename then
-- we need to constantly call AppleScript Runner to activate, because user can
-- focus on other windows during this script exec proces
-- (this is a trimmed script from that I wrote as personalized iTunes script plugin- there calling
-- AppleScript Runner to activate was critical for itunes not to get in front of this window)
tell application "AppleScript Runner"
activate
display dialog servvicename & " is running" with title ¬
servvicename buttons {"Preferences", "Good to know", "Stop"} default button 2 with icon 1
end tell
if the button returned of the result is "Stop" then
setrun(servvicename & "Stopped")
else if button returned of the result is "Preferences" then
setprefs()
end if
else if getrun() is (servvicename & "Stopped") then
tell application "AppleScript Runner"
activate
display dialog servvicename & " is stopped" with title ¬
servvicename buttons {"Preferences", "Good to know", "Start"} default button 2 with icon 1
end tell
if the button returned of the result is "Start" then
setrun(servvicename)
else if button returned of the result is "Preferences" then
setprefs()
end if
else if getrun() is "cannotgetrun" then
tell application "AppleScript Runner"
activate
display dialog "Could't read " & servvicename & " Preferences.
Have You set them up?" with title ¬
servvicename buttons {"Fuck this", "Edit Preferences"} default button 2 with icon 0
end tell
if button returned of the result is "Edit Preferences" then
setprefs()
end if
else
tell application "AppleScript Runner"
activate
display dialog "Unknown error!
Couldn't talk to " & servvicename with title servvicename buttons {"Damn!"} default button 1 with icon 2
end tell
end if
on getrun()
tell application "Keychain Scripting"
try
set myKey to first key of current keychain ¬
whose service is servvicename
return name of myKey
on error
return "cannotgetrun"
end try
end tell
end getrun
on setrun(runstate)
tell application "Keychain Scripting"
try
set myKey to first key of current keychain ¬
whose service is servvicename
set programid to account of myKey
set programuri to comment of myKey
set programpw to password of myKey
delay 1
delete myKey
delay 1
make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
try
do shell script "launchctl stop com.foo.MyProgram && launchctl start com.foo.MyProgram"
tell application "AppleScript Runner"
activate
display dialog "DONE!
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
end tell
on error
do shell script "launchctl start com.foo.MyProgram"
tell application "AppleScript Runner"
activate
display dialog "DONE! (But with unknown error)
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
end tell
end try
on error
tell application "AppleScript Runner"
activate
display dialog "Error!" with title servvicename buttons {"Damn!"} default button 1 with icon 2
end tell
end try
end tell
end setrun
on setprefs()
tell application "AppleScript Runner"
activate
display dialog "Here You can (re)set " & servvicename & " account data " with title ¬
servvicename buttons {"Exit", "Continue"} default button 2 with icon 1
end tell
if the button returned of the result is "Continue" then
tell application "AppleScript Runner"
activate
display dialog "Enter new ID" with title ¬
servvicename default answer ""
end tell
set programid to (text returned of result)
tell application "AppleScript Runner"
activate
display dialog "Enter new Password" with title ¬
servvicename default answer "" with hidden answer
end tell
set programpw to (text returned of result)
tell application "AppleScript Runner"
activate
display dialog "Enter new URI" with title ¬
servvicename default answer "http://www.foo.com/"
end tell
set programuri to (text returned of result)
tell application "AppleScript Runner"
activate
display dialog "Do You want to start " & servvicename & "?" with title ¬
servvicename buttons {"No", "Yes"} default button 2 with icon 1
end tell
if the button returned of the result is "Yes" then
set runstate to servvicename
else
set runstate to (servvicename & "Stopped")
end if
tell application "Keychain Scripting"
try
set myKey to first key of current keychain ¬
whose service is servvicename
try
delete myKey
delay 0.2
make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
try
do shell script "launchctl stop com.foo.MyProgram && launchctl start com.foo.MyProgram"
tell application "AppleScript Runner"
activate
display dialog "DONE!
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
end tell
on error
do shell script "launchctl start com.foo.MyProgram"
tell application "AppleScript Runner"
activate
display dialog "DONE! (But with unknown error)
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
end tell
end try
on error
tell application "AppleScript Runner"
activate
display dialog "Error!" with title servvicename buttons {"Damn!"} default button 1 with icon 2
end tell
end try
on error
-- This will exec on "Could't read MyProgram Preferences", because set myKey has already failed once on getrun()
try
make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
try
do shell script "launchctl stop com.foo.MyProgram && launchctl start com.foo.MyProgram"
tell application "AppleScript Runner"
activate
display dialog "DONE!
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
end tell
on error
do shell script "launchctl start com.foo.MyProgram"
tell application "AppleScript Runner"
activate
display dialog "DONE! (But with unknown error)
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
end tell
end try
end try
end try
end tell
else
quit me
end if
end setprefs
Hope it's apparent that the fields can contain anything that Your Perl script uses. Store whatever varibles You want. And each key field can contain many paremeters, divided by some predefined char/string; reading them out in Perl script simply takes some modification the regex part.
#!/usr/bin/perl
# MyProgram (all variables stored in Keychain Access comments field) © kroko GNU GLP
use strict;
use warnings;
use diagnostics;
use vars qw { @VALUESTORAGE $SERVICENAME };
$SERVICENAME = "MyProgram";
# If getuserinfo returns -3 it means that the key does not exist. Return the info.
if (getuserinfo() == -3) {
print("Exiting ${SERVICENAME}. Key does not exist.\n");
exit; }
# Else do anything with those values stored in @VALUESTORAGE
# Here we simply print them out each in its own line
foreach my $outputter (@VALUESTORAGE) {
print $outputter . "\n";
}
exit;
# Get info from keychain
sub getuserinfo {
my(@alluserdata);
# Read the MyProgram from Keychain Access, redirect all stderr to stdout
open(USERINFO, "security 2>&1 find-generic-password -g -s $SERVICENAME |") || return -3;
@alluserdata = <USERINFO>;
close(USERINFO);
# If MyProgram key does not exsist return -3
if ($alluserdata[0] =~ m/SecKeychainFindGenericPassword/i) {return -3;}
# Read all the values stored in key "comments" field
$alluserdata[12] =~ /"icmt"<blob>="(.*)"/ ;
# Put them in array @VALUESTORAGE, assuming that values are divided by ":"
@VALUESTORAGE = split(/:/, $1);
return 0;
}
P.S. Some basic AppleScript to work with Keychain Access
(* This AppleScript makes a key*)
tell application "Keychain Scripting"
set namename to "MyProgram"
set servicename to "MyProgram"
set accountname to "myuser"
set commentname to "http://www.foo.com"
set passwordname to "uuberpassword"
try
make new generic key with properties {name:namename, kind:"application password", account:accountname, service:servicename, comment:commentname, password:passwordname}
return "Done"
on error
return "Failed"
end try
end tell
(* This AppleScript checks if a key exists, arguments- service and account*)
tell application "Keychain Scripting"
set servicename to "MyProgram"
set accountname to "myuser"
try
set myKey to first key of current keychain ¬
whose service is servicename and account is accountname
return "Key Exists"
on error
return "Failed"
end try
end tell
(* This AppleScript gets password from a key, arguments- service and account*)
tell application "Keychain Scripting"
set servicename to "MyProgram"
set accountname to "myuser"
try
set myKey to first key of current keychain ¬
whose name is servicename and account is accountname
return password of myKey
on error
return "Failed"
end try
end tell
(* This AppleScript deletes a key, arguments- service and account*)
tell application "Keychain Scripting"
set servicename to "MyProgram"
set accountname to "myuser"
try
set myKey to first key of current keychain ¬
whose service is servicename and account is accountname
delete myKey
return "Key Deleted"
on error
return "Failed"
end try
end tell