1 /*
2  * Copyright (c) 2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #undef _POSIX_C_SOURCE
8 #define _POSIX_C_SOURCE 200809L /* For strnlen() */
9 
10 #include <zephyr/kernel.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <zephyr/shell/shell.h>
14 #include <zephyr/init.h>
15 
16 #include <zephyr/net/net_if.h>
17 #include <zephyr/net/wifi_mgmt.h>
18 #include <zephyr/net/net_event.h>
19 #include <zephyr/net/net_l2.h>
20 #include <zephyr/net/ethernet.h>
21 
22 #include <zephyr/net/wifi_credentials.h>
23 
24 #define MACSTR "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"
25 
print_network_info(void * cb_arg,const char * ssid,size_t ssid_len)26 static void print_network_info(void *cb_arg, const char *ssid, size_t ssid_len)
27 {
28 	int ret = 0;
29 	struct wifi_credentials_personal creds = {0};
30 	const struct shell *sh = (const struct shell *)cb_arg;
31 
32 	ret = wifi_credentials_get_by_ssid_personal_struct(ssid, ssid_len, &creds);
33 	if (ret) {
34 		shell_error(sh,
35 			    "An error occurred when trying to load credentials for network \"%.*s\""
36 			    ". err: %d",
37 			    (int)ssid_len, ssid, ret);
38 		return;
39 	}
40 
41 	shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT,
42 		      "  network ssid: \"%.*s\", ssid_len: %d, type: %s", (int)ssid_len, ssid,
43 		      ssid_len, wifi_security_txt(creds.header.type));
44 
45 	if (creds.header.type == WIFI_SECURITY_TYPE_PSK ||
46 	    creds.header.type == WIFI_SECURITY_TYPE_PSK_SHA256 ||
47 	    creds.header.type == WIFI_SECURITY_TYPE_SAE ||
48 	    creds.header.type == WIFI_SECURITY_TYPE_WPA_PSK) {
49 		shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT,
50 			      ", password: \"%.*s\", password_len: %d", (int)creds.password_len,
51 			      creds.password, creds.password_len);
52 	}
53 
54 	if (creds.header.flags & WIFI_CREDENTIALS_FLAG_BSSID) {
55 		shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", bssid: " MACSTR,
56 			      creds.header.bssid[0], creds.header.bssid[1], creds.header.bssid[2],
57 			      creds.header.bssid[3], creds.header.bssid[4], creds.header.bssid[5]);
58 	}
59 
60 	if (creds.header.flags & WIFI_CREDENTIALS_FLAG_2_4GHz) {
61 		shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", band: 2.4GHz");
62 	}
63 
64 	if (creds.header.flags & WIFI_CREDENTIALS_FLAG_5GHz) {
65 		shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", band: 5GHz");
66 	}
67 
68 	if (creds.header.channel) {
69 		shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", channel: %d", creds.header.channel);
70 	}
71 
72 	if (creds.header.flags & WIFI_CREDENTIALS_FLAG_FAVORITE) {
73 		shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", favorite");
74 	}
75 
76 	if (creds.header.flags & WIFI_CREDENTIALS_FLAG_MFP_REQUIRED) {
77 		shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", MFP_REQUIRED");
78 	} else if (creds.header.flags & WIFI_CREDENTIALS_FLAG_MFP_DISABLED) {
79 		shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", MFP_DISABLED");
80 	} else {
81 		shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", MFP_OPTIONAL");
82 	}
83 
84 	if (creds.header.timeout) {
85 		shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", timeout: %d", creds.header.timeout);
86 	}
87 
88 	shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, "\n");
89 }
90 
parse_sec_type(const char * s)91 static enum wifi_security_type parse_sec_type(const char *s)
92 {
93 	if (strcmp("OPEN", s) == 0) {
94 		return WIFI_SECURITY_TYPE_NONE;
95 	}
96 
97 	if (strcmp("WPA2-PSK", s) == 0) {
98 		return WIFI_SECURITY_TYPE_PSK;
99 	}
100 
101 	if (strcmp("WPA2-PSK-SHA256", s) == 0) {
102 		return WIFI_SECURITY_TYPE_PSK_SHA256;
103 	}
104 
105 	if (strcmp("WPA3-SAE", s) == 0) {
106 		return WIFI_SECURITY_TYPE_SAE;
107 	}
108 
109 	if (strcmp("WPA-PSK", s) == 0) {
110 		return WIFI_SECURITY_TYPE_WPA_PSK;
111 	}
112 
113 	return WIFI_SECURITY_TYPE_UNKNOWN;
114 }
115 
parse_band(const char * s)116 static enum wifi_frequency_bands parse_band(const char *s)
117 {
118 	if (strcmp("2.4GHz", s) == 0) {
119 		return WIFI_FREQ_BAND_2_4_GHZ;
120 	}
121 
122 	if (strcmp("5GHz", s) == 0) {
123 		return WIFI_FREQ_BAND_5_GHZ;
124 	}
125 
126 	if (strcmp("6GHz", s) == 0) {
127 		return WIFI_FREQ_BAND_6_GHZ;
128 	}
129 
130 	return WIFI_FREQ_BAND_UNKNOWN;
131 }
132 
cmd_add_network(const struct shell * sh,size_t argc,char * argv[])133 static int cmd_add_network(const struct shell *sh, size_t argc, char *argv[])
134 {
135 	int ret;
136 
137 	if (argc < 3) {
138 		goto help;
139 	}
140 
141 	if (strnlen(argv[1], WIFI_SSID_MAX_LEN + 1) > WIFI_SSID_MAX_LEN) {
142 		shell_error(sh, "SSID too long");
143 		goto help;
144 	}
145 
146 	struct wifi_credentials_personal creds = {
147 		.header.ssid_len = strlen(argv[1]),
148 		.header.type = parse_sec_type(argv[2]),
149 	};
150 
151 	memcpy(creds.header.ssid, argv[1], creds.header.ssid_len);
152 
153 	if (creds.header.type == WIFI_SECURITY_TYPE_UNKNOWN) {
154 		shell_error(sh, "Cannot parse security type");
155 		goto help;
156 	}
157 
158 	size_t arg_idx = 3;
159 
160 	if (creds.header.type == WIFI_SECURITY_TYPE_PSK ||
161 	    creds.header.type == WIFI_SECURITY_TYPE_PSK_SHA256 ||
162 	    creds.header.type == WIFI_SECURITY_TYPE_SAE ||
163 	    creds.header.type == WIFI_SECURITY_TYPE_WPA_PSK) {
164 		/* parse passphrase */
165 		if (argc < 4) {
166 			shell_error(sh, "Missing password");
167 			goto help;
168 		}
169 		creds.password_len = strlen(argv[3]);
170 		if (creds.password_len < WIFI_PSK_MIN_LEN) {
171 			shell_error(sh, "Passphrase should be minimum %d characters",
172 				    WIFI_PSK_MIN_LEN);
173 			goto help;
174 		}
175 		if ((creds.password_len > CONFIG_WIFI_CREDENTIALS_SAE_PASSWORD_LENGTH &&
176 		     creds.header.type == WIFI_SECURITY_TYPE_SAE) ||
177 		    (creds.password_len > WIFI_PSK_MAX_LEN &&
178 		     creds.header.type != WIFI_SECURITY_TYPE_SAE)) {
179 			shell_error(sh, "Password is too long for this security type");
180 			goto help;
181 		}
182 		memcpy(creds.password, argv[3], creds.password_len);
183 		++arg_idx;
184 	}
185 
186 	if (arg_idx < argc) {
187 		/* look for bssid */
188 		ret = sscanf(argv[arg_idx], MACSTR, &creds.header.bssid[0], &creds.header.bssid[1],
189 			     &creds.header.bssid[2], &creds.header.bssid[3], &creds.header.bssid[4],
190 			     &creds.header.bssid[5]);
191 		if (ret == 6) {
192 			creds.header.flags |= WIFI_CREDENTIALS_FLAG_BSSID;
193 			++arg_idx;
194 		}
195 	}
196 
197 	if (arg_idx < argc) {
198 		/* look for band */
199 		enum wifi_frequency_bands band = parse_band(argv[arg_idx]);
200 
201 		if (band == WIFI_FREQ_BAND_2_4_GHZ) {
202 			creds.header.flags |= WIFI_CREDENTIALS_FLAG_2_4GHz;
203 			++arg_idx;
204 		}
205 		if (band == WIFI_FREQ_BAND_5_GHZ) {
206 			creds.header.flags |= WIFI_CREDENTIALS_FLAG_5GHz;
207 			++arg_idx;
208 		}
209 	}
210 
211 	if (arg_idx < argc) {
212 		/* look for channel */
213 		char *end;
214 
215 		creds.header.channel = strtol(argv[arg_idx], &end, 10);
216 		if (*end == '\0') {
217 			++arg_idx;
218 		}
219 	}
220 
221 	if (arg_idx < argc) {
222 		/* look for favorite flag */
223 		if (strncmp("favorite", argv[arg_idx], strlen("favorite")) == 0) {
224 			creds.header.flags |= WIFI_CREDENTIALS_FLAG_FAVORITE;
225 			++arg_idx;
226 		}
227 	}
228 
229 	if (arg_idx < argc) {
230 		/* look for mfp_disabled flag */
231 		if (strncmp("mfp_disabled", argv[arg_idx], strlen("mfp_disabled")) == 0) {
232 			creds.header.flags |= WIFI_CREDENTIALS_FLAG_MFP_DISABLED;
233 			++arg_idx;
234 		} else if (strncmp("mfp_required", argv[arg_idx], strlen("mfp_required")) == 0) {
235 			creds.header.flags |= WIFI_CREDENTIALS_FLAG_MFP_REQUIRED;
236 			++arg_idx;
237 		}
238 	}
239 
240 	if (arg_idx < argc) {
241 		/* look for timeout */
242 		char *end;
243 
244 		creds.header.timeout = strtol(argv[arg_idx], &end, 10);
245 		if (*end == '\0') {
246 			++arg_idx;
247 		}
248 	}
249 
250 	if (arg_idx != argc) {
251 		for (size_t i = arg_idx; i < argc; ++i) {
252 			shell_warn(sh, "Unparsed arg: [%s]", argv[i]);
253 		}
254 	}
255 
256 	return wifi_credentials_set_personal_struct(&creds);
257 help:
258 	shell_print(sh, "Usage: wifi_cred add \"network name\""
259 			" {OPEN, WPA2-PSK, WPA2-PSK-SHA256, WPA3-SAE, WPA-PSK}"
260 			" [psk/password]"
261 			" [bssid]"
262 			" [{2.4GHz, 5GHz}]"
263 			" [channel]"
264 			" [favorite]"
265 			" [mfp_disabled|mfp_required]"
266 			" [timeout]");
267 	return -EINVAL;
268 }
269 
cmd_delete_network(const struct shell * sh,size_t argc,char * argv[])270 static int cmd_delete_network(const struct shell *sh, size_t argc, char *argv[])
271 {
272 	if (argc != 2) {
273 		shell_print(sh, "Usage: wifi_cred delete \"network name\"");
274 		return -EINVAL;
275 	}
276 
277 	if (strnlen(argv[1], WIFI_SSID_MAX_LEN + 1) > WIFI_SSID_MAX_LEN) {
278 		shell_error(sh, "SSID too long");
279 		return -EINVAL;
280 	}
281 
282 	shell_print(sh, "\tDeleting network ssid: \"%s\", ssid_len: %d", argv[1], strlen(argv[1]));
283 	return wifi_credentials_delete_by_ssid(argv[1], strlen(argv[1]));
284 }
285 
cmd_list_networks(const struct shell * sh,size_t argc,char * argv[])286 static int cmd_list_networks(const struct shell *sh, size_t argc, char *argv[])
287 {
288 	wifi_credentials_for_each_ssid(print_network_info, (void *)sh);
289 	return 0;
290 }
291 
292 #if CONFIG_WIFI_CREDENTIALS_CONNECT_STORED
293 
cmd_auto_connect(const struct shell * sh,size_t argc,char * argv[])294 static int cmd_auto_connect(const struct shell *sh, size_t argc, char *argv[])
295 {
296 	struct net_if *iface = net_if_get_first_by_type(&NET_L2_GET_NAME(ETHERNET));
297 	int rc = net_mgmt(NET_REQUEST_WIFI_CONNECT_STORED, iface, NULL, 0);
298 
299 	if (rc) {
300 		shell_error(sh,
301 			    "An error occurred when trying to auto-connect to a network. err: %d",
302 			    rc);
303 	}
304 
305 	return 0;
306 }
307 
308 #endif /* CONFIG_WIFI_CREDENTIALS_CONNECT_STORED */
309 
310 SHELL_STATIC_SUBCMD_SET_CREATE(sub_wifi_cred,
311 	SHELL_CMD_ARG(add, NULL,
312 		      "Add network to storage.\n",
313 		      cmd_add_network, 0, 0),
314 	SHELL_CMD_ARG(delete, NULL,
315 		      "Delete network from storage.\n",
316 		      cmd_delete_network,
317 		      0, 0),
318 	SHELL_CMD_ARG(list, NULL,
319 		      "List stored networks.\n",
320 		      cmd_list_networks,
321 		      0, 0),
322 
323 #if CONFIG_WIFI_CREDENTIALS_CONNECT_STORED
324 	SHELL_CMD_ARG(auto_connect, NULL,
325 		      "Connect to any stored network.\n",
326 		      cmd_auto_connect,
327 		      0, 0),
328 #endif /* CONFIG_WIFI_CREDENTIALS_CONNECT_STORED */
329 
330 	SHELL_SUBCMD_SET_END
331 );
332 
333 SHELL_SUBCMD_ADD((wifi), cred, &sub_wifi_cred,
334 		 "Wifi credentials management.\n",
335 		 NULL,
336 		 0, 0);
337