#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <rpc/types.h>
#include <rpc/auth.h>
#include <sys/t_lock.h>
#include <netdb.h>
#include "clnt.h"
#include <rpc/xdr.h>
#include <rpc/rpc_msg.h>
#include <rpc/rpc.h>
#include "brpc.h"
#include "auth_inet.h"
#include "pmap.h"
#include <rpcsvc/nfs_prot.h>
#include <rpcsvc/nfs4_prot.h>
#include "nfs_inet.h"
#include <rpcsvc/bootparam.h>
#include <dhcp_impl.h>
#include <rpcsvc/mount.h>
#include <sys/promif.h>
#include <sys/salib.h>
#include "socket_inet.h"
#include "ipv4.h"
#include "mac.h"
#include <sys/bootdebug.h>
#include <errno.h>
#include "dhcpv4.h"
#include <sys/mntent.h>
#define ARP_INETBOOT_TIMEOUT 1000
struct nfs_file roothandle;
static char root_hostname[SYS_NMLN];
static char my_hostname[MAXHOSTNAMELEN];
static char root_pathbuf[NFS_MAXPATHLEN];
static char root_boot_file[NFS_MAXPATHLEN];
static struct sockaddr_in root_to;
CLIENT *root_CLIENT = NULL;
int dontroute = FALSE;
char rootopts[MAX_PATH_LEN];
static gid_t fake_gids = 1;
extern void set_default_filename(char *);
bool_t
xdr_fhstatus(XDR *xdrs, struct fhstatus *fhsp)
{
if (!xdr_int(xdrs, (int *)&fhsp->fhs_status))
return (FALSE);
if (fhsp->fhs_status == 0) {
return (xdr_fhandle(xdrs, fhsp->fhstatus_u.fhs_fhandle));
}
return (TRUE);
}
bool_t
xdr_fhandle(XDR *xdrs, fhandle fhp)
{
return (xdr_opaque(xdrs, (char *)fhp, NFS_FHSIZE));
}
bool_t
xdr_path(XDR *xdrs, char **pathp)
{
return (xdr_string(xdrs, pathp, MNTPATHLEN));
}
bool_t
xdr_fhandle3(XDR *xdrs, fhandle3 *objp)
{
return (xdr_bytes(xdrs, (char **)&objp->fhandle3_val,
(uint_t *)&objp->fhandle3_len, FHSIZE3));
}
bool_t
xdr_mountstat3(XDR *xdrs, mountstat3 *objp)
{
return (xdr_enum(xdrs, (enum_t *)objp));
}
bool_t
xdr_mountres3_ok(XDR *xdrs, mountres3_ok *objp)
{
if (!xdr_fhandle3(xdrs, &objp->fhandle))
return (FALSE);
return (xdr_array(xdrs, (char **)&objp->auth_flavors.auth_flavors_val,
(uint_t *)&objp->auth_flavors.auth_flavors_len, ~0,
sizeof (int), (xdrproc_t)xdr_int));
}
bool_t
xdr_mountres3(XDR *xdrs, mountres3 *objp)
{
if (!xdr_mountstat3(xdrs, &objp->fhs_status))
return (FALSE);
if (objp->fhs_status == MNT_OK)
return (xdr_mountres3_ok(xdrs, &objp->mountres3_u.mountinfo));
return (TRUE);
}
static int
nfsmountroot(char *path, struct nfs_file *filep)
{
int rexmit;
int resp_wait;
enum clnt_stat status;
struct fhstatus root_tmp;
rexmit = 0;
resp_wait = 16;
do {
status = brpc_call((rpcprog_t)MOUNTPROG, (rpcvers_t)MOUNTVERS,
(rpcproc_t)MOUNTPROC_MNT, xdr_path, (caddr_t)&path,
xdr_fhstatus, (caddr_t)&(root_tmp), rexmit, resp_wait,
&root_to, NULL, AUTH_UNIX);
if (status == RPC_TIMEDOUT) {
dprintf("boot: %s:%s mount server not responding.\n",
root_hostname, path);
}
rexmit = resp_wait;
resp_wait = 0;
} while (status == RPC_TIMEDOUT);
if ((status != RPC_SUCCESS) || (root_tmp.fhs_status != 0)) {
nfs_error(root_tmp.fhs_status);
root_to.sin_port = 0;
return (-1);
}
bcopy(&root_tmp.fhstatus_u.fhs_fhandle, &filep->fh.fh2, FHSIZE);
filep->ftype.type2 = NFDIR;
filep->version = NFS_VERSION;
nfs_readsize = nfs_readsize < NFS_MAXDATA ? nfs_readsize : NFS_MAXDATA;
nfs_readsize = (nfs_readsize != 0 && nfs_readsize < 512) ?
512 : nfs_readsize;
return (0);
}
int
setup_root_vars(void)
{
size_t buflen;
uint16_t readsize;
buflen = sizeof (root_hostname);
if (dhcp_getinfo(DSYM_VENDOR, VS_NFSMNT_ROOTSRVR_NAME, 0,
root_hostname, &buflen)) {
root_hostname[buflen] = '\0';
} else {
dprintf("BOUND: Missing Root Server Name Option\n");
errno = EINVAL;
return (-1);
}
buflen = sizeof (root_to.sin_addr);
if (!dhcp_getinfo(DSYM_VENDOR, VS_NFSMNT_ROOTSRVR_IP, 0,
&root_to.sin_addr, &buflen)) {
dprintf("BOUND: Missing Root Server IP Option\n");
errno = EINVAL;
return (-1);
}
buflen = sizeof (root_pathbuf);
if (dhcp_getinfo(DSYM_VENDOR, VS_NFSMNT_ROOTPATH, 0,
root_pathbuf, &buflen)) {
root_pathbuf[buflen] = '\0';
} else {
dprintf("BOUND: Missing Root Path Option\n");
errno = EINVAL;
return (-1);
}
buflen = sizeof (root_boot_file);
if (dhcp_getinfo(DSYM_VENDOR, VS_NFSMNT_BOOTFILE, 0,
root_boot_file, &buflen)) {
root_boot_file[buflen] = '\0';
dprintf("BOUND: Optional Boot File is: %s\n", root_boot_file);
}
if (root_boot_file[0] != '\0')
set_default_filename(root_boot_file);
buflen = sizeof (readsize);
if (dhcp_getinfo(DSYM_VENDOR, VS_BOOT_NFS_READSIZE, 0,
&readsize, &buflen)) {
nfs_readsize = ntohs(readsize);
if (boothowto & RB_VERBOSE) {
printf("Boot NFS read size: %d\n", nfs_readsize);
}
}
buflen = sizeof (rootopts);
if (dhcp_getinfo(DSYM_VENDOR, VS_NFSMNT_ROOTOPTS, 0,
rootopts, &buflen)) {
rootopts[buflen] = '\0';
dprintf("BOUND: Optional Rootopts is: %s\n", rootopts);
}
return (0);
}
static void
mnt3_error(enum mountstat3 status)
{
if (!(boothowto & RB_DEBUG))
return;
switch (status) {
case MNT_OK:
printf("Mount: No error.\n");
break;
case MNT3ERR_PERM:
printf("Mount: Not owner.\n");
break;
case MNT3ERR_NOENT:
printf("Mount: No such file or directory.\n");
break;
case MNT3ERR_IO:
printf("Mount: I/O error.\n");
break;
case MNT3ERR_ACCES:
printf("Mount: Permission denied.\n");
break;
case MNT3ERR_NOTDIR:
printf("Mount: Not a directory.\n");
break;
case MNT3ERR_INVAL:
printf("Mount: Invalid argument.\n");
break;
case MNT3ERR_NAMETOOLONG:
printf("Mount: File name too long.\n");
break;
case MNT3ERR_NOTSUPP:
printf("Mount: Operation not supported.\n");
break;
case MNT3ERR_SERVERFAULT:
printf("Mount: Server fault.\n");
break;
default:
printf("Mount: unknown error.\n");
break;
}
}
static int
nfs3mountroot(char *path, struct nfs_file *filep)
{
int rexmit;
int resp_wait;
struct mountres3 res3;
enum clnt_stat status;
rexmit = 0;
resp_wait = 16;
do {
bzero(&res3, sizeof (struct mountres3));
status = brpc_call((rpcprog_t)MOUNTPROG, (rpcvers_t)MOUNTVERS3,
(rpcproc_t)MOUNTPROC_MNT, xdr_path, (caddr_t)&path,
xdr_mountres3, (caddr_t)&res3, rexmit, resp_wait,
&root_to, NULL, AUTH_UNIX);
if (status != RPC_TIMEDOUT)
break;
dprintf("boot: %s:%s mount server not responding.\n",
root_hostname, path);
rexmit = resp_wait;
resp_wait = 0;
xdr_free(xdr_mountres3, (caddr_t)&res3);
} while (status == RPC_TIMEDOUT);
if ((status != RPC_SUCCESS) || (res3.fhs_status != MNT_OK)) {
mnt3_error(res3.fhs_status);
root_to.sin_port = 0;
return (-1);
}
filep->fh.fh3.len = res3.mountres3_u.mountinfo.fhandle.fhandle3_len;
bcopy(res3.mountres3_u.mountinfo.fhandle.fhandle3_val,
filep->fh.fh3.data,
filep->fh.fh3.len);
filep->ftype.type3 = NF3DIR;
filep->version = NFS_V3;
nfs_readsize = nfs_readsize < 32 * 1024 ? nfs_readsize : 32 * 1024;
nfs_readsize = (nfs_readsize != 0 && nfs_readsize < 512) ?
512 : nfs_readsize;
xdr_free(xdr_mountres3, (caddr_t)&res3);
return (0);
}
static int
nfs4init(char *path, uint16_t nfs_port)
{
struct timeval wait;
int fd = -1;
int error = 0;
enum clnt_stat rpc_stat;
struct nfs_file rootpath;
wait.tv_sec = RPC_RCVWAIT_MSEC / 1000;
wait.tv_usec = 0;
if (nfs_port == 0)
nfs_port = 2049;
root_to.sin_port = htons(nfs_port);
root_CLIENT = clntbtcp_create(&root_to, NFS_PROGRAM,
NFS_V4, wait, &fd,
NFS4BUF_SIZE, NFS4BUF_SIZE);
if (root_CLIENT == NULL) {
root_to.sin_port = 0;
return (-1);
}
root_CLIENT->cl_auth =
authunix_create(my_hostname, 0, 1, 1, &fake_gids);
rpc_stat = CLNT_CALL(root_CLIENT, NFSPROC4_NULL, xdr_void, NULL,
xdr_void, NULL, wait);
if (rpc_stat != RPC_SUCCESS) {
dprintf("boot: NULL proc failed NFSv4 service not available\n");
AUTH_DESTROY(root_CLIENT->cl_auth);
CLNT_DESTROY(root_CLIENT);
root_to.sin_port = 0;
return (-1);
}
roothandle.version = NFS_V4;
roothandle.ftype.type4 = NF4DIR;
roothandle.fh.fh4.len = 0;
roothandle.offset = (uint_t)0;
error = lookup(path, &rootpath, TRUE);
if (error) {
printf("boot: lookup %s failed\n", path);
return (-1);
}
roothandle = rootpath;
nfs_readsize = nfs_readsize < 32 * 1024 ? nfs_readsize : 32 * 1024;
nfs_readsize = (nfs_readsize != 0 && nfs_readsize < 512) ?
512 : nfs_readsize;
return (0);
}
static int
atoi(const char *p)
{
int n;
int c, neg = 0;
if (!isdigit(c = *p)) {
while (c == ' ' || c == '\t' || c == '\n')
c = *++p;
switch (c) {
case '-':
neg++;
case '+':
c = *++p;
}
if (!isdigit(c))
return (0);
}
for (n = '0' - c; isdigit(c = *++p); ) {
n *= 10;
n += '0' - c;
}
return (neg ? n : -n);
}
static int
getsubopt(char **optionsp, char * const *tokens, char **valuep)
{
char *s = *optionsp, *p;
int i;
size_t optlen;
*valuep = NULL;
if (*s == '\0')
return (-1);
p = strchr(s, ',');
if (p == NULL) {
p = s + strlen(s);
} else {
*p++ = '\0';
}
*optionsp = p;
p = strchr(s, '=');
if (p == NULL) {
optlen = strlen(s);
*valuep = NULL;
} else {
optlen = p - s;
*valuep = ++p;
}
for (i = 0; tokens[i] != NULL; i++) {
if ((optlen == strlen(tokens[i])) &&
(strncmp(s, tokens[i], optlen) == 0))
return (i);
}
*valuep = s;
return (-1);
}
static char *optlist[] = {
#define OPT_RSIZE 0
MNTOPT_RSIZE,
#define OPT_TIMEO 1
MNTOPT_TIMEO,
#define OPT_VERS 2
MNTOPT_VERS,
#define OPT_PROTO 3
MNTOPT_PROTO,
#define OPT_PORT 4
MNTOPT_PORT,
NULL
};
int
boot_nfs_mountroot(char *str)
{
int status;
enum clnt_stat rpc_stat;
char *root_path = &root_pathbuf[0];
struct timeval wait;
int fd;
int bufsize;
char *opts, *val;
int nfs_version = 0;
int istcp = 1;
int nfs_port = 0;
struct sockaddr_in tmp_addr;
if (root_CLIENT != NULL) {
AUTH_DESTROY(root_CLIENT->cl_auth);
CLNT_DESTROY(root_CLIENT);
root_CLIENT = NULL;
}
root_to.sin_family = AF_INET;
root_to.sin_addr.s_addr = htonl(INADDR_ANY);
root_to.sin_port = htons(0);
mac_init(str);
(void) ipv4_setpromiscuous(TRUE);
if (get_netconfig_strategy() == NCT_BOOTP_DHCP) {
if (boothowto & RB_VERBOSE)
printf("Using BOOTP/DHCP...\n");
if (dhcp() != 0 || setup_root_vars() != 0) {
(void) ipv4_setpromiscuous(FALSE);
if (boothowto & RB_VERBOSE)
printf("BOOTP/DHCP configuration failed!\n");
return (-1);
}
(void) ipv4_setpromiscuous(FALSE);
} else {
if (boothowto & RB_VERBOSE)
printf("Using RARP/BOOTPARAMS...\n");
mac_call_rarp();
dontroute = TRUE;
mac_set_arp_timeout(ARP_INETBOOT_TIMEOUT);
(void) ipv4_setpromiscuous(FALSE);
if (whoami() == FALSE)
return (-1);
if (getfile("root", root_hostname, &root_to.sin_addr,
root_pathbuf) == FALSE)
return (-1);
(void) getfile("rootopts", root_hostname, &tmp_addr.sin_addr,
rootopts);
}
if (boothowto & RB_VERBOSE) {
printf("root server: %s (%s)\n", root_hostname,
inet_ntoa(root_to.sin_addr));
printf("root directory: %s\n", root_pathbuf);
}
(void) gethostname(my_hostname, sizeof (my_hostname));
wait.tv_sec = RPC_RCVWAIT_MSEC / 1000;
wait.tv_usec = 0;
opts = rootopts;
while (*opts) {
int ival;
switch (getsubopt(&opts, optlist, &val)) {
case OPT_RSIZE:
if (val == NULL || !isdigit(*val))
break;
nfs_readsize = atoi(val);
break;
case OPT_TIMEO:
if (val == NULL || !isdigit(*val))
break;
ival = atoi(val);
wait.tv_sec = ival / 10;
wait.tv_usec = (ival % 10) * 100000;
break;
case OPT_VERS:
if (val == NULL || !isdigit(*val))
break;
nfs_version = atoi(val);
break;
case OPT_PROTO:
if (val == NULL || isdigit(*val))
break;
if ((strncmp(val, "udp", 3) == 0))
istcp = 0;
else
istcp = 1;
break;
case OPT_PORT:
if (val == NULL || !isdigit(*val))
break;
nfs_port = atoi(val);
nfs_port = 0;
break;
default:
break;
}
}
switch (nfs_version) {
case NFS_VERSION:
if (nfsmountroot(root_path, &roothandle) == 0)
goto domount;
break;
case NFS_V3:
if (nfs3mountroot(root_path, &roothandle) == 0)
goto domount;
break;
case NFS_V4:
if (istcp && nfs4init(root_path, nfs_port) == 0) {
return (0);
}
}
if (istcp && nfs4init(root_path, nfs_port) == 0) {
return (0);
}
if (nfs3mountroot(root_path, &roothandle) == 0)
goto domount;
if ((status = nfsmountroot(root_path, &roothandle)) != 0)
return (status);
domount:
roothandle.offset = (uint_t)0;
root_to.sin_port = htons(nfs_port);
if (roothandle.version == NFS_VERSION)
bufsize = NFSBUF_SIZE;
else
bufsize = NFS3BUF_SIZE;
if (istcp) {
fd = -1;
root_CLIENT = clntbtcp_create(&root_to, NFS_PROGRAM,
roothandle.version, wait, &fd, bufsize, bufsize);
if (root_CLIENT != NULL) {
root_CLIENT->cl_auth =
authunix_create(my_hostname, 0, 1, 1, &fake_gids);
rpc_stat = CLNT_CALL(root_CLIENT, 0,
xdr_void, NULL, xdr_void, NULL, wait);
if (rpc_stat == RPC_SUCCESS)
return (0);
AUTH_DESTROY(root_CLIENT->cl_auth);
CLNT_DESTROY(root_CLIENT);
root_CLIENT = NULL;
}
}
fd = -1;
root_CLIENT = clntbudp_bufcreate(&root_to, NFS_PROGRAM,
roothandle.version, wait, &fd, bufsize, bufsize);
if (root_CLIENT == NULL)
return (-1);
root_CLIENT->cl_auth =
authunix_create(my_hostname, 0, 1, 1, &fake_gids);
rpc_stat = CLNT_CALL(root_CLIENT, 0,
xdr_void, NULL, xdr_void, NULL, wait);
if (rpc_stat == RPC_SUCCESS)
return (0);
AUTH_DESTROY(root_CLIENT->cl_auth);
CLNT_DESTROY(root_CLIENT);
root_CLIENT = NULL;
return (-1);
}