Exemple #1
0
/*
 *	Create possibly many directories.
 *
 *	Note that the input directory name is NOT a constant!
 *	This is so that IF an error is returned, the 'directory' ptr
 *	points to the name of the file which caused the error.
 */
int rad_mkdir(char *directory, mode_t mode)
{
	int rcode;
	char *p;
	struct stat st;

	/*
	 *	If the directory exists, don't do anything.
	 */
	if (stat(directory, &st) == 0) {
		return 0;
	}

	/*
	 *	Look for the LAST directory name.  Try to create that,
	 *	failing on any error.
	 */
	p = strrchr(directory, FR_DIR_SEP);
	if (p != NULL) {
		*p = '\0';
		rcode = rad_mkdir(directory, mode);

		/*
		 *	On error, we leave the directory name as the
		 *	one which caused the error.
		 */
		if (rcode < 0) {
			if (errno == EEXIST) return 0;
			return rcode;
		}

		/*
		 *	Reset the directory delimiter, and go ask
		 *	the system to make the directory.
		 */
		*p = FR_DIR_SEP;
	} else {
		return 0;
	}

	/*
	 *	Having done everything successfully, we do the
	 *	system call to actually go create the directory.
	 */
	rcode = mkdir(directory, mode & 0777);
	if (rcode < 0) {
		return rcode;
	}

	/*
	 *	Set things like sticky bits that aren't supported by
	 *	mkdir.
	 */
	if (mode & ~0777) {
		rcode = chmod(directory, mode);
	}

	return rcode;
}
/*
 *	Write the line into file (with lock)
 */
static int sql_log_write(rlm_sql_log_t *inst, REQUEST *request, const char *line)
{
	int fd;
	FILE *fp;
	int locked = 0;
	struct stat st;
	char *p, path[1024];

	path[0] = '\0';
	radius_xlat(path, sizeof(path), inst->path, request, NULL, NULL);
	if (path[0] == '\0') {
		return RLM_MODULE_FAIL;
	}

	p = strrchr(path, '/');
	if (p) {
		*p = '\0';
		rad_mkdir(path, 0755);
		*p = '/';
	}

	while (!locked) {
		if ((fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0666)) < 0) {
			radlog_request(L_ERR, 0, request, "Couldn't open file %s: %s",
				       path, strerror(errno));
			return RLM_MODULE_FAIL;
		}
		if (setlock(fd) != 0) {
			radlog_request(L_ERR, 0, request, "Couldn't lock file %s: %s",
				       path, strerror(errno));
			close(fd);
			return RLM_MODULE_FAIL;
		}
		if (fstat(fd, &st) != 0) {
			radlog_request(L_ERR, 0, request, "Couldn't stat file %s: %s",
				       path, strerror(errno));
			close(fd);
			return RLM_MODULE_FAIL;
		}
		if (st.st_nlink == 0) {
			RDEBUG("File %s removed by another program, retrying",
			      path);
			close(fd);
			continue;
		}
		locked = 1;
	}

	if ((fp = fdopen(fd, "a")) == NULL) {
		radlog_request(L_ERR, 0, request, "Couldn't associate a stream with file %s: %s",
			       path, strerror(errno));
		close(fd);
		return RLM_MODULE_FAIL;
	}
	fputs(line, fp);
	putc('\n', fp);
	fclose(fp);	/* and unlock */
	return RLM_MODULE_OK;
}
Exemple #3
0
/*
 *	Create possibly many directories.
 *
 *	Note that the input directory name is NOT a constant!
 *	This is so that IF an error is returned, the 'directory' ptr
 *	points to the name of the file which caused the error.
 */
int rad_mkdir(char *directory, mode_t mode)
{
	int rcode;
	char *p;

	/*
	 *	Try to make the directory.  If it exists, chmod it.
	 *	If a path doesn't exist, that's OK.  Otherwise
	 *	return with an error.
	 */
	rcode = mkdir(directory, mode & 0777);
	if (rcode < 0) {
		if (errno == EEXIST) {
			return chmod(directory, mode);
		}

		if (errno != ENOENT) {
			return rcode;
		}

		/*
		 *	A component in the directory path doesn't
		 *	exist.  Look for the LAST directory name.  Try
		 *	to create that.  If there's an error, we leave
		 *	the directory path as the one at which the
		 *	error occured.
		 */
		p = strrchr(directory, FR_DIR_SEP);
		if (!p || (p == directory)) return -1;

		*p = '\0';
		rcode = rad_mkdir(directory, mode);
		if (rcode < 0) return rcode;

		/*
		 *	Reset the directory path, and try again to
		 *	make the directory.
		 */
		*p = FR_DIR_SEP;
		rcode = mkdir(directory, mode & 0777);
		if (rcode < 0) return rcode;
	} /* else we successfully created the directory */

	return chmod(directory, mode);
}
void radlog_request(int lvl, int priority, REQUEST *request, const char *msg, ...)
{
    size_t len = 0;
    const char *filename = request_log_file;
    FILE *fp = NULL;
    va_list ap;
    char buffer[1024];

    va_start(ap, msg);

    /*
     *	Debug messages get treated specially.
     */
    if (lvl == L_DBG) {
        /*
         *	There is log function, but the debug level
         *	isn't high enough.  OR, we're in debug mode,
         *	and the debug level isn't high enough.  Return.
         */
        if ((request && request->radlog &&
                (priority > request->options)) ||
                ((debug_flag != 0) && (priority > debug_flag))) {
            va_end(ap);
            return;
        }

        /*
         *	Use the debug output file, if specified,
         *	otherwise leave it as "request_log_file".
         */
        filename = debug_log_file;
        if (!filename) filename = request_log_file;

        /*
         *	Debug messages get mashed to L_INFO for
         *	radius.log.
         */
        if (!filename) lvl = L_INFO;
    }

    if (request && filename) {
        char *p;
        radlog_func_t rl = request->radlog;

        request->radlog = NULL;

        /*
         *	This is SLOW!  Doing it for every log message
         *	in every request is NOT recommended!
         */

        radius_xlat(buffer, sizeof(buffer), filename,
                    request, NULL); /* FIXME: escape chars! */
        request->radlog = rl;

        p = strrchr(buffer, FR_DIR_SEP);
        if (p) {
            *p = '\0';
            if (rad_mkdir(buffer, S_IRWXU) < 0) {
                radlog(L_ERR, "Failed creating %s: %s",
                       buffer,strerror(errno));
                va_end(ap);
                return;
            }
            *p = FR_DIR_SEP;
        }

        fp = fopen(buffer, "a");
    }

    /*
     *	Print timestamps to the file.
     */
    if (fp) {
        char *s;
        time_t timeval;
        timeval = time(NULL);

        CTIME_R(&timeval, buffer + len, sizeof(buffer) - len - 1);

        s = strrchr(buffer, '\n');
        if (s) {
            s[0] = ' ';
            s[1] = '\0';
        }

        s = fr_int2str(levels, (lvl & ~L_CONS), ": ");

        strcat(buffer, s);
        len = strlen(buffer);
    }

    if (request && request->module[0]) {
        snprintf(buffer + len, sizeof(buffer) + len, "[%s] ", request->module);
        len = strlen(buffer);
    }
    vsnprintf(buffer + len, sizeof(buffer) - len, msg, ap);

    if (!fp) {
        if (request) {
            radlog(lvl, "(%u) %s", request->number, buffer);
        } else {
            radlog(lvl, "%s", buffer);
        }
    } else {
        if (request) fprintf(fp, "(%u) ", request->number);
        fputs(buffer, fp);
        fputc('\n', fp);
        fclose(fp);
    }

    va_end(ap);
}
static rlm_rcode_t do_linelog(void *instance, REQUEST *request)
{
	int fd = -1;
	char buffer[4096];
	char *p;
	char line[1024];
	rlm_linelog_t *inst = (rlm_linelog_t*) instance;
	const char *value = inst->line;

#ifdef HAVE_GRP_H
	gid_t gid;
	struct group *grp;
	char *endptr;
#endif

	if (inst->reference) {
		CONF_ITEM *ci;
		CONF_PAIR *cp;

		radius_xlat(line + 1, sizeof(line) - 2, inst->reference,
			    request, linelog_escape_func, NULL);
		line[0] = '.';	/* force to be in current section */

		/*
		 *	Don't allow it to go back up
		 */
		if (line[1] == '.') goto do_log;

		ci = cf_reference_item(NULL, inst->cs, line);
		if (!ci) {
			RDEBUG2("No such entry \"%s\"", line);
			return RLM_MODULE_NOOP;
		}

		if (!cf_item_is_pair(ci)) {
			RDEBUG2("Entry \"%s\" is not a variable assignment ", line);
			goto do_log;
		}

		cp = cf_itemtopair(ci);
		value = cf_pair_value(cp);
		if (!value) {
			RDEBUG2("Entry \"%s\" has no value", line);
			goto do_log;
		}

		/*
		 *	Value exists, but is empty.  Don't log anything.
		 */
		if (!*value) return RLM_MODULE_OK;
	}

 do_log:
	/*
	 *	FIXME: Check length.
	 */
	if (strcmp(inst->filename, "syslog") != 0) {
		radius_xlat(buffer, sizeof(buffer), inst->filename, request,
			    NULL, NULL);
		
		/* check path and eventually create subdirs */
		p = strrchr(buffer,'/');
		if (p) {
			*p = '\0';
			if (rad_mkdir(buffer, 0700) < 0) {
				radlog_request(L_ERR, 0, request, "rlm_linelog: Failed to create directory %s: %s", buffer, strerror(errno));
				return RLM_MODULE_FAIL;
			}
			*p = '/';
		}

		fd = open(buffer, O_WRONLY | O_APPEND | O_CREAT, inst->permissions);
		if (fd == -1) {
			radlog(L_ERR, "rlm_linelog: Failed to open %s: %s",
			       buffer, strerror(errno));
			return RLM_MODULE_FAIL;
		}

#ifdef HAVE_GRP_H
		if (inst->group != NULL) {
			gid = strtol(inst->group, &endptr, 10);
			if (*endptr != '\0') {
				grp = getgrnam(inst->group);
				if (grp == NULL) {
					RDEBUG2("Unable to find system group \"%s\"", inst->group);
					goto skip_group;
				}
				gid = grp->gr_gid;
			}
	
			if (chown(buffer, -1, gid) == -1) {
				RDEBUG2("Unable to change system group of \"%s\"", buffer);
			}
		}
#endif
	}

 skip_group:

	/*
	 *	FIXME: Check length.
	 */
	radius_xlat(line, sizeof(line) - 1, value, request,
		    linelog_escape_func, NULL);

	if (fd >= 0) {
		strcat(line, "\n");
		
		write(fd, line, strlen(line));
		close(fd);

#ifdef HAVE_SYSLOG_H
	} else {
		syslog(inst->facility, "%s", line);
#endif
	}

	return RLM_MODULE_OK;
}
Exemple #6
0
/** Create possibly many directories.
 *
 * @note that the input directory name is NOT treated as a constant. This is so that
 *	 if an error is returned, the 'directory' ptr points to the name of the file
 *	 which caused the error.
 *
 * @param dir path to directory to create.
 * @param mode for new directories.
 * @param uid to set on new directories, may be -1 to use effective uid.
 * @param gid to set on new directories, may be -1 to use effective gid.
 * @return
 *	- 0 on success.
 *	- -1 on failure. Error available as errno.
 */
int rad_mkdir(char *dir, mode_t mode, uid_t uid, gid_t gid)
{
	int rcode, fd;
	char *p;

	/*
	 *	Try to make the dir.  If it exists, chmod it.
	 *	If a path doesn't exist, that's OK.  Otherwise
	 *	return with an error.
	 *
	 *	Directories permissions are initially set so
	 *	that only we should have access. This prevents
	 *	an attacker removing them and swapping them
	 *	out for a link to somewhere else.
	 *	We change them to the correct permissions later.
	 */
	rcode = mkdir(dir, 0700);
	if (rcode < 0) {
		switch (errno) {
		case EEXIST:
			return 0; /* don't change permissions */

		case ENOENT:
			break;

		default:
			return rcode;
		}

		/*
		 *	A component in the dir path doesn't
		 *	exist.  Look for the LAST dir name.  Try
		 *	to create that.  If there's an error, we leave
		 *	the dir path as the one at which the
		 *	error occured.
		 */
		p = strrchr(dir, FR_DIR_SEP);
		if (!p || (p == dir)) return -1;

		*p = '\0';
		rcode = rad_mkdir(dir, mode, uid, gid);
		if (rcode < 0) return rcode;

		/*
		 *	Reset the dir path, and try again to
		 *	make the dir.
		 */
		*p = FR_DIR_SEP;
		rcode = mkdir(dir, 0700);
		if (rcode < 0) return rcode;
	} /* else we successfully created the dir */

	/*
	 *	Set the permissions on the directory we created
	 *	this should never fail unless there's a race.
	 */
	fd = open(dir, O_DIRECTORY);
	if (fd < 0) return -1;

	rcode = fchmod(fd, mode);
	if (rcode < 0) {
		close(fd);
		return rcode;
	}

	if ((uid != (uid_t)-1) || (gid != (gid_t)-1)) {
		rad_suid_up();
		rcode = fchown(fd, uid, gid);
		rad_suid_down();
	}
	close(fd);

	return rcode;
}
Exemple #7
0
/*
 *  Do chroot, if requested.
 *
 *  Switch UID and GID to what is specified in the config file
 */
static int switch_users(CONF_SECTION *cs)
{
	bool do_suid = false;
	bool do_sgid = false;

	/*
	 *	Get the current maximum for core files.  Do this
	 *	before anything else so as to ensure it's properly
	 *	initialized.
	 */
	if (fr_set_dumpable_init() < 0) {
		fr_perror("%s", main_config.name);
		return 0;
	}

	/*
	 *	Don't do chroot/setuid/setgid if we're in debugging
	 *	as non-root.
	 */
	if (rad_debug_lvl && (getuid() != 0)) return 1;

	if (cf_section_parse(cs, NULL, bootstrap_config) < 0) {
		fprintf(stderr, "%s: Error: Failed to parse user/group information.\n",
			main_config.name);
		return 0;
	}

#ifdef HAVE_GRP_H
	/*
	 *	Get the correct GID for the server.
	 */
	server_gid = getgid();

	if (gid_name) {
		struct group *gr;

		gr = getgrnam(gid_name);
		if (!gr) {
			fprintf(stderr, "%s: Cannot get ID for group %s: %s\n",
				main_config.name, gid_name, fr_syserror(errno));
			return 0;
		}

		if (server_gid != gr->gr_gid) {
			server_gid = gr->gr_gid;
			do_sgid = true;
		}
	}
#endif

	/*
	 *	Get the correct UID for the server.
	 */
	server_uid = getuid();

	if (uid_name) {
		struct passwd *user;

		if (rad_getpwnam(cs, &user, uid_name) < 0) {
			fprintf(stderr, "%s: Cannot get passwd entry for user %s: %s\n",
				main_config.name, uid_name, fr_strerror());
			return 0;
		}

		/*
		 *	We're not the correct user.  Go set that.
		 */
		if (server_uid != user->pw_uid) {
			server_uid = user->pw_uid;
			do_suid = true;
#ifdef HAVE_INITGROUPS
			if (initgroups(uid_name, server_gid) < 0) {
				fprintf(stderr, "%s: Cannot initialize supplementary group list for user %s: %s\n",
					main_config.name, uid_name, fr_syserror(errno));
				talloc_free(user);
				return 0;
			}
#endif
		}

		talloc_free(user);
	}

	/*
	 *	Do chroot BEFORE changing UIDs.
	 */
	if (chroot_dir) {
		if (chroot(chroot_dir) < 0) {
			fprintf(stderr, "%s: Failed to perform chroot %s: %s",
				main_config.name, chroot_dir, fr_syserror(errno));
			return 0;
		}

		/*
		 *	Note that we leave chdir alone.  It may be
		 *	OUTSIDE of the root.  This allows us to read
		 *	the configuration from "-d ./etc/raddb", with
		 *	the chroot as "./chroot/" for example.  After
		 *	the server has been loaded, it does a "cd
		 *	${logdir}" below, so that core files (if any)
		 *	go to a logging directory.
		 *
		 *	This also allows the configuration of the
		 *	server to be outside of the chroot.  If the
		 *	server is statically linked, then the only
		 *	things needed inside of the chroot are the
		 *	logging directories.
		 */
	}

#ifdef HAVE_GRP_H
	/*
	 *	Set the GID.  Don't bother checking it.
	 */
	if (do_sgid) {
		if (setgid(server_gid) < 0){
			fprintf(stderr, "%s: Failed setting group to %s: %s",
				main_config.name, gid_name, fr_syserror(errno));
			return 0;
		}
	}
#endif

	/*
	 *	The directories for PID files and logs must exist.  We
	 *	need to create them if we're told to write files to
	 *	those directories.
	 *
	 *	Because this creation is new in 3.0.9, it's a soft
	 *	fail.
	 *
	 */
	if (main_config.write_pid) {
		char *my_dir;

		my_dir = talloc_strdup(NULL, run_dir);
		if (rad_mkdir(my_dir, 0750, server_uid, server_gid) < 0) {
			DEBUG("Failed to create run_dir %s: %s",
			      my_dir, strerror(errno));
		}
		talloc_free(my_dir);
	}

	if (default_log.dst == L_DST_FILES) {
		char *my_dir;

		my_dir = talloc_strdup(NULL, radlog_dir);
		if (rad_mkdir(my_dir, 0750, server_uid, server_gid) < 0) {
			DEBUG("Failed to create logdir %s: %s",
			      my_dir, strerror(errno));
		}
		talloc_free(my_dir);
	}

	/*
	 *	If we don't already have a log file open, open one
	 *	now.  We may not have been logging anything yet.  The
	 *	server normally starts up fairly quietly.
	 */
	if ((default_log.dst == L_DST_FILES) &&
	    (default_log.fd < 0)) {
		default_log.fd = open(main_config.log_file,
				      O_WRONLY | O_APPEND | O_CREAT, 0640);
		if (default_log.fd < 0) {
			fprintf(stderr, "%s: Failed to open log file %s: %s\n",
				main_config.name, main_config.log_file, fr_syserror(errno));
			return 0;
		}
	}

	/*
	 *	If we need to change UID, ensure that the log files
	 *	have the correct owner && group.
	 *
	 *	We have to do this because some log files MAY already
	 *	have been written as root.  We need to change them to
	 *	have the correct ownership before proceeding.
	 */
	if ((do_suid || do_sgid) &&
	    (default_log.dst == L_DST_FILES)) {
		if (fchown(default_log.fd, server_uid, server_gid) < 0) {
			fprintf(stderr, "%s: Cannot change ownership of log file %s: %s\n",
				main_config.name, main_config.log_file, fr_syserror(errno));
			return 0;
		}
	}

	/*
	 *	Once we're done with all of the privileged work,
	 *	permanently change the UID.
	 */
	if (do_suid) {
		rad_suid_set_down_uid(server_uid);
		rad_suid_down();
	}

	/*
	 *	This also clears the dumpable flag if core dumps
	 *	aren't allowed.
	 */
	if (fr_set_dumpable(allow_core_dumps) < 0) {
		ERROR("%s", fr_strerror());
	}

	if (allow_core_dumps) {
		INFO("Core dumps are enabled");
	}

	return 1;
}
Exemple #8
0
void vradlog_request(log_type_t type, log_debug_t lvl, REQUEST *request, char const *msg, va_list ap)
{
	size_t len = 0;
	char const *filename = default_log.file;
	FILE *fp = NULL;
	char buffer[10240];	/* The largest config item size, then extra for prefixes and suffixes */
	char *p;
	char const *extra = "";
	va_list aq;

	/*
	 *	Debug messages get treated specially.
	 */
	if ((type & L_DBG) != 0) {

		if (!radlog_debug_enabled(type, lvl, request)) {
			return;
		}

		/*
		 *	Use the debug output file, if specified,
		 *	otherwise leave it as the default log file.
		 */
#ifdef WITH_COMMAND_SOCKET
		filename = default_log.debug_file;
		if (!filename)
#endif

		filename = default_log.file;
	}

	if (request && filename) {
		radlog_func_t rl = request->radlog;

		request->radlog = NULL;

		/*
		 *	This is SLOW!  Doing it for every log message
		 *	in every request is NOT recommended!
		 */

		 /* FIXME: escape chars! */
		if (radius_xlat(buffer, sizeof(buffer), request, filename, NULL, NULL) < 0) {
			return;
		}
		request->radlog = rl;

		p = strrchr(buffer, FR_DIR_SEP);
		if (p) {
			*p = '\0';
			if (rad_mkdir(buffer, S_IRWXU) < 0) {
				ERROR("Failed creating %s: %s", buffer, fr_syserror(errno));
				return;
			}
			*p = FR_DIR_SEP;
		}

		fp = fopen(buffer, "a");
	}

	/*
	 *	Print timestamps to the file.
	 */
	if (fp) {
		time_t timeval;
		timeval = time(NULL);

#ifdef HAVE_GMTIME_R
		if (log_dates_utc) {
			struct tm utc;
			gmtime_r(&timeval, &utc);
			ASCTIME_R(&utc, buffer, sizeof(buffer) - 1);
		} else
#endif
		{
			CTIME_R(&timeval, buffer, sizeof(buffer) - 1);
		}

		len = strlen(buffer);
		p = strrchr(buffer, '\n');
		if (p) {
			p[0] = ' ';
			p[1] = '\0';
		}

		len += strlcpy(buffer + len, fr_int2str(levels, type, ": "), sizeof(buffer) - len);
		if (len >= sizeof(buffer)) goto finish;
	}

	if (request && request->module[0]) {
		len = snprintf(buffer + len, sizeof(buffer) - len, "%s : ", request->module);
		if (len >= sizeof(buffer)) goto finish;
	}

	/*
	 *  If we don't copy the original ap we get a segfault from vasprintf. This is apparently
	 *  due to ap sometimes being implemented with a stack offset which is invalidated if
	 *  ap is passed into another function. See here:
	 *  http://julipedia.meroh.net/2011/09/using-vacopy-to-safely-pass-ap.html
	 *
	 *  I don't buy that explanation, but doing a va_copy here does prevent SEGVs seen when
	 *  running unit tests which generate errors under CI.
	 */
	va_copy(aq, ap);
	vsnprintf(buffer + len, sizeof(buffer) - len, msg, aq);
	va_end(aq);

	finish:
	switch (type) {
	case L_DBG_WARN:
		extra = "WARNING: ";
		type = L_DBG_WARN_REQ;
		break;

	case L_DBG_ERR:
		extra = "ERROR: ";
		type = L_DBG_ERR_REQ;
		break;
	default:
		break;
	}

	if (!fp) {
		if (debug_flag > 2) extra = "";
		request ? radlog(type, "(%u) %s%s", request->number, extra, buffer) :
			  radlog(type, "%s%s", extra, buffer);
	} else {
		if (request) {
			fprintf(fp, "(%u) %s", request->number, extra);
		}
		fputs(buffer, fp);
		fputc('\n', fp);
		fclose(fp);
	}
}
Exemple #9
0
/** Open a new log file, or maybe an existing one.
 *
 * When multithreaded, the FD is locked via a mutex.  This way we're
 * sure that no other thread is writing to the file.
 *
 * @param lf The logfile context returned from fr_logfile_init().
 * @param filename the file to open.
 * @param permissions to use.
 * @return an FD used to write to the file, or -1 on error.
 */
int fr_logfile_open(fr_logfile_t *lf, char const *filename, mode_t permissions)
{
	int i;
	uint32_t hash;
	time_t now = time(NULL);
	struct stat st;

	if (!lf || !filename) return -1;

	hash = fr_hash_string(filename);

	PTHREAD_MUTEX_LOCK(&lf->mutex);

	/*
	 *	Clean up old entries.
	 */
	for (i = 0; i < lf->max_entries; i++) {
		if (!lf->entries[i].filename) continue;

		/*
		 *	FIXME: make this configurable?
		 */
		if ((lf->entries[i].last_used + 30) < now) {
			/*
			 *	This will block forever if a thread is
			 *	doing something stupid.
			 */
			TALLOC_FREE(lf->entries[i].filename);
			close(lf->entries[i].fd);
		}
	}

	/*
	 *	Find the matching entry.
	 */
	for (i = 0; i < lf->max_entries; i++) {
		if (!lf->entries[i].filename) continue;

		if (lf->entries[i].hash == hash) {
			/*
			 *	Same hash but different filename.  Give up.
			 */
			if (strcmp(lf->entries[i].filename, filename) != 0) {
				PTHREAD_MUTEX_UNLOCK(&lf->mutex);
				return -1;
			}
			/*
			 *	Someone else failed to create the entry.
			 */
			if (!lf->entries[i].filename) {
				PTHREAD_MUTEX_UNLOCK(&lf->mutex);
				return -1;
			}
			goto do_return;
		}
	}

	/*
	 *	Find an unused entry
	 */
	for (i = 0; i < lf->max_entries; i++) {
		if (!lf->entries[i].filename) break;
	}

	if (i >= lf->max_entries) {
		fr_strerror_printf("Too many different filenames");
		PTHREAD_MUTEX_UNLOCK(&(lf->mutex));
		return -1;
	}

	/*
	 *	Create a new entry.
	 */

	lf->entries[i].hash = hash;
	lf->entries[i].filename = talloc_strdup(lf->entries, filename);
	lf->entries[i].fd = -1;

	lf->entries[i].fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, permissions);
	if (lf->entries[i].fd < 0) {
		mode_t dirperm;
		char *p, *dir;

		/*
		 *	Maybe the directory doesn't exist.  Try to
		 *	create it.
		 */
		dir = talloc_strdup(lf, filename);
		if (!dir) goto error;
		p = strrchr(dir, FR_DIR_SEP);
		if (!p) goto error;
		*p = '\0';

		/*
		 *	Ensure that the 'x' bit is set, so that we can
		 *	read the directory.
		 */
		dirperm = permissions;
		if ((dirperm & 0600) != 0) dirperm |= 0100;
		if ((dirperm & 0060) != 0) dirperm |= 0010;
		if ((dirperm & 0006) != 0) dirperm |= 0001;

		if (rad_mkdir(dir, dirperm) < 0) {
			talloc_free(dir);
			goto error;
		}
		talloc_free(dir);

		lf->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
		if (lf->entries[i].fd < 0) {
			fr_strerror_printf("Failed to open file %s: %s",
					   filename, strerror(errno));
			goto error;
		} /* else fall through to creating the rest of the entry */
	} /* else the file was already opened */

do_return:
	/*
	 *	Lock from the start of the file.
	 */
	if (lseek(lf->entries[i].fd, 0, SEEK_SET) < 0) {
		fr_strerror_printf("Failed to seek in file %s: %s",
				   filename, strerror(errno));

	error:
		lf->entries[i].hash = 0;
		TALLOC_FREE(lf->entries[i].filename);
		close(lf->entries[i].fd);
		lf->entries[i].fd = -1;

		PTHREAD_MUTEX_UNLOCK(&(lf->mutex));
		return -1;
	}

	if (rad_lockfd(lf->entries[i].fd, 0) < 0) {
		fr_strerror_printf("Failed to lock file %s: %s",
				   filename, strerror(errno));
		goto error;
	}

	/*
	 *	Maybe someone deleted the file while we were waiting
	 *	for the lock.  If so, re-open it.
	 */
	if (fstat(lf->entries[i].fd, &st) < 0) {
		fr_strerror_printf("Failed to stat file %s: %s",
				   filename, strerror(errno));
		goto error;
	}

	if (st.st_nlink == 0) {
		close(lf->entries[i].fd);
		lf->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
		if (lf->entries[i].fd < 0) {
			fr_strerror_printf("Failed to open file %s: %s",
					   filename, strerror(errno));
			goto error;
		}
	}

	/*
	 *	Seek to the end of the file before returning the FD to
	 *	the caller.
	 */
	lseek(lf->entries[i].fd, 0, SEEK_END);

	/*
	 *	Return holding the mutex for the entry.
	 */
	lf->entries[i].last_used = now;
	lf->entries[i].dup = dup(lf->entries[i].fd);
	if (lf->entries[i].dup < 0) goto error;

	return lf->entries[i].dup;
}
Exemple #10
0
/** Send a log message to its destination, possibly including fields from the request
 *
 * @param[in] type	of log message, #L_ERR, #L_WARN, #L_INFO, #L_DBG.
 * @param[in] lvl	Minimum required server or request level to output this message.
 * @param[in] request	The current request.
 * @param[in] msg	with printf style substitution tokens.
 * @param[in] ap	Substitution arguments.
 * @param[in] uctx	The #fr_log_t specifying the destination for log messages.
 */
void vlog_request(fr_log_type_t type, fr_log_lvl_t lvl, REQUEST *request, char const *msg, va_list ap, void *uctx)
{
	char const	*filename;
	FILE		*fp = NULL;

	char		*p;
	char const	*extra = "";
	uint8_t		unlang_indent, module_indent;
	va_list		aq;

	char		*msg_prefix = NULL;
	char		*msg_module = NULL;
	char		*msg_exp = NULL;

	fr_log_t	*log_dst = uctx;

	/*
	 *	No output means no output.
	 */
	if (!log_dst) return;

	filename = log_dst->file;

	/*
	 *	Debug messages get treated specially.
	 */
	if ((type & L_DBG) != 0) {
		if (!log_debug_enabled(type, lvl, request)) return;

		/*
		 *	If we're debugging to a file, then use that.
		 *
		 *	@todo: have fr_vlog() take a fr_log_t*, so
		 *	that we can cache the opened descriptor, and
		 *	we don't need to re-open it on every log
		 *	message.
		 */
		switch (log_dst->dst) {
		case L_DST_FILES:
			fp = fopen(log_dst->file, "a");
			if (!fp) goto finish;
			break;

#if defined(HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
		case L_DST_EXTRA:
		{
#  ifdef HAVE_FOPENCOOKIE
			cookie_io_functions_t io;

			/*
			 *	These must be set separately as they have different prototypes.
			 */
			io.read = NULL;
			io.seek = NULL;
			io.close = NULL;
			io.write = log_dst->cookie_write;

			fp = fopencookie(log_dst->cookie, "w", io);
#  else
			fp = funopen(log_dst->cookie, NULL, log_dst->cookie_write, NULL, NULL);

#  endif
			if (!fp) goto finish;
		}
		break;
#endif
		default:
			break;
		}
		goto print_msg;
	}

	if (filename) {
		char		*exp;
		log_dst_t	*dst;

		dst = request->log.dst;

		/*
		 *	Prevent infinitely recursive calls if
		 *	xlat_aeval attempts to write to the request log.
		 */
		request->log.dst = NULL;

		/*
		 *	This is SLOW!  Doing it for every log message
		 *	in every request is NOT recommended!
		 */
		if (xlat_aeval(request, &exp, request, filename, rad_filename_escape, NULL) < 0) return;

		/*
		 *	Restore the original logging function
		 */
		request->log.dst = dst;

		/*
		 *	Ensure the directory structure exists, for
		 *	where we're going to write the log file.
		 */
		p = strrchr(exp, FR_DIR_SEP);
		if (p) {
			*p = '\0';
			if (rad_mkdir(exp, S_IRWXU, -1, -1) < 0) {
				ERROR("Failed creating %s: %s", exp, fr_syserror(errno));
				talloc_free(exp);
				return;
			}
			*p = FR_DIR_SEP;
		}

		fp = fopen(exp, "a");
		talloc_free(exp);
	}

print_msg:
	/*
	 *	Request prefix i.e.
	 *
	 *	(0) <msg>
	 */
	if ((request->seq_start == 0) || (request->number == request->seq_start)) {
		msg_prefix = talloc_typed_asprintf(request, "(%s)  ", request->name);
	} else {
		msg_prefix = talloc_typed_asprintf(request, "(%s,%" PRIu64 ")  ",
					     request->name, request->seq_start);
	}

	/*
	 *	Make sure the indent isn't set to something crazy
	 */
	unlang_indent = request->log.unlang_indent > sizeof(spaces) - 1 ?
			sizeof(spaces) - 1 :
			request->log.unlang_indent;

	module_indent = request->log.module_indent > sizeof(spaces) - 1 ?
			sizeof(spaces) - 1 :
			request->log.module_indent;

	/*
	 *	Module name and indentation i.e.
	 *
	 *	test -     <msg>
	 */
	if (request->module) {
		msg_module = talloc_typed_asprintf(request, "%s - %.*s", request->module, module_indent, spaces);
	}

	/*
	 *  If we don't copy the original ap we get a segfault from vasprintf. This is apparently
	 *  due to ap sometimes being implemented with a stack offset which is invalidated if
	 *  ap is passed into another function. See here:
	 *  http://julipedia.meroh.net/2011/09/using-vacopy-to-safely-pass-ap.html
	 *
	 *  I don't buy that explanation, but doing a va_copy here does prevent SEGVs seen when
	 *  running unit tests which generate errors under CI.
	 */
	va_copy(aq, ap);
	msg_exp = fr_vasprintf(request, msg, aq);
	va_end(aq);

	/*
	 *	Logging to a file descriptor
	 */
	if (fp) {
		char time_buff[64];	/* The current timestamp */

		time_t timeval;
		timeval = time(NULL);

#ifdef HAVE_GMTIME_R
		if (log_dates_utc) {
			struct tm utc;
			gmtime_r(&timeval, &utc);
			ASCTIME_R(&utc, time_buff, sizeof(time_buff));
		} else
#endif
		{
			CTIME_R(&timeval, time_buff, sizeof(time_buff));
		}

		/*
		 *	Strip trailing new lines
		 */
		p = strrchr(time_buff, '\n');
		if (p) p[0] = '\0';

		fprintf(fp, "%s" "%s : " "%s" "%.*s" "%s" "%s" "\n",
			msg_prefix,
			time_buff,
			fr_int2str(fr_log_levels, type, ""),
			unlang_indent, spaces,
			msg_module ? msg_module : "",
			msg_exp);
		fclose(fp);
		goto finish;
	}

	/*
	 *	Logging everywhere else
	 */
	if (!DEBUG_ENABLED3) switch (type) {
	case L_DBG_WARN:
		extra = "WARNING: ";
		type = L_DBG_WARN_REQ;
		break;

	case L_DBG_ERR:
		extra = "ERROR: ";
		type = L_DBG_ERR_REQ;
		break;
	default:
		break;
	};

	log_always(log_dst,
		      type, "%s" "%.*s" "%s" "%s" "%s",
		      msg_prefix,
		      unlang_indent, spaces,
		      msg_module ? msg_module : "",
		      extra,
		      msg_exp);

finish:
	talloc_free(msg_exp);
	talloc_free(msg_module);
	talloc_free(msg_prefix);
}
Exemple #11
0
/** Open a new log file, or maybe an existing one.
 *
 * When multithreaded, the FD is locked via a mutex.  This way we're
 * sure that no other thread is writing to the file.
 *
 * @param ef The logfile context returned from exfile_init().
 * @param filename the file to open.
 * @param permissions to use.
 * @param append If true seek to the end of the file.
 * @return an FD used to write to the file, or -1 on error.
 */
int exfile_open(exfile_t *ef, char const *filename, mode_t permissions, bool append)
{
	uint32_t i, tries;
	uint32_t hash;
	time_t now = time(NULL);
	struct stat st;

	if (!ef || !filename) return -1;

	hash = fr_hash_string(filename);

	PTHREAD_MUTEX_LOCK(&ef->mutex);

	/*
	 *	Clean up old entries.
	 */
	for (i = 0; i < ef->max_entries; i++) {
		if (!ef->entries[i].filename) continue;
		if ((ef->entries[i].last_used + ef->max_idle) < now) {
			/*
			 *	This will block forever if a thread is
			 *	doing something stupid.
			 */
			TALLOC_FREE(ef->entries[i].filename);
			close(ef->entries[i].fd);
		}
	}

	/*
	 *	Find the matching entry.
	 */
	for (i = 0; i < ef->max_entries; i++) {
		if (!ef->entries[i].filename) continue;

		if (ef->entries[i].hash == hash) {
			/*
			 *	Same hash but different filename.  Give up.
			 */
			if (strcmp(ef->entries[i].filename, filename) != 0) {
				PTHREAD_MUTEX_UNLOCK(&ef->mutex);
				return -1;
			}
			/*
			 *	Someone else failed to create the entry.
			 */
			if (!ef->entries[i].filename) {
				PTHREAD_MUTEX_UNLOCK(&ef->mutex);
				return -1;
			}
			goto do_return;
		}
	}

	/*
	 *	Find an unused entry
	 */
	for (i = 0; i < ef->max_entries; i++) {
		if (!ef->entries[i].filename) break;
	}

	if (i >= ef->max_entries) {
		fr_strerror_printf("Too many different filenames");
		PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
		return -1;
	}

	/*
	 *	Create a new entry.
	 */

	ef->entries[i].hash = hash;
	ef->entries[i].filename = talloc_strdup(ef->entries, filename);
	ef->entries[i].fd = -1;

	ef->entries[i].fd = open(filename, O_RDWR | O_APPEND | O_CREAT, permissions);
	if (ef->entries[i].fd < 0) {
		mode_t dirperm;
		char *p, *dir;

		/*
		 *	Maybe the directory doesn't exist.  Try to
		 *	create it.
		 */
		dir = talloc_strdup(ef, filename);
		if (!dir) goto error;
		p = strrchr(dir, FR_DIR_SEP);
		if (!p) {
			fr_strerror_printf("No '/' in '%s'", filename);
			goto error;
		}
		*p = '\0';

		/*
		 *	Ensure that the 'x' bit is set, so that we can
		 *	read the directory.
		 */
		dirperm = permissions;
		if ((dirperm & 0600) != 0) dirperm |= 0100;
		if ((dirperm & 0060) != 0) dirperm |= 0010;
		if ((dirperm & 0006) != 0) dirperm |= 0001;

		if (rad_mkdir(dir, dirperm, -1, -1) < 0) {
			fr_strerror_printf("Failed to create directory %s: %s",
					   dir, strerror(errno));
			talloc_free(dir);
			goto error;
		}
		talloc_free(dir);

		ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
		if (ef->entries[i].fd < 0) {
			fr_strerror_printf("Failed to open file %s: %s",
					   filename, strerror(errno));
			goto error;
		} /* else fall through to creating the rest of the entry */
	} /* else the file was already opened */

do_return:
	/*
	 *	Lock from the start of the file.
	 */
	if (lseek(ef->entries[i].fd, 0, SEEK_SET) < 0) {
		fr_strerror_printf("Failed to seek in file %s: %s", filename, strerror(errno));

	error:
		ef->entries[i].hash = 0;
		TALLOC_FREE(ef->entries[i].filename);
		close(ef->entries[i].fd);
		ef->entries[i].fd = -1;

		PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
		return -1;
	}

	/*
	 *	Try to lock it.  If we can't lock it, it's because
	 *	some reader has re-named the file to "foo.work" and
	 *	locked it.  So, we close the current file, re-open it,
	 *	and try again/
	 */
	tries = 0;
	while ((rad_lockfd_nonblock(ef->entries[i].fd, 0) < 0) &&
	       (tries < 4)) {
		if (errno != EAGAIN) {
			fr_strerror_printf("Failed to lock file %s: %s", filename, strerror(errno));
			goto error;
		}

		close(ef->entries[i].fd);
		ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
		if (ef->entries[i].fd < 0) {
			fr_strerror_printf("Failed to open file %s: %s",
					   filename, strerror(errno));
			goto error;
		}
	}

	if (tries >= 4) {
		fr_strerror_printf("Failed to lock file %s: too many tries", filename);
		goto error;
	}

	/*
	 *	Maybe someone deleted the file while we were waiting
	 *	for the lock.  If so, re-open it.
	 */
	if (fstat(ef->entries[i].fd, &st) < 0) {
		fr_strerror_printf("Failed to stat file %s: %s", filename, strerror(errno));
		goto error;
	}

	if (st.st_nlink == 0) {
		close(ef->entries[i].fd);
		ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
		if (ef->entries[i].fd < 0) {
			fr_strerror_printf("Failed to open file %s: %s",
					   filename, strerror(errno));
			goto error;
		}
	}

	/*
	 *	Seek to the end of the file before returning the FD to
	 *	the caller.
	 */
	if (append) lseek(ef->entries[i].fd, 0, SEEK_END);

	/*
	 *	Return holding the mutex for the entry.
	 */
	ef->entries[i].last_used = now;
	ef->entries[i].dup = dup(ef->entries[i].fd);
	if (ef->entries[i].dup < 0) {
		fr_strerror_printf("Failed calling dup(): %s", strerror(errno));
		goto error;
	}

	return ef->entries[i].dup;
}