]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_permchannels.cpp
41a5daf333efb06cb1a6881ad342796ea354779d
[user/henk/code/inspircd.git] / src / modules / m_permchannels.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15
16 /* $ModDesc: Provides support for channel mode +P to provide permanent channels */
17
18 // Not in a class due to circular dependancy hell.
19 static std::string permchannelsconf;
20 static bool WriteDatabase(InspIRCd *ServerInstance)
21 {
22         FILE *f;
23
24         if (permchannelsconf.empty())
25         {
26                 // Fake success.
27                 return true;
28         }
29
30         std::string tempname = permchannelsconf + ".tmp";
31
32         /*
33          * We need to perform an atomic write so as not to fuck things up.
34          * So, let's write to a temporary file, flush and sync the FD, then rename the file..
35          *              -- w00t
36          */
37         f = fopen(tempname.c_str(), "w");
38         if (!f)
39         {
40                 ServerInstance->Logs->Log("m_permchannels",DEFAULT, "permchannels: Cannot create database! %s (%d)", strerror(errno), errno);
41                 ServerInstance->SNO->WriteToSnoMask('a', "database: cannot create new db: %s (%d)", strerror(errno), errno);
42                 return false;
43         }
44
45         // Now, let's write.
46         Channel *c = NULL;
47
48         for (chan_hash::const_iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end(); i++)
49         {
50                 c = i->second;
51
52                 if (c->IsModeSet('P'))
53                 {
54                         fprintf(f, "<permchannels channel=\"%s\" topic=\"%s\" modes=\"%s\">\n", c->name.c_str(), c->topic.c_str(), c->ChanModes(true));
55                 }
56         }
57
58         int write_error = 0;
59         write_error = ferror(f);
60         write_error |= fclose(f);
61         if (write_error)
62         {
63                 ServerInstance->Logs->Log("m_permchannels",DEFAULT, "permchannels: Cannot write to new database! %s (%d)", strerror(errno), errno);
64                 ServerInstance->SNO->WriteToSnoMask('x', "database: cannot write to new db: %s (%d)", strerror(errno), errno);
65                 return false;
66         }
67
68         // Use rename to move temporary to new db - this is guarenteed not to fuck up, even in case of a crash.
69         if (rename(tempname.c_str(), permchannelsconf.c_str()) < 0)
70         {
71                 ServerInstance->Logs->Log("m_permchannels",DEFAULT, "permchannels: Cannot move new to old database! %s (%d)", strerror(errno), errno);
72                 ServerInstance->SNO->WriteToSnoMask('x', "database: cannot replace old with new db: %s (%d)", strerror(errno), errno);
73                 return false;
74         }
75
76         return true;
77 }
78
79
80
81 /** Handles the +P channel mode
82  */
83 class PermChannel : public ModeHandler
84 {
85  public:
86         PermChannel(InspIRCd* Instance, Module* Creator) : ModeHandler(Creator, 'P', PARAM_NONE, MODETYPE_CHANNEL) { }
87
88         ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string &parameter, bool adding)
89         {
90                 if (!source->HasPrivPermission("channels/set-permanent"))
91                 {
92                         source->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - You do not have the required operator privileges", source->nick.c_str());
93                         return MODEACTION_DENY;
94                 }
95
96                 if (adding)
97                 {
98                         if (!channel->IsModeSet('P'))
99                         {
100                                 channel->SetMode('P',true);
101
102                                 // Save permchannels db if needed.
103                                 WriteDatabase(ServerInstance);
104                                 return MODEACTION_ALLOW;
105                         }
106                 }
107                 else
108                 {
109                         if (channel->IsModeSet('P'))
110                         {
111                                 if (channel->GetUserCounter() == 0 && !IS_FAKE(source))
112                                 {
113                                         /*
114                                          * ugh, ugh, UGH!
115                                          *
116                                          * We can't delete this channel the way things work at the moment,
117                                          * because of the following scenario:
118                                          * s1:#c <-> s2:#c
119                                          *
120                                          * s1 has a user in #c, s2 does not. s2 has +P set. s2 has a losing TS.
121                                          *
122                                          * On netmerge, s2 loses, so s2 removes all modes (including +P) which
123                                          * would subsequently delete the channel here causing big fucking problems.
124                                          *
125                                          * I don't think there's really a way around this, so just deny -P on a 0 user chan.
126                                          * -- w00t
127                                          *
128                                          * delete channel;
129                                          */
130                                         return MODEACTION_DENY;
131                                 }
132
133                                 /* for servers, remove +P (to avoid desyncs) but don't bother trying to delete. */
134                                 channel->SetMode('P',false);
135
136                                 // Save permchannels db if needed.
137                                 WriteDatabase(ServerInstance);
138                                 return MODEACTION_ALLOW;
139                         }
140                 }
141
142                 return MODEACTION_DENY;
143         }
144 };
145
146 class ModulePermanentChannels : public Module
147 {
148         PermChannel p;
149 public:
150
151         ModulePermanentChannels(InspIRCd* Me) : Module(Me), p(Me, this)
152         {
153                 if (!ServerInstance->Modes->AddMode(&p))
154                         throw ModuleException("Could not add new modes!");
155                 Implementation eventlist[] = { I_OnChannelPreDelete, I_OnPostTopicChange, I_OnRawMode };
156                 ServerInstance->Modules->Attach(eventlist, this, 3);
157
158                 OnRehash(NULL);
159         }
160
161         virtual ~ModulePermanentChannels()
162         {
163                 ServerInstance->Modes->DelMode(&p);
164                 /*
165                  * DelMode can't remove the +P mode on empty channels, or it will break
166                  * merging modes with remote servers. Remove the empty channels now as
167                  * we know this is not the case.
168                  */
169                 chan_hash::iterator iter = ServerInstance->chanlist->begin();
170                 while (iter != ServerInstance->chanlist->end())
171                 {
172                         Channel* c = iter->second;
173                         if (c->GetUserCounter() == 0)
174                         {
175                                 chan_hash::iterator at = iter;
176                                 iter++;
177                                 ServerInstance->chanlist->erase(at);
178                                 delete c;
179                         }
180                         else
181                                 iter++;
182                 }
183         }
184
185         virtual void OnRehash(User *user)
186         {
187                 /*
188                  * Process config-defined list of permanent channels.
189                  * -- w00t
190                  */
191                 ConfigReader MyConf(ServerInstance);
192
193                 permchannelsconf = MyConf.ReadValue("permchanneldb", "filename", "", 0, false);
194
195                 for (int i = 0; i < MyConf.Enumerate("permchannels"); i++)
196                 {
197                         std::string channel = MyConf.ReadValue("permchannels", "channel", i);
198                         std::string topic = MyConf.ReadValue("permchannels", "topic", i);
199                         std::string modes = MyConf.ReadValue("permchannels", "modes", i);
200
201                         if (channel.empty())
202                         {
203                                 ServerInstance->Logs->Log("blah", DEBUG, "Malformed permchannels tag with empty channel name.");
204                                 continue;
205                         }
206
207                         Channel *c = ServerInstance->FindChan(channel);
208
209                         if (!c)
210                         {
211                                 c = new Channel(ServerInstance, channel, ServerInstance->Time());
212                                 if (!topic.empty())
213                                 {
214                                         c->SetTopic(NULL, topic, true);
215
216                                         /*
217                                          * Due to the way protocol works in 1.2, we need to hack the topic TS in such a way that this
218                                          * topic will always win over others.
219                                          *
220                                          * This is scheduled for (proper) fixing in a later release, and can be removed at a later date.
221                                          */
222                                         c->topicset = 42;
223                                 }
224                                 ServerInstance->Logs->Log("blah", DEBUG, "Added %s with topic %s", channel.c_str(), topic.c_str());
225
226                                 if (modes.empty())
227                                         continue;
228
229                                 irc::spacesepstream list(modes);
230                                 std::string modeseq;
231                                 std::string par;
232
233                                 list.GetToken(modeseq);
234
235                                 // XXX bleh, should we pass this to the mode parser instead? ugly. --w00t
236                                 for (std::string::iterator n = modeseq.begin(); n != modeseq.end(); ++n)
237                                 {
238                                         ModeHandler* mode = ServerInstance->Modes->FindMode(*n, MODETYPE_CHANNEL);
239                                         if (mode)
240                                         {
241                                                 if (mode->GetNumParams(true))
242                                                         list.GetToken(par);
243                                                 else
244                                                         par.clear();
245
246                                                 mode->OnModeChange(ServerInstance->FakeClient, ServerInstance->FakeClient, c, par, true);
247                                         }
248                                 }
249                         }
250                 }
251         }
252
253         virtual ModResult OnRawMode(User* user, Channel* chan, const char mode, const std::string &param, bool adding, int pcnt)
254         {
255                 if (chan && chan->IsModeSet('P'))
256                         WriteDatabase(ServerInstance);
257
258                 return MOD_RES_PASSTHRU;
259         }
260
261         virtual void OnPostTopicChange(User*, Channel *c, const std::string&)
262         {
263                 if (c->IsModeSet('P'))
264                         WriteDatabase(ServerInstance);
265         }
266
267         virtual Version GetVersion()
268         {
269                 return Version("$Id$",VF_COMMON|VF_VENDOR,API_VERSION);
270         }
271
272         virtual ModResult OnChannelPreDelete(Channel *c)
273         {
274                 if (c->IsModeSet('P'))
275                         return MOD_RES_DENY;
276
277                 return MOD_RES_PASSTHRU;
278         }
279 };
280
281 MODULE_INIT(ModulePermanentChannels)