blob: 13eba70b7bcbab152d2d6a50f4776175c6e48879 [file] [log] [blame]
/*
* utils/mountd/auth.c
*
* Authentication procedures for mountd.
*
* Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include "misc.h"
#include "nfslib.h"
#include "exportfs.h"
#include "mountd.h"
#include "xmalloc.h"
#include "v4root.h"
enum auth_error
{
bad_path,
unknown_host,
no_entry,
not_exported,
illegal_port,
success
};
static void auth_fixpath(char *path);
static char *export_file = NULL;
static nfs_export my_exp;
static nfs_client my_client;
extern int new_cache;
extern int use_ipaddr;
void
auth_init(char *exports)
{
export_file = exports;
auth_reload();
xtab_mount_write();
}
/*
* A client can match many different netgroups and it's tough to know
* beforehand whether it will. If the concatenated string of netgroup
* m_hostnames is >512 bytes, then enable the "use_ipaddr" mode. This
* makes mountd change how it matches a client ip address when a mount
* request comes in. It's more efficient at handling netgroups at the
* expense of larger kernel caches.
*/
static void
check_useipaddr(void)
{
nfs_client *clp;
int old_use_ipaddr = use_ipaddr;
unsigned int len = 0;
/* add length of m_hostname + 1 for the comma */
for (clp = clientlist[MCL_NETGROUP]; clp; clp = clp->m_next)
len += (strlen(clp->m_hostname) + 1);
if (len > (NFSCLNT_IDMAX / 2))
use_ipaddr = 1;
else
use_ipaddr = 0;
if (use_ipaddr != old_use_ipaddr)
cache_flush(1);
}
unsigned int
auth_reload()
{
struct stat stb;
static ino_t last_inode;
static int last_fd;
static unsigned int counter;
int fd;
if ((fd = open(_PATH_ETAB, O_RDONLY)) < 0) {
xlog(L_FATAL, "couldn't open %s", _PATH_ETAB);
} else if (fstat(fd, &stb) < 0) {
xlog(L_FATAL, "couldn't stat %s", _PATH_ETAB);
} else if (stb.st_ino == last_inode) {
close(fd);
return counter;
} else {
close(last_fd);
last_fd = fd;
last_inode = stb.st_ino;
}
export_freeall();
memset(&my_client, 0, sizeof(my_client));
xtab_export_read();
check_useipaddr();
v4root_set();
++counter;
return counter;
}
static char *get_client_hostname(struct sockaddr_in *caller, struct hostent *hp, enum auth_error *error)
{
char *n;
if (use_ipaddr)
return strdup(inet_ntoa(caller->sin_addr));
n = client_compose(hp);
*error = unknown_host;
if (!n)
return NULL;
if (*n)
return n;
free(n);
return strdup("DEFAULT");
}
/* return static nfs_export with details filled in */
static nfs_export *
auth_authenticate_newcache(char *what, struct sockaddr_in *caller,
char *path, struct hostent *hp,
enum auth_error *error)
{
nfs_export *exp;
int i;
free(my_client.m_hostname);
my_client.m_hostname = get_client_hostname(caller, hp, error);
if (my_client.m_hostname == NULL)
return NULL;
my_client.m_naddr = 1;
my_client.m_addrlist[0] = caller->sin_addr;
my_exp.m_client = &my_client;
exp = NULL;
for (i = 0; !exp && i < MCL_MAXTYPES; i++)
for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
if (strcmp(path, exp->m_export.e_path))
continue;
if (!use_ipaddr && !client_member(my_client.m_hostname, exp->m_client->m_hostname))
continue;
if (use_ipaddr && !client_check(exp->m_client, hp))
continue;
break;
}
*error = not_exported;
if (!exp)
return NULL;
my_exp.m_export = exp->m_export;
exp = &my_exp;
return exp;
}
static nfs_export *
auth_authenticate_internal(char *what, struct sockaddr_in *caller,
char *path, struct hostent *hp,
enum auth_error *error)
{
nfs_export *exp;
if (new_cache) {
exp = auth_authenticate_newcache(what, caller, path, hp, error);
if (!exp)
return NULL;
} else {
if (!(exp = export_find(hp, path))) {
*error = no_entry;
return NULL;
}
}
if (exp->m_export.e_flags & NFSEXP_V4ROOT) {
*error = no_entry;
return NULL;
}
if (!(exp->m_export.e_flags & NFSEXP_INSECURE_PORT) &&
ntohs(caller->sin_port) >= IPPORT_RESERVED) {
*error = illegal_port;
return NULL;
}
*error = success;
return exp;
}
nfs_export *
auth_authenticate(char *what, struct sockaddr_in *caller, char *path)
{
nfs_export *exp = NULL;
char epath[MAXPATHLEN+1];
char *p = NULL;
struct hostent *hp = NULL;
struct in_addr addr = caller->sin_addr;
enum auth_error error = bad_path;
if (path [0] != '/') {
xlog(L_WARNING, "bad path in %s request from %s: \"%s\"",
what, inet_ntoa(addr), path);
return exp;
}
strncpy(epath, path, sizeof (epath) - 1);
epath[sizeof (epath) - 1] = '\0';
auth_fixpath(epath); /* strip duplicate '/' etc */
hp = client_resolve(caller->sin_addr);
if (!hp)
return exp;
/* Try the longest matching exported pathname. */
while (1) {
exp = auth_authenticate_internal(what, caller, epath,
hp, &error);
if (exp || (error != not_exported && error != no_entry))
break;
/* We have to treat the root, "/", specially. */
if (p == &epath[1]) break;
p = strrchr(epath, '/');
if (p == epath) p++;
*p = '\0';
}
switch (error) {
case bad_path:
xlog(L_WARNING, "bad path in %s request from %s: \"%s\"",
what, inet_ntoa(addr), path);
break;
case unknown_host:
xlog(L_WARNING, "refused %s request from %s for %s (%s): unmatched host",
what, inet_ntoa(addr), path, epath);
break;
case no_entry:
xlog(L_WARNING, "refused %s request from %s for %s (%s): no export entry",
what, hp->h_name, path, epath);
break;
case not_exported:
xlog(L_WARNING, "refused %s request from %s for %s (%s): not exported",
what, hp->h_name, path, epath);
break;
case illegal_port:
xlog(L_WARNING, "refused %s request from %s for %s (%s): illegal port %d",
what, hp->h_name, path, epath, ntohs(caller->sin_port));
break;
case success:
xlog(L_NOTICE, "authenticated %s request from %s:%d for %s (%s)",
what, hp->h_name, ntohs(caller->sin_port), path, epath);
break;
default:
xlog(L_NOTICE, "%s request from %s:%d for %s (%s) gave %d",
what, hp->h_name, ntohs(caller->sin_port), path, epath, error);
}
if (hp)
free (hp);
return exp;
}
static void
auth_fixpath(char *path)
{
char *sp, *cp;
for (sp = cp = path; *sp; sp++) {
if (*sp != '/' || sp[1] != '/')
*cp++ = *sp;
}
while (cp > path+1 && cp[-1] == '/')
cp--;
*cp = '\0';
}