SPOCP: Use of S-expressions

Status of this memo

Distribution of this memo is unlimited.

This document is a working document of the SPOCP project, dated 2002-09-05, and it may be updated at any time.

Abstract

This memo decribes S-expression as they are used within the SPOCP project.

1. Introduction

1.1 Why S-expressions ?

The SPOCP project has developed software for an authorisation server. At the heart of the SPOCP server is a policy engine that tests if an authorisation request from a client ought to be allowed or not, given a set of policies. When dealing with policies in computer systems one has to have a clear way of describing the policies so that programs can test whether an entry has a specific right or not. By choosing a syntax for the policies and queries that is independent of the application area one can build a policy evaluator which can work well for many applications. We believe that S-expressions is such a language and have therefore chosen it as the basis for our work with a general authorization server. In addition we assume that both the policy rules and the queries have the form of a Subject doing an Action on a Resource.

1.2 History of S-expressions

The use of S-expressions for authentication/authorisation purposes can be traced back to 1997, when Ron Rivest published an Internet draft [SEXP2] descibing their basic data structure and encoding. And, as mentioned in that paper, further back to work done on "A Simple Distributed Security Infrastructure" (SDSI) in 1996 and possibly also back to the LISP programming language. Later, "The Simple Public Key Infrastructure" (SPKI) working group within the IETF, based its work on Rivest's work, but made restrictions on the syntax of the S-expressions to be used in that context.

In SPOCP, we are building on both of these works and are in our turn further restricting the syntax. In this document, we describe restricted S-expressions as we are using them.

A restricted S-expression is a nested list enclosed in matching "(" and ")". The first element in the list is always a "tag" or "name" of the object represented by the list and must be a byte-string. With that exception, every element in the list may in turn be an S-expression. In comparison with the S-expression technology of [SEXP], we impose the further restriction that no empty lists are allowed. An S-expression can also be interpreted as an ordered tree: The tag is the root and its first subtree is the second element in the list, and so on. As in SPKI, we have chosen Rivest's compact "canonical form", see [SEXP], as our internal representation of an S-expression. In most of the examples we use the "advanced form" which is easier to read.

SPOCP objects are defined below using ABNF [RFC 2234].

S-expressions are used at the core in the authorization server, and may be sent between computers. If they are, the canonical form is to be used [SEXP]. A canonical S-expression is formed from binary byte strings, each prefixed by its length, and enclosed in parenthesis. The length of a byte string is a non-negative ASCII decimal number, with no unnecessary leading "0" digits, terminated by ":". The canonical form is a unique representation of an S-expression and is used as the input to all hash and signature functions.

  s-expr     = "(" bytestring *s-part ")"
  s-part     = bytestring / s-expr / starforms
  bytestring = decimal ":" 1*bytes
               ; The number of bytes should be equal to the decimal specification
  decimal    = nzdigit *digit
  nzdigit    = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9"
  digit      = "0" / nzdigit
  bytes      = %x00-FF
  starforms  = "(1:*" ( set / range / prefix / any ) ")"

Even though the canonical form is the one described by the ABNF definitions, the so called advanced form will be used in the examples since it is easier for humans to read. In the advanced form, the elements are separated by spaces and there is no length prefix.

Example:

  (5:spocp(8:Resource6:mailer)) -- canonical form
  (spocp (Resource mailer))     -- advanced form

These are two representations of the same S-expression, consisting of a bytestring (the tag) "spocp" and another S-expression, that consists of two bytestrings "Resource" and "mailer".

2. Restricted S-expressions

Apart from simple lists and bytestring, we use a couple of other constructs, these are:

Even though the so called starforms (sets, ranges, prefixes and any) look like lists they are not. They are just a short form, representing every element that fits into a specific set. So what they are, are really sets of S-expressions.

2.1 Sets

Described by the ABNF

  set = "3:set" 1*s-expr

They are a way of specifying a limited set of elements.

Example:

  (* set apple orange lemon)

2.2 Ranges

Since one needs to know the type when one deals with ranges, there are a couple of types predefined.

In the specification of a range you may use constants in these types in combination with relational operators in a straght forward way. The ABNF specification for range is:

  range          = "5:range" rangespec
  rangespec      = alpha / numeric / date / time / ipv4
  alpha          = "5:alpha" [lole utf8string [goge utf8string]] / [goge utf8string [lole utf8string]]
  numeric        = "7:numeric" [ lole number [ goge number ]] / [ goge number [ lole number ]]
  date           = "4:date" [ goge dat [ lole dat ]] / [ lole dat [ goge dat ]]
  time           = "4:time" [ lole hms [ goge hms ]] / [ goge hms [ lole hms ]]
  ipv4           = "4:ipv4" [ lole ipnum [ goge ipnum ]] / [ goge ipnum [lole ipnum ]]
  lole           = "1:l" / "2:le"
  goge           = "1:g" / "2:ge"
  number         = decimal ":" 1*digit
  dat            = decimal ":" date
  ; date format as specified by RFC3339
  date-fullyear  = 4DIGIT
  date-month     = 2DIGIT  ; 01-12
  date-mday      = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on
                             ; month/year
  time-hour      = 2DIGIT  ; 00-23
  time-minute    = 2DIGIT  ; 00-59
  time-second    = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second
                             ; rules
  time-secfrac   = "." 1*DIGIT
  time-numoffset = ("+" / "-") time-hour ":" time-minute
  time-offset    = "Z" / time-numoffset

  partial-time   = time-hour ":" time-minute ":" time-second
                     [time-secfrac]
  full-date      = date-fullyear "-" date-month "-" date-mday
  full-time      = partial-time time-offset

  date-time      = full-date "T" full-time

  hms            = decimal ":" partial-time

  ipnum          = decimal ":" 1*3digit "." 1*3digit "." 1*3digit "." 1*3digit
  utf8string     = decimal ":" 1*UTF8
  UTF8           = %x01-09 / %x0B-0C / %x0E-7F / UTF8-2 /
                   UTF8-3 / UTF8-4 / UTF8-5 / UTF8-6
  UTF8-1         = %x80-BF
  UTF8-2         = %xC0-DF UTF8-1
  UTF8-3         = %xE0-EF 2UTF8-1
  UTF8-4         = %xF0-F7 3UTF8-1
  UTF8-5         = %xF8-FB 4UTF8-1
  UTF8-6         = %xFC-FD 5UTF8-1

Example

  (worktime (* range time ge 08:00:00 le 17:00:00))
or
  (* range numeric l 15 ge 10)
which is the same as
  (* set 10 11 12 13 14)

2.3 Prefixes

Used for string comparisions

ABNF

  prefix = "6:prefix utf8string

Example

  (file (* prefix conf))

This expression will match any expression with the tag "file", whose second element is a bytestring that starts with the string "conf".

2.4 Any

Somewhat similar to set in that it represents a limited set of individually specified elements. The difference is in how it is dealt with when ordering S-expressions. While one can think of a set as a a group of S-expressions, an any S-expression can be thought of as representing any s-expression out of a set of possible s-expressions.

ABNF

  any = "3:any" 1*S-exp

2.5 External reference

External references in policy rules are specific to SPOCP. Through them we can allow evaluation of constraints outside the process of comparing S-expressions.

ABNF

  extref       = decimal ":" ["!"] "urn:spocp:" type ":" typespecific
  type         = 1*lc
  lc           = %x61-7A
  typespecific = *UTF8

Example

  (spocp ... (group urn:spocp:gdbm:groups:eva:member) ...)

Which is a link to a GDBM file and will evaluate to true if the gdbm file 'groups' has a key 'eva' that points to a data item that is equal to 'member'. We will expand on external references in EXTREF.

3. Ordering S-expressions

In order to be able to model any kind of rights as a set of rules in the form of S-expressions, we need to define a binary relation, <=, that can be used to order S-expressions. We want a relation where A<=B belongs to the relation if rule A is less permissive than rule B. For many S-expressions, neither A<=B nor B<=A belongs to the relation.. Once the relation is defined, we also need an efficient way to decide (compute) if A<=B. In SPKI [SPKI], an algorithm to compute "Intersection of tag sets" is specified. Applied to two rules, A and B, it evaluates to the less permissive rule, if A<=B or B<=A. If there is no <=-relation between A and B, it evaluates to the empty S-expression. This type of relation is called a "preorder", and is characterised by being reflexive and transitive, that is by satisfying the two conditions:

  1. If A is an S-expression then A <= A is always true (reflexive)
  2. If A, B and C are S-expressions that satisfies A <= B and B <= C, then it is also true that A <= C (transitive)
Note that if A and B are any S-expressions then there exists three possible states for the comparision relation: A <= B, B <= A or neither. The most typical case is that neither A <= B or B <= A is true.

Note also that we will leave external references out of the picture for a while.

Trying to define the comparision relation we will start with S-expressions without starform. First some basic definitions:

  1. For two bytestrings A and B, A <= B only if A == B
  2. For a bytestring A and an S-expression B, it is neither the case that A<= B nor B<=A.
Given these cases one can define the comparison between two starform-free S-expressions as:

If S and T are *-free S-expressions, then S <= T if S has at least as many elements as T and every element in S is <= the corresponding element in T.

Example:

and these are neither <= nor >= Note that order is absolutely vital

(apple (weight 100)(colour red)) is not <= (apple (colour red)(weight 100))

The relationship can also more formally be defined as:

  (X_1 X_2 ... X_m) <= (Y_1 Y_2 ... Y_n)
  if and only if
  n <= m and X_i <= Y_i for i = 1,...,n

To do the more general comparison you have to go through a number of steps and if anyone of them is true then the comparison is true. If none is true then the comparison is false.

S <= T if:

  1. S and T are strings and S == T
  2. S is a string and T is a range or prefix-form that contains S
  3. S and T are range-forms where T contains S
  4. S and T are prefix-forms where T contains S
  5. S and T are lists that fullfills the relationship defined above
  6. S = (* any X_1 ... X_m) and X_i <= T for any i=1,..,m
  7. T = (* any Y_1 ... Y_n) and S <= Y_i for any i=1,..,n
  8. S = (* set X_1 ... X_m) and X_i <= T for i=1,...,m
  9. T = (* set Y_1 ... Y_n) and S <= Y_i for any i=1,...,n
Note that the last case is not complete, there are cases where different but equal representations of S-expression given different results when comparing, Take for example:

  S = ((* set A B )) and T = (* set (A) (B))

The only existing solution to this is that you the user has to define the allowed S-expressions in your application such that this problem never arises.

3.1 Ordering and external references

External references are a completely different kind of beast and if they are used they add another dimension to S-expressions.

For one thing, external references in one S-expression are never compared to anything in another S-expression. They never enter into the comparison rules as descibed above at all. Instead every external reference is expanded on its own and is passed by/ignored in the evaluation of the comparison relation if it is true but will fail the comparison if false.

Building on the descriptions above, with external references the following is true:

Given two lists X = (X_1 X_2 ... X_n) and Y = (Y_1 Y_2 .. Y_n), and given that the number of external references in X is l and in Y k. Then if all the external references evaluates to true, you can when making the comparison between the lists reduce them to lists without the external references, namely X' = (X_1' X_2' ... X_r') and Y' = (Y_1' Y_2' ... Y_s') where r = n-l and s = n-k and then compare them as described above for lists without external references.

When it comes to set comparisions: S <= T if:

  1. S = (* set X_1 X_2 ... X_m) and for i=1,...,m X_i either is an external references that evaluates to true or X_i <= T
  2. T = (* set Y_1 Y_2 ... Y_n) and for any i=1,...,n Y_1 either is an external references that evaluates to true or S <= Y_i

Note that external references are not allowed in range or prefix specifications.

4. Common format for S-expressions used in SPOCP

When describing rules and thereby queries we have chosen to structure the rules into sub-expressions tagged with resource, action and subject. This is form of expressing authorisation rules that is frequent in many applications.

Resource
The item someone/something wants to do something on
Action
The action to be applied to the resource
Subject
Who/what and under what circumstance this object wants to apply the specified action to the specified resource

Example:

  (spocp (resource mailer)(action send)(subject (email eva@minorg.se)))

Which as a rule could be taken to mean that the user that has the emailaddress 'eva@minorg.se' is allowed to use a speified mailserver to send emails to anyone.

A query, on the same topic, could then be:

  (spocp (resource mailer)(action send (to roland@dinorg.se))(subject (email eva@minorg.se)))

5. On external references

External references are used to extend policies expressed as pure S-expressions, to include information from other information resources. The syntax and semantics of external references are built in features of the SPOCP Policy Engine which both interprets and acts upon these references. This in contrast to the expressions describing for instance a particular resource, which is meaningful only to the authorisation client.

5.1 Variable substitution

A simple variable substitution is allowed in external references. The place where such a substitution is supposed to appear in the external reference is marked by the appearance of "${" and "}" separated by a word (the variable), for example "${user}". During evaluation of the external reference, the query S-expression is scanned for lists with the tag "user" and the remaining elements in that list is replacing "${user}" in the external reference.

Example:

If the external reference is:

  urn:spocp:extref:uid=${uid},domain="${domain}"

and the query S-expression is

  (spocp (resource res)(action some)(subject (uid roland)(domain se catalogix)))

After substitution the external reference will be:

  ufn:spocp:extref:uid=roland,domain="se catalogix"

Note that recursive references, like "${foo${bar}}", are not allowed.

5.2 The flat file external reference

Building on the definition in 2.5, this external reference is described by:

  type         = "flatfile"
  typespecific = file [":" keyword [":" value *( "," value )]]
  file         = utf8string
  keyword      = utf8string      ; keywords are not allowed to start with a '#' se below
  value        = utf8string

Further the format of the flat file has to adher to the following format

  line     = (data / comment) CR
  comment  = '#' whatever
  whatever = utf8string
  data     = keyword ":" value *( "," value )

Three cases can appear:

Only file is specified
If the file exists and can be opened for reading, the reference will evaluate to true. If the file does not exist or if it can not be opened for reading, the reference will evaluate to false.
File and keyword are specified
If the file contains the specified keyword, the reference will evaluate to true, if not it will evaluate to false
File, keyword and one or more values are specified
If the file contains the keyword and if every value specified in the reference also appears as values for that keyword in the file then the reference will evaluate to true otherwise it will evaluate to false.

5.3 The time external reference

This external reference can be used when a rule only shall be valid for a limited time, or at recurring intervals.

The ABNF of the reference:

  type         = "time"
  typespecific = [start] [";" [end] [";" days [";" starttime [";" endtime]]]]
  start        = date                                  ; as specified in 1.2
  end          = date
  days         = ["0"]["1"]["2"]["3"]["4"]["5"]["6"]   ; Sun = 0, Mon = 1, ...
  starttime    = timeofday                             ; as specified in 1.2
  endtime      = timeofday

Example:

  ufn:spocp:time:2002-08-01_00:00:00;;12345;08:00:00;17:00:00

Which means; starting at 00:00:00 on the 1th of August 2002, every monday, tuesday, wednesday, thursday and friday between 08:00:00 and 17:00:00 inclusive this expression will evaluate to true.

5.4 The GDBM external reference

The workings of the GDBM external reference is very similar to flat file, the ABNF is exactly the same except for 'type'.

  type         = "gdbm"
  typespecific = file [":" keyword [":" value *( "," value )]]
  file         = utf8string
  keyword      = utf8string      ; keywords are not allowed to start with a '#' se below
  value        = utf8string

The format of the datum in the GDBM file is supposed to be 'value *("," value)'.

And the semantics are also very similar to flat file, three cases can appear:

Only the file is specified
If the gdbm file exists and is can be opened for reading, the reference will evaluate to true. If the file does not exist or if it can not be opened for reading, the reference will evaluate to false.
File and keyword are specified
If the gdbm file contains the specified keyword, the reference will evaluate to true. If not, it will evaluate to false
File, keyword and one or more values are specified
If the gdbm file contains the keyword and if every value specified in the reference also appear as a value for that keyword in the gdbm file, the reference will evaluate to true. Otherwise it will evaluate to false.

5.5 The LDAP external reference

If you have lots of information about objects in an LDAP directory this should be a very useful reference to you.

ABNF for the LDAP external reference:

  type         = "ldap"
  typespecific =  ldapserver ";" thisDN ";" userDN ";" vset
  ldapserver   =  < hostport from Section 5 of RFC1738 [RFC1738] >
  thisDN       =  dn
  userDN       =  dn
  dn           =  < distinguishedName from Section 3 of RFC2253 [RFC2253] >
  vset         =  dnvset / valvset
  dnvset       =  base
                  / "(" dnvset ")"
                  / "{" dnvset ext attribute "}"
                  / dnvset SP conj SP dnvset
                  / dnvset ext dnattribute "*"
                  / "<" dn ">"
   valvset      =  "[" string "]"
                  / "(" valvset ")"
                  / dnvset ext attribute
                  / valvset SP conj SP valvset
   base        =  "this" / "user"                     ; refers back to thisDN resp. userDN
   conj        =  "&" / "|"
   ext         =  "/" / "%" / "$"                     ; base, onelevel resp. subtree search
   a           =  %x41-5A / %x61-7A                   ; lower and upper case ASCII
   d           =  %x30-39
   k           =  a / d / "-" / ";"
   anhstring   =  1*k
   attribute   =  a [ anhstring]                      ; as defined by [RFC2252]
   dnattribute =  < any attribute name which have attributetype
                  distinguishedName (1.3.6.1.4.1.1466.115.121.1.12)
                  like member, owner, roleOccupant, seeAlso, modifiersName, creatorsName,...>
   SP          =  %x20

Example:

<cn=Group,dc=minorg,dc=se>/member
The set of DNs that appear as attribute values of the member attribute in the entry with the DN "cn=Group,dc=minorg,dc=se"
{user$mail & [sven.svensson@minorg.se]}/title & [mib]
Can be thought of as being evaluated in two steps:
1) Find all the objects in the subtree starting at whatever is given as userDN in the LDAP external reference and that has "sven.svensson@minorg.se" as an attribute value for the attribute "mail".
2) Among that set of objects, find the object that has "mib" as an attribute value for the attribute "title".
If this was put into a LDAP filter it would probably be "(&(mail=sven.svensson@minorg.se)(title=mib))"
<cn=Group,dc=minorg,dc=se>/member & {user%mail & [tvw@minorg.se]}
Find any object which has the attribute value "tvw@minorg.se" for the "mail" attribute, using a onelevel search below the DN provided as userDN in the LDAP external reference. And then check whether any of the DNs of these objects appear as attribute values for the attribute member in the object "cn=Group,dc=minorg,dc=se".

6. Notes on hierarchical names

In many situations your application has organised and named both Sources, Actions and Resources as hierarchies. If you want to take full advantage of the hierarchical names in rules and queries you have to study carefully how S-expressions are evaluated by the policy engine. Assume that a name is (name P_1 P_2 ... P_n ) where P_1 is the part of the name that is closest to the root of the hierarchy. Then you can represent the whole space of names below P_1, by just specifying the top part of the namespace: (name P_1). Correspondingly you can specifyf another part of the namespace by (name P_1 P_2 ... P_m), m < n.

But what if you'd like to represent everyone that has the same last part P_n. An example of when this would be is if you defined role names within a organization as a concatenation of the organization name, the name of all the organizational units from the top with the roletype. Like this: (role O OU_1 ... OU_n R)

"(role UmU Umdac boss)" would then be the rolename for the boss of the organizational unit Umdac within the organization UmU.

Using this structure you could say (role UmU Umdac) and mean every role within that organizational unit and all the organizational units below. But if you said (role UmU boss ) you would mean the boss of UmU and not all the bosses within UmU. This since (role UmU umdac boss) is not <= (role UmU boss). So adding a role type to a list of O and OU's would mean exactly that role at that level in the organization.

If you instead would define the rolename to be (role R O OU_1 ... OU_n ), you could address every specific roletype within the organization by writing things like (role boss UmU), which would then mean every 'boss' within the organization UmU. Which follows since (role boss UmU OU) is <= (role boss UmU). But you could not specificly target the boss at UmU.

One can add complexity to this by using role types that are hierarchical such that the name would be (role O OU_1 .. OU_n R_1 .. R_m) or (role R_1 .. R_m OU_1 ... OU_n). By using the first form you could address every role within a role hierarchy at a specific place in the organization hierarchy but not in the whole organization tree. Using the later role you could address one whole subtree of the role hierarchy anywhere within a subtree of the organizational hierarchy.

  (role UmU admin finance) <= (role UmU admin)
  (role UmU umdac admin) is not <= (role UmU admin)
  and
  (role admin UmU umdac) <= (role admin UmU)
  (role admin finance UmU) is not <= (role admin UmU)

Remember that the decision of the meaning of a particular rule is taken when modelling the authorisation policy for a particular application. The Policy Engine does not know anything about the application. It only compares queries to rules according to builtin evaluation rules for restricted S-expressions, as described in this document. What we are discussing in this section are the consequences of choosing certain meanings of a particular S-expression, given how the Policy Engine tests for the <=-relation. These properties of the Policy Engine must be fully understood by those deciding the structures of rules and queries.

When you have two hierarchies that are linked to each other it might be best to decouple them and make two lists of them. (role (org O OU_1 .. OU_n)(type R_1 .. R_m)) which gives you freedome to express the relationship "any role whithin a role hierarchy anywhere within a organization hierarchy".

  (role (org UmU) (type admin finance)) <= (role (org UmU) (type admin))
  (role (org UmU umdac) (type admin)) <= (role (org UmU) (type admin))

There is of course nothing that prevents you from using one nameform in one set of rules and another form in another as long as the queries you pose to the policy engine use the appropriate one. What you should make certain though is that the form you choose gives you the possibility to express exactly what you are aiming for.

7. Example

7.1 Mailrelay

7.1.1 problem statement

Due to the maluses of mailservers on the Internet, many organizations have decided to limit what kind of mails that can be routed through their mailserver. Normally this amounts to ascertain that a mail either has as originator someone within the organization or that the claimed recipient belongs to the organization.

The problem then becomes: who belongs to the organization ?

One often used solution when it is the sender that is checked is to make certain that the ip address of the host used by the client belongs to the set of ip addresses that the organization owns/uses.
One drawback of such a check is that anyone that is able to send a mail from a machine within the organization is regarded as a member of the organization. By some this might be regarded as a feature by other as a bug. We might also have the situation that the organization may want to restrict the rights of certain persons within the organization so that they can only send to certain recipients or receive from specified senders. And of course anyone knows about the much wanted possibility to restrict for instance spam from reaching users mailboxes. But that problem is however not dealt with in this example.

So summing up we have three tests that should be performed before a mail is allowed to be forwarded by the organization's mailserver

If anyone of these questions are answered with no then the mail should be rejected.

7.1.2 SPOCP solution

Normal SMTP communication gives access to the following information: If SMTP AUTH is used, authentication information is also present.
On a high level one could then specify the following SPOCP rules to match the tests specified above
  1. (spocp (resource mailrelay)(action mail)(subject knownUnrestrictedSender))
  2. (spocp (resource mailrelay)(action mail)(subject knownUnrestrictedReceiver))
  3. (spocp (resource mailrelay)(action mail allowedReceiver)(subject knownRestrictedSender))
  4. (spocp (resource mailrelay)(action mail allowedSender)(subject knownRestrictedReceiver))
The problem then becomes defining knownUnrestrictedSender, knownUnrestrictedReceiver, knownRestrictedSender, knownRestrictedReceiver as well as allowedReceivers and allowedSenders.
7.1.2.1 Using LDAP enterprise directory
If one can assume that every entity that belongs to an organization is represented in the enterprise directory, then the knownUnrestrictedSender/Receiver, knownRestrictedSender/Receiver tests can be expressed as searches for entries within the directory. But in order for that to work, it is necessary that some special information is present in the directory. If "inetOrgPerson" is used as the objectclass for persons within the organization, then the attribute "mail" is present and should be used. But that isn't enough, we have to be able to store information about who are restricted and who are not, as well as who they are allowed to send or receive mail from. In order for the rest of the example to work: Assume that every person is also described by the objectclass "xMailUser" which contains the following three attributes:

restricted
Can have four values: "send", "receive", "noSend" (which means "no send restrictions") and "noReceive"
relayTo
A list of allowed receivers
realyFrom
A list of allowed senders

Using the ldapset external reference, the test of "knownUnrestrictedSender" can be expressed as:

urn:spocp:ldapset:ldap.org.com;;dc=org,dc=com;{user$mail & ${sender}}/restricted & noSend

A corresponding query could then include:

(spocp ... (subject (sender torbjorn.wiberg@adm.umu.se) ... )))

Note ldap.org.com is assumed to be the name of the organization's ldapserver, and "dc=org,dc=com" is the root of the organization's subtree in that ldapserver.

The meaning of this rule is that if there is an entry in the enterprise directory with the sender's mail address as one of the values for the "mail" attribute and that entry also has the value "noSend" (remenber that this means "no send restriction") on the "restricted" attribute, that entry is regarded as representing a "known unrestricted sender". Given how easy it is to fake sender addresses in mails, one might want to add an extra test for sender host ipaddress. Thereby the subject part of the rule is expanded to be:

(subject
  urn:spocp:ldapset:ldap.org.com;;dc=org,dc=com;{user$mail & ${sender}}/restricted & noSend
  (ipnum (* range ipv4 <organizations ip number series>)) 
)

That is, not only must the sender be regarded as a knownUnstrictedSender, the mail must also originate from a machine within the organization.

To satisfy this rule, the corresponding query should include:

... (subject (sender torbjorn.wiberg@adm.umu.se)

             (ipnum 193.195.52.1))

To cover the situation when users belonging to the organization use SMTP AUTH when not on premises, one could add an extra possibility (necessitates a slight rewrite of the above rule):

(subject
  (* set
    (smptauth)
    (internal
      urn:spocp:ldapset:ldap.org.com;;dc=org,dc=com;{user$mail & ${sender}}/restricted & noSend
      (ipnum (* range ipv4 <organizations ip number series>))
    )
  )
)

What the mailrelay would send to the policy engine would be queries similar to these ones:

The first version is used whenever SMTP AUTH is not used, the second when it is. Being able to authenticate to the mailserver using SMTP AUTH is regarded as enough to decide whether the entity is part of the organization or not.

If the sender is not an unrestricted sender, another rule will be checked. Building that rule, again we assume that the restrictions are stored in the enterprise directory. Two checks need to be done in order to decide whether the sender is allowed to send to the specified recipient or not, if she is a restricted sender.

knownRestrictedSender
urn:spocp:ldapset:ldap.org.com;;dc=org,dc=com;{user$mail & ${sender}}/restricted & send
allowedReceiver
urn:spocp:ldapset:ldap.org.com;;dc=org,dc=com;{user$mail & ${sender}}/relayTo & ${receiver}

Combining the two tests we would get the following SPOCP rule:

(spocp
  (resource mailrelay)
  (action mail
  urn:spocp:ldapset:ldap.org.com;;dc=org,dc=com;{user$mail & ${sender}}/relayTo & ${receiver}
))
  (subject
    (* set
      (smptauth )
      (internal
        urn:spocp:ldapset:ldap.org.com;;dc=org,dc=com;{user$mail & ${sender}}/restricted & send
        (ipnum (* range ipv4 <organizations ip number series>))
      )
    )
  )
)

And the corresponding query would be something like this:

(spocp
  (resource mailrelay)
  (action mail (receiver leif@it.su.se))
  (subject (internal (sender roland@catalogix.se)(ipnum 193.195.52.1)))
)

Two tests are also needed to handle the receiver cases (knownUnrestrictedReceiver and knownRestrictedReceiver) but those are left as an excercise to the reader.

Perhaps there also ought to be a test that checked whether the uid that was received from the authentication is connected to the sender mail address, but that would be rather easy to add.

Note that there are limitations to this solution which probably makes it impractical to use in real life, so regard it only as an example. There is for instance a limitation in the ldapset external reference presently, that only allows you to define exact match tests, hence you can not specify a mail domain as one value for "relayTo" or "relayFrom" since the tests are always with the complete mail address. This will probably change in the future.

References

RFC1738
Berners-Lee, T., Masinter, L. and M. McCahill, "Uniform Resource Locators (URL)", RFC 1738, December 1994.
 
RFC2252
Wahl, M., Coulbeck, A., Howes, T. and S. Kille, "Lightweight Directory Access Protocol (v3): Attribute Syntax Definitions", RFC 2252, December 1997.
 
RFC2253
Wahl, M., Kille, S., and T. Howes, "Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names", RFC 2253, December 1997.
 
RFC2255
Wahl, M. and T. Howes, "LDAP URL Format", RFC 2253, December 1997.
 
RFC2693
Ellison, C. et al. , "SPKI Certificate Theory", RFC 2693, September 1999.
 
RFC3339
Klyne, O. and Newman, C. , "Date and Time on the Internet: Timestamps", RFC3339, July 2002.
 
SDSI
Ron Rivest and Butler Lampson, "SDSI - A Simple Distributed Security Infrastructure [SDSI]", <http://theory.lcs.mit.edu/~cis/sdsi.html>.
 
SEXP
Ron Rivest, code and description of S-expressions, <http://theory.lcs.mit.edu/~rivest/sexp.html>.
 
SEXP2
Ron Rivest, "S-Expressions", draft-rivest-sexp-00.txt, May 1997, <http://theory.lcs.mit.edu/~rivest/sexp.txt>.