1 /*
2  * Copyright (C) 2004-2020 ZNC, see the NOTICE file for details.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <znc/Chan.h>
18 #include <znc/Modules.h>
19 
20 using std::vector;
21 
22 class CAttachMatch {
23   public:
CAttachMatch(CModule * pModule,const CString & sChannels,const CString & sSearch,const CString & sHostmasks,bool bNegated)24     CAttachMatch(CModule* pModule, const CString& sChannels,
25                  const CString& sSearch, const CString& sHostmasks,
26                  bool bNegated) {
27         m_pModule = pModule;
28         m_sChannelWildcard = sChannels;
29         m_sSearchWildcard = sSearch;
30         m_sHostmaskWildcard = sHostmasks;
31         m_bNegated = bNegated;
32 
33         if (m_sChannelWildcard.empty()) m_sChannelWildcard = "*";
34         if (m_sSearchWildcard.empty()) m_sSearchWildcard = "*";
35         if (m_sHostmaskWildcard.empty()) m_sHostmaskWildcard = "*!*@*";
36     }
37 
IsMatch(const CString & sChan,const CString & sHost,const CString & sMessage) const38     bool IsMatch(const CString& sChan, const CString& sHost,
39                  const CString& sMessage) const {
40         if (!sHost.WildCmp(m_sHostmaskWildcard, CString::CaseInsensitive))
41             return false;
42         if (!sChan.WildCmp(m_sChannelWildcard, CString::CaseInsensitive))
43             return false;
44         if (!sMessage.WildCmp(m_pModule->ExpandString(m_sSearchWildcard),
45                               CString::CaseInsensitive))
46             return false;
47         return true;
48     }
49 
IsNegated() const50     bool IsNegated() const { return m_bNegated; }
51 
GetHostMask() const52     const CString& GetHostMask() const { return m_sHostmaskWildcard; }
53 
GetSearch() const54     const CString& GetSearch() const { return m_sSearchWildcard; }
55 
GetChans() const56     const CString& GetChans() const { return m_sChannelWildcard; }
57 
ToString()58     CString ToString() {
59         CString sRes;
60         if (m_bNegated) sRes += "!";
61         sRes += m_sChannelWildcard;
62         sRes += " ";
63         sRes += m_sSearchWildcard;
64         sRes += " ";
65         sRes += m_sHostmaskWildcard;
66         return sRes;
67     }
68 
69   private:
70     bool m_bNegated;
71     CModule* m_pModule;
72     CString m_sChannelWildcard;
73     CString m_sSearchWildcard;
74     CString m_sHostmaskWildcard;
75 };
76 
77 class CChanAttach : public CModule {
78   public:
79     typedef vector<CAttachMatch> VAttachMatch;
80     typedef VAttachMatch::iterator VAttachIter;
81 
82   private:
HandleAdd(const CString & sLine)83     void HandleAdd(const CString& sLine) {
84         CString sMsg = sLine.Token(1, true);
85         bool bHelp = false;
86         bool bNegated = sMsg.TrimPrefix("!");
87         CString sChan = sMsg.Token(0);
88         CString sSearch = sMsg.Token(1);
89         CString sHost = sMsg.Token(2);
90 
91         if (sChan.empty()) {
92             bHelp = true;
93         } else if (Add(bNegated, sChan, sSearch, sHost)) {
94             PutModule(t_s("Added to list"));
95         } else {
96             PutModule(t_f("{1} is already added")(sLine.Token(1, true)));
97             bHelp = true;
98         }
99         if (bHelp) {
100             PutModule(t_s("Usage: Add [!]<#chan> <search> <host>"));
101             PutModule(t_s("Wildcards are allowed"));
102         }
103     }
104 
HandleDel(const CString & sLine)105     void HandleDel(const CString& sLine) {
106         CString sMsg = sLine.Token(1, true);
107         bool bNegated = sMsg.TrimPrefix("!");
108         CString sChan = sMsg.Token(0);
109         CString sSearch = sMsg.Token(1);
110         CString sHost = sMsg.Token(2);
111 
112         if (Del(bNegated, sChan, sSearch, sHost)) {
113             PutModule(t_f("Removed {1} from list")(sChan));
114         } else {
115             PutModule(t_s("Usage: Del [!]<#chan> <search> <host>"));
116         }
117     }
118 
HandleList(const CString & sLine)119     void HandleList(const CString& sLine) {
120         CTable Table;
121         Table.AddColumn(t_s("Neg"));
122         Table.AddColumn(t_s("Chan"));
123         Table.AddColumn(t_s("Search"));
124         Table.AddColumn(t_s("Host"));
125 
126         VAttachIter it = m_vMatches.begin();
127         for (; it != m_vMatches.end(); ++it) {
128             Table.AddRow();
129             Table.SetCell(t_s("Neg"), it->IsNegated() ? "!" : "");
130             Table.SetCell(t_s("Chan"), it->GetChans());
131             Table.SetCell(t_s("Search"), it->GetSearch());
132             Table.SetCell(t_s("Host"), it->GetHostMask());
133         }
134 
135         if (Table.size()) {
136             PutModule(Table);
137         } else {
138             PutModule(t_s("You have no entries."));
139         }
140     }
141 
142   public:
MODCONSTRUCTOR(CChanAttach)143     MODCONSTRUCTOR(CChanAttach) {
144         AddHelpCommand();
145         AddCommand(
146             "Add", t_d("[!]<#chan> <search> <host>"),
147             t_d("Add an entry, use !#chan to negate and * for wildcards"),
148             [=](const CString& sLine) { HandleAdd(sLine); });
149         AddCommand("Del", t_d("[!]<#chan> <search> <host>"),
150                    t_d("Remove an entry, needs to be an exact match"),
151                    [=](const CString& sLine) { HandleDel(sLine); });
152         AddCommand("List", "", t_d("List all entries"),
153                    [=](const CString& sLine) { HandleList(sLine); });
154     }
155 
~CChanAttach()156     ~CChanAttach() override {}
157 
OnLoad(const CString & sArgs,CString & sMessage)158     bool OnLoad(const CString& sArgs, CString& sMessage) override {
159         VCString vsChans;
160         sArgs.Split(" ", vsChans, false);
161 
162         for (VCString::const_iterator it = vsChans.begin(); it != vsChans.end();
163              ++it) {
164             CString sAdd = *it;
165             bool bNegated = sAdd.TrimPrefix("!");
166             CString sChan = sAdd.Token(0);
167             CString sSearch = sAdd.Token(1);
168             CString sHost = sAdd.Token(2, true);
169 
170             if (!Add(bNegated, sChan, sSearch, sHost)) {
171                 PutModule(t_f("Unable to add [{1}]")(*it));
172             }
173         }
174 
175         // Load our saved settings, ignore errors
176         MCString::iterator it;
177         for (it = BeginNV(); it != EndNV(); ++it) {
178             CString sAdd = it->first;
179             bool bNegated = sAdd.TrimPrefix("!");
180             CString sChan = sAdd.Token(0);
181             CString sSearch = sAdd.Token(1);
182             CString sHost = sAdd.Token(2, true);
183 
184             Add(bNegated, sChan, sSearch, sHost);
185         }
186 
187         return true;
188     }
189 
TryAttach(const CNick & Nick,CChan & Channel,CString & Message)190     void TryAttach(const CNick& Nick, CChan& Channel, CString& Message) {
191         const CString& sChan = Channel.GetName();
192         const CString& sHost = Nick.GetHostMask();
193         const CString& sMessage = Message;
194         VAttachIter it;
195 
196         if (!Channel.IsDetached()) return;
197 
198         // Any negated match?
199         for (it = m_vMatches.begin(); it != m_vMatches.end(); ++it) {
200             if (it->IsNegated() && it->IsMatch(sChan, sHost, sMessage)) return;
201         }
202 
203         // Now check for a positive match
204         for (it = m_vMatches.begin(); it != m_vMatches.end(); ++it) {
205             if (!it->IsNegated() && it->IsMatch(sChan, sHost, sMessage)) {
206                 Channel.AttachUser();
207                 return;
208             }
209         }
210     }
211 
OnChanNotice(CNick & Nick,CChan & Channel,CString & sMessage)212     EModRet OnChanNotice(CNick& Nick, CChan& Channel,
213                          CString& sMessage) override {
214         TryAttach(Nick, Channel, sMessage);
215         return CONTINUE;
216     }
217 
OnChanMsg(CNick & Nick,CChan & Channel,CString & sMessage)218     EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) override {
219         TryAttach(Nick, Channel, sMessage);
220         return CONTINUE;
221     }
222 
OnChanAction(CNick & Nick,CChan & Channel,CString & sMessage)223     EModRet OnChanAction(CNick& Nick, CChan& Channel,
224                          CString& sMessage) override {
225         TryAttach(Nick, Channel, sMessage);
226         return CONTINUE;
227     }
228 
FindEntry(const CString & sChan,const CString & sSearch,const CString & sHost)229     VAttachIter FindEntry(const CString& sChan, const CString& sSearch,
230                           const CString& sHost) {
231         VAttachIter it = m_vMatches.begin();
232         for (; it != m_vMatches.end(); ++it) {
233             if (sHost.empty() || it->GetHostMask() != sHost) continue;
234             if (sSearch.empty() || it->GetSearch() != sSearch) continue;
235             if (sChan.empty() || it->GetChans() != sChan) continue;
236             return it;
237         }
238         return m_vMatches.end();
239     }
240 
Add(bool bNegated,const CString & sChan,const CString & sSearch,const CString & sHost)241     bool Add(bool bNegated, const CString& sChan, const CString& sSearch,
242              const CString& sHost) {
243         CAttachMatch attach(this, sChan, sSearch, sHost, bNegated);
244 
245         // Check for duplicates
246         VAttachIter it = m_vMatches.begin();
247         for (; it != m_vMatches.end(); ++it) {
248             if (it->GetHostMask() == attach.GetHostMask() &&
249                 it->GetChans() == attach.GetChans() &&
250                 it->GetSearch() == attach.GetSearch())
251                 return false;
252         }
253 
254         m_vMatches.push_back(attach);
255 
256         // Also save it for next module load
257         SetNV(attach.ToString(), "");
258 
259         return true;
260     }
261 
Del(bool bNegated,const CString & sChan,const CString & sSearch,const CString & sHost)262     bool Del(bool bNegated, const CString& sChan, const CString& sSearch,
263              const CString& sHost) {
264         VAttachIter it = FindEntry(sChan, sSearch, sHost);
265         if (it == m_vMatches.end() || it->IsNegated() != bNegated) return false;
266 
267         DelNV(it->ToString());
268         m_vMatches.erase(it);
269 
270         return true;
271     }
272 
273   private:
274     VAttachMatch m_vMatches;
275 };
276 
277 template <>
TModInfo(CModInfo & Info)278 void TModInfo<CChanAttach>(CModInfo& Info) {
279     Info.AddType(CModInfo::UserModule);
280     Info.SetWikiPage("autoattach");
281     Info.SetHasArgs(true);
282     Info.SetArgsHelpText(Info.t_s(
283         "List of channel masks and channel masks with ! before them."));
284 }
285 
286 NETWORKMODULEDEFS(CChanAttach, t_s("Reattaches you to channels on activity."))
287