#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <ctype.h>
#include <arpa/tftp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include "tftpyale.h"
#include "classes/config.h"
#include "classes/dict.h"
#include "classes/access.h"
/* Enhancements to tftpd made at Yale:
* 07-Mar-89
* Alan S. Watt
* watt-alan@cs.yale.edu
*
* Control such things as default directory and debugging level from
* configuration file (/usr/local/etc/tftpd.conf)
*
* Permit access lists to say which systems are allowed access over
* specific files.
*
* 10-Aug-92
* Alan S. Watt
* Fixed a number of botches resulting from the merge with the UUNET
* base source. Converted to use the Dictionary and AccessGroup classes.
*
* 12-Sept-89
* Alan S. Watt
* watt-alan@cs.yale.edu
*
* Modified the access list maintenance so exact matches (no mask bits)
* are kept at the head of the list and permission searches end on the
* first address match. This has the effect of giving exact matches
* a higher priority than wildcard matches. This is also the way the
* cisco access lists work.
*/
/* We export only the following globals:
*
* char *tftpDefaultDirectory;
* char *tftpRootDirectory;
* int tftpDebugLevel;
* int configLineNumber;
* int validateAccessToFile(char* filename, unsigned long sourceAddress);
* int readConfigFile(int argc, char** argv);
* int realPath(char* orig, char* buf);
*
*/
/* location of the configuration file */
int configLineNumber;
/* If set, it means we have chdir() and chroot() to this directory and
* unqualified pathnames are OK. Otherwise they are illegal.
*/
char* tftpDefaultDirectory = (char*)0;
char* tftpRootDirectory = (char*)0;
int tftpDebugLevel = 0;
int maxInputWait = 5*60; /* default wait for 5 minutes */
static Dict fileAccessDict;
static AccessGroup accessLists;
static Dict commandDict;
/* declare locals required for access lists
*/
static char* accessFormatError;
static char* cfgets();
static struct stat configStat;
static int defaultAccessList;
/* for debugging printouts */
static char* permNames[] ={ "deny", "readonly", "writeonly", "readwrite" };
#define NITEMS(X) (sizeof(X) / sizeof (X)[0])
#define PERMNAME(N) ((unsigned)(N) >= NITEMS(permNames) \
? "<unknown>" : permNames[N])
#define CMD_DEFAULT_DIR 1
#define CMD_DEBUG_LEVEL 2
#define CMD_ROOT_DIR 3
#define CMD_ACCESS_LIST 4
#define CMD_DEFAULT_ACCESS_LIST 5
#define CMD_RESTRICT 6
#define CMD_INPUT_WAIT 7
/* Add a file restriction clause to the list */
static
addFileRestriction (ac, av)
int ac;
char** av;
{
int list;
ac--; av++;
if (ac != 2) {
accessFormatError = "'restrict' command requires 2 arguments";
return 0;
}
/* get list number */
list = atoi (av[1]);
if (list <= 0) {
accessFormatError = "list argument not positive integer";
return 0;
}
dict_add (fileAccessDict, *av, (void*)list);
return 0;
}
/* primitive version of cfgets function; doesn't process continuations */
static char*
cfgets (buf, num, file, lineCount)
char* buf;
int num;
FILE* file;
int *lineCount;
{
char* ret;
for (;;) {
ret = fgets(buf, num, file);
if (ret == NULL)
break;
if (lineCount != (int*)0)
*lineCount += 1;
if (buf[0] != '#')
break;
}
return ret;
}
/* Return whether the file named by the argument stat structure
* is the same as the configuration file.
*/
isConfigFile (sb)
struct stat* sb;
{
if (sb->st_dev == configStat.st_dev
&& sb->st_ino == configStat.st_ino)
return 1;
return 0;
}
/* Get the access list which covers <fileName>. If
* no explict specification for this file, return the
* default access list. If <fileName> is fully
* qualified (starts with '/') check to see if the
* prefix matches the default directory.
*/
static int
getAccessList (fileName)
char* fileName;
{
unsigned int list;
char* rindex();
if (*fileName == '/') {
char* sep = rindex (fileName, '/');
int count = sep - fileName;
if (count > 0)
count--;
if (tftpDefaultDirectory
&& strncmp(fileName, tftpDefaultDirectory,
strlen(tftpDefaultDirectory))==0) {
fileName = sep+1;
}
}
list = (int)dict_find (fileAccessDict, fileName);
return list ? list : defaultAccessList;
}
/* allocate space for a string and copy it there */
static char*
newString (arg)
char* arg;
{
char* tmp;
if (arg == NULL)
return NULL;
tmp = malloc(strlen(arg)+1);
strcpy (tmp, arg);
return tmp;
}
/* (re)-read the tftpd config file. We keep an open handle
* on the file and check to see if it's been modified since
* the last read.
*/
readConfigFile(argc, argv)
int argc;
char** argv;
{
static FILE* configf;
static unsigned long configModTime;
char buf[256];
int cargc;
char** cargv;
char* fileName;
struct stat statb;
Config cnf;
/* If given an argument, it must be the name
* of a configuration file. Otherwise use
* the default.
*/
if (tftpDebugLevel > 3) {
syslog(LOG_DEBUG, "readConfigFile(%d,0x%08x)", argc, argv);
}
if (configf == 0) {
fileName = dfltConfigFile;
if ((argc--,argv++), argc > 0) {
if (access(*argv, 0) == 0)
fileName = *argv;
}
if ((configf = fopen (fileName, "r")) == NULL) {
syslog (LOG_ERR,
"Cannot open configFile '%s'; reason = %s",
fileName, strerror(errno));
return 0;
}
}
if (tftpDebugLevel > 3) {
syslog(LOG_DEBUG, "configFile = '%s'; FILE* = 0x%08x",
fileName, configf);
}
/* See if the file has been changed since the last time we
* read it.
* @@@ This needs to use "stat" instead, in case people
* @@@ use editors which replace the file instead of over-write
* @@@ it.
*/
fstat(fileno(configf), &statb);
configStat = statb;
if (statb.st_mtime == configModTime)
return;
configModTime = statb.st_mtime;
/* Read the config file */
rewind (configf);
resetConfig();
configLineNumber = 0;
cnf = config_new();
config_setoptions (cnf, config_getoptions (cnf)& ~CFG_OPT_CASEMAP);
while (cfgets (buf, sizeof buf, configf, &configLineNumber) != NULL) {
char* end;
cargc = config_scanbuf (cnf, buf);
if (cargc == 0)
continue;
cargv = config_fields(cnf);
switch ((int)dict_find (commandDict, cargv[0])) {
/* specify default directory */
case CMD_DEFAULT_DIR:
if (cargc != 2)
break;
if (chdir(cargv[1])) {
if (tftpDebugLevel > 0) {
syslog (LOG_DEBUG,
"chdir fails; '%s'",
strerror(errno));
}
}
else
tftpDefaultDirectory = newString(cargv[1]);
break;
/* specify virtual root directory */
case CMD_ROOT_DIR:
if (cargc == 2) {
if (access(cargv[1], 0) != 0)
syslog(LOG_DEBUG, "directory '%s': %s",
cargv[1], strerror(errno));
else
tftpRootDirectory = newString(cargv[1]);
}
break;
/* set debugging level */
case CMD_DEBUG_LEVEL:
if (cargc != 2)
break;
/* error log ?? */
tftpDebugLevel = atoi(cargv[1]);
break;
/* create or extend an access list */
case CMD_ACCESS_LIST:
accessGroup_add (accessLists, cargc, cargv);
break;
/* specify default access list */
case CMD_DEFAULT_ACCESS_LIST:
if (cargc == 2)
defaultAccessList = atoi(cargv[1]);
/* error log ?? */
break;
/* restrict access to a file by access list */
case CMD_RESTRICT:
addFileRestriction(cargc, cargv);
break;
/* set input timeout period */
case CMD_INPUT_WAIT:
{ int n;
if (cargc != 2)
break;
n = atoi (cargv[1]);
if (n > 0)
maxInputWait = n;
}
break;
default:
/* log illegal configuration options */
syslog (LOG_DEBUG,
"Line %d: illegal configuration keyword '%s'",
configLineNumber, cargv[0]);
break;
}
}
config_dispose (cnf);
if (tftpDebugLevel > 0) {
static char logInfo[] =
"init_config: default=%s; root=%s; debug=%d";
syslog (LOG_DEBUG, logInfo,
(tftpDefaultDirectory ? tftpDefaultDirectory : ""),
(tftpRootDirectory ? tftpRootDirectory : ""),
tftpDebugLevel);
}
if (tftpDebugLevel > 4) {
accessDebugDump();
}
return 1;
}
/* realPath(char* orig, char* buf)
* Deposit into <buf> the compressed pathname derived from <orig>.
* This deletes any "." and ".." components, as well as redundant
* separator characters.
*
* The re-created path always starts with a '/'.
*
* UNIX-specific.
*/
#define MAXCOMPONENT 256
#define MAXPATH 1024
#define SEPCHAR '/'
#define ISCURDIR(S) (strcmp(S,thisDir)==0)
#define ISPARENTDIR(S) (strcmp(S,parentDir)==0)
static char thisDir[] = ".";
static char parentDir[] = "..";
static char sepDir[] = "/";
static char rootDir[] = "/";
int
realPath(orig, buf)
char* orig;
char* buf;
{
char* obuf[MAXCOMPONENT];
char* nbuf[MAXCOMPONENT];
char tmp[MAXPATH];
int numComponents;
int pathSize;
int num;
/* Count the maximum number of components which could be
* in the source path.
*/
{
register char* p;
numComponents = 1;
for (p = orig; *p != '\0'; p++)
if (*p == SEPCHAR)
numComponents++;
}
/* make sure the stack locals have enough space to hold
* everything. If not return failure.
*/
if (numComponents >= MAXCOMPONENT)
return -1;
pathSize = strlen (orig) + 1;
if (pathSize >= MAXPATH)
return -1;
/* copy in path to temporary */
strcpy (tmp, orig);
/* dissect it into components */
{
register char* p, *q;
num = 0;
for (p = q = tmp; p != 0; p = q) {
/* skip any separators */
while (*p == SEPCHAR)
p++;
/* if at end of string quit */
if (*p == '\0')
break;
/* store this component away */
obuf[num++] = p;
/* get end of this component and replace
* with string terminator
*/
q = strchr (p, SEPCHAR);
if (q != 0)
*q++ = '\0';
}
}
/* squeeze out current and parent entries */
{
register i, j;
for (i=0, j=0; i < num; i++) {
char* cur = obuf[i];
if (ISCURDIR(cur))
continue;
else if (ISPARENTDIR(cur)) {
if (j > 0)
j--;
}
else
nbuf[j++] = cur;
}
nbuf[j] = 0;
num = j;
}
/* re-create copy of path */
buf[0] = '\0';
if (num == 0)
strcpy (buf, rootDir);
else {
register int i;
for (i = 0; i < num; i++) {
strcat (buf, sepDir);
strcat (buf, nbuf[i]);
}
}
return 0;
}
static struct CMDS {
char* cmdName;
int cmdVal;
} configCmds[] ={
"default-directory", CMD_DEFAULT_DIR,
"defaultDirectory", CMD_DEFAULT_DIR,
"debug-level", CMD_DEBUG_LEVEL,
"debugLevel", CMD_DEBUG_LEVEL,
"root-directory", CMD_ROOT_DIR,
"rootDirectory", CMD_ROOT_DIR,
"access-list", CMD_ACCESS_LIST,
"accessList", CMD_ACCESS_LIST,
"default-access-list", CMD_DEFAULT_ACCESS_LIST,
"defaultAccessList", CMD_DEFAULT_ACCESS_LIST,
"restrict", CMD_RESTRICT,
"inputWait", CMD_INPUT_WAIT,
"input-wait", CMD_INPUT_WAIT,
(char*)0, 0
};
/* reset configuration options to defaults */
resetConfig()
{
register int i;
extern char* dfltDefaultDirectory;
extern char* dfltRootDirectory;
extern int dfltDebugLevel;
extern char* dfltConfigFile;
extern Dict fileAccessDict;
extern AccessGroup accessLists;
extern Dict commandDict;
dict_dispose (fileAccessDict);
fileAccessDict = dict_new();
accessGroup_dispose (accessLists);
accessLists = accessGroup_new();
if (commandDict == 0) {
register struct CMDS* cp;
commandDict = dict_new();
for (cp = configCmds; cp->cmdName != 0 ; cp++)
dict_add (commandDict, cp->cmdName, (void*)cp->cmdVal);
}
/* reset debug level */
tftpDebugLevel = dfltDebugLevel;
/* reset default directory */
if (tftpDefaultDirectory) {
free (tftpDefaultDirectory);
tftpDefaultDirectory = 0;
if (dfltDefaultDirectory)
tftpDefaultDirectory = newString(dfltDefaultDirectory);
}
/* reset root directory */
if (tftpRootDirectory) {
free (tftpRootDirectory);
tftpRootDirectory = 0;
if (dfltRootDirectory)
tftpRootDirectory = newString(dfltRootDirectory);
}
}
/* validate that the source system named by:
* 'source' is permitted to access the
* given filename according to access lists.
*
* Return 1 if access is permitted under old default rules
* (i.e., no access list governs); 0 if not.
*
* Return 2 if more particular access is permitted by an access list.
* This gets around the old public read/writable requirements.
*/
int
validateAccessToFile (filename, source, type)
char* filename;
unsigned long source;
int type;
{
int list;
int ret;
list = getAccessList (filename);
if (list == 0)
return 1;
/* If the named list doesn't exist, use the default */
ret = accessGroup_validateAddress (accessLists, list, source, 3);
if (ret < 0) {
if (list == defaultAccessList
|| ((list = defaultAccessList) == 0))
return 1;
}
/* If that doesn't exist, return OK */
ret = accessGroup_validateAddress (accessLists, list, source, 3);
if (ret < 0)
return 1;
if (tftpDebugLevel > 1) {
struct in_addr t;
t.s_addr = source;
syslog(LOG_DEBUG, "access to '%s' for %s is %s in list %d",
filename, inet_ntoa(t), PERMNAME(ret), list);
}
if (type == RRQ)
return (ret & 1) ? 2 : 0;
return (ret & 2) ? 2 : 0;
}
/* Debugging functions */
#ifndef DEBUG
accessDebugDump()
{
}
#else
#include <stdio.h>
accessDebugDump()
{
FILE* logf;
char** keys;
int val;
int i;
logf = fopen ("tftpdebug.log", "a");
if (logf == 0)
return;
fprintf (logf, "After reading config file:\n");
fprintf (logf, "File Restriction list:\n");
dict_printOn (fileAccessDict, logf);
keys = dict_keys (fileAccessDict);
for (i = 0; keys[i] != 0; i++)
fprintf (logf, "\trestrict(%s) = %d\n",
keys[i], (int)dict_find (fileAccessDict, keys[i]));
free ((char*)keys);
fprintf (logf, "Access lists:\n");
accessGroup_printOn (accessLists, logf);
fprintf (logf, "\n\n");
fclose (logf);
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1