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