]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/coremods/core_hostname_lookup.cpp
core_hostname_lookup: find answer record of the correct type instead of assuming...
[user/henk/code/inspircd.git] / src / coremods / core_hostname_lookup.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2013-2016 Adam <Adam@anope.org>
5  *
6  * This file is part of InspIRCd.  InspIRCd is free software: you can
7  * redistribute it and/or modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation, version 2.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19
20 #include "inspircd.h"
21 #include "modules/dns.h"
22
23 namespace
24 {
25         LocalIntExt* dl;
26         LocalStringExt* ph;
27 }
28
29 /** Derived from Resolver, and performs user forward/reverse lookups.
30  */
31 class UserResolver : public DNS::Request
32 {
33         /** UUID we are looking up */
34         const std::string uuid;
35
36         /** True if the lookup is forward, false if is a reverse lookup
37          */
38         const bool fwd;
39
40         const DNS::ResourceRecord* FindAnswerOfType(const DNS::Query* response, DNS::QueryType qtype)
41         {
42                 for (std::vector<DNS::ResourceRecord>::const_iterator it = response->answers.begin(); it != response->answers.end(); ++it)
43                 {
44                         const DNS::ResourceRecord& rr = *it;
45
46                         if (rr.type == qtype)
47                         {
48                                 return &rr;
49                         }
50                 }
51
52                 return NULL;
53         }
54
55  public:
56         /** Create a resolver.
57          * @param mgr DNS Manager
58          * @param me this module
59          * @param user The user to begin lookup on
60          * @param to_resolve The IP or host to resolve
61          * @param qt The query type
62          */
63         UserResolver(DNS::Manager* mgr, Module* me, LocalUser* user, const std::string& to_resolve, DNS::QueryType qt)
64                 : DNS::Request(mgr, me, to_resolve, qt)
65                 , uuid(user->uuid)
66                 , fwd(qt == DNS::QUERY_A || qt == DNS::QUERY_AAAA)
67         {
68         }
69
70         /** Called on successful lookup
71          * if a previous result has already come back.
72          * @param r The finished query
73          */
74         void OnLookupComplete(const DNS::Query* r)
75         {
76                 LocalUser* bound_user = (LocalUser*)ServerInstance->FindUUID(uuid);
77                 if (!bound_user)
78                 {
79                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Resolution finished for user '%s' who is gone", uuid.c_str());
80                         return;
81                 }
82
83                 const DNS::ResourceRecord* ans_record = FindAnswerOfType(r, this->type);
84                 if (ans_record == NULL)
85                 {
86                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "DNS result for %s: no record of type %d", uuid.c_str(), this->type);
87                         return;
88                 }
89
90                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "DNS result for %s: '%s' -> '%s'", uuid.c_str(), ans_record->name.c_str(), ans_record->rdata.c_str());
91
92                 if (!fwd)
93                 {
94                         // first half of resolution is done. We now need to verify that the host matches.
95                         ph->set(bound_user, ans_record->rdata);
96
97                         UserResolver* res_forward;
98                         if (bound_user->client_sa.sa.sa_family == AF_INET6)
99                         {
100                                 /* IPV6 forward lookup */
101                                 res_forward = new UserResolver(this->manager, this->creator, bound_user, ans_record->rdata, DNS::QUERY_AAAA);
102                         }
103                         else
104                         {
105                                 /* IPV4 lookup */
106                                 res_forward = new UserResolver(this->manager, this->creator, bound_user, ans_record->rdata, DNS::QUERY_A);
107                         }
108                         try
109                         {
110                                 this->manager->Process(res_forward);
111                         }
112                         catch (DNS::Exception& e)
113                         {
114                                 delete res_forward;
115                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Error in resolver: " + e.GetReason());
116
117                                 bound_user->WriteNotice("*** There was an internal error resolving your host, using your IP address (" + bound_user->GetIPString() + ") instead.");
118                                 dl->set(bound_user, 0);
119                         }
120                 }
121                 else
122                 {
123                         /* Both lookups completed */
124
125                         irc::sockets::sockaddrs* user_ip = &bound_user->client_sa;
126                         bool rev_match = false;
127                         if (user_ip->sa.sa_family == AF_INET6)
128                         {
129                                 struct in6_addr res_bin;
130                                 if (inet_pton(AF_INET6, ans_record->rdata.c_str(), &res_bin))
131                                 {
132                                         rev_match = !memcmp(&user_ip->in6.sin6_addr, &res_bin, sizeof(res_bin));
133                                 }
134                         }
135                         else
136                         {
137                                 struct in_addr res_bin;
138                                 if (inet_pton(AF_INET, ans_record->rdata.c_str(), &res_bin))
139                                 {
140                                         rev_match = !memcmp(&user_ip->in4.sin_addr, &res_bin, sizeof(res_bin));
141                                 }
142                         }
143
144                         dl->set(bound_user, 0);
145
146                         if (rev_match)
147                         {
148                                 std::string* hostname = ph->get(bound_user);
149
150                                 if (hostname == NULL)
151                                 {
152                                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: User has no hostname attached when doing a forward lookup");
153                                         bound_user->WriteNotice("*** There was an internal error resolving your host, using your IP address (" + bound_user->GetIPString() + ") instead.");
154                                         return;
155                                 }
156                                 else if (hostname->length() <= ServerInstance->Config->Limits.MaxHost)
157                                 {
158                                         /* Hostnames starting with : are not a good thing (tm) */
159                                         if ((*hostname)[0] == ':')
160                                                 hostname->insert(0, "0");
161
162                                         bound_user->WriteNotice("*** Found your hostname (" + *hostname + (r->cached ? ") -- cached" : ")"));
163                                         bound_user->host.assign(*hostname, 0, ServerInstance->Config->Limits.MaxHost);
164                                         bound_user->dhost = bound_user->host;
165
166                                         /* Invalidate cache */
167                                         bound_user->InvalidateCache();
168                                 }
169                                 else
170                                 {
171                                         bound_user->WriteNotice("*** Your hostname is longer than the maximum of " + ConvToStr(ServerInstance->Config->Limits.MaxHost) + " characters, using your IP address (" + bound_user->GetIPString() + ") instead.");
172                                 }
173
174                                 ph->unset(bound_user);
175                         }
176                         else
177                         {
178                                 bound_user->WriteNotice("*** Your hostname does not match up with your IP address. Sorry, using your IP address (" + bound_user->GetIPString() + ") instead.");
179                         }
180                 }
181         }
182
183         /** Called on failed lookup
184          * @param query The errored query
185          */
186         void OnError(const DNS::Query* query)
187         {
188                 LocalUser* bound_user = (LocalUser*)ServerInstance->FindUUID(uuid);
189                 if (bound_user)
190                 {
191                         bound_user->WriteNotice("*** Could not resolve your hostname: " + this->manager->GetErrorStr(query->error) + "; using your IP address (" + bound_user->GetIPString() + ") instead.");
192                         dl->set(bound_user, 0);
193                 }
194         }
195 };
196
197 class ModuleHostnameLookup : public Module
198 {
199         LocalIntExt dnsLookup;
200         LocalStringExt ptrHosts;
201         dynamic_reference<DNS::Manager> DNS;
202
203  public:
204         ModuleHostnameLookup()
205                 : dnsLookup("dnsLookup", ExtensionItem::EXT_USER, this)
206                 , ptrHosts("ptrHosts", ExtensionItem::EXT_USER, this)
207                 , DNS(this, "DNS")
208         {
209                 dl = &dnsLookup;
210                 ph = &ptrHosts;
211         }
212
213         void OnUserInit(LocalUser *user)
214         {
215                 if (!DNS || !user->MyClass->resolvehostnames)
216                 {
217                         user->WriteNotice("*** Skipping host resolution (disabled by server administrator)");
218                         return;
219                 }
220
221                 user->WriteNotice("*** Looking up your hostname...");
222
223                 UserResolver* res_reverse = new UserResolver(*this->DNS, this, user, user->GetIPString(), DNS::QUERY_PTR);
224                 try
225                 {
226                         /* If both the reverse and forward queries are cached, the user will be able to pass DNS completely
227                          * before Process() completes, which is why dnsLookup.set() is here, before Process()
228                          */
229                         this->dnsLookup.set(user, 1);
230                         this->DNS->Process(res_reverse);
231                 }
232                 catch (DNS::Exception& e)
233                 {
234                         this->dnsLookup.set(user, 0);
235                         delete res_reverse;
236                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Error in resolver: " + e.GetReason());
237                 }
238         }
239
240         ModResult OnCheckReady(LocalUser* user)
241         {
242                 return this->dnsLookup.get(user) ? MOD_RES_DENY : MOD_RES_PASSTHRU;
243         }
244
245         Version GetVersion()
246         {
247                 return Version("Provides support for DNS lookups on connecting clients", VF_CORE|VF_VENDOR);
248         }
249 };
250
251 MODULE_INIT(ModuleHostnameLookup)