summaryrefslogtreecommitdiffstats
path: root/doc/nis-getting-started.txt
blob: 0724dce11b00608dce3cf29e30e992378933b1cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
= Getting Started with the NIS Server Plugin =

As an example, let's set up a NIS server to serve user and group
information for a domain named "example.com".

Because NIS servers need to listen on privileged ports, and because the
directory server is usually configured to drop privileges at startup,
getting the plugin running is usually a two-step process.  First, the
server must be configured to load, and then the server must be restarted
to actually get the plugin loaded.

== Loading the plugin ==

While the server is running (or not), add an entry like this to the
cn=config tree:

    dn: cn=NIS Server, cn=plugins, cn=config
    objectClass: top
    objectClass: nsSlapdPlugin
    objectClass: extensibleObject
    cn: NIS Server
    nsslapd-pluginPath: /usr/lib/dirsrv/plugins/nisserver-plugin.so
    nsslapd-pluginInitfunc: nis_plugin_init
    nsslapd-pluginType: postoperation
    nsslapd-pluginEnabled: on
    nsslapd-pluginDescription: NIS Server Plugin
    nsslapd-pluginVendor: redhat.com
    nsslapd-pluginVersion: 0
    nsslapd-pluginID: nis-plugin
    nis-tcp-wrappers-name: ypserv
    nsslapd-pluginarg0: 541

The name of the entry can be anything you like, but the path to the
plugin itself needs to be correct for your system.

In this example, we set two options -- the name used when evaluating the
tcp_wrappers (/etc/hosts.allow and /etc/hosts.deny) configuration, and
the port number on which we want the server to listen.  We could leave
the port number unset and let the server select a port at startup-time,
but my example system is firewalled, and I need to know which port it'll
be using in order to punch a hole in the firewall for it.

If the entry was added to a running server, then the server should be
restarted.  Running the "/usr/sbin/rpcinfo -p | grep ypserv" command
should now show that the server is running and listening on port 541:

    # /usr/sbin/rpcinfo -p | grep ypserv
    100004    2   udp    541  ypserv
    100004    2   tcp    541  ypserv

The module itself isn't configured to serve any maps for any domains
yet, but we're doing pretty well so far.

== Configuring a Domain and a Map ==

Before we can start serving any data using the NIS protocols, we need to
have some idea of what the data in the directory looks like.  For this
example, we'll assume that the directory's main naming suffix is
"dc=example, dc=com", that under it there are separate containers for
users and groups with relative names "ou=People" and "ou=Groups",
respectively, and that we have two users ("timtom" and "kevin") who both
belong to two groups ("minions" and "moppets").  The tree looks
something like this:

   + dc=example,dc=com
     + ou=People,dc=example,dc=com
       + uid=timtom,ou=People,dc=example,dc=com
       + uid=kevin,ou=People,dc=example,dc=com
     + ou=Groups,dc=example,dc=com
       + cn=minions,ou=Groups,dc=example,dc=com
       + cn=moppets,ou=Groups,dc=example,dc=com

In order to expose data from directory server entries via a NIS map, the
plugin needs to be provided with enough information to let it find the
set of directory entries which need to be exposed, and it needs to be
provided with instructions for building key/value pairs using those
entries.  Of course, it also needs to know the name of the NIS domain
and the map's name.

The plugin expects that information to be provided in an entry which is
an immediate child of the entry which caused the plugin to be loaded.
To avoid having to make up a more meaningful name, we'll just use the
combination of the map name and the domain name to name the map's
configuration entry.

    dn: nis-domain=example.com+nis-map=users,cn=NIS Server,cn=plugins,cn=config
    objectclass: extensibleObject
    nis-domain: example.com
    nis-map: users
    nis-base: ou=People, dc=example, dc=com
    nis-filter: (objectClass=posixAccount)
    nis-key-format: %{uid}
    nis-value-format: %{uid}

In this example, we've defined a map named "users" in a domain named
"example.com".  The entries which will be used to construct the map's
contents will be found by performing a subtree search starting at
"ou=People,dc=example,dc=com" for entries matching the filter
"(objectClass=posixAccount").  For each entry that is found by the
search, the "uid" attribute will be used as the key in the NIS map, and
the corresponding value will also be the value of the "uid" attribute.

And just like that, our NIS server is serving the domain, and the
"ypbind" service running on a client system will be able to find it.
Of course, that domain contains only one marginally-useful map, but
that's a different problem.

== Formatting Entries ==

So far, we've set up a map named "users" which contains the names of our
users, but we want to be able to provide more than just the user's name.
Going back to the configuration for the map, we can see that we set the
configuration entry's "nis-value-format" attribute to "%{uid}".  This
attribute holds a format specifier, and changing that format specifier
is how we'll provide more information about the user.

The format specifier syntax is similar to the use of shell variables in
scripts.  Attributes can be referenced and intermixed with literal text.
A simple-but-usable specification for a user entry might look like this:

    %{uid}:%{userPassword}:%{uidNumber}:%{gidNumber}:%{cn}:%{homeDirectory}:%{loginShell}

We shouldn't just use that as-is, though, because it could create a few
problems.  First, as a safety measure, if any attribute which is
referenced in a key or value format isn't part of an entry, that entry
will be ignored.  This is done to prevent empty values from showing up
in unexpected places, for example where the user's numeric UID should
be.  Client software which doesn't check the data could easily treat an
empty numeric UID field as a "0", for example, which would be a problem.

Other attributes, however, should be allowed to not be given.  The
schema for user accounts makes a "loginShell" optional, and in order to
support that, the plugin borrows a bit of shell syntax to allow default
and alternate values to be used.

To make use of the entry's "loginShell" attribute, but to use the value
"/bin/bash" if it doesn't contain that attribute, we can reference it
like so:

   %{loginShell:-/bin/bash}

The default or alternate value is recursively evaluated, so it, too, can
reference other variables.  This is useful for user attributes, such as
the GECOS attribute, which typically contain the user's name:

   %{gecos:-%{cn:-Some Unnamed User}}

Because attributes which hold non-security-sensitive information are
often allowed to be edited by users, a user could attempt to throw
client software off by abusing that ability.  (For example, the fields
in a user's passwd entry are separated by a ":" character, and the
directory server doesn't prohibit that from being used in a "cn"
attribute, so a user could try adding a ":" to their name.)  If the
format specifier looked like this:

    %{uid}:%{userPassword}:%{uidNumber}:%{gidNumber}:%{cn}:%{homeDirectory}:%{loginShell}

The resulting entry value could look like this:

   timtom:*:1000:1000:Tim:Tom 0wns:/home/timtom:/bin/bash

We can prohibit these kinds of shenanigans by configuring the map to
reject any attribute value which contains a particular character, by
setting a "nis-disallowed-chars" value in the map's configuration entry.

    dn: nis-domain=example.com+nis-map=users,cn=NIS Server,cn=plugins,cn=config
    objectclass: extensibleObject
    nis-domain: example.com
    nis-map: users
    nis-base: ou=People, dc=example, dc=com
    nis-filter: (objectClass=posixAccount)
    nis-key-format: %{uid}
    nis-value-format: %{uid}:%{userPassword-:*}:%{uidNumber}:%{gidNumber}:%{gecos:-%{cn:-Some Unnamed User}}:%{homeDirectory}:%{loginShell:-/bin/bash}
    nis-disallowed-chars: :

== Functions ==

=== Matching ===

We have another problem: the user has multiple values for the
"userPassword" attribute.  One of them looks like a Unix-style hash:

    {CRYPT}sa3tHJ3/KuYvI

The other is a plaintext password, and we don't want to expose that.
To provide a solution, the plugin's format specifier syntax also
provides a small number of function-like operators.  In this case, we
can use the one called "regsub" to:

    * Try to select one value by using a regular expression.
    * If a match is found, construct the value which will be used.
    * If a match is not found, use a default value.

Depending on whether or not we want a default value, the "regsub"
function takes either three or four arguments:

    %regsub(EXPRESSION,PATTERN,TEMPLATE[,DEFAULT_EXPRESSION])

In this case, selecting the "userPassword" value which matches the
regular expression "^\{CRYPT\}(..*)", building the result using the
first substring match, and providing a default value of "*" if no
matches are found can be done like so:

    %regsub("%{userPassword}","^\{CRYPT\}(..*)","%1","*")

Other matching functions are provided to perform wildcard matches
("match") and regular expression matches ("regmatch").

The parameters passed to functions need to be enclosed in quotation
marks and separated by a ",".

=== Lists ===

As an additional measure, attempting to reference attributes which have
more than one value (consider multiple "uidNumber" values in a user
entry) will also cause the entry to be rejected.

In some cases, we'll want just one value, and it's not particularly
important which one is used, so long as there is one.

    %first(EXPRESSION)

At other times, though, we want to use all of the values of a particular
attribute, for example when that attribute names a member of a group.

The plugin provides the "merge" function to handle this.

    %merge(SEPARATOR,EXPRESSION[,...])

The function simply evaluates every expression it is given, creating a
list of every value it finds, separated by the given separator.  The
specifier for a group entry which stores its members' names in its
"memberUid" attribute might look like this:

    %{cn}:%{userPassword}:%{gidNumber}:%merge(",","%{memberUid}")

=== References ===

Sometimes it's not enough to read an attribute from the entry which is
being examined.  Sometimes the entry contains the distinguished name of
another entry, and it's that entry which actually holds the data we
seek.  Take for example, this group:

   dn: cn=minions,ou=Groups,dc=example,dc=com
   objectClass: posixGroup
   cn: minions
   member: uid=timtom,ou=People,dc=example,dc=com
   member: uid=kevin,ou=People,dc=example,dc=com

The "deref" function is provided to handle this case.  Given two
attribute names, it will search out entries whose names are stored in
that attribute in the current entry, and will read the contents of the
second attribute from those entries.  It will typically be used in
combination with the "merge" function.

   %merge(",","%deref(\"member\",\"uid\")")

Now, the plugin will read the "member" attribute from the group entry,
visit the entries it names, read the "uid" attribute from those entries,
and build a list from the values.

=== Backward References ===

Keeping track of group memberships by storing the names of groups of
which the user is a member in the user's entry is also common.  For
example:

   dn: uid=timtom,ou=People,dc=example,dc=com
   objectClass: posixAccount
   uid: timtom
   uidNumber: 1000
   gidNumber: 1000
   cn: Tim Tom
   homeDirectory: /home/timtom
   loginShell: /bin/sh
   memberOf: cn=minions,ou=Groups,dc=example,dc=com
   memberOf: cn=moppets,ou=Groups,dc=example,dc=com

The "referred" function is provided to handle this case.  Given the name
of another map in the same domain, and two attribute names, it will
search out entries which are part of the other map which contain this
entry's distinguished name as a value for the first attribute, and will
read the contents of the second attribute from those entries.  It will
typically be used in combination with the "merge" function.

   %merge(",","%referred(\"passwd.byname\",\"memberOf\",\"uid\")")

== Defaults ==

In order to save time, the plugin includes compiled-in default entry
filters and default key and value format specifiers which correspond for
commonly-used map names.  Using the default settings is recommended
because they will always be the most correct settings that the project
maintainer(s) know of.  To use the defaults for a map, simply omit them
from the configuration:

    dn: nis-domain=example.com+nis-map=passwd.byname,cn=NIS Server,cn=plugins,cn=config
    objectclass: extensibleObject
    nis-domain: example.com
    nis-map: passwd.byname
    nis-base: ou=People, dc=example, dc=com

    dn: nis-domain=example.com+nis-map=passwd.byuid,cn=NIS Server,cn=plugins,cn=config
    objectclass: extensibleObject
    nis-domain: example.com
    nis-map: passwd.byuid
    nis-base: ou=People, dc=example, dc=com