/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
#pragma ident "%Z%%M% %I% %E% SMI"
#ifndef UUCHECK
#include "uucp.h"
#endif
/* field array indexes for PERMISSIONS parameters */
#define U_LOGNAME 0
#define U_MACHINE 1
#define U_CALLBACK 2
#define U_REQUEST 3
#define U_SENDFILES 4
#define U_READPATH 5
#define U_WRITEPATH 6
#define U_NOREADPATH 7
#define U_NOWRITEPATH 8
#define U_MYNAME 9
#define U_COMMANDS 10
#define U_VALIDATE 11
#define U_PUBDIR 12
#define U_DIRECT 13
#define U_ALIAS 14
#define U_PATH 15
/* NUMFLDS should be one more than the highest U_ value */
#define NUMFLDS 16
/* fields found in PERMISSIONS for requested system/login */
static char *_Flds[NUMFLDS];
/* keyword/value structure */
struct keywords {
char* kword;
int kvalue;
};
static struct keywords _Kwords[] = {
{"LOGNAME", U_LOGNAME},
{"MACHINE", U_MACHINE},
{"CALLBACK", U_CALLBACK},
{"REQUEST", U_REQUEST},
{"SENDFILES", U_SENDFILES},
{"READ", U_READPATH},
{"WRITE", U_WRITEPATH},
{"NOREAD", U_NOREADPATH},
{"NOWRITE", U_NOWRITEPATH},
{"MYNAME", U_MYNAME},
{"COMMANDS", U_COMMANDS},
{"VALIDATE", U_VALIDATE},
{"PUBDIR", U_PUBDIR},
{"DIRECT", U_DIRECT},
{"ALIAS", U_ALIAS},
{"PATH", U_PATH},
};
#define MAXCMDS 30
#define MAXPATHS 20
/* for all options on paths - read, write, noread, nowrite */
/* NB: all pointers assumed to point to static data */
static char *_RPaths[MAXPATHS+1];
static char *_WPaths[MAXPATHS+1];
static char *_NoRPaths[MAXPATHS+1];
static char *_NoWPaths[MAXPATHS+1];
static char *_Commands[MAXCMDS+1];
static char _Cmd_defaults[BUFSIZ];
/* option variables */
static int _Request; /* TRUE can request, FALSE can not request files */
static int _Switch; /* FALSE requires a call back to send any files */
static int _CallBack; /* TRUE for call back for any transaction */
static int _NoSpool; /* TRUE if delivering directly to destination file */
static char _MyName[MAXBASENAME+1]; /* Myname from PERMISSIONS file */
/* NB: _Pubdir and _Path assumed to point to dynamic data */
static char *_Pubdir = NULL; /* PUBDIR from PERMISSIONS file */
static char *_Path = NULL; /* PATH from PERMISSIONS file */
struct name_value
{
char *name;
char *value;
};
/* file pointer for PERMISSIONS */
static FILE *Fp = NULL;
/* functions */
extern char *next_token(), *nextarg();
extern int parse_tokens(), canPath(), mkdirs();
static void fillFlds();
static void fillList();
static int cmdMatch(), listMatch(), nameMatch(),
userFind(), validateFind();
int
noSpool()
{
return(_NoSpool);
}
/*
* fill in fields for login name
* name - the login id
* rmtname - remote system name
*
* return:
* 0 -> found login name
* FAIL -> did not find login
*/
int
logFind(name, rmtname)
char *name, *rmtname;
{
int ret;
DEBUG(5, "logFind called (name: %s, ", name);
DEBUG(5, "rmtname: %s)\n", rmtname);
ret = validateFind (rmtname);
if (ret == SUCCESS) { /* found VALIDATE entry */
ret = userFind (name, rmtname, U_VALIDATE);
if (ret) {
DEBUG(5, "machine/login match failed%s", "");
return(FAIL);
}
}
else
ret = userFind (name, "", U_LOGNAME);
DEBUG(7, "_Request (%s), ",
requestOK() ? "TRUE" : "FALSE");
DEBUG(7, "_Switch (%s), ",
switchRole() ? "TRUE" : "FALSE");
DEBUG(7, "_CallBack (%s), ",
callBack() ? "TRUE" : "FALSE");
DEBUG(7, "_MyName (%s), ", _MyName);
DEBUG(7, "_NoSpool (%s), ",
noSpool() ? "TRUE" : "FALSE");
return(ret);
}
/*
* fill in fields for machine name
* return:
* 0 -> found machine name
* FAIL -> did not find machine
*/
int
mchFind(name)
char *name;
{
int i, ret;
DEBUG(5, "mchFind called (%s)\n", name);
if ( (ret = userFind (name, "", U_MACHINE)) == FAIL)
/* see if there is a default line */
(void) userFind ("OTHER", "", U_MACHINE);
/* mchFind is from MASTER mode - switch role is always ok */
_Switch = TRUE;
DEBUG(7, "_Request (%s), ",
requestOK() ? "TRUE" : "FALSE");
DEBUG(7, "_Switch (%s), ",
switchRole() ? "TRUE" : "FALSE");
DEBUG(7, "_CallBack (%s), ",
callBack() ? "TRUE" : "FALSE");
DEBUG(7, "_MyName (%s), ", _MyName);
DEBUG(7, "_NoSpool (%s), ",
noSpool() ? "TRUE" : "FALSE");
for (i=0; _Commands[i] != NULL; i++)
DEBUG(7, "_Commands %s\n", _Commands[i]);
return(ret);
}
/*
* this function will find a login name in the LOGNAME
* field.
* input:
* name -> who the remote says he/she is
* return:
* SUCCESS -> found
* FAIL -> not found
*/
static int
nameMatch(name, fld)
char *name, *fld;
{
char *arg;
if (fld == NULL)
return(FAIL);
while (*fld) {
fld = nextarg(fld, &arg);
if (EQUALS(arg, name))
return(SUCCESS);
}
return (FAIL);
}
/*
* interpret the _Flds options and set the option variables
*/
static void
fillFlds()
{
if (_Flds[U_REQUEST] != NULL) {
if (EQUALS(_Flds[U_REQUEST], "yes"))
_Request = TRUE;
else
_Request = FALSE;
}
if (_Flds[U_SENDFILES] != NULL) {
if (EQUALS(_Flds[U_SENDFILES], "yes"))
_Switch = TRUE;
else
_Switch = FALSE;
}
if (_Flds[U_CALLBACK] != NULL) {
if (EQUALS(_Flds[U_CALLBACK], "yes"))
_CallBack = TRUE;
else
_CallBack = FALSE;
}
if (_Flds[U_DIRECT] != NULL) {
if (EQUALS(_Flds[U_DIRECT], "yes"))
_NoSpool = TRUE;
else
_NoSpool = FALSE;
}
if (_Flds[U_MYNAME] != NULL) {
strncpy(_MyName, _Flds[U_MYNAME], MAXBASENAME);
_MyName[MAXBASENAME] = NULLCHAR;
}
if (_Flds[U_PUBDIR] != NULL) {
if (_Pubdir != NULL)
free(_Pubdir); /* get rid of previous one */
_Pubdir = strdup(_Flds[U_PUBDIR]);
#ifndef UUCHECK
ASSERT(_Pubdir != NULL, Ct_ALLOCATE, _Flds[U_PUBDIR], 0);
#else /* UUCHECK */
if (_Pubdir == NULL) {
perror(gettext("malloc() error"));
exit(1);
}
#endif /* UUCHECK */
Pubdir = _RPaths[0] = _WPaths[0] = _Pubdir; /* reset default */
}
if (_Flds[U_PATH] != NULL) {
if (_Path != NULL)
free(_Path); /* get rid of previous one */
_Path = strdup(_Flds[U_PATH]);
#ifndef UUCHECK
ASSERT(_Path != NULL, Ct_ALLOCATE, _Flds[U_PATH], 0);
#else /* UUCHECK */
if (_Path == NULL) {
perror(gettext("malloc() error"));
exit(1);
}
#endif /* UUCHECK */
}
return;
}
/*
* fill in the list vector for the system/login
* input:
* type - list type (read, write, noread, nowrite, command)
* output:
* list - filled in with items.
* return:
* number of items in list
*/
static void
fillList(type, list)
int type;
char *list[];
{
char *p;
int num;
int maxlist = 0;
p = _Flds[type];
/* find list limit */
if (type == U_READPATH || type == U_WRITEPATH
|| type == U_NOREADPATH || type == U_NOWRITEPATH)
maxlist = MAXPATHS;
else if (type == U_COMMANDS)
maxlist = MAXCMDS;
if (p == NULL || !*p) {
/* no names specified, default already setup */
return;
}
num = 0;
while (*p && num < maxlist) {
list[num] = p;
if (*p == ':') { /* null path */
*p++ = NULLCHAR;
continue;
}
while (*p && *p != ':')
p++;
if (*p == ':')
*p++ = NULLCHAR;
DEBUG(7, "list (%s) ", list[num]);
num++;
}
DEBUG(7, "num = %d\n", num);
list[num] = NULL;
return;
}
/*
* Find the line of PERMISSIONS for login.
* The search is determined by the type field
* (type=U_LOGNAME, U_MACHINE or U_VALIDATE)
* For U_LOGNAME:
* search for "name" in a LOGNAME= option
* For U_MACHINE:
* search for "name" in a MACHINE= option
* For U_VALIDATE:
* search for "rmtname" in a VALIDATE= option and
* for the same entry see if "name" is in the LOGNAME= option
* input:
* name -> search name
* logname -> for validate entry
* type -> U_MACHINE or U_LOGNAME
* output:
* The global values of all options will be set
* (e.g. _RPaths, _WPaths, _Request, ...)
* return:
* 0 -> ok
* FAIL -> no match found
*/
static int
userFind(name, rmtname, type)
char *name, *rmtname;
int type;
{
char *p, *arg, *buf = NULL;
static char default_buf[BUFSIZ];
if (name != NULL && strcmp(name, "DEFAULT") != 0) {
/* call ourself recursively to set defaults */
(void) userFind("DEFAULT", "", U_MACHINE);
} else {
/*
* Handle case where looking for DEFAULT entry.
* First initialize all defaults to their "base"
* values. Then the DEFAULT entry, if found,
* will override these settings.
*/
_Request = FALSE;
_CallBack = FALSE;
_Switch = FALSE;
_NoSpool = FALSE;
_MyName[0] = NULLCHAR;
_RPaths[0] = _WPaths[0] = PUBDIR; /* default is public */
_RPaths[1] = _WPaths[1] = NULLCHAR;
_NoRPaths[0] = NULLCHAR;
_NoWPaths[0] = NULLCHAR;
if (_Pubdir != NULL)
free(_Pubdir);
Pubdir = _Pubdir = strdup(PUBDIR);
if (_Path != NULL)
free(_Path);
_Path = strdup(PATH);
/* set up Commands defaults */
_Flds[U_COMMANDS] = strcpy(_Cmd_defaults, DEFAULTCMDS);
fillList(U_COMMANDS, _Commands);
/*
* put defaults we read in in here so they're not overwritten
* by non-DEFAULT entries.
*/
buf = default_buf;
}
if (name == NULL) /* use defaults */
return(0); /* I don't think this will ever happen */
if ( (Fp = fopen(PERMISSIONS, "r")) == NULL) {
DEBUG(5, "can't open %s\n", PERMISSIONS);
return(FAIL);
}
for (;;) {
if (parse_tokens (_Flds, buf) != 0) {
(void) fclose(Fp);
DEBUG(5, "name (%s) not found; return FAIL\n", name);
return(FAIL);
}
p = _Flds[type];
while (p && *p) {
p = nextarg(p, &arg);
switch (type) {
case U_VALIDATE:
if (EQUALS(arg, rmtname)
&& nameMatch(name, _Flds[U_LOGNAME])==SUCCESS)
break;
continue;
case U_LOGNAME:
if (EQUALS(arg, name))
break;
continue;
case U_MACHINE:
if (EQUALSN(arg, name, MAXBASENAME))
break;
continue;
}
(void) fclose(Fp);
fillFlds();
/* fill in path lists */
fillList(U_READPATH, _RPaths);
fillList(U_WRITEPATH, _WPaths);
if (!requestOK())
_Flds[U_NOREADPATH] = "/";
fillList(U_NOREADPATH, _NoRPaths);
fillList(U_NOWRITEPATH, _NoWPaths);
/* fill in command list */
fillList(U_COMMANDS, _Commands);
return(0);
}
}
}
/*
* see if name is in a VALIDATE option
* return:
* FAIL -> not found
* SUCCESS -> found
*/
static int
validateFind(name)
char *name;
{
if ( (Fp = fopen(PERMISSIONS, "r")) == NULL) {
DEBUG(5, "can't open %s\n", PERMISSIONS);
return(FAIL);
}
for (;;) {
if (parse_tokens (_Flds, NULL) != 0) {
DEBUG(5, "validateFind (%s) FAIL\n", name);
(void) fclose(Fp);
return(FAIL);
}
if (_Flds[U_VALIDATE] == NULL)
continue;
if (nameMatch(name, _Flds[U_VALIDATE])==SUCCESS) {
(void) fclose(Fp);
return (SUCCESS);
}
}
}
/*
* see if name is in an ALIAS option
* return:
* NULL -> not found
* otherwise -> machine name
*/
char *
aliasFind(name)
char *name;
{
if ( (Fp = fopen(PERMISSIONS, "r")) == NULL) {
DEBUG(5, "can't open %s\n", PERMISSIONS);
return(NULL);
}
for (;;) {
if (parse_tokens (_Flds, NULL) != 0) {
DEBUG(5, "aliasFind (%s) FAIL\n", name);
(void) fclose(Fp);
return(NULL);
}
if (_Flds[U_ALIAS] == NULL)
continue;
if (nameMatch(name, _Flds[U_ALIAS])==SUCCESS) {
(void) fclose(Fp);
#ifndef UUCHECK
ASSERT(strchr(_Flds[U_MACHINE], ':') == NULL,
"PERMISSIONS file: ALIAS is one-to-many:",
_Flds[U_MACHINE], 0);
#else /* UUCHECK */
if (strchr(_Flds[U_MACHINE], ':') != NULL) {
printf(gettext("ALIAS is one-to-many: %s -> %s\n"),
name, _Flds[U_MACHINE]);
return(NULL);
}
#endif /* UUCHECK */
return(_Flds[U_MACHINE]);
}
}
}
/*
* parse a line in PERMISSIONS and return a vector
* of fields (flds)
*
* return:
* 0 - OK
* EOF - at end of file
*/
int
parse_tokens(flds, buf)
char *flds[];
char *buf;
{
int i;
char *p;
struct name_value pair;
static char _line[BUFSIZ];
char *line = buf;
if (buf == NULL)
line = _line; /* if no buffer specified, use default */
/* initialize defaults in case parameter is not specified */
for (i=0;i<NUMFLDS;i++)
flds[i] = NULL;
if (getuline(Fp, line) == 0)
return(EOF);
for (p=line;p && *p;) {
p = next_token (p, &pair);
for (i=0; i<NUMFLDS; i++) {
if (EQUALS(pair.name, _Kwords[i].kword)) {
flds[i] = pair.value;
break;
}
}
#ifndef UUCHECK
ASSERT(i<NUMFLDS, "PERMISSIONS file: BAD OPTION--",
pair.name, NUMFLDS);
#else /* UUCHECK */
if (i >= NUMFLDS) {
DEBUG(3, "bad option (%s) in PERMISSIONS\n",pair.name);
(void) printf("\n*****************************\n");
(void) printf(gettext("**BAD OPTION in PERMISSIONS file: %s\n"),
pair.name);
(void) printf("*****************************\n");
Uerrors++;
return(0);
}
#endif /* UUCHECK */
}
return(0);
}
/*
* return a name value pair
* string -> input pointer
* pair -> name value pair
* return:
* pointer to next character
*/
char *
next_token (string, pair)
char *string;
struct name_value *pair;
{
char *prev = _uu_setlocale(LC_ALL, "C");
while ( (*string) && ((*string == '\t') || (*string == ' ')) )
string++;
pair->name = string;
while ((*string) && (*string != '='))
string++;
if (*string)
*string++ = NULLCHAR;
pair->value = string;
while ((*string) && (*string != '\t') && (*string != ' ')
&& (*string != '\n'))
string++;
if (*string)
*string++ = NULLCHAR;
(void) _uu_resetlocale(LC_ALL, prev);
return (string);
}
/*
* get a line from the PERMISSIONS
* take care of comments (#) in col 1
* and continuations (\) in last col
* return:
* len of line
* 0 -> end of file
*/
int
getuline(fp, line)
FILE *fp;
char *line;
{
char *p, *c;
char buf[BUFSIZ];
p = line;
for (;fgets(buf, BUFSIZ, fp) != NULL;) {
/* remove trailing white space */
c = &buf[strlen(buf)-1];
while (c>=buf && (*c == '\n' || *c == '\t' || *c == ' ') )
*c-- = NULLCHAR;
if (buf[0] == '#' || buf[0] == '\n' || buf[0] == NULLCHAR)
continue;
(void) strcpy(p, buf);
p += strlen(buf);
if ( *(p-1) == '\\')
p--;
else
break;
}
return(p-line);
}
#define SMAX 15
/*
* get the next colon separated argument from the list
* return:
* p -> pointer to next arg in string
* input:
* str -> pointer to input string
* output:
* name -> pointer to arg string
*/
char *
nextarg(str, name)
char *str, **name;
{
char *p, *b;
static char buf[SMAX+1];
for(b=buf,p=str; *p != ':' && *p && b < buf+SMAX;)
*b++ = *p++;
*b++ = NULLCHAR;
if (*p == ':')
p++;
*name = buf;
return(p);
}
/*
* check if requesting files is permitted
* return
* TRUE -> request permitted
* FALSE -> request denied
*/
int
requestOK()
{
return(_Request);
}
/*
* myName - return my name from PERMISSIONS file
* or if not there, from uucpname()
* return: none
*/
void
myName(name)
char *name;
{
if (*_MyName)
strcpy(name, _MyName);
else
uucpname(name);
return;
}
/*
* check for callback required for any transaction
* return:
* TRUE -> callback required
* FALSE-> callback NOT required
*/
int
callBack()
{
return(_CallBack);
}
/*
* check for callback to send any files from here
* This means that the called (SLAVE) system will not switch roles.
* return:
* TRUE -> callback requried to send files
* FALSE-> callback NOT required to send files
*/
int
switchRole()
{
return(_Switch);
}
/*
* Check to see if command is valid for a specific machine.
* The PERMISSIONS file has an option COMMANDS=name1:name2:... for
* any machine that does not have the default list which is
* rmail
* Note that the PERMISSIONS file is read once for each system
* at the time the Rmtname is set in xprocess().
* Return codes:
* ok: TRUE
* fail: FALSE
*/
int
cmdOK(cmd, fullcmd)
char *cmd, *fullcmd;
{
DEBUG(7, "cmdOK(%s, )\n", cmd);
return(cmdMatch(cmd, fullcmd));
}
/*
* check a name against a list
* input:
* name -> name
* list -> list of names
* return:
* TRUE -> found path
* FALSE -> not found
*/
static int
listMatch(name, list)
char *name, *list[];
{
int i;
char *temp, *tend;
struct stat statbuf;
dev_t _dev[MAXPATHS+1];
ino_t _ino[MAXPATHS+1];
/* ino set to 0 so stat is only done first time */
for (i=0; list[i] != NULL; i++)
_ino[i] = 0;
/* try to match inodes */
if ( (temp = strdup(name)) != NULL ) {
for ( tend = temp + strlen(temp) ; *temp; ) {
if ( stat(temp, &statbuf) == 0 ) {
for (i=0; list[i] != NULL; i++) {
if ( _ino[i] == 0 ) {
struct stat tempbuf;
if ( stat(list[i], &tempbuf) == 0 ) {
_dev[i] = tempbuf.st_dev;
_ino[i] = tempbuf.st_ino;
}
}
if ( _dev[i] == statbuf.st_dev
&& _ino[i] == statbuf.st_ino ) {
free(temp);
return(TRUE);
}
}
}
*tend = '\0';
if ( (tend = strrchr(temp, '/')) == NULL ) {
free(temp);
break;
} else
*(tend+1) = '\0';
}
}
return(FALSE);
}
/*
* Check "name" against a BASENAME or full name of _Commands list.
* If "name" specifies full path, check full, else check BASENAME.
* e.g. "name" rmail matches list item /usr/bin/rmail
* input:
* name -> name
* output:
* fullname -> copy full command name into fullname if
* a full path was specified in _Commands;
* if not, put name into fullname.
* return:
* TRUE -> found path
* FALSE -> not found
*/
static int
cmdMatch(name, fullname)
char *name;
char *fullname;
{
int i;
char *bname;
int allok = FALSE;
for (i=0; _Commands[i] != NULL; i++) {
if (EQUALS(_Commands[i], "ALL")) {
/* if ALL specified in the list
* set allok and continue in case
* a full path name is specified for the command
*/
allok = TRUE;
continue;
}
if (name[0] != '/')
bname = BASENAME(_Commands[i], '/');
else
bname = _Commands[i];
DEBUG(7, "bname=%s\n", bname);
if (EQUALS(bname, name)) {
(void) strcpy(fullname, _Commands[i]);
return(TRUE);
}
}
if (allok == TRUE) {
/* ALL was specified and the command was not found in list */
(void) strcpy(fullname, name);
return(TRUE);
}
(void) strcpy(fullname, "NuLL"); /* this is a dummy command */
return(FALSE);
}
/*
* check the paths for this login/machine
* input:
* path pathname
* flag CK_READ or CK_WRITE
* output:
* path may be modified to canonical form
* (../, ./, // will be interpreted/removed)
* returns:
* 0 -> success
* FAIL -> failure - not a valid path for access
*/
int
chkpth(path, flag)
char *path;
{
char *s;
/*
* this is probably redundant,
* because expfile did it, but that's ok
* Note - the /../ check is not required because of canPath
*/
if (canPath(path) == FAIL)
return(FAIL);
if (flag == CK_READ)
if (listMatch(path, _RPaths)
&& !listMatch(path, _NoRPaths))
return(0);
if (flag == CK_WRITE)
if (listMatch(path, _WPaths)
&& !listMatch(path, _NoWPaths))
return(0);
/* ok if uucp generated D. or X. name for the spool directory */
if (PREFIX(RemSpool, path) ) {
s = &path[strlen(RemSpool)];
if ( (*s++ == '/')
&& (*s == DATAPRE || *s == XQTPRE)
&& (*(++s) == '.')
&& (strchr(s, '/') == NULL) )
return(0);
}
/* path name not valid */
return(FAIL);
}
/*
* check write permission of file.
* if mopt != NULL and permissions are ok,
* a side effect of this routine is to make
* directories up to the last part of the
* "to" ( if they do not exit).
* Input:
* to - a path name of the destination file or directory
* from - full path name of source file
* opt - create directory option (NULL - don't create)
* Output:
* to - will be the full path name of the destination file
* returns:
* 0 ->success
* FAIL -> failure
*/
int
chkperm(from, to, opt)
char *from, *to, *opt;
{
char *lxp, *p;
struct stat s;
char dir[MAXFULLNAME];
if (*(p = LASTCHAR(to)) == '/') {
if (strlcpy(p+1, BASENAME(from, '/'), MAXFULLNAME - strlen(to)) >=
MAXFULLNAME - strlen(to)) {
return(FAIL);
}
} else if (DIRECTORY(to)) {
*++p = '/';
if (strlcpy(p+1, BASENAME(from, '/'), MAXFULLNAME - strlen(to)) >=
MAXFULLNAME - strlen(to)) {
return(FAIL);
}
}
/* to is now the full path name of the destination file */
if (WRITEANY(to))
return(0);
if (stat(to, &s) == 0)
return(FAIL); /* file exists, but not writeable */
/* file does not exist--check directory and make when necessary */
(void) strcpy(dir, to);
if ( (lxp=strrchr(dir, '/')) == NULL)
return(FAIL); /* no directory part of name */
if (lxp == dir) /* at root */
lxp++;
*lxp = NULLCHAR;
/* should check WRITEANY on parent before mkdirs() */
if (!DIRECTORY(dir)) {
if (opt == NULL)
return(FAIL); /* no directory and no opt to make them */
else if (mkdirs(dir, PUBMASK) == FAIL)
return(FAIL);
}
/* the directory now exists--check for writability */
if (EQUALS(RemSpool, dir) || WRITEANY(dir))
return(0);
return(FAIL);
}