mountd: prepend '$' to make use_ipaddr clients self-describing
[nfs-utils.git] / utils / mountd / auth.c
1 /*
2  * utils/mountd/auth.c
3  *
4  * Authentication procedures for mountd.
5  *
6  * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
7  */
8
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #include <sys/stat.h>
14 #include <netinet/in.h>
15 #include <arpa/inet.h>
16 #include <errno.h>
17 #include <unistd.h>
18
19 #include "sockaddr.h"
20 #include "misc.h"
21 #include "nfslib.h"
22 #include "exportfs.h"
23 #include "mountd.h"
24 #include "xmalloc.h"
25 #include "v4root.h"
26
27 enum auth_error
28 {
29   bad_path,
30   unknown_host,
31   no_entry,
32   not_exported,
33   illegal_port,
34   success
35 };
36
37 static void             auth_fixpath(char *path);
38 static char     *export_file = NULL;
39 static nfs_export my_exp;
40 static nfs_client my_client;
41
42 extern int new_cache;
43 extern int use_ipaddr;
44
45 void
46 auth_init(char *exports)
47 {
48
49         export_file = exports;
50         auth_reload();
51         xtab_mount_write();
52 }
53
54 /*
55  * A client can match many different netgroups and it's tough to know
56  * beforehand whether it will. If the concatenated string of netgroup
57  * m_hostnames is >512 bytes, then enable the "use_ipaddr" mode. This
58  * makes mountd change how it matches a client ip address when a mount
59  * request comes in. It's more efficient at handling netgroups at the
60  * expense of larger kernel caches.
61  */
62 static void
63 check_useipaddr(void)
64 {
65         nfs_client *clp;
66         int old_use_ipaddr = use_ipaddr;
67         unsigned int len = 0;
68
69         /* add length of m_hostname + 1 for the comma */
70         for (clp = clientlist[MCL_NETGROUP]; clp; clp = clp->m_next)
71                 len += (strlen(clp->m_hostname) + 1);
72
73         if (len > (NFSCLNT_IDMAX / 2))
74                 use_ipaddr = 1;
75         else
76                 use_ipaddr = 0;
77
78         if (use_ipaddr != old_use_ipaddr)
79                 cache_flush(1);
80 }
81
82 unsigned int
83 auth_reload()
84 {
85         struct stat             stb;
86         static ino_t            last_inode;
87         static int              last_fd;
88         static unsigned int     counter;
89         int                     fd;
90
91         if ((fd = open(_PATH_ETAB, O_RDONLY)) < 0) {
92                 xlog(L_FATAL, "couldn't open %s", _PATH_ETAB);
93         } else if (fstat(fd, &stb) < 0) {
94                 xlog(L_FATAL, "couldn't stat %s", _PATH_ETAB);
95         } else if (stb.st_ino == last_inode) {
96                 close(fd);
97                 return counter;
98         } else {
99                 close(last_fd);
100                 last_fd = fd;
101                 last_inode = stb.st_ino;
102         }
103
104         export_freeall();
105         memset(&my_client, 0, sizeof(my_client));
106         xtab_export_read();
107         check_useipaddr();
108         v4root_set();
109
110         ++counter;
111
112         return counter;
113 }
114
115 static char *get_client_ipaddr_name(const struct sockaddr *caller)
116 {
117         char buf[INET6_ADDRSTRLEN + 1];
118
119         buf[0] = '$';
120         host_ntop(caller, buf + 1, sizeof(buf) - 1);
121         return strdup(buf);
122 }
123
124 static char *
125 get_client_hostname(const struct sockaddr *caller, struct addrinfo *ai,
126                 enum auth_error *error)
127 {
128         char *n;
129
130         if (use_ipaddr)
131                 return get_client_ipaddr_name(caller);
132         n = client_compose(ai);
133         *error = unknown_host;
134         if (!n)
135                 return NULL;
136         if (*n)
137                 return n;
138         free(n);
139         return strdup("DEFAULT");
140 }
141
142 bool ipaddr_client_matches(nfs_export *exp, struct addrinfo *ai)
143 {
144         return client_check(exp->m_client, ai);
145 }
146
147 bool namelist_client_matches(nfs_export *exp, char *dom)
148 {
149         return client_member(dom, exp->m_client->m_hostname);
150 }
151
152 bool client_matches(nfs_export *exp, char *dom, struct addrinfo *ai)
153 {
154         if (is_ipaddr_client(dom))
155                 return ipaddr_client_matches(exp, ai);
156         return namelist_client_matches(exp, dom);
157 }
158
159 /* return static nfs_export with details filled in */
160 static nfs_export *
161 auth_authenticate_newcache(const struct sockaddr *caller,
162                            const char *path, struct addrinfo *ai,
163                            enum auth_error *error)
164 {
165         nfs_export *exp;
166         int i;
167
168         free(my_client.m_hostname);
169
170         my_client.m_hostname = get_client_hostname(caller, ai, error);
171         if (my_client.m_hostname == NULL)
172                 return NULL;
173
174         my_client.m_naddr = 1;
175         set_addrlist(&my_client, 0, caller);
176         my_exp.m_client = &my_client;
177
178         exp = NULL;
179         for (i = 0; !exp && i < MCL_MAXTYPES; i++)
180                 for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
181                         if (strcmp(path, exp->m_export.e_path))
182                                 continue;
183                         if (!client_matches(exp, my_client.m_hostname, ai))
184                                 continue;
185                         if (exp->m_export.e_flags & NFSEXP_V4ROOT)
186                                 /* not acceptable for v[23] export */
187                                 continue;
188                         break;
189                 }
190         *error = not_exported;
191         if (!exp)
192                 return NULL;
193
194         my_exp.m_export = exp->m_export;
195         exp = &my_exp;
196         return exp;
197 }
198
199 static nfs_export *
200 auth_authenticate_internal(const struct sockaddr *caller, const char *path,
201                 struct addrinfo *ai, enum auth_error *error)
202 {
203         nfs_export *exp;
204
205         if (new_cache) {
206                 exp = auth_authenticate_newcache(caller, path, ai, error);
207                 if (!exp)
208                         return NULL;
209         } else {
210                 exp = export_find(ai, path);
211                 if (exp == NULL) {
212                         *error = no_entry;
213                         return NULL;
214                 }
215         }
216         if (!(exp->m_export.e_flags & NFSEXP_INSECURE_PORT) &&
217                      nfs_get_port(caller) >= IPPORT_RESERVED) {
218                 *error = illegal_port;
219                 return NULL;
220         }
221         *error = success;
222
223         return exp;
224 }
225
226 nfs_export *
227 auth_authenticate(const char *what, const struct sockaddr *caller,
228                 const char *path)
229 {
230         nfs_export      *exp = NULL;
231         char            epath[MAXPATHLEN+1];
232         char            *p = NULL;
233         char            buf[INET6_ADDRSTRLEN];
234         struct addrinfo *ai = NULL;
235         enum auth_error error = bad_path;
236
237         if (path[0] != '/') {
238                 xlog(L_WARNING, "Bad path in %s request from %s: \"%s\"",
239                              what, host_ntop(caller, buf, sizeof(buf)), path);
240                 return exp;
241         }
242
243         strncpy(epath, path, sizeof (epath) - 1);
244         epath[sizeof (epath) - 1] = '\0';
245         auth_fixpath(epath); /* strip duplicate '/' etc */
246
247         ai = client_resolve(caller);
248         if (ai == NULL)
249                 return exp;
250
251         /* Try the longest matching exported pathname. */
252         while (1) {
253                 exp = auth_authenticate_internal(caller, epath, ai, &error);
254                 if (exp || (error != not_exported && error != no_entry))
255                         break;
256                 /* We have to treat the root, "/", specially. */
257                 if (p == &epath[1]) break;
258                 p = strrchr(epath, '/');
259                 if (p == epath) p++;
260                 *p = '\0';
261         }
262
263         switch (error) {
264         case bad_path:
265                 xlog(L_WARNING, "bad path in %s request from %s: \"%s\"",
266                      what, host_ntop(caller, buf, sizeof(buf)), path);
267                 break;
268
269         case unknown_host:
270                 xlog(L_WARNING, "refused %s request from %s for %s (%s): unmatched host",
271                      what, host_ntop(caller, buf, sizeof(buf)), path, epath);
272                 break;
273
274         case no_entry:
275                 xlog(L_WARNING, "refused %s request from %s for %s (%s): no export entry",
276                      what, ai->ai_canonname, path, epath);
277                 break;
278
279         case not_exported:
280                 xlog(L_WARNING, "refused %s request from %s for %s (%s): not exported",
281                      what, ai->ai_canonname, path, epath);
282                 break;
283
284         case illegal_port:
285                 xlog(L_WARNING, "refused %s request from %s for %s (%s): illegal port %u",
286                      what, ai->ai_canonname, path, epath, nfs_get_port(caller));
287                 break;
288
289         case success:
290                 xlog(L_NOTICE, "authenticated %s request from %s:%u for %s (%s)",
291                      what, ai->ai_canonname, nfs_get_port(caller), path, epath);
292                 break;
293         default:
294                 xlog(L_NOTICE, "%s request from %s:%u for %s (%s) gave %d",
295                      what, ai->ai_canonname, nfs_get_port(caller),
296                         path, epath, error);
297         }
298
299         freeaddrinfo(ai);
300         return exp;
301 }
302
303 static void
304 auth_fixpath(char *path)
305 {
306         char    *sp, *cp;
307
308         for (sp = cp = path; *sp; sp++) {
309                 if (*sp != '/' || sp[1] != '/')
310                         *cp++ = *sp;
311         }
312         while (cp > path+1 && cp[-1] == '/')
313                 cp--;
314         *cp = '\0';
315 }