]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_permchannels.cpp
Use iostream instead of C-style file operations.
[user/henk/code/inspircd.git] / src / modules / m_permchannels.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
5  *   Copyright (C) 2008-2009 Robin Burchell <robin+git@viroteck.net>
6  *
7  * This file is part of InspIRCd.  InspIRCd is free software: you can
8  * redistribute it and/or modify it under the terms of the GNU General Public
9  * License as published by the Free Software Foundation, version 2.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
14  * details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20
21 #include "inspircd.h"
22 #include <fstream>
23
24 /* $ModDesc: Provides support for channel mode +P to provide permanent channels */
25
26 // Not in a class due to circular dependancy hell.
27 static std::string permchannelsconf;
28 static bool WriteDatabase()
29 {
30         /*
31          * We need to perform an atomic write so as not to fuck things up.
32          * So, let's write to a temporary file, flush it, then rename the file..
33          *     -- w00t
34          */
35         
36         // If the user has not specified a configuration file then we don't write one.
37         if (permchannelsconf.empty())
38                 return true;
39
40         std::string permchannelsnewconf = permchannelsconf + ".tmp";
41         std::ofstream stream(permchannelsnewconf.c_str());
42         if (!stream.is_open())
43         {
44                 ServerInstance->Logs->Log("m_permchannels", LOG_DEFAULT, "permchannels: Cannot create database! %s (%d)", strerror(errno), errno);
45                 ServerInstance->SNO->WriteToSnoMask('a', "database: cannot create new db: %s (%d)", strerror(errno), errno);
46                 return false;
47         }
48         
49         stream << "# This file is automatically generated by m_permchannels. Any changes will be overwritten." << std::endl
50                 << "<config format=\"xml\">" << std::endl;
51
52         for (chan_hash::const_iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end(); i++)
53         {
54                 Channel* chan = i->second;
55                 if (!chan->IsModeSet('P'))
56                         continue;
57
58                 stream << "<permchannels channel=\"" << ServerConfig::Escape(chan->name)
59                         << "\" topic=\"" << ServerConfig::Escape(chan->topic)
60                         << "\" modes=\"" << ServerConfig::Escape(chan->ChanModes(true))
61                         << "\">" << std::endl;
62         }
63
64         if (stream.fail())
65         {
66                 ServerInstance->Logs->Log("m_permchannels", LOG_DEFAULT, "permchannels: Cannot write to new database! %s (%d)", strerror(errno), errno);
67                 ServerInstance->SNO->WriteToSnoMask('a', "database: cannot write to new db: %s (%d)", strerror(errno), errno);
68                 return false;
69         }
70         stream.close();
71
72 #ifdef _WIN32
73         if (remove(permchannelsconf.c_str()))
74         {
75                 ServerInstance->Logs->Log("m_permchannels", LOG_DEFAULT, "permchannels: Cannot remove old database! %s (%d)", strerror(errno), errno);
76                 ServerInstance->SNO->WriteToSnoMask('a', "database: cannot remove old database: %s (%d)", strerror(errno), errno);
77                 return false;
78         }
79 #endif
80         // Use rename to move temporary to new db - this is guarenteed not to fuck up, even in case of a crash.
81         if (rename(permchannelsnewconf.c_str(), permchannelsconf.c_str()) < 0)
82         {
83                 ServerInstance->Logs->Log("m_permchannels", LOG_DEFAULT, "permchannels: Cannot move new to old database! %s (%d)", strerror(errno), errno);
84                 ServerInstance->SNO->WriteToSnoMask('a', "database: cannot replace old with new db: %s (%d)", strerror(errno), errno);
85                 return false;
86         }
87
88         return true;
89 }
90
91
92 /** Handles the +P channel mode
93  */
94 class PermChannel : public ModeHandler
95 {
96  public:
97         PermChannel(Module* Creator) : ModeHandler(Creator, "permanent", 'P', PARAM_NONE, MODETYPE_CHANNEL) { oper = true; }
98
99         ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string &parameter, bool adding)
100         {
101                 if (adding)
102                 {
103                         if (!channel->IsModeSet('P'))
104                         {
105                                 channel->SetMode('P',true);
106                                 return MODEACTION_ALLOW;
107                         }
108                 }
109                 else
110                 {
111                         if (channel->IsModeSet('P'))
112                         {
113                                 channel->SetMode(this,false);
114                                 channel->CheckDestroy();
115                                 return MODEACTION_ALLOW;
116                         }
117                 }
118
119                 return MODEACTION_DENY;
120         }
121 };
122
123 class ModulePermanentChannels : public Module
124 {
125         PermChannel p;
126         bool dirty;
127 public:
128
129         ModulePermanentChannels() : p(this), dirty(false)
130         {
131         }
132
133         void init() CXX11_OVERRIDE
134         {
135                 ServerInstance->Modules->AddService(p);
136                 Implementation eventlist[] = { I_OnChannelPreDelete, I_OnPostTopicChange, I_OnRawMode, I_OnRehash, I_OnBackgroundTimer };
137                 ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
138
139                 OnRehash(NULL);
140         }
141
142         CullResult cull()
143         {
144                 /*
145                  * DelMode can't remove the +P mode on empty channels, or it will break
146                  * merging modes with remote servers. Remove the empty channels now as
147                  * we know this is not the case.
148                  */
149                 chan_hash::iterator iter = ServerInstance->chanlist->begin();
150                 while (iter != ServerInstance->chanlist->end())
151                 {
152                         Channel* c = iter->second;
153                         if (c->GetUserCounter() == 0)
154                         {
155                                 chan_hash::iterator at = iter;
156                                 iter++;
157                                 FOREACH_MOD(I_OnChannelDelete, OnChannelDelete(c));
158                                 ServerInstance->chanlist->erase(at);
159                                 ServerInstance->GlobalCulls.AddItem(c);
160                         }
161                         else
162                                 iter++;
163                 }
164                 ServerInstance->Modes->DelMode(&p);
165                 return Module::cull();
166         }
167
168         void OnRehash(User *user) CXX11_OVERRIDE
169         {
170                 permchannelsconf = ServerInstance->Config->ConfValue("permchanneldb")->getString("filename");
171         }
172
173         void LoadDatabase()
174         {
175                 /*
176                  * Process config-defined list of permanent channels.
177                  * -- w00t
178                  */
179                 ConfigTagList permchannels = ServerInstance->Config->ConfTags("permchannels");
180                 for (ConfigIter i = permchannels.first; i != permchannels.second; ++i)
181                 {
182                         ConfigTag* tag = i->second;
183                         std::string channel = tag->getString("channel");
184                         std::string topic = tag->getString("topic");
185                         std::string modes = tag->getString("modes");
186
187                         if (channel.empty())
188                         {
189                                 ServerInstance->Logs->Log("m_permchannels", LOG_DEBUG, "Malformed permchannels tag with empty channel name.");
190                                 continue;
191                         }
192
193                         Channel *c = ServerInstance->FindChan(channel);
194
195                         if (!c)
196                         {
197                                 c = new Channel(channel, ServerInstance->Time());
198                                 if (!topic.empty())
199                                 {
200                                         c->SetTopic(ServerInstance->FakeClient, topic, true);
201
202                                         /*
203                                          * Due to the way protocol works in 1.2, we need to hack the topic TS in such a way that this
204                                          * topic will always win over others.
205                                          *
206                                          * This is scheduled for (proper) fixing in a later release, and can be removed at a later date.
207                                          */
208                                         c->topicset = 42;
209                                 }
210                                 ServerInstance->Logs->Log("m_permchannels", LOG_DEBUG, "Added %s with topic %s", channel.c_str(), topic.c_str());
211
212                                 if (modes.empty())
213                                         continue;
214
215                                 irc::spacesepstream list(modes);
216                                 std::string modeseq;
217                                 std::string par;
218
219                                 list.GetToken(modeseq);
220
221                                 // XXX bleh, should we pass this to the mode parser instead? ugly. --w00t
222                                 for (std::string::iterator n = modeseq.begin(); n != modeseq.end(); ++n)
223                                 {
224                                         ModeHandler* mode = ServerInstance->Modes->FindMode(*n, MODETYPE_CHANNEL);
225                                         if (mode)
226                                         {
227                                                 if (mode->GetNumParams(true))
228                                                         list.GetToken(par);
229                                                 else
230                                                         par.clear();
231
232                                                 mode->OnModeChange(ServerInstance->FakeClient, ServerInstance->FakeClient, c, par, true);
233                                         }
234                                 }
235                         }
236                 }
237         }
238
239         ModResult OnRawMode(User* user, Channel* chan, const char mode, const std::string &param, bool adding, int pcnt) CXX11_OVERRIDE
240         {
241                 if (chan && (chan->IsModeSet('P') || mode == 'P'))
242                         dirty = true;
243
244                 return MOD_RES_PASSTHRU;
245         }
246
247         void OnPostTopicChange(User*, Channel *c, const std::string&) CXX11_OVERRIDE
248         {
249                 if (c->IsModeSet('P'))
250                         dirty = true;
251         }
252
253         void OnBackgroundTimer(time_t) CXX11_OVERRIDE
254         {
255                 if (dirty)
256                         WriteDatabase();
257                 dirty = false;
258         }
259
260         void Prioritize()
261         {
262                 // XXX: Load the DB here because the order in which modules are init()ed at boot is
263                 // alphabetical, this means we must wait until all modules have done their init()
264                 // to be able to set the modes they provide (e.g.: m_stripcolor is inited after us)
265                 // Prioritize() is called after all module initialization is complete, consequently
266                 // all modes are available now
267
268                 static bool loaded = false;
269                 if (loaded)
270                         return;
271
272                 loaded = true;
273
274                 // Load only when there are no linked servers - we set the TS of the channels we
275                 // create to the current time, this can lead to desync because spanningtree has
276                 // no way of knowing what we do
277                 ProtoServerList serverlist;
278                 ServerInstance->PI->GetServerList(serverlist);
279                 if (serverlist.size() < 2)
280                 {
281                         try
282                         {
283                                 LoadDatabase();
284                         }
285                         catch (CoreException& e)
286                         {
287                                 ServerInstance->Logs->Log("m_permchannels", LOG_DEFAULT, "Error loading permchannels database: " + std::string(e.GetReason()));
288                         }
289                 }
290         }
291
292         Version GetVersion() CXX11_OVERRIDE
293         {
294                 return Version("Provides support for channel mode +P to provide permanent channels",VF_VENDOR);
295         }
296
297         ModResult OnChannelPreDelete(Channel *c) CXX11_OVERRIDE
298         {
299                 if (c->IsModeSet('P'))
300                         return MOD_RES_DENY;
301
302                 return MOD_RES_PASSTHRU;
303         }
304 };
305
306 MODULE_INIT(ModulePermanentChannels)