1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2013-2014 Attila Molnar <attilamolnar@hush.com>
5  *   Copyright (C) 2013, 2017-2018, 2020-2021 Sadie Powell <sadie@witchery.services>
6  *   Copyright (C) 2013 Daniel Vassdal <shutter@canternet.org>
7  *   Copyright (C) 2012 Robby <robby@chatbelgie.be>
8  *   Copyright (C) 2010 Craig Edwards <brain@inspircd.org>
9  *   Copyright (C) 2010 Adam <Adam@anope.org>
10  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
11  *   Copyright (C) 2009 Uli Schlachter <psychon@inspircd.org>
12  *   Copyright (C) 2008-2009 Robin Burchell <robin+git@viroteck.net>
13  *   Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
14  *
15  * This file is part of InspIRCd.  InspIRCd is free software: you can
16  * redistribute it and/or modify it under the terms of the GNU General Public
17  * License as published by the Free Software Foundation, version 2.
18  *
19  * This program is distributed in the hope that it will be useful, but WITHOUT
20  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
22  * details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
26  */
27 
28 
29 #include "inspircd.h"
30 
31 /*
32  * Suggested implementation...
33  *	class LogManager
34  *		bool AddLogType(const std::string &type, enum loglevel, LogStream *)
35  *		bool DelLogType(const std::string &type, LogStream *)
36  *		Log(const std::string &type, enum loglevel, const std::string &msg)
37  *		std::map<std::string, std::vector<LogStream *> > logstreams (holds a 'chain' of logstreams for each type that are all notified when a log happens)
38  *
39  *  class LogStream
40  *		std::string type
41  *      virtual void OnLog(enum loglevel, const std::string &msg)
42  *
43  * How it works:
44  *  Modules create their own logstream types (core will create one for 'file logging' for example) and create instances of these logstream types
45  *  and register interest in a certain logtype. Globbing is not here, with the exception of * - for all events.. loglevel is used to drop
46  *  events that are of no interest to a logstream.
47  *
48  *  When Log is called, the vector of logstreams for that type is iterated (along with the special vector for "*"), and all registered logstreams
49  *  are called back ("OnLog" or whatever) to do whatever they like with the message. In the case of the core, this will write to a file.
50  *  In the case of the module I plan to write (m_logtochannel or something), it will log to the channel(s) for that logstream, etc.
51  *
52  * NOTE: Somehow we have to let LogManager manage the non-blocking file streams and provide an interface to share them with various LogStreams,
53  *       as, for example, a user may want to let 'KILL' and 'XLINE' snotices go to /home/ircd/inspircd/logs/operactions.log, or whatever. How
54  *       can we accomplish this easily? I guess with a map of pre-loved logpaths, and a pointer of FILE *..
55  *
56  */
57 
58 const char LogStream::LogHeader[] =
59 	"Log started for " INSPIRCD_VERSION;
60 
LogManager()61 LogManager::LogManager()
62 	: Logging(false)
63 {
64 }
65 
~LogManager()66 LogManager::~LogManager()
67 {
68 }
69 
OpenFileLogs()70 void LogManager::OpenFileLogs()
71 {
72 	if (ServerInstance->Config->cmdline.forcedebug)
73 	{
74 		ServerInstance->Config->RawLog = true;
75 		return;
76 	}
77 	/* Skip rest of logfile opening if we are running -nolog. */
78 	if (!ServerInstance->Config->cmdline.writelog)
79 		return;
80 	std::map<std::string, FileWriter*> logmap;
81 	ConfigTagList tags = ServerInstance->Config->ConfTags("log");
82 	for(ConfigIter i = tags.first; i != tags.second; ++i)
83 	{
84 		ConfigTag* tag = i->second;
85 		const std::string method = tag->getString("method", "file", 1);
86 		if (!stdalgo::string::equalsci(method, "file"))
87 		{
88 			continue;
89 		}
90 		std::string type = tag->getString("type");
91 		std::string level = tag->getString("level");
92 		LogLevel loglevel = LOG_DEFAULT;
93 		if (stdalgo::string::equalsci(level, "rawio"))
94 		{
95 			loglevel = LOG_RAWIO;
96 			ServerInstance->Config->RawLog = true;
97 		}
98 		else if (stdalgo::string::equalsci(level, "debug"))
99 		{
100 			loglevel = LOG_DEBUG;
101 		}
102 		else if (stdalgo::string::equalsci(level, "verbose"))
103 		{
104 			loglevel = LOG_VERBOSE;
105 		}
106 		else if (stdalgo::string::equalsci(level, "sparse"))
107 		{
108 			loglevel = LOG_SPARSE;
109 		}
110 		else if (stdalgo::string::equalsci(level, "none"))
111 		{
112 			loglevel = LOG_NONE;
113 		}
114 		FileWriter* fw;
115 		std::string target = ServerInstance->Config->Paths.PrependLog(tag->getString("target"));
116 		std::map<std::string, FileWriter*>::iterator fwi = logmap.find(target);
117 		if (fwi == logmap.end())
118 		{
119 			char realtarget[256];
120 			time_t time = ServerInstance->Time();
121 			struct tm *mytime = gmtime(&time);
122 			strftime(realtarget, sizeof(realtarget), target.c_str(), mytime);
123 			FILE* f = fopen(realtarget, "a");
124 			fw = new FileWriter(f, tag->getUInt("flush", 20, 1, UINT_MAX));
125 			logmap.insert(std::make_pair(target, fw));
126 		}
127 		else
128 		{
129 			fw = fwi->second;
130 		}
131 		FileLogStream* fls = new FileLogStream(loglevel, fw);
132 		fls->OnLog(LOG_SPARSE, "HEADER", LogStream::LogHeader);
133 		AddLogTypes(type, fls, true);
134 	}
135 }
136 
CloseLogs()137 void LogManager::CloseLogs()
138 {
139 	if (ServerInstance->Config && ServerInstance->Config->cmdline.forcedebug)
140 		return;
141 
142 	LogStreams.clear();
143 	GlobalLogStreams.clear();
144 
145 	for (std::map<LogStream*, int>::iterator i = AllLogStreams.begin(); i != AllLogStreams.end(); ++i)
146 	{
147 		delete i->first;
148 	}
149 
150 	AllLogStreams.clear();
151 }
152 
AddLogTypes(const std::string & types,LogStream * l,bool autoclose)153 void LogManager::AddLogTypes(const std::string &types, LogStream* l, bool autoclose)
154 {
155 	irc::spacesepstream css(types);
156 	std::string tok;
157 	std::vector<std::string> excludes;
158 	while (css.GetToken(tok))
159 	{
160 		if (tok.empty())
161 		{
162 			continue;
163 		}
164 		if (tok.at(0) == '-')
165 		{
166 			/* Exclude! */
167 			excludes.push_back(tok.substr(1));
168 		}
169 		else
170 		{
171 			AddLogType(tok, l, autoclose);
172 		}
173 	}
174 	// Handle doing things like: USERINPUT USEROUTPUT -USERINPUT should be the same as saying just USEROUTPUT.
175 	// (This is so modules could, for example, inject exclusions for logtypes they can't handle.)
176 	for (std::vector<std::string>::iterator i = excludes.begin(); i != excludes.end(); ++i)
177 	{
178 		if (*i == "*")
179 		{
180 			/* -* == Exclude all. Why someone would do this, I dunno. */
181 			DelLogStream(l);
182 			return;
183 		}
184 		DelLogType(*i, l);
185 	}
186 	// Now if it's registered as a global, add the exclusions there too.
187 	std::map<LogStream *, std::vector<std::string> >::iterator gi = GlobalLogStreams.find(l);
188 	if (gi != GlobalLogStreams.end())
189 	{
190 		gi->second.swap(excludes); // Swap with the vector in the hash.
191 	}
192 }
193 
AddLogType(const std::string & type,LogStream * l,bool autoclose)194 bool LogManager::AddLogType(const std::string &type, LogStream *l, bool autoclose)
195 {
196 	LogStreams[type].push_back(l);
197 
198 	if (type == "*")
199 		GlobalLogStreams.insert(std::make_pair(l, std::vector<std::string>()));
200 
201 	if (autoclose)
202 		AllLogStreams[l]++;
203 
204 	return true;
205 }
206 
DelLogStream(LogStream * l)207 void LogManager::DelLogStream(LogStream* l)
208 {
209 	for (std::map<std::string, std::vector<LogStream*> >::iterator i = LogStreams.begin(); i != LogStreams.end(); ++i)
210 	{
211 		while (stdalgo::erase(i->second, l))
212 		{
213 			// Keep erasing while it exists
214 		}
215 	}
216 
217 	GlobalLogStreams.erase(l);
218 
219 	std::map<LogStream*, int>::iterator ai = AllLogStreams.begin();
220 	if (ai == AllLogStreams.end())
221 	{
222 		return; /* Done. */
223 	}
224 
225 	delete ai->first;
226 	AllLogStreams.erase(ai);
227 }
228 
DelLogType(const std::string & type,LogStream * l)229 bool LogManager::DelLogType(const std::string &type, LogStream *l)
230 {
231 	std::map<std::string, std::vector<LogStream *> >::iterator i = LogStreams.find(type);
232 	if (type == "*")
233 	{
234 		GlobalLogStreams.erase(l);
235 	}
236 
237 	if (i != LogStreams.end())
238 	{
239 		if (stdalgo::erase(i->second, l))
240 		{
241 			if (i->second.size() == 0)
242 			{
243 				LogStreams.erase(i);
244 			}
245 		}
246 		else
247 		{
248 			return false;
249 		}
250 	}
251 	else
252 	{
253 		return false;
254 	}
255 
256 	std::map<LogStream*, int>::iterator ai = AllLogStreams.find(l);
257 	if (ai == AllLogStreams.end())
258 	{
259 		return true;
260 	}
261 
262 	if ((--ai->second) < 1)
263 	{
264 		AllLogStreams.erase(ai);
265 		delete l;
266 	}
267 
268 	return true;
269 }
270 
Log(const std::string & type,LogLevel loglevel,const char * fmt,...)271 void LogManager::Log(const std::string &type, LogLevel loglevel, const char *fmt, ...)
272 {
273 	if (Logging)
274 		return;
275 
276 	std::string buf;
277 	VAFORMAT(buf, fmt, fmt);
278 	this->Log(type, loglevel, buf);
279 }
280 
Log(const std::string & type,LogLevel loglevel,const std::string & msg)281 void LogManager::Log(const std::string &type, LogLevel loglevel, const std::string &msg)
282 {
283 	if (Logging)
284 	{
285 		return;
286 	}
287 
288 	Logging = true;
289 
290 	for (std::map<LogStream *, std::vector<std::string> >::iterator gi = GlobalLogStreams.begin(); gi != GlobalLogStreams.end(); ++gi)
291 	{
292 		if (stdalgo::isin(gi->second, type))
293 		{
294 			continue;
295 		}
296 		gi->first->OnLog(loglevel, type, msg);
297 	}
298 
299 	std::map<std::string, std::vector<LogStream *> >::iterator i = LogStreams.find(type);
300 
301 	if (i != LogStreams.end())
302 	{
303 		for (std::vector<LogStream *>::iterator it = i->second.begin(); it != i->second.end(); ++it)
304 		{
305 			(*it)->OnLog(loglevel, type, msg);
306 		}
307 	}
308 
309 	Logging = false;
310 }
311 
312 
FileWriter(FILE * logfile,unsigned int flushcount)313 FileWriter::FileWriter(FILE* logfile, unsigned int flushcount)
314 	: log(logfile)
315 	, flush(flushcount)
316 	, writeops(0)
317 {
318 }
319 
WriteLogLine(const std::string & line)320 void FileWriter::WriteLogLine(const std::string &line)
321 {
322 	if (log == NULL)
323 		return;
324 // XXX: For now, just return. Don't throw an exception. It'd be nice to find out if this is happening, but I'm terrified of breaking so close to final release. -- w00t
325 //		throw CoreException("FileWriter::WriteLogLine called with a closed logfile");
326 
327 	fputs(line.c_str(), log);
328 	if (++writeops % flush == 0)
329 	{
330 		fflush(log);
331 	}
332 }
333 
~FileWriter()334 FileWriter::~FileWriter()
335 {
336 	if (log)
337 	{
338 		fflush(log);
339 		fclose(log);
340 		log = NULL;
341 	}
342 }
343