mhost 0.11.3

Fast, async DNS lookup library and CLI -- modern dig/host replacement with parallel multi-server queries, DoH, DoT, subdomain discovery, and zone verification
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
// Copyright 2017-2021 Lukas Pustina <lukas@pustina.de>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

use anyhow::{Context, Result};
use std::path::Path;
use std::slice::Iter;
use tokio::fs::File;
use tokio::io::AsyncBufReadExt;
use tokio::io::BufReader;
use tracing::trace;

use crate::{IntoName, Name};

static DEFAULT_WORD_LIST: &str = r#"_amazonses
abuse
academy
account
accounts
accounting
ad
admin
admin1
admin2
adminer
alertmanager
alpha
analytics
ansible
api
api-docs
api-gateway
api1
api2
app
app1
app2
apps
archive
argo
assets
auth
auth0
autodiscover
autoconfig
aws
azure
backend
backup
backup1
backup2
bastion
beta
billing
bitbucket
blog
board
box
build
bulk
cache
cache1
calendar
canary
careers
cdn
cdn1
cdn2
chat
checkout
ci
citrix
clickhouse
client
cloud
cluster
cms
cockpit
code
community
confluence
connect
console
consul
contact
content
corp
cpanel
crm
cron
dashboard
data
database
db
db1
db2
db3
dc
demo
deploy
dev
dev1
dev2
development
devops
dhcp
dialin
direct
directory
dmz
dns
dns1
dns2
dns3
docker
docs
download
downloads
edge
elastic
elasticsearch
email
env
erp
es
etcd
events
exchange
external
extranet
faq
feed
file
files
firewall
forum
ftp
ftp1
ftp2
fw
gallery
gateway
gcp
gerrit
git
gitea
github
gitlab
go
grafana
graphql
gw
harbor
haproxy
health
help
helpdesk
home
host
host1
host2
hosting
hq
hr
hub
iam
id
identity
images
img
imap
in
inbound
influxdb
info
infra
intern
internal
intranet
iot
it
jenkins
jira
jobs
jump
k8s
kafka
kb
keycloak
kibana
kong
kubernetes
lab
labs
landing
lb
lb1
lb2
ldap
legacy
link
linux
listserv
live
lms
loadbalancer
local
log
login
logstash
loki
m
mail
mail1
mail2
mail3
mailer
mailhost
manage
management
manager
marketing
marketplace
mattermost
media
meet
memcached
metrics
mfa
minio
mirror
mobi
mobile
mongo
mongodb
monitor
monitoring
mqtt
ms
mssql
mta
mx
mx1
mx2
mx3
mysql
nagios
nas
net
new
news
newsletter
next
nexus
nginx
noc
node
node1
node2
nomad
notes
ns
ns1
ns2
ns3
ns4
ntp
oauth
office
okta
old
openid
ops
oracle
order
orders
origin
outbound
outlook
owa
packages
panel
partner
partners
pay
payment
pbx
phpmyadmin
pki
platform
plesk
pop
pop3
portal
portainer
postgres
postgresql
preprod
preview
print
private
prod
production
prometheus
proxy
proxy1
proxy2
public
puppet
push
qa
queue
rabbitmq
radius
rancher
rdp
redis
redirect
redirector
redmine
registry
relay
relay1
relay2
remote
render
repo
reporting
rest
review
rocketchat
roundcube
router
rss
rt
s3
sales
saml
sandbox
scan
scheduler
search
secure
security
sentry
server
server1
server2
service
sftp
share
sharepoint
shop
signin
signup
sip
site
slack
smtp
smtp01
smtp02
smtp1
smtp2
sonar
sonarqube
splunk
sql
srv
ssh
ssl
sso
stage
staging
static
stats
status
storage
store
stream
streaming
support
survey
svn
syslog
teams
terminal
terraform
test
test1
test2
testing
ticket
tickets
time
tls
tools
traefik
training
tunnel
uat
update
upload
v1
v2
v3
vault
video
vm
voip
vpn
vpn1
vpn2
waf
wap
web
web1
web2
web3
webadmin
webdav
webmail
webmin
webproxy
wiki
wopi
workspace
wpad
www
www-server
www-test
www1
www2
www3
wwwtest
zabbix
zendesk1
zendeskverification
zookeeper"#;

#[derive(Debug)]
pub struct Wordlist {
    words: Vec<Name>,
}

impl Wordlist {
    pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Wordlist> {
        let file = File::open(path).await?;
        let mut buf_reader = BufReader::new(file);

        let mut line_counter = 0;
        let mut words = Vec::new();
        loop {
            let mut buffer = String::new();
            let len = buf_reader.read_line(&mut buffer).await?;
            if len == 0 {
                break;
            }
            line_counter += 1;
            if Wordlist::is_comment(&buffer) {
                continue;
            }
            trace!("Parsing wordlist item '{}'.", buffer);
            let buffer = buffer.trim_end(); // BufReader::read_line returns trailing line break
            let name: Name = buffer.into_name().context(format!(
                "failed to read word list because of invalid domain name '{}' at line {}",
                buffer, line_counter
            ))?;
            words.push(name);
        }

        Ok(Wordlist { words })
    }

    fn is_comment(line: &str) -> bool {
        line.starts_with("//") || line.starts_with('#')
    }

    pub fn parse(data: &str) -> Result<Wordlist> {
        let mut words = Vec::new();

        for line in data.lines() {
            if Wordlist::is_comment(line) {
                continue;
            }
            trace!("Parsing wordlist item '{}'.", line);
            let name: Name = line.into_name().context(format!(
                "failed to read word list because of invalid domain name '{}'",
                line
            ))?;
            words.push(name);
        }

        Ok(Wordlist { words })
    }

    pub fn built_in() -> Result<Wordlist> {
        Wordlist::parse(DEFAULT_WORD_LIST)
    }

    #[allow(dead_code)]
    pub fn iter(&self) -> Iter<'_, Name> {
        self.words.iter()
    }

    #[allow(dead_code)]
    pub fn len(&self) -> usize {
        self.words.len()
    }

    #[allow(dead_code)]
    pub fn is_empty(&self) -> bool {
        self.words.is_empty()
    }
}

impl IntoIterator for Wordlist {
    type Item = Name;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.words.into_iter()
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    use spectral::prelude::*;

    #[tokio::test]
    async fn read_from_file_5000() {
        crate::utils::tests::logging::init();
        let path = "contrib/subdomains-top1mil-5000.txt";

        let wordlist = Wordlist::from_file(path).await;

        asserting("Wordlist with 5000 elements loaded from file")
            .that(&wordlist)
            .is_ok()
            .map(|x| &x.words)
            .has_length(5000)
    }

    #[tokio::test]
    async fn read_from_file_20000() {
        crate::utils::tests::logging::init();
        let path = "contrib/subdomains-top1mil-20000.txt";

        let wordlist = Wordlist::from_file(path).await;

        asserting("Wordlist with 20000 elements loaded from file")
            .that(&wordlist)
            .is_ok()
            .map(|x| &x.words)
            .has_length(19998)
    }

    #[test]
    fn read_from_string() {
        crate::utils::tests::logging::init();
        let wordlist = Wordlist::parse(DEFAULT_WORD_LIST);

        asserting("Wordlist with 5000 elements loaded from string")
            .that(&wordlist)
            .is_ok()
            .map(|x| &x.words)
            .has_length(424)
    }
}