/* * Copyright (c) 1989 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Rick Macklem at The University of Guelph. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint char copyright[] = "@(#) Copyright (c) 1989 Regents of the University of California.\n\ All rights reserved.\n"; #endif not lint #ifndef lint /*static char sccsid[] = "from: @(#)mountd.c 5.14 (Berkeley) 2/26/91";*/ static char rcsid[] = "$Id: mountd.c,v 1.11 1994/01/06 22:48:51 ws Exp $"; #endif not lint #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pathnames.h" struct ufid { u_short ufid_len; ino_t ufid_ino; long ufid_gen; }; /* * Structures for keeping the mount list and export list */ struct mountlist { struct mountlist *ml_next; char ml_host[MNTNAMLEN+1]; char ml_dirp[MNTPATHLEN+1]; }; struct exportlist { struct exportlist *ex_next; struct exportlist *ex_prev; struct grouplist *ex_groups; int ex_rootuid; int ex_exflags; int ex_alldirflg; dev_t ex_dev; char ex_dirp[MNTPATHLEN+1]; }; struct grouplist { struct grouplist *gr_next; struct hostent *gr_hp; }; /* Global defs */ void mntsrv(); int umntall_each(), xdr_fhs(), xdr_mlist(), xdr_dir(), xdr_explist(); void add_mlist(), del_mlist(), get_exportlist(), get_mountlist(); void send_umntall(); struct exportlist exphead; struct mountlist *mlhead; char exname[MAXPATHLEN]; int def_rootuid = -2; int root_only = 1; extern int errno; /* * Mountd server for NFS mount protocol as described in: * NFS: Network File System Protocol Specification, RFC1094, Appendix A * The optional arguments are the exports file name * default: _PATH_EXPORTS * and "-n" to allow nonroot mount. */ main(argc, argv) int argc; char **argv; { FILE *pidfile; extern int optind; extern char *optarg; SVCXPRT *transp; int c; int sock = 0; int proto = 0; int from_inetd = 1; struct sockaddr_in from; int fromlen; while ((c = getopt(argc, argv, "n")) != EOF) switch (c) { case 'n': root_only = 0; break; default: fprintf(stderr, "Usage: mountd [-n] [export_file]\n"); exit(1); }; argc -= optind; argv += optind; exphead.ex_next = exphead.ex_prev = (struct exportlist *)0; mlhead = (struct mountlist *)0; if (argc == 1) { strncpy(exname, *argv, MAXPATHLEN-1); exname[MAXPATHLEN-1] = '\0'; } else strcpy(exname, _PATH_EXPORTS); if (getsockname(0, (struct sockaddr *)&from, &fromlen) < 0) { from_inetd = 0; sock = RPC_ANYSOCK; proto = IPPROTO_UDP; } if (!from_inetd) { daemon(0, 0); pmap_unset(MOUNTPROG, MOUNTVERS); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); } openlog("mountd", LOG_PID, LOG_DAEMON); if ((transp = svcudp_create(sock)) == NULL) { syslog(LOG_ERR, "Can't create socket: %m"); exit(1); } if (!svc_register(transp, MOUNTPROG, MOUNTVERS, mntsrv, proto)) { syslog(LOG_ERR, "Can't register mount"); exit(1); } get_exportlist(); get_mountlist(); signal(SIGHUP, get_exportlist); signal(SIGTERM, send_umntall); pidfile = fopen(_PATH_MOUNTDPID, "w"); if (pidfile != NULL) { fprintf(pidfile, "%d\n", getpid()); fclose(pidfile); } svc_run(); syslog(LOG_ERR, "Mountd died"); exit(1); } /* * The mount rpc service */ void mntsrv(rqstp, transp) register struct svc_req *rqstp; register SVCXPRT *transp; { register struct grouplist *grp; register u_long **addrp; register struct exportlist *ep; nfsv2fh_t nfh; struct authunix_parms *ucr; struct stat stb; struct hostent *hp; u_long saddr; char dirpath[MNTPATHLEN+1]; int bad = ENOENT; int omask; uid_t uid = -2; /* Get authorization */ switch (rqstp->rq_cred.oa_flavor) { case AUTH_UNIX: ucr = (struct authunix_parms *)rqstp->rq_clntcred; uid = ucr->aup_uid; break; case AUTH_NULL: default: break; } saddr = transp->xp_raddr.sin_addr.s_addr; hp = (struct hostent *)0; switch (rqstp->rq_proc) { case NULLPROC: if (!svc_sendreply(transp, xdr_void, (caddr_t)0)) syslog(LOG_ERR, "Can't send reply"); return; case MOUNTPROC_MNT: if (uid != 0 && root_only) { svcerr_weakauth(transp); return; } if (!svc_getargs(transp, xdr_dir, dirpath)) { svcerr_decode(transp); return; } /* Check to see if it's a valid dirpath */ if (stat(dirpath, &stb) < 0 || (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode))) { if (!svc_sendreply(transp, xdr_long, (caddr_t)&bad)) syslog(LOG_ERR, "Can't send reply"); return; } /* Check in the exports list */ omask = sigblock(sigmask(SIGHUP)); ep = exphead.ex_next; while (ep != NULL) { if (!strcmp(ep->ex_dirp, dirpath) || (stb.st_dev == ep->ex_dev && ep->ex_alldirflg)) { grp = ep->ex_groups; if (grp == NULL) break; /* Check for a host match */ addrp = (u_long **)grp->gr_hp->h_addr_list; for (;;) { if (**addrp == saddr) break; if (*++addrp == NULL) if (grp = grp->gr_next) { addrp = (u_long **) grp->gr_hp->h_addr_list; } else { bad = EACCES; if (!svc_sendreply(transp, xdr_long, (caddr_t)&bad)) syslog(LOG_ERR, "Can't send reply"); sigsetmask(omask); return; } } hp = grp->gr_hp; break; } ep = ep->ex_next; } sigsetmask(omask); if (ep == NULL) { bad = EACCES; if (!svc_sendreply(transp, xdr_long, (caddr_t)&bad)) syslog(LOG_ERR, "Can't send reply"); return; } /* Get the file handle */ bzero((caddr_t)&nfh, sizeof(nfh)); if (getfh(dirpath, (fhandle_t *)&nfh) < 0) { bad = errno; if (!svc_sendreply(transp, xdr_long, (caddr_t)&bad)) syslog(LOG_ERR, "Can't send reply"); return; } if (!svc_sendreply(transp, xdr_fhs, (caddr_t)&nfh)) syslog(LOG_ERR, "Can't send reply"); if (hp == NULL) hp = gethostbyaddr((caddr_t)&saddr, sizeof(saddr), AF_INET); if (hp) add_mlist(hp->h_name, dirpath); return; case MOUNTPROC_DUMP: if (!svc_sendreply(transp, xdr_mlist, (caddr_t)0)) syslog(LOG_ERR, "Can't send reply"); return; case MOUNTPROC_UMNT: if (uid != 0 && root_only) { svcerr_weakauth(transp); return; } if (!svc_getargs(transp, xdr_dir, dirpath)) { svcerr_decode(transp); return; } if (!svc_sendreply(transp, xdr_void, (caddr_t)0)) syslog(LOG_ERR, "Can't send reply"); hp = gethostbyaddr((caddr_t)&saddr, sizeof(saddr), AF_INET); if (hp) del_mlist(hp->h_name, dirpath); return; case MOUNTPROC_UMNTALL: if (uid != 0 && root_only) { svcerr_weakauth(transp); return; } if (!svc_sendreply(transp, xdr_void, (caddr_t)0)) syslog(LOG_ERR, "Can't send reply"); hp = gethostbyaddr((caddr_t)&saddr, sizeof(saddr), AF_INET); if (hp) del_mlist(hp->h_name, (char *)0); return; case MOUNTPROC_EXPORT: case MOUNTPROC_EXPORTALL: get_exportlist(); if (!svc_sendreply(transp, xdr_explist, (caddr_t)exphead.ex_next)) syslog(LOG_ERR, "Can't send reply"); return; default: svcerr_noproc(transp); return; } } /* * Xdr conversion for a dirpath string */ xdr_dir(xdrsp, dirp) XDR *xdrsp; char *dirp; { return (xdr_string(xdrsp, &dirp, MNTPATHLEN)); } /* * Xdr routine to generate fhstatus */ xdr_fhs(xdrsp, nfh) XDR *xdrsp; nfsv2fh_t *nfh; { long ok = 0; if (!xdr_long(xdrsp, &ok)) return (0); return (xdr_opaque(xdrsp, (caddr_t)nfh, NFSX_FH)); } xdr_mlist(xdrsp, cp) XDR *xdrsp; caddr_t cp; { register struct mountlist *mlp; int true = 1; int false = 0; char *strp; mlp = mlhead; while (mlp) { if (!xdr_bool(xdrsp, &true)) return (0); strp = &mlp->ml_host[0]; if (!xdr_string(xdrsp, &strp, MNTNAMLEN)) return (0); strp = &mlp->ml_dirp[0]; if (!xdr_string(xdrsp, &strp, MNTPATHLEN)) return (0); mlp = mlp->ml_next; } if (!xdr_bool(xdrsp, &false)) return (0); return (1); } /* * Xdr conversion for export list */ xdr_explist(xdrsp, cp) XDR *xdrsp; caddr_t cp; { register struct exportlist *ep = (struct exportlist *)cp; register struct grouplist *grp; int true = 1; int false = 0; char *strp; int omask; omask = sigblock(sigmask(SIGHUP)); while (ep != NULL) { if (!xdr_bool(xdrsp, &true)) goto errout; strp = &ep->ex_dirp[0]; if (!xdr_string(xdrsp, &strp, MNTPATHLEN)) goto errout; grp = ep->ex_groups; while (grp != NULL) { if (!xdr_bool(xdrsp, &true)) goto errout; strp = grp->gr_hp->h_name; if (!xdr_string(xdrsp, &strp, MNTNAMLEN)) goto errout; grp = grp->gr_next; } if (!xdr_bool(xdrsp, &false)) goto errout; ep = ep->ex_next; } sigsetmask(omask); if (!xdr_bool(xdrsp, &false)) return (0); return (1); errout: sigsetmask(omask); return (0); } #define LINESIZ 10240 char line[LINESIZ]; /* * Get the export list */ void get_exportlist() { register struct hostent *hp, *nhp; register char **addrp, **naddrp; register int i; register struct grouplist *grp; register struct exportlist *ep, *ep2; struct statfs stfsbuf; nfsv2fh_t nfh; struct export_args args; struct stat sb; FILE *inf; char *cp, *endcp; char savedc; int len, dirplen; int rootuid, exflags, alldirflg; u_long saddr; struct exportlist *fep; static int first = 0; static struct stat last_exportstat; /* * Check if the file has changed */ if (first++) { if (stat(exname, &sb) < 0) { syslog(LOG_ERR, "stat of export file %s failed. %m", exname); ep = exphead.ex_next; while (ep != NULL) { ep2 = ep; ep = ep->ex_next; free_exp(ep2); } return; } if (last_exportstat.st_mtime == sb.st_mtime) return; } /* * First, get rid of the old list */ ep = exphead.ex_next; while (ep != NULL) { ep2 = ep; ep = ep->ex_next; free_exp(ep2); } /* * Read in the exports file and build the list, calling * exportfs() as we go along */ exphead.ex_next = exphead.ex_prev = (struct exportlist *)0; if ((inf = fopen(exname, "r")) == NULL) { syslog(LOG_ERR, "Can't open %s", exname); exit(2); } while (fgets(line, LINESIZ, inf)) { exflags = MNT_EXPORTED; rootuid = def_rootuid; alldirflg = 0; cp = line; nextfield(&cp, &endcp); /* * Get file system devno and see if an entry for this * file system already exists. */ savedc = *endcp; *endcp = '\0'; if (stat(cp, &sb) < 0 || (!S_ISDIR(sb.st_mode) && !S_ISREG(sb.st_mode))) { syslog(LOG_ERR, "Bad Exports File, %s: %s, mountd Failed", cp, "Not a directory or regular file"); exit(2); } fep = (struct exportlist *)0; ep = exphead.ex_next; while (ep) { if (ep->ex_dev == sb.st_dev) { fep = ep; break; } ep = ep->ex_next; } *endcp = savedc; /* * Create new exports list entry */ len = endcp-cp; if (len <= MNTPATHLEN && len > 0) { ep = (struct exportlist *)malloc(sizeof(*ep)); if (ep == NULL) goto err; ep->ex_next = ep->ex_prev = (struct exportlist *)0; ep->ex_groups = (struct grouplist *)0; bcopy(cp, ep->ex_dirp, len); ep->ex_dirp[len] = '\0'; dirplen = len; } else { syslog(LOG_ERR, "Bad Exports File, mountd Failed"); exit(2); } cp = endcp; nextfield(&cp, &endcp); len = endcp-cp; while (len > 0) { savedc = *endcp; *endcp = '\0'; if (len > MNTNAMLEN) goto more; if (*cp == '-') { do_opt(cp + 1, fep, ep, &exflags, &rootuid, &alldirflg); goto more; } if (isdigit(*cp)) { saddr = inet_addr(cp); if (saddr == -1 || (hp = gethostbyaddr((caddr_t)&saddr, sizeof(saddr), AF_INET)) == NULL) { syslog(LOG_ERR, "Bad Exports File, %s: %s", cp, "Gethostbyaddr failed, ignored"); goto more; } } else if ((hp = gethostbyname(cp)) == NULL) { syslog(LOG_ERR, "Bad Exports File, %s: %s", cp, "Gethostbyname failed, ignored"); goto more; } grp = (struct grouplist *) malloc(sizeof(struct grouplist)); if (grp == NULL) goto err; nhp = grp->gr_hp = (struct hostent *) malloc(sizeof(struct hostent)); if (nhp == NULL) goto err; bcopy((caddr_t)hp, (caddr_t)nhp, sizeof(struct hostent)); i = strlen(hp->h_name)+1; nhp->h_name = (char *)malloc(i); if (nhp->h_name == NULL) goto err; bcopy(hp->h_name, nhp->h_name, i); addrp = hp->h_addr_list; i = 1; while (*addrp++) i++; naddrp = nhp->h_addr_list = (char **) malloc(i*sizeof(char *)); if (naddrp == NULL) goto err; addrp = hp->h_addr_list; while (*addrp) { *naddrp = (char *) malloc(hp->h_length); if (*naddrp == NULL) goto err; bcopy(*addrp, *naddrp, hp->h_length); addrp++; naddrp++; } *naddrp = (char *)0; grp->gr_next = ep->ex_groups; ep->ex_groups = grp; more: cp = endcp; *cp = savedc; nextfield(&cp, &endcp); len = endcp - cp; } if (fep == NULL) { args.exroot = rootuid; cp = (char *)0; while (statfs(ep->ex_dirp, &stfsbuf) < 0 || mount(MOUNT_EXPORT, ep->ex_dirp, stfsbuf.f_flags|(MNT_UPDATE|exflags), &args) < 0) { /* 08 Sep 92*/ if (cp) *cp-- = savedc; else cp = ep->ex_dirp + dirplen - 1; #ifdef OMIT if (cp == NULL) cp = ep->ex_dirp + dirplen - 1; else *cp = savedc; #endif /* OMIT*/ /* back up over the last component */ while (*cp == '/' && cp > ep->ex_dirp) cp--; /* 08 Sep 92*/ while (cp > ep->ex_dirp && *(cp - 1) != '/') cp--; if (cp == ep->ex_dirp) { syslog(LOG_WARNING, "Can't export %s", ep->ex_dirp); free_exp(ep); goto nextline; } savedc = *cp; *cp = '\0'; } if (cp) *cp = savedc; ep->ex_rootuid = rootuid; ep->ex_exflags = exflags; ep->ex_alldirflg = alldirflg; } else { if (alldirflg || fep->ex_alldirflg) { syslog(LOG_WARNING, "Can't export alldirs plus other exports"); free_exp(ep); goto nextline; } ep->ex_rootuid = fep->ex_rootuid; ep->ex_exflags = fep->ex_exflags; ep->ex_alldirflg = 0; } if (getfh(ep->ex_dirp, (fhandle_t *)&nfh) < 0) { syslog(LOG_WARNING, "Can't export %s", ep->ex_dirp); free_exp(ep); goto nextline; } ep->ex_dev = sb.st_dev; ep->ex_next = exphead.ex_next; ep->ex_prev = &exphead; if (ep->ex_next != NULL) ep->ex_next->ex_prev = ep; exphead.ex_next = ep; nextline: ; } fclose(inf); return; err: syslog(LOG_ERR, "No more memory: mountd Failed"); exit(2); } /* * Parse out the next white space separated field */ nextfield(cp, endcp) char **cp; char **endcp; { register char *p; p = *cp; while (*p == ' ' || *p == '\t') p++; if (*p == '\n' || *p == '\0') { *cp = *endcp = p; return; } *cp = p++; while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0') p++; *endcp = p; } /* * Parse the option string */ do_opt(cpopt, fep, ep, exflagsp, rootuidp, alldirflgp) register char *cpopt; struct exportlist *fep, *ep; int *exflagsp, *rootuidp, *alldirflgp; { register char *cpoptarg, *cpoptend; while (cpopt && *cpopt) { if (cpoptend = index(cpopt, ',')) *cpoptend++ = '\0'; if (cpoptarg = index(cpopt, '=')) *cpoptarg++ = '\0'; if (!strcmp(cpopt, "ro") || !strcmp(cpopt, "o")) { if (fep && (fep->ex_exflags & MNT_EXRDONLY) == 0) syslog(LOG_WARNING, "ro failed for %s", ep->ex_dirp); else *exflagsp |= MNT_EXRDONLY; } else if (!strcmp(cpopt, "root") || !strcmp(cpopt, "r")) { if (cpoptarg && isdigit(*cpoptarg)) { *rootuidp = atoi(cpoptarg); if (fep && fep->ex_rootuid != *rootuidp) syslog(LOG_WARNING, "uid failed for %s", ep->ex_dirp); } else syslog(LOG_WARNING, "uid failed for %s", ep->ex_dirp); } else if (!strcmp(cpopt, "alldirs") || !strcmp(cpopt, "a")) { *alldirflgp = 1; } else syslog(LOG_WARNING, "opt %s ignored for %s", cpopt, ep->ex_dirp); cpopt = cpoptend; } } #define STRSIZ (MNTNAMLEN+MNTPATHLEN+50) /* * Routines that maintain the remote mounttab */ void get_mountlist() { register struct mountlist *mlp, **mlpp; register char *eos, *dirp; int len; char str[STRSIZ]; FILE *mlfile; if ((mlfile = fopen(_PATH_RMOUNTLIST, "r")) == NULL) { syslog(LOG_WARNING, "Can't open %s", _PATH_RMOUNTLIST); return; } mlpp = &mlhead; while (fgets(str, STRSIZ, mlfile) != NULL) { if ((dirp = index(str, '\t')) == NULL && (dirp = index(str, ' ')) == NULL) continue; mlp = (struct mountlist *)malloc(sizeof (*mlp)); len = dirp-str; if (len > MNTNAMLEN) len = MNTNAMLEN; bcopy(str, mlp->ml_host, len); mlp->ml_host[len] = '\0'; while (*dirp == '\t' || *dirp == ' ') dirp++; if ((eos = index(dirp, '\t')) == NULL && (eos = index(dirp, ' ')) == NULL && (eos = index(dirp, '\n')) == NULL) len = strlen(dirp); else len = eos-dirp; if (len > MNTPATHLEN) len = MNTPATHLEN; bcopy(dirp, mlp->ml_dirp, len); mlp->ml_dirp[len] = '\0'; mlp->ml_next = (struct mountlist *)0; *mlpp = mlp; mlpp = &mlp->ml_next; } fclose(mlfile); } void del_mlist(hostp, dirp) register char *hostp, *dirp; { register struct mountlist *mlp, **mlpp; FILE *mlfile; int fnd = 0; mlpp = &mlhead; mlp = mlhead; while (mlp) { if (!strcmp(mlp->ml_host, hostp) && (!dirp || !strcmp(mlp->ml_dirp, dirp))) { fnd = 1; *mlpp = mlp->ml_next; free((caddr_t)mlp); } mlpp = &mlp->ml_next; mlp = mlp->ml_next; } if (fnd) { if ((mlfile = fopen(_PATH_RMOUNTLIST, "w")) == NULL) { syslog(LOG_WARNING, "Can't update %s", _PATH_RMOUNTLIST); return; } mlp = mlhead; while (mlp) { fprintf(mlfile, "%s %s\n", mlp->ml_host, mlp->ml_dirp); mlp = mlp->ml_next; } fclose(mlfile); } } void add_mlist(hostp, dirp) register char *hostp, *dirp; { register struct mountlist *mlp, **mlpp; FILE *mlfile; mlpp = &mlhead; mlp = mlhead; while (mlp) { if (!strcmp(mlp->ml_host, hostp) && !strcmp(mlp->ml_dirp, dirp)) return; mlpp = &mlp->ml_next; mlp = mlp->ml_next; } mlp = (struct mountlist *)malloc(sizeof (*mlp)); strncpy(mlp->ml_host, hostp, MNTNAMLEN); mlp->ml_host[MNTNAMLEN] = '\0'; strncpy(mlp->ml_dirp, dirp, MNTPATHLEN); mlp->ml_dirp[MNTPATHLEN] = '\0'; mlp->ml_next = (struct mountlist *)0; *mlpp = mlp; if ((mlfile = fopen(_PATH_RMOUNTLIST, "a")) == NULL) { syslog(LOG_WARNING, "Can't update %s", _PATH_RMOUNTLIST); return; } fprintf(mlfile, "%s %s\n", mlp->ml_host, mlp->ml_dirp); fclose(mlfile); } /* * This function is called via. SIGTERM when the system is going down. * It sends a broadcast RPCMNT_UMNTALL. */ void send_umntall() { (void) clnt_broadcast(MOUNTPROG, MOUNTVERS, MOUNTPROC_UMNTALL, xdr_void, (caddr_t)0, xdr_void, (caddr_t)0, umntall_each); exit(); } umntall_each(resultsp, raddr) caddr_t resultsp; struct sockaddr_in *raddr; { return (1); } /* * Free up an exports list component */ free_exp(ep) register struct exportlist *ep; { register struct grouplist *grp; register char **addrp; struct grouplist *grp2; grp = ep->ex_groups; while (grp != NULL) { addrp = grp->gr_hp->h_addr_list; while (*addrp) free(*addrp++); free((caddr_t)grp->gr_hp->h_addr_list); free(grp->gr_hp->h_name); free((caddr_t)grp->gr_hp); grp2 = grp; grp = grp->gr_next; free((caddr_t)grp2); } free((caddr_t)ep); }