Line data Source code
1 : // Snap Websites Server -- snap websites serving children
2 : // Copyright (c) 2011-2019 Made to Order Software Corp. All Rights Reserved
3 : //
4 : // This program is free software; you can redistribute it and/or modify
5 : // it under the terms of the GNU General Public License as published by
6 : // the Free Software Foundation; either version 2 of the License, or
7 : // (at your option) any later version.
8 : //
9 : // This program is distributed in the hope that it will be useful,
10 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : // GNU General Public License for more details.
13 : //
14 : // You should have received a copy of the GNU General Public License
15 : // along with this program; if not, write to the Free Software
16 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 :
18 : // self
19 : //
20 : #include "snapwebsites/mail_exchanger.h"
21 :
22 : // libsnapwebsites library
23 : //
24 : #include "snapwebsites/log.h"
25 : #include "snapwebsites/process.h"
26 :
27 :
28 : // snapdev lib
29 : //
30 : #include <snapdev/tokenize_string.h>
31 :
32 :
33 : // libtld library
34 : //
35 : #include <libtld/tld.h>
36 :
37 :
38 : // last include
39 : //
40 : #include <snapdev/poison.h>
41 :
42 :
43 :
44 : namespace snap
45 : {
46 :
47 :
48 :
49 :
50 0 : mail_exchanger::mail_exchanger(int priority, std::string const & domain)
51 : : f_priority(priority)
52 0 : , f_domain(domain)
53 : {
54 0 : }
55 :
56 :
57 0 : int mail_exchanger::get_priority() const
58 : {
59 0 : return f_priority;
60 : }
61 :
62 :
63 0 : std::string mail_exchanger::get_domain() const
64 : {
65 0 : return f_domain;
66 : }
67 :
68 :
69 0 : bool mail_exchanger::operator < (mail_exchanger const & rhs) const
70 : {
71 0 : return f_priority < rhs.f_priority;
72 : }
73 :
74 :
75 :
76 :
77 :
78 :
79 :
80 0 : mail_exchangers::mail_exchangers(std::string const & domain)
81 : {
82 : // use plain domain name to query the MX record
83 : // (i.e. a query with "mail.m2osw.com" fails!)
84 : //
85 0 : tld_object domain_obj(domain);
86 0 : if(!domain_obj.is_valid())
87 : {
88 : // f_domain_found is false by default... it failed
89 0 : SNAP_LOG_DEBUG("mail_exchanger called with an invalid domain name: \"")(domain)("\"");
90 0 : return;
91 : }
92 :
93 : // got the plain domain name now
94 : //
95 0 : std::string const full_domain(domain_obj.full_domain());
96 :
97 : // generate a command line to execute `dig`
98 : //
99 0 : process dig("dig");
100 0 : dig.set_mode(snap::process::mode_t::PROCESS_MODE_OUTPUT);
101 0 : dig.set_command("/usr/bin/dig");
102 0 : dig.add_argument(QString::fromUtf8(full_domain.c_str()));
103 0 : dig.add_argument("mx"); // get MX field
104 0 : int const r(dig.run());
105 :
106 : // retrieve the dig output
107 : //
108 0 : std::string const output(dig.get_output(true).toUtf8().data());
109 :
110 0 : if(r != 0)
111 : {
112 : // dig command failed
113 0 : SNAP_LOG_DEBUG("dig.run() returned ")(r)(" and output: [")(output)("]");
114 0 : return;
115 : }
116 :
117 : // dig worked, check the results
118 : //
119 0 : std::vector<std::string> lines;
120 0 : int const count(tokenize_string(lines, output, "\n", true, " "));
121 0 : if(count <= 0)
122 : {
123 : // no output?
124 : //
125 0 : SNAP_LOG_DEBUG("dig returned no output [")(output)("]");
126 0 : return;
127 : }
128 :
129 0 : for(int l(0); l < count; ++l)
130 : {
131 : // if no MX are found, we generally get a line with the authority
132 : //
133 0 : if(strncmp(";; AUTHORITY SECTION:", lines[l].c_str(), 21) == 0)
134 : {
135 0 : ++l;
136 0 : if(l >= count)
137 : {
138 0 : break;
139 : }
140 0 : std::vector<std::string> fields;
141 0 : /*int const ln_count*/ (tokenize_string(fields, lines[l], " \t", true, " ."));
142 0 : if(fields.empty()
143 0 : || fields[0] != full_domain)
144 : {
145 0 : SNAP_LOG_DEBUG("authority (")(fields.empty() ? "<empty>" : fields[0])(") does not match the domain we used (")(full_domain)(")");
146 0 : return;
147 : }
148 0 : f_domain_found = true;
149 : }
150 0 : else if(strncmp(";; ANSWER SECTION:", lines[l].c_str(), 18) == 0)
151 : {
152 0 : mail_exchanger::mail_exchange_vector_t exchangers;
153 0 : for(++l; l < count; ++l)
154 : {
155 0 : if(lines[l].empty())
156 : {
157 0 : break;
158 : }
159 :
160 : // TODO: instead of just taking the priority and MX
161 : // priority and domain names, we probably should
162 : // take all the fields available even if we are
163 : // to not use them in some circumstances
164 :
165 0 : std::string::size_type pos(lines[l].find("MX"));
166 0 : if(pos != std::string::npos)
167 : {
168 : // skip the MX (+2) and then skip spaces
169 : //
170 0 : char const * mx(lines[l].c_str() + pos + 2);
171 :
172 : // skip spaces
173 : //
174 0 : for(; *mx != '\0' && std::isspace(*mx); ++mx);
175 :
176 : // if valid, we now have a decimal number
177 : //
178 0 : if(*mx < '0' || *mx > '9')
179 : {
180 0 : SNAP_LOG_DEBUG("priority missing in \"")(lines[l])("\"");
181 0 : return;
182 : }
183 :
184 0 : int priority(0);
185 0 : for(; *mx >= '0' && *mx <= '9'; ++mx)
186 : {
187 : // some random maximum; valid MX priority is generally
188 : // under 1,000 anyway
189 : //
190 0 : if(priority > 500000000)
191 : {
192 0 : SNAP_LOG_DEBUG("priority too large in \"")(lines[l])("\"");
193 0 : return;
194 : }
195 0 : priority = priority * 10 + *mx - '0';
196 : }
197 :
198 : // skip spaces between priority and domain name
199 : //
200 0 : for(; *mx != '\0' && (std::isspace(*mx) || *mx == '.'); ++mx);
201 :
202 : // now we have a domain name that ends with a period
203 : //
204 0 : if(*mx == '\0')
205 : {
206 0 : SNAP_LOG_DEBUG("invalid domain entry in \"")(lines[l])("\"");
207 0 : return;
208 : }
209 0 : std::string mx_domain(mx);
210 0 : if(mx_domain[mx_domain.length() - 1] == '.')
211 : {
212 : // remove the ending period if present
213 : //
214 0 : mx_domain.pop_back();
215 : }
216 :
217 : // it is considered valid, add it and move on
218 : //
219 0 : mail_exchanger const mx_item(priority, mx_domain);
220 0 : exchangers.push_back(mx_item);
221 : }
222 : }
223 :
224 : // this also means the domain is considered valid
225 : // even if we do not find any authoritative section...
226 : // however, we expect at least one entry to be valid
227 : //
228 0 : f_domain_found = !exchangers.empty();
229 :
230 0 : f_mail_exchangers.swap(exchangers);
231 0 : break;
232 : }
233 : }
234 : }
235 :
236 :
237 0 : bool mail_exchangers::domain_found() const
238 : {
239 0 : return f_domain_found;
240 : }
241 :
242 :
243 0 : size_t mail_exchangers::size() const
244 : {
245 0 : return f_mail_exchangers.size();
246 : }
247 :
248 :
249 0 : mail_exchanger::mail_exchange_vector_t mail_exchangers::get_mail_exchangers() const
250 : {
251 0 : return f_mail_exchangers;
252 : }
253 :
254 :
255 :
256 :
257 :
258 6 : } // namespace snap
259 :
260 : // vim: ts=4 sw=4 et
|