Cure for Public / Private key mismatch in Lotus Notes

Lotus Notes LogoA fairly common problem in Domino / Lotus Notes is to have a mismatch between the private key in some user’s user.id file and the public key in the Directory (Address Book).

This can come about through a number of ways: restoring a really old copy of a user ID, recreating a user ID without updating the person document are two that spring immediately to mind.

Of course this can be avoided by configuring your servers to enforce key checking on the security tab but that brings with it its own set of access issues for folks from foreign directories.

The problem with public / private key mismatches is that it’s usually not obvious to the end user but it will prevent a bunch of very useful adminp functions from working.

The most obvious symptom for the user, and the main reason the problem normally gets addressed, is when they receive an encrypted message and Lotus Notes reports that “you cannot access portions of this document because it is encrypted and was not intended for you or you do not have the decryption key”.

But from an administration point of view, when this problem is in play, renames do not work, recertifying folks from the directory (as opposed to having them merge a safe ID) will not work and any adminp function that requires a signed request (delegating a mail file if the owner does not have manager access for example) will not work.

The approach I took for this issue was to note that, whenever a user authenticates with a server (which they do when they first try to talk to it with Lotus Notes) a unique message is entered into the log that I can identify with an Event Handler.

Here’s what I did:

– I created a mail-in database (mail-in document in directory, database is located on my administration server, though it really doesn’t matter, I locate most of these kinds of databases on this server. It keeps everything in one place.)

– I created an Event Handler in the events4.nsf database on all my mail servers. It looks for “The public key for” (specify this in the “Events must have this text in the event message” box) with an action to send mail to the address of the above mail-in database.

– In the mail-in database I created two views, one to receive incoming messages from the event handler and one to display the results of the agent that processes them which runs once a day

– I further have 4 forms, one to represent the incoming memos, one for the reports and one each for a public key handler profile (to guide the main agent) and another overall profile – this database was intended to handle more than just the one type of event but so far all I’m dealing with are the public / private key issues.

The database can be downloaded from here. You want to set default access to “No Access” and give yourself manager access.

There are two agents, the minor one just cleans up any reports that are over a month old. Of course you plan for the maintenance cycle up front to keep your databases tidy, right? 🙂 This runs weekly.

The main agent may be of some use all on its own so I include it here. It reviews the items in the “Incoming Memos” view. Probably the only slightly confusing part is that it actually discards the warnings two out of every three days. This is so an affected user doesn’t get bombarded with these messages. The first day the event is triggered, overnight a warning is sent out but the user is going to reauthenticate before they can even see the warning. This would generate another warning even if they take immediate action. The extra day is in there as a grace period. But if the user fails to respond they will receive the warning once every three days until they comply.
You’ll note that the agent below references a script library (included in the above database) that I’ve created and that contains useful functions that I’ve either created or picked up over the years and are common to many of the LotusScript agents that I write. The first thing I ever put into an agent is the error handling since that tends to make troubleshooting a LOT easier.

Option Public
Option Declare
Use “AgentUtils”

%REM

Inspect queue of incoming monitor reports and deal with each one as required.

Should be set to run on behalf of “Notes Administrator”

%END REM

Sub Initialize
Dim sess As New NotesSession
Dim dbCur As NotesDatabase
Dim docProf As NotesDocument
Dim sPrintText As String

Dim viewIncoming As NotesView
Dim docIncoming As Notesdocument
Dim docDel As NotesDocument
Dim docReport As NotesDocument
Dim bNeedReport As Boolean
Dim sRepBody As String

Dim docNotify As NotesDocument
Dim namNotify As NotesName
Dim rtiBody As NotesRichTextItem
Dim itmNotified As Notesitem

On Error Goto ErrorGeneral

Set dbCur = sess.CurrentDatabase

Set docProf = dbCur.GetProfileDocument(“overallProfile”)
If docProf.Notify(0) = “” Then ‘probably not initialized
Print “Overall Profile document not initialized. Agent will not proceed without notifications specified.”
Goto leavesub
End If

sRepBody = “”

‘Walk through all documents in the incoming view
Set docReport = dbCur.CreateDocument
docReport.Form = “Report”
docReport.DateCreated = Now
docReport.Agent = sess.CurrentAgent.name
sRepBody = “Started: ” & Cstr(Now) & Chr$(13) & Chr$(13)

Set viewIncoming = dbCur.GetView(“Incoming”)
If viewIncoming Is Nothing Then
sPrintText = “Problem opening “”Incoming”” view. Terminating agent.”
sRepBody = sRepBody & sPrintText & Chr$(13)
Call ProblemNotify(docProf,sPrintText)
Goto LeaveSubWithReport
End If
viewIncoming.AutoUpdate=False
Set docIncoming = viewIncoming.GetFirstDocument

While Not docIncoming Is Nothing
‘Identify each type of monitor and direct to appropriate function
If Left$(docIncoming.Subject(0),27) = “WARNING: The public key for” Then
If Not ProcessPublicKeyWarning(docIncoming, sRepBody) Then
‘there was a problem
Goto LeaveSubWithReport
End If
Else ‘If none of the conditions are met, just report on this and throw away
sRepBody = sRepBody & “Unprocessed message from ” & docIncoming.From(0) &_
” with subject “”” & docIncoming.Subject(0) & “””. Message discarded.” & Chr$(13)
End If

Set docDel = docIncoming ‘all messages are deleted by the time the agent has finished.
Set docIncoming = viewIncoming.GetNextDocument(docIncoming)
If Not docDel Is Nothing Then ‘well… delete it
Call docDel.remove(True)
End If
Wend

LeaveSubWithReport:
sRepBody = sRepBody & Chr$(13) & “Finished: ” & Cstr(Now)
docReport.Body = sRepBody
Call docReport.Save(True,False)
LeaveSub:
Exit Sub

ErrorGeneral:
sPrintText= “Error: ” + Cstr(Err) + ” defn: ” + Error$ + “. Aborting Agent”
Call ProblemNotify(docProf,sPrintText)
If docReport Is Nothing Then
Resume LeaveSub
Else
Resume LeaveSubWithReport
End If
End Sub

Function ProcessPublicKeyWarning(docIncoming As NotesDocument, sRepBody As String) As Boolean
‘We asssume we only get here if there is a valid subject string in docIncoming
Static sess As Notessession
Static dbCur As NotesDatabase
Static docProf As NotesDocument
Static bSend As Boolean
Static docHoldItem As NotesDocument
Static itmPublicKeyNames As NotesItem
Static itmServerNames As NotesItem
Static itmExceptionNames
Static sExceptionNames() As String
Static itmIgnoreNames
Static sIgnoreNames() As String
Dim docNotify As NotesDocument
Dim sPrintText As String
Dim rtiBody As NotesRichTextItem
Dim sName As String
Dim namSendTo As NotesName
Dim iEndPos As Integer
Dim bExceptionServer As Boolean
Dim namFromServer As NotesName
Dim bExceptionNames As Boolean
Dim iExcpNm As Integer
Dim namExceptionName As NotesName
Dim bIgnoreNames As Boolean
Dim iIgnoreNm As Integer
Dim namIgnoreName As NotesName
Static namExceptionNamesMb As NotesName
Static namExceptionMb As NotesName

Static dbHints As NotesDatabase
Static docHints As NotesDocument

ProcessPublicKeyWarning=False ‘Assume failure
If sess Is Nothing Then
‘for efficiency only want to initialize these variables on the first call.
‘preserve their values across calls
Set sess = New notessession
Set dbCur = sess.currentdatabase
Set docProf = dbCur.GetProfileDocument(“pkProfile”)
If docProf.Notify(0) = “” Then ‘probably not initialized
sPrintText = “Public Key Profile document not initialized. Agent will not proceed without notifications specified.”
sRepBody = sRepBody & sPrintText & Chr$(13)
Call ProblemNotify(docProf,sPrintText)
Goto leaveFunction
End If
Set itmServerNames = docProf.GetFirstItem(“ServerNames”)
Set itmExceptionNames = docProf.GetFirstItem(“ExceptionNames”)
iExcpNm = -1
Forall ExcpNm In itmExceptionNames.values
If Not ExcpNm = “” Then
Set namExceptionName = New NotesName(ExcpNm)
iExcpNm = iExcpNm + 1
Redim Preserve sExceptionNames(iExcpNm)
sExceptionNames(iExcpNm) = Lcase(namExceptionName.Abbreviated)
End If
End Forall

Set itmIgnoreNames = docProf.GetFirstItem(“IgnoreNames”)
iIgnoreNm = -1
Forall IgnoreNm In itmIgnoreNames.values
If Not IgnoreNm = “” Then
Set namIgnoreName = New NotesName(IgnoreNm)
iIgnoreNm = iIgnoreNm + 1
Redim Preserve sIgnoreNames(iIgnoreNm)
sIgnoreNames(iIgnoreNm) = Lcase(namIgnoreName.Abbreviated)
End If
End Forall

If docProf.NextAction(0) = “Send” Then
bSend = True
docProf.NextAction = “Del1”
Elseif docProf.NextAction(0) = “Del1” Then
bSend = False
docProf.NextAction = “Del2”
Elseif docProf.NextAction(0) = “Del2” Then
bSend = False
docProf.NextAction = “Send”
Else ‘ Catch all – just init to send
bSend = False
docProf.NextAction = “Send”
End If
Call docProf.Save(True,False)

If bSend Then ‘ only bother initializing all this if we’re going to use it
‘I send the end user a link to a document explaining how to send me a copy of their public key
Set dbHints = New NotesDatabase(“YourServer”,”common//ourhowto.nsf”) ‘<–CHANGE THIS
Set docHints = dbHints.GetDocumentByUNID(“8037E8A85B57EC8B85256F1E0043B375”) ‘<–CHANGE THIS
If docHints Is Nothing Then ‘couldn’t find the doc
sPrintText = “Can’t find notice in Hints database regarding Mailing Public Keys. Aborting Agent”
sRepBody = sRepBody & sPrintText & Chr$(13)
Call ProblemNotify(docProf,sPrintText)
Goto LeaveFunction
End If
Set docHoldItem = dbCur.CreateDocument
Set itmPublicKeyNames = New NotesItem(docHoldItem,”PublicKeynames”,”Dummyfirstentry”)

Set namExceptionNamesMb = New NotesName(docProf.ExceptionNamesMailbox(0))
Set namExceptionMb = New NotesName(docProf.ExceptionMailbox(0))
End If
End If

iEndPos = Instr (29 ,docIncoming.Subject(0) , “/YOURORG”,5) ‘<–CHANGE THIS – MAKE SURE TO ADD YOUR ORG HERE
If iEndPos = 0 Then ‘Not an YourORG employee, skip
sPrintText = “Can’t find name of YourORG employee in Subject: ” & docIncoming.Subject(0) ‘<–CHANGE THIS
sRepBody = sRepBody & sPrintText & Chr$(13)
Call ProblemNotify(docProf,sPrintText)
Else
‘ “/YourORG” is 8 characters long, the starting point is at 29, so the length will be iEndpos – 21 (29-8) ‘<–CHANGE THIS
sName = Mid$(docIncoming.Subject(0),29,iEndPos -21)
Set namSendTo = New Notesname(sName)

bIgnoreNames = False
bExceptionNames = False
bExceptionServer = False

Forall IgnoreNms In sIgnoreNames
If Lcase(namSendTo.Abbreviated) = IgnoreNms Then
bIgnoreNames=True
Exit Forall
End If
End Forall

If Not bIgnoreNames Then
Forall ExcpNms In sExceptionNames
If Lcase(namSendTo.Abbreviated) = ExcpNms Then
bExceptionNames=True
Exit Forall
End If
End Forall

If Not bExceptionNames Then
Forall srvnm In itmServerNames.Values
If docIncoming.From(0) = srvnm Then
bExceptionServer=True
Exit Forall
End If
End Forall
End If
End If

Set namFromServer = New NotesName(docIncoming.from(0))

If bSend And Not bIgnoreNames Then ‘We only want to take action on these every three days. Otherwise we allow the monitor
‘documents to simply be deleted.

If Not itmPublicKeyNames.contains(namSendTo.Abbreviated) Then
Set docNotify = dbCur.CreateDocument
docNotify.Form = “Memo”
docNotify.SaveMessageOnSend=False
docNotify.ReplyTo = “Notes ID”
docNotify.Principal = “YourCompany Notes Monitor” ‘<–CHANGE THIS
docNotify.Subject = “Your Lotus Notes Public Key does not match our copy in the YourORG Directory” ‘<–CHANGE THIS
Set rtiBody = New NotesRichTextItem(docNotify,”Body”)
Call rtiBody.AppendText(“Hi ” & namSendTo.Common & “,”)
Call rtiBody.AddNewline(2)
Call rtiBody.AppendText(“You are receiving this automated email because our Lotus Notes logs”)
Call rtiBody.AppendText(” show a mismatch between the public key in your Lotus Notes User ID”)
Call rtiBody.AppendText(” file and the corresponding one in our YourORG Directory.”) ‘<–CHANGE THIS
Call rtiBody.AddNewline(2)
Call rtiBody.AppendText(“This happens most often if you ever had an issue that may have”)
Call rtiBody.AppendText(” required recovery of your Lotus Notes user ID file”)
Call rtiBody.AppendText(” (a damaged or stolen computer is often the cause). “)
Call rtiBody.AddNewline(2)
Call rtiBody.AppendText(“Please follow this link for directions on how to update your public key “)
Call rtiBody.AppendDocLink(docHints, docHints.subject(0))
Call rtiBody.AddNewline(2)
Call rtiBody.AppendText(“This should take less than two minutes.”)
Call rtiBody.AddNewline(2)
Call rtiBody.AppendText(“It will also explain why the mismatched keys are a bad thing that can”)
Call rtiBody.AppendText(” ultimately prevent you from seeing the contents of your incoming mail.”)
Call rtiBody.AddNewline(2)
Call rtiBody.AppendText(“Domino Administrator”)

If bExceptionNames Then
docNotify.Subject = namSendTo.Common & ” – Lotus Notes Public Key does not match our copy in the YourORG Directory” ‘<–CHANGE THIS
Call docNotify.Send(False,docProf.ExceptionNamesMailbox) ‘send to exception Names mailbox if from specified server(s)
‘ Call docNotify.Send(False,”marc Bourassa”)
Elseif bExceptionServer Then
docNotify.Subject = namSendTo.Common & ” – Lotus Notes Public Key does not match our copy in the YourORg Directory” ‘<–CHANGE THIS
Call docNotify.Send(False,docProf.ExceptionMailbox) ‘send to exception mailbox if from specified server(s)
‘ Call docNotify.Send(False,”marc Bourassa”)
Else
docNotify.Subject = “Your Lotus Notes Public Key does not match our copy in the YourORG Directory” ‘<–CHANGE THIS
Call docNotify.Send(False,namSendTo.Canonical)
‘ Call docNotify.Send(False,”marc Bourassa”)
End If
Call itmPublicKeyNames.AppendToTextList(namSendTo.Abbreviated)
If bExceptionNames Then
sRepBody = sRepBody & “[Public Key Mismatch] User: ” & namSendTo.Common &_
” Server: ” & namFromServer.Common &_
” via ” & namExceptionNamesMb.Common & ” [Name Exception]” & Chr$(13)
Elseif bExceptionServer Then
sRepBody = sRepBody & “[Public Key Mismatch] User: ” & namSendTo.Common &_
” Server: ” & namFromServer.Common &_
” via ” & namExceptionMb.Common & ” [Server Exception]” & Chr$(13)
Else
sRepBody = sRepBody & “[Public Key Mismatch] User: ” & namSendTo.Common &_
” Server: ” & namFromServer.Common & Chr$(13)
End If
End If

Else
If bIgnoreNames Then
sRepBody = sRepBody & “[Public Key Mismatch] User: ” & namSendTo.Common &_
” Server: ” & namFromServer.Common &_
” [Ignored]” & Chr$(13)
Else
sPrintText = “[Ignoring Public Key Mismatch] User: ” & namSendTo.Common &_
” Server: ” & namFromServer.Common
sRepBody = sRepBody & sPrintText & Chr$(13)
End If
End If
End If

ProcessPublicKeyWarning=True ‘made it this far, must be OK
LeaveFunction:
Exit Function

End Function

I’ve seen many samples and have taken advantage of a lot of knowledge offered on the web so I thought I could contribute something back. If you do use this code, I’d appreciate it if you referenced this page so that others may find this information if they are looking for solutions.

Autoresponder LotusScript

Below is code to create an automated response whenever someone sends a message to a now obsolete mail file.

It can also be used for a “live” mailbox to indicate something like “We’ve received your message and will respond in 24 hours.”

The only requirements are that the Person document (or mail-in document) must still be present in the directory and a valid name is used in the “Run on Behalf of” field of the agent properties.

Usually this is done for someone who is no longer with the company and so is already in the “Deny Access” groups so using the mailfile owner’s name is often not an option.

It is set up to respond to EVERY message sent to it (unlike the Out of Office Agent) to assure the sender that there message HAS been received.

This particular variation was developed after someone with an autoresponder sent a message to one of our autoresponder  mailboxes and ended up with an endless loop. Messages with identical sender and subject will receive only 1 response per day.

Make sure you customize everything in the agent below that has been bolded.

Agent Information
Name:    Autoresponder – Ensure Unique
Last Modification:    01/03/2008 04:39:40 PM
Comment:    [Not Assigned]
Shared Agent:    Yes
Type:    LotusScript
State:    Disabled
Trigger:    Before New Mail Arrives
Acts On:    Each incoming mail document
LotusScript Code:

Option Public
Option Declare

‘This autoresponder will protect against responses from *other* autoresponders by
‘checking to see if we’ve already received a message from them with the same subject already that day.
‘see the “IsMessageUnique” logic for details
‘Also checks for $AssistMail field which indicates that the message was sent by some
‘automated process. This comes from the case where someone sent a message FROM
‘this inbox TO this inbox and started an endless loop.
‘Note also that there is a name in the “Run on Behalf of” field on the security tab.
‘If this field is populated it *must* be represented in the ACL (At least Reader, probably author is safer) or you
‘will get weird errors when the agent runs.

Sub Initialize
    Dim sess As New NotesSession
    Dim docNotify As NotesDocument
    Dim docCur As NotesDocument
    Dim sPrintText As String
    Dim sMessage As String
    Dim sSendTo As String
    On Error Goto ErrGeneral
    On Error 4294 Goto ErrBadAddress
    Set docCur = sess.DocumentContext
    If Not docCur Is Nothing Then
        If Not Lcase$(docCur.Form(0)) = “nondelivery report” Then  ‘Don’t bother for failed delivery reports
            ‘Only internet messages
        ‘    If docCur.hasitem(“SMTPOriginator”)  Or docCur.hasitem(“MIME_Version”) Or docCur.hasitem(“$MIMETrack”) Then
            If Not docCur.HasItem(“$AssistMail”) Then ‘isn’t sent by another agent
                If IsMessageUnique(docCur) Then
                    Gosub ResponseMessage
                End If
            End If
        ‘    End If
        End If
    End If
LeaveSub:
    Exit Sub

ResponseMessage:
    Gosub SetupMessage
    Set docNotify = sess.CurrentDatabase.CreateDocument
    docNotify.SaveMessageOnSend = False
    docNotify.Form = “Memo”
    If Not docCur.ReplyTo(0)=”” Then
        sSendTo = docCur.ReplyTo(0)
    Else
        sSendTo = docCur.From(0)       
    End If
    docNotify.SendTo = sSendTo
‘Do not need the following fields since this is to be signed by the database owner.
‘if we ever change the signer to “Automail” then these should be set up properly
‘    docNotify.Principal=
‘    docNotify.ReplyTo=
    docNotify.Subject = “re: ” & docCur.subject(0)
    docNotify.Body = sMessage
    Call docNotify.Send(False)
    docCur.EFXResponded= Now
    Call docCur.Save(True,False)
    Return

SetupMessage:
    sMessage= “This is no longer a valid email address for John Doe.  ” &_
    “If you would like you may contact him at john.doe@gmail.com, phone – 555-555-5555.“& Chr$(13) & Chr$(13)
    Return

ErrBadAddress:
    Resume LeaveSub

ErrGeneral:
    sPrintText=    “Error: ” + Cstr(Err) + ” defn: ” + Error$ +  “.  Aborting Agent”
    Call ProblemNotify(sPrintText)   
    Resume LeaveSub
End Sub

Function IsMessageUnique(docCur As NotesDocument) As Boolean
    ‘Check to see if document is the only one with this sender and subject. If so return True
    ‘Otherwise, return false. We expect that multiple messages in a day from the same source with the same subject are probably
    ‘automated responses.
    ‘We only look at the most recent day’s worth of documents, if this doc is for a new day, great,
    ‘or if no match is found for the current day, also great
    Dim sess As New notessession
    Dim viewInbox As NotesView
    Dim docChk  As NotesDocument
    Dim docComp As notesdocument
    Dim bAscending As Boolean
    Dim datRcvd As  Notesdatetime
    Dim datDoc As NotesDateTime
    Dim datComp As NotesDateTime
    IsMessageUnique=True
    Set viewInbox =sess.CurrentDatabase.GetView(“($Inbox)”)
    If viewInbox Is Nothing Then  ‘Returns false
        IsMessageUnique=False
        Exit Function   
    End If
    Set docChk = viewInbox.GetFirstDocument
    Set datDoc = New NotesDateTime(docChk.Created)
    Set docComp= viewInbox.GetLastDocument
    Set datComp = New NotesDateTime(doccomp.Created)
    If datDoc.TimeDifference(datComp) > 0 Then ‘First Document is older than last document, sorted in Descending order
        bAscending = False
    Else
        bAscending = True
    End If
    Set datRcvd = New NotesDateTime(docCur.Created)  ‘get created date from newly received document
    Call datRcvd.SetAnyTime
    If bAscending Then
        Set docChk = viewInbox.GetLastDocument
    Else
        Set docChk = viewInbox.GetFirstDocument
    End If
    While Not docChk Is Nothing
        Set datDoc = New NotesDateTime(docChk.Created)
        Call datDoc.SetAnyTime
        If Not datDoc.TimeDifference(datRcvd) = 0 Then ‘not sent on same date – either new day or have reached previous day
            IsMessageUnique=True
            Exit Function
        End If
        If docChk.From(0) = docCur.From(0) Then ‘Match, might not be not unique, check subject.
            If docChk.Subject(0) = docCur.Subject(0) Then ‘Match, definitely not unique, end now.
                IsMessageUnique=False
                Exit Function
            End If
        End If
        If viewInbox Is Nothing Then  ‘Returns false
            IsMessageUnique=False
            Exit Function   
        End If
        If bAscending Then
            Set docChk = viewInbox.GetPrevDocument(docChk)
        Else
            Set docChk = viewInbox.GetNextDocument(docChk)
        End If
    Wend
End Function
Sub ProblemNotify(sPrintText As String)
    Dim sess As New NotesSession
    Dim db As NotesDatabase
    Dim docProblem As NotesDocument
    Dim item As NotesItem
    If sess.isonserver Then ‘send an e-mail or print to Log   
        Set db = sess.currentdatabase
        Set docProblem = db.CreateDocument       
        Set Item = docProblem.ReplaceItemValue(“Form”,”Memo”)
        Set Item = docProblem.ReplaceItemValue(“SendTo”,”Your Default Notification Mailbox“)
        Set Item = docProblem.ReplaceItemValue(“Principal”,sess.currentagent.name & ” Agent”)
        Set Item = docProblem.ReplaceItemValue(“Subject”,”Error: ” & db.Title & ” DB – ” & sess.currentagent.name & ” Agent”)       
        Set Item = docProblem.ReplaceItemValue(“Body”,”On ” & db.server & “!!” &db.filepath & Chr$(13) & Chr$(13) & sPrintText)
        Call docProblem.Send(False)
    Else
        Print sPrintText
        Messagebox sPrintText,0,”Problem with ” & sess.currentagent.name & ” Agent”
    End If
End Sub

Powered by ScribeFire.

Make the NotesURL property usable

I wanted to be able to get the url for the current document(s) in my mail database so I could paste them in another application and then click on the URLs to access the original messages directly from within that other app as needed.

I cobbled together a little LotusScript agent that grabs the NotesURL from each document and then scrubs out that junk that Lotus includes for no discernible reason.

So

notes://NotesServer@mydomain/__85256EDA00695075.nsf/0/EEF8D00A7E50A458852573BE0069A402?OpenDocument

becomes

notes://NotesServer/85256EDA00695075/0/EEF8D00A7E50A458852573BE0069A402?OpenDocument

This script will show the result in a messagebox. I also have it insert the URL into the clipboard for me but I’ve scraped that out so as not to confuse the issue.

It’s not as pretty as I’d like, but this should help others trying to get a useful URL out of Lotus’ terrible implementation.

Dim sess As New NotesSession
Dim doc As NotesDocument
Dim coll As NotesDocumentCollection
Dim sTempURL As String
Dim sURL As String
Dim iPosAT As Integer
Dim iPosSlashUnderscores As Integer
Dim iPosNSF As Integer

Set coll = sess.CurrentDatabase.UnprocessedDocuments
Set doc = coll.GetFirstDocument

sURL = “”
While Not doc Is Nothing
    sTempURL = doc.NotesURL
    iPosAT=Instr(sTempURL,”@”)
    iPosSlashUnderscores = Instr(sTempURL,”/__”)
    iPosNSF = Instr(iPosSlashUnderscores+1,sTempURL,”.nsf/”)
    If sess.CurrentDatabase.Server=”” Then
        sTempURL = Left$(sTempURL,8) & “/” & Mid$(sTempURL,iPosSlashUnderscores+3,iPosNSF-iPosSlashUnderscores-3) & Mid$(sTempURL,iPosNSF+4)
    Else
        sTempURL = Left$(sTempURL,iPosAT-1) & “/” &    Mid$(sTempURL,iPosSlashUnderscores+3,iPosNSF-iPosSlashUnderscores-3) & Mid$(sTempURL,iPosNSF+4)
    End If

    sURL = sURL & sTempURL
    Set doc = coll.GetNextDocument(doc)
    If Not doc Is Nothing Then
        sURL = sURL & Chr$(13)
    End If
Wend
If sURL = “” Then
    Messagebox “No Documents Selected”,0,”Nothing to do”
Else
    Messagebox sURL,0,”URL(s) for selected docs”
End If

(also posted in http://www-10.lotus.com/ldd/nd6forum.nsf/ShowMyTopicsAllFlatweb/9324f9e90784fbd5852573be0071f311?OpenDocument)