1 /*
2  * Copyright (c) 2018-2021 Free Software Foundation, Inc.
3  *
4  * This file is part of Wget.
5  *
6  * Wget is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Wget is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Wget.  If not, see <https://www.gnu.org/licenses/>.
18  *
19  *
20  * Server statistics functions
21  */
22 #include <config.h>
23 
24 #include <stdio.h>
25 #include <stdint.h>
26 
27 #include <wget.h>
28 #include "wget_main.h"
29 #include "wget_stats.h"
30 #include "wget_options.h"
31 #include "../libwget/http.h"
32 #include "../libwget/net.h"
33 
34 typedef struct
35 {
36 	const char
37 		*hostname,
38 		*ip;
39 	wget_hpkp_stats_result
40 		hpkp;
41 	wget_iri_scheme
42 		scheme;
43 	char
44 		hsts,
45 		csp,
46 		hpkp_new;
47 } server_stats_data;
48 
49 typedef struct {
50 	const char
51 		*hostname,
52 		*ip;
53 	wget_iri_scheme
54 		scheme;
55 } server_stats_host;
56 
57 static wget_hashmap
58 	*hosts;
59 
60 static wget_thread_mutex
61 	mutex;
62 
63 static FILE
64 	*fp;
65 
host_compare(const server_stats_host * host1,const server_stats_host * host2)66 static int host_compare(const server_stats_host *host1, const server_stats_host *host2)
67 {
68 	int n;
69 
70 	if ((n = wget_strcmp(host1->hostname, host2->hostname)))
71 		return n;
72 
73 	if ((n = wget_strcmp(host1->ip, host2->ip)))
74 		return n;
75 
76 	return host1->scheme - host2->scheme;
77 }
78 
79 #ifdef __clang__
80 __attribute__((no_sanitize("integer")))
81 #endif
host_hash(const server_stats_host * host)82 static unsigned int host_hash(const server_stats_host *host)
83 {
84 	unsigned int hash = host->scheme; // use 0 as SALT if hash table attacks doesn't matter
85 	const unsigned char *p;
86 
87 	for (p = (unsigned char *)host->hostname; p && *p; p++)
88 			hash = hash * 101 + *p;
89 
90 	for (p = (unsigned char *)host->ip; p && *p; p++)
91 		hash = hash * 101 + *p;
92 
93 	return hash;
94 }
95 
free_host_entry(server_stats_host * host)96 static void free_host_entry(server_stats_host *host)
97 {
98 	if (host) {
99 		wget_xfree(host->hostname);
100 		wget_xfree(host->ip);
101 		wget_xfree(host);
102 	}
103 }
104 
105 WGET_GCC_CONST
hpkp_string(wget_hpkp_stats_result hpkp)106 static const char *hpkp_string(wget_hpkp_stats_result hpkp)
107 {
108 	switch (hpkp) {
109 	case WGET_STATS_HPKP_NO: return "HPKP_NO";
110 	case WGET_STATS_HPKP_MATCH: return "HPKP_MATCH";
111 	case WGET_STATS_HPKP_NOMATCH: return "HPKP_NOMATCH";
112 	case WGET_STATS_HPKP_ERROR: return "HPKP_ERROR";
113 	default: return "?";
114 	}
115 }
116 
server_stats_print(server_stats_data * stats)117 static void server_stats_print(server_stats_data *stats)
118 {
119 	if (config.stats_server_args->format == WGET_STATS_FORMAT_HUMAN) {
120 		wget_fprintf(fp, "  %s:\n", NULL_TO_DASH(stats->hostname));
121 		wget_fprintf(fp, "    IP             : %s\n", NULL_TO_DASH(stats->ip));
122 		wget_fprintf(fp, "    Scheme         : %s\n", wget_iri_scheme_get_name(stats->scheme));
123 		wget_fprintf(fp, "    HPKP           : %s\n", hpkp_string(stats->hpkp));
124 		wget_fprintf(fp, "    HPKP New Entry : %s\n", ON_OFF_DASH(stats->hpkp_new));
125 		wget_fprintf(fp, "    HSTS           : %s\n", ON_OFF_DASH(stats->hsts));
126 		wget_fprintf(fp, "    CSP            : %s\n\n", ON_OFF_DASH(stats->csp));
127 	} else {
128 		wget_fprintf(fp, "%s,%s,%s,%d,%d,%d,%d\n",
129 			stats->hostname ? stats->hostname : "",
130 			stats->ip ? stats->ip : "",
131 			wget_iri_scheme_get_name(stats->scheme),
132 			(int) stats->hpkp,
133 			stats->hpkp_new,
134 			stats->hsts,
135 			stats->csp);
136 	}
137 }
138 
server_stats_add(wget_http_connection * conn,wget_http_response * resp)139 static void server_stats_add(wget_http_connection *conn, wget_http_response *resp)
140 {
141 	server_stats_host *hostp = wget_malloc(sizeof(server_stats_host));
142 
143 	hostp->hostname = wget_strdup(wget_http_get_host(conn));
144 	hostp->ip = wget_strdup(wget_tcp_get_ip(conn->tcp));
145 	hostp->scheme = conn->scheme;
146 
147 	wget_thread_mutex_lock(mutex);
148 
149 	if (!wget_hashmap_contains(hosts, hostp)) {
150 		server_stats_data stats;
151 
152 		stats.hostname = hostp->hostname;
153 		stats.ip = hostp->ip;
154 		stats.scheme = hostp->scheme;
155 		stats.hpkp = conn->tcp->hpkp;
156 		stats.hpkp_new = resp ? (resp->hpkp ? 1 : 0): -1;
157 		stats.hsts = resp ? (resp->hsts ? 1 : 0) : -1;
158 		stats.csp = resp ? (resp->csp ? 1 : 0) : -1;
159 
160 		server_stats_print(&stats);
161 		wget_hashmap_put(hosts, hostp, hostp);
162 	} else
163 		free_host_entry(hostp);
164 
165 	wget_thread_mutex_unlock(mutex);
166 }
167 
server_stats_init(FILE * fpout)168 void server_stats_init(FILE *fpout)
169 {
170 	wget_thread_mutex_init(&mutex);
171 
172 	hosts = wget_hashmap_create(16, (wget_hashmap_hash_fn *) host_hash, (wget_hashmap_compare_fn *) host_compare);
173 	wget_hashmap_set_key_destructor(hosts, (wget_hashmap_key_destructor *) free_host_entry);
174 
175 	fp = fpout;
176 
177 	wget_server_set_stats_callback(server_stats_add);
178 }
179 
server_stats_exit(void)180 void server_stats_exit(void)
181 {
182 	// We don't need mutex locking here - this function is called on exit when all threads have ceased.
183 	wget_hashmap_free(&hosts);
184 	wget_thread_mutex_destroy(&mutex);
185 }
186