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.

Leave a Reply

Your email address will not be published. Required fields are marked *