1 /*
2  *	aprsc
3  *
4  *	(c) Heikki Hannikainen, OH7LZB <hessu@hes.iki.fi>
5  *
6  *     This program is licensed under the BSD license, which can be found
7  *     in the file LICENSE.
8  *
9  */
10 
11 #define _GNU_SOURCE
12 
13 #include <string.h>
14 #include <strings.h>
15 #include <ctype.h>
16 
17 #include "login.h"
18 #include "hmalloc.h"
19 #include "hlog.h"
20 #include "passcode.h"
21 #include "incoming.h"
22 #include "config.h"
23 #include "filter.h"
24 #include "clientlist.h"
25 #include "parse_qc.h"
26 #include "ssl.h"
27 
28 /* a static list of usernames which are not allowed to log in */
29 static const char *disallow_login_usernames[] = {
30 	"pass", /* a sign of "user  pass -1" login with no configured username */
31 	NULL
32 };
33 
34 /* a static list of unmaintained applications which receive some
35  * special "treatment"
36  */
37 static const char *quirks_mode_blacklist[] = {
38 	"HR-IXPWIND", /* Haute Networks HauteWIND: transmits LF NUL for line termination */
39 	"HR-IXP-WIND", /* Variation of Haute Networks HauteWIND */
40 	"IXP-WIND", /* Variation of Haute Networks HauteWIND */
41 	"Oww/", /* One-Wire Weather CWOP client sends a NUL byte in beginning of packet (in end of previous line) */
42 	NULL
43 };
44 
45 /*
46  *	Parse the login string in a HTTP POST or UDP submit packet
47  *	Argh, why are these not in standard POST parameters in HTTP?
48  *
49  *	TODO: Used for UDP too, so should not say HTTP in log errors...
50  */
51 
http_udp_upload_login(const char * addr_rem,char * s,char ** username,const char * log_source)52 int http_udp_upload_login(const char *addr_rem, char *s, char **username, const char *log_source)
53 {
54 	int argc;
55 	char *argv[256];
56 	int i;
57 	int username_len;
58 
59 	/* parse to arguments */
60 	if ((argc = parse_args_noshell(argv, s)) == 0)
61 		return -1;
62 
63 	if (argc < 2) {
64 		hlog(LOG_WARNING, "%s: %s: Invalid login string, too few arguments: '%s'", addr_rem, log_source, s);
65 		return -1;
66 	}
67 
68 	if (strcasecmp(argv[0], "user") != 0) {
69 		hlog(LOG_WARNING, "%s: %s: Invalid login string, no 'user': '%s'", addr_rem, log_source, s);
70 		return -1;
71 	}
72 
73 	*username = argv[1];
74 	username_len = strlen(*username);
75 	/* limit username length */
76 	if (username_len > CALLSIGNLEN_MAX) {
77 		hlog(LOG_WARNING, "%s: %s: Invalid login string, too long 'user' username: '%s'", addr_rem, log_source, *username);
78 		return -1;
79 	}
80 
81 	/* check the username against a static list of disallowed usernames */
82 	for (i = 0; (disallow_login_usernames[i]); i++) {
83 		if (strcasecmp(*username, disallow_login_usernames[i]) == 0) {
84 			hlog(LOG_WARNING, "%s: %s: Login by user '%s' not allowed", addr_rem, log_source, *username);
85 			return -1;
86 		}
87 	}
88 
89 	/* check the username against a dynamic list of disallowed usernames */
90 	if (disallow_login_glob && check_call_glob_match(disallow_login_glob, *username, username_len)) {
91 		hlog(LOG_WARNING, "%s: %s: Login by user '%s' not allowed due to config", addr_rem, log_source, *username);
92 		return -1;
93 	}
94 
95 	/* make sure the callsign is OK on the APRS-IS */
96 	if (check_invalid_q_callsign(*username, username_len)) {
97 		hlog(LOG_WARNING, "%s: %s: Invalid login string, invalid 'user': '%s'", addr_rem, log_source, *username);
98 		return -1;
99 	}
100 
101 	int given_passcode = -1;
102 	int validated = 0;
103 
104 	for (i = 2; i < argc; i++) {
105 		if (strcasecmp(argv[i], "pass") == 0) {
106 			if (++i >= argc) {
107 				hlog(LOG_WARNING, "%s (%s): %s: No passcode after pass command", addr_rem, log_source, username);
108 				break;
109 			}
110 
111 			given_passcode = atoi(argv[i]);
112 			if (given_passcode >= 0)
113 				if (given_passcode == aprs_passcode(*username))
114 					validated = 1;
115 		} else if (strcasecmp(argv[i], "vers") == 0) {
116 			if (i+2 >= argc) {
117 				hlog(LOG_DEBUG, "%s (%s): %s: No application name and version after vers command", addr_rem, username, log_source);
118 				break;
119 			}
120 
121 			// skip app name and version
122 			i += 2;
123 		}
124 	}
125 
126 	return validated;
127 }
128 
129 /*
130  *	Check if string haystack starts with needle, return 1 if true
131  */
132 
prefixmatch(const char * haystack,const char * needle)133 static int prefixmatch(const char *haystack, const char *needle)
134 {
135 	do {
136 		if (*needle == 0)
137 			return 1; /* we're at the end of the needle, and no mismatches found */
138 
139 		if (*haystack == 0)
140 			return 0; /* haystack is shorter than needle, cannot match */
141 
142 		if (*haystack != *needle)
143 			return 0; /* mismatch found... */
144 
145 		/* advance pointers */
146 		haystack++;
147 		needle++;
148 	} while (1);
149 }
150 
151 /*
152  *	Set and sanitize application name and version strings
153  */
154 
login_set_app_name(struct client_t * c,const char * app_name,const char * app_ver)155 void login_set_app_name(struct client_t *c, const char *app_name, const char *app_ver)
156 {
157 	int i;
158 
159 	strncpy(c->app_name, app_name, sizeof(c->app_name));
160 	c->app_name[sizeof(c->app_name)-1] = 0;
161 	sanitize_ascii_string(c->app_name);
162 
163 	strncpy(c->app_version, app_ver, sizeof(c->app_version));
164 	c->app_version[sizeof(c->app_version)-1] = 0;
165 	sanitize_ascii_string(c->app_version);
166 
167 	/* check the application name against a static list of broken apps */
168 	if (quirks_mode) {
169         	c->quirks_mode = 1;
170 		return;
171 	}
172 
173 	c->quirks_mode = 0;
174 	for (i = 0; (quirks_mode_blacklist[i]); i++) {
175 		if (prefixmatch(c->app_name, quirks_mode_blacklist[i])) {
176 			hlog(LOG_DEBUG, "%s/%s: Enabling quirks mode for application %s %s",
177 				c->addr_rem, c->username, c->app_name, c->app_version);
178 			c->quirks_mode = 1;
179 			break;
180 		}
181 	}
182 
183 }
184 
login_setup_udp_feed(struct client_t * c,int port)185 int login_setup_udp_feed(struct client_t *c, int port)
186 {
187 	if (!c->udpclient)
188 		return -1;
189 
190 	c->udp_port = port;
191 	c->udpaddr = c->addr;
192 	if (c->udpaddr.sa.sa_family == AF_INET) {
193 		c->udpaddr.si.sin_port = htons(c->udp_port);
194 		c->udpaddrlen = sizeof(c->udpaddr.si);
195 	} else {
196 		c->udpaddr.si6.sin6_port = htons(c->udp_port);
197 		c->udpaddrlen = sizeof(c->udpaddr.si6);
198 	}
199 
200 	inbound_connects_account(3, c->udpclient->portaccount); /* "3" = udp, not listening..  */
201 
202 	return 0;
203 }
204 
205 /*
206  *	login.c: works in the context of the worker thread
207  */
208 
login_handler(struct worker_t * self,struct client_t * c,int l4proto,char * s,int len)209 int login_handler(struct worker_t *self, struct client_t *c, int l4proto, char *s, int len)
210 {
211 	int argc;
212 	char *argv[256];
213 	int i, rc;
214 
215 	/* make it null-terminated for our string processing */
216 	/* TODO: do not modify incoming stream - make s a const char! */
217 	char *e = s + len;
218 	*e = 0;
219 	hlog_packet(LOG_DEBUG, s, len, "%s: login string: ", c->addr_rem);
220 
221 	/* parse to arguments */
222 	if ((argc = parse_args_noshell(argv, s)) == 0 || *argv[0] == '#')
223 		return 0;
224 
225 	if (argc < 2) {
226 		hlog(LOG_WARNING, "%s: Invalid login string, too few arguments: '%s'", c->addr_rem, s);
227 		rc = client_printf(self, c, "# Invalid login string, too few arguments\r\n");
228 		goto failed_login;
229 	}
230 
231 	if (strcasecmp(argv[0], "user") != 0) {
232 		if (strcasecmp(argv[0], "GET") == 0)
233 			c->failed_cmds = 10; /* bail out right away for a HTTP client */
234 
235 		c->failed_cmds++;
236 		hlog(LOG_WARNING, "%s: Invalid login string, no 'user': '%s'", c->addr_rem, s);
237 		rc = client_printf(self, c, "# Invalid login command\r\n");
238 		goto failed_login;
239 	}
240 
241 	char *username = argv[1];
242 
243 	/* limit username length */
244 	if (strlen(username) > CALLSIGNLEN_MAX) {
245 		hlog(LOG_WARNING, "%s: Invalid login string, too long 'user' username: '%s'", c->addr_rem, username);
246 		username[CALLSIGNLEN_MAX] = 0;
247 		rc = client_printf(self, c, "# Invalid username format\r\n");
248 		goto failed_login;
249 	}
250 
251 	/* ok, it's somewhat valid, write it down */
252 	strncpy(c->username, username, sizeof(c->username));
253 	c->username[sizeof(c->username)-1] = 0;
254 	c->username_len = strlen(c->username);
255 
256 	/* check the username against a static list of disallowed usernames */
257 	for (i = 0; (disallow_login_usernames[i]); i++) {
258 		if (strcasecmp(c->username, disallow_login_usernames[i]) == 0) {
259 			hlog(LOG_WARNING, "%s: Login by user '%s' not allowed", c->addr_rem, c->username);
260 			rc = client_printf(self, c, "# Login by user not allowed\r\n");
261 			goto failed_login;
262 		}
263 	}
264 
265 	/* check the username against a dynamic list of disallowed usernames */
266 	if (disallow_login_glob && check_call_glob_match(disallow_login_glob, c->username, c->username_len)) {
267 		hlog(LOG_WARNING, "%s: Login by user '%s' not allowed due to config", c->addr_rem, c->username);
268 		rc = client_printf(self, c, "# Login by user not allowed\r\n");
269 		goto failed_login;
270 	}
271 
272 	/* make sure the callsign is OK on the APRS-IS */
273 	if (check_invalid_q_callsign(c->username, c->username_len)) {
274 		hlog(LOG_WARNING, "%s: Invalid login string, invalid 'user': '%s'", c->addr_rem, c->username);
275 		rc = client_printf(self, c, "# Invalid username format, not allowed\r\n");
276 		goto failed_login;
277 	}
278 
279 	/* make sure the client's callsign is not my Server ID */
280 	if (strcasecmp(c->username, serverid) == 0) {
281 		hlog(LOG_WARNING, "%s: Invalid login string, username equals our serverid: '%s'", c->addr_rem, c->username);
282 		rc = client_printf(self, c, "# Login by user not allowed (our serverid)\r\n");
283 		goto failed_login;
284 	}
285 
286 	/* if SSL client cert verification is enabled, check it */
287 	int ssl_validated = 0;
288 #ifdef USE_SSL
289 	if (c->ssl_con && c->ssl_con->validate) {
290 		hlog(LOG_DEBUG, "%s/%s: login: doing SSL client cert validation", c->addr_rem, c->username);
291 		int ssl_res = ssl_validate_peer_cert_phase1(c);
292 		if (ssl_res == 0)
293 			ssl_res = ssl_validate_peer_cert_phase2(c);
294 
295 		if (ssl_res == 0) {
296 			c->validated = VALIDATED_STRONG;
297 			ssl_validated = 1;
298 		} else {
299 			hlog(LOG_WARNING, "%s/%s: SSL client cert validation failed: %s", c->addr_rem, c->username, ssl_strerror(ssl_res));
300 			if (ssl_res == SSL_VALIDATE_CLIENT_CERT_UNVERIFIED)
301 				rc = client_printf(self, c, "# Client certificate not accepted: %s\r\n", X509_verify_cert_error_string(c->ssl_con->ssl_err_code));
302 			else
303 				rc = client_printf(self, c, "# Client certificate authentication failed: %s\r\n", ssl_strerror(ssl_res));
304 			c->failed_cmds = 10; /* bail out right away for a HTTP client */
305 			goto failed_login;
306 		}
307 	}
308 #endif
309 
310 
311 	int given_passcode = -1;
312 
313 	for (i = 2; i < argc; i++) {
314 		if (strcasecmp(argv[i], "pass") == 0) {
315 			if (++i >= argc) {
316 				hlog(LOG_WARNING, "%s/%s: No passcode after pass command", c->addr_rem, username);
317 				break;
318 			}
319 
320 			if (!ssl_validated) {
321 				given_passcode = atoi(argv[i]);
322 				if (given_passcode >= 0)
323 					if (given_passcode == aprs_passcode(c->username))
324 						c->validated = VALIDATED_WEAK;
325 			}
326 		} else if (strcasecmp(argv[i], "vers") == 0) {
327 			/* Collect application name and version separately.
328 			 * Some clients only give out application name but
329 			 * no version. If those same applications do try to
330 			 * use filter or udp, the filter/udp keyword will end
331 			 * up as the version number. So good luck with that.
332 			 */
333 
334 			if (i+1 >= argc) {
335 				hlog(LOG_INFO, "%s/%s: No application name after 'vers' in login", c->addr_rem, username);
336 				break;
337 			}
338 
339 			login_set_app_name(c, argv[i+1], (i+2 < argc) ? argv[i+2] : "");
340 			i += 2;
341 
342 		} else if (strcasecmp(argv[i], "udp") == 0) {
343 			if (++i >= argc) {
344 				hlog(LOG_WARNING, "%s/%s: Missing UDP port number after UDP command", c->addr_rem, username);
345 				break;
346 			}
347 
348 			int udp_port = atoi(argv[i]);
349 			if (udp_port < 1024 || udp_port > 65535) {
350 				hlog(LOG_WARNING, "%s/%s: UDP port number %s is out of range", c->addr_rem, username, argv[i]);
351 				break;
352 			}
353 
354 			if (login_setup_udp_feed(c, udp_port) != 0) {
355 				/* Sorry, no UDP service for this port.. */
356 				hlog(LOG_DEBUG, "%s/%s: Requested UDP on client port with no UDP configured", c->addr_rem, username);
357 				rc = client_printf(self, c, "# No UDP service available on this port\r\n");
358 				if (rc < -2)
359 					return rc; // client got destroyed
360 
361 			}
362 
363 		} else if (strcasestr(argv[i], "filter")) {
364                         /* Follows javaaprssrvr's example - any command having 'filter' in the
365                          * end is OK. Case insensitive.
366                          */
367 			if (!(c->flags & CLFLAGS_USERFILTEROK)) {
368 				rc = client_printf(self, c, "# No user-specified filters on this port\r\n");
369 				if (rc < -2)
370                         		return rc; // client got destroyed
371 				break;
372 			}
373 
374 			/* copy the null-separated filter arguments back to a space-separated
375 			 * string, for the status page to show
376 			 */
377 			char *fp = c->filter_s;
378 			char *fe = c->filter_s + FILTER_S_SIZE;
379 			int f_non_first = 0;
380 
381 			while (++i < argc) {
382 				int l = strlen(argv[i]);
383 				if (fp + l + 2 < fe) {
384 					if (f_non_first) {
385 						*fp++ = ' ';
386 					}
387 					memcpy(fp, argv[i], l);
388 					fp += l;
389 					*fp = 0;
390 
391 					f_non_first = 1;
392 				}
393 
394 				/* parse filters in argv[i] */
395 				rc = filter_parse(c, argv[i], 1);
396 				if (rc) {
397 					rc = client_printf( self, c, "# Parse errors on filter spec: '%s'\r\n", argv[i]);
398 					if (rc < -2)
399 						return rc; // The client probably got destroyed!
400 				}
401 			}
402 		}
403 	}
404 
405 	/* clean up the filter string so that it doesn't contain invalid
406 	 * UTF-8 or other binary stuff. */
407 	sanitize_ascii_string(c->filter_s);
408 
409 	/* ok, login succeeded, switch handler */
410 	c->handler_line_in = &incoming_handler; /* handler of all incoming APRS-IS data during a connection */
411 
412 	rc = client_printf( self, c, "# logresp %s %s, server %s\r\n",
413 			    username,
414 			    (c->validated) ? "verified" : "unverified",
415 			    serverid );
416 	if (rc < -2)
417 		return rc; // The client probably got destroyed!
418 
419 	hlog(LOG_DEBUG, "%s: login '%s'%s%s%s%s%s%s%s%s",
420 	     c->addr_rem, username,
421 	     (c->validated) ? " pass_ok" : "",
422 	     (!c->validated && given_passcode >= 0) ? " pass_invalid" : "",
423 	     (given_passcode < 0) ? " pass_none" : "",
424 	     (c->udp_port) ? " UDP" : "",
425 	     (*c->app_name) ? " app " : "",
426 	     (*c->app_name) ? c->app_name : "",
427 	     (*c->app_version) ? " ver " : "",
428 	     (*c->app_version) ? c->app_version : ""
429 	);
430 
431 	/* mark as connected and classify */
432 	worker_mark_client_connected(self, c);
433 
434 	/* Add the client to the client list.
435 	 *
436 	 * If the client logged in with a valid passcode, check if there are
437 	 * other validated clients logged in with the same username.
438 	 * If one is found, it needs to be disconnected.
439 	 *
440 	 * The lookup is done while holding the write lock to the clientlist,
441 	 * instead of a separate lookup call, so that two clients logging in
442 	 * at exactly the same time won't make it.
443 	 */
444 
445 	int old_fd = clientlist_add(c);
446 	if (c->validated && old_fd != -1) {
447 		/* TODO: If old connection is SSL validated, and this one is not, do not disconnect it. */
448 		hlog(LOG_INFO, "fd %d: Disconnecting duplicate validated client with username '%s'", old_fd, username);
449 		/* The other client may be on another thread, so cannot client_close() it.
450 		 * There is a small potential race here, if the old client disconnected and
451 		 * the fd was recycled for another client right after the clientlist check.
452 		 */
453 		shutdown(old_fd, SHUT_RDWR);
454 	}
455 
456 	return 0;
457 
458 failed_login:
459 
460 	/* if we already lost the client, just return */
461 	if (rc < -2)
462 		return rc;
463 
464 	c->failed_cmds++;
465 	if (c->failed_cmds >= 3) {
466 		client_close(self, c, CLIERR_LOGIN_RETRIES);
467 		return -3;
468 	}
469 
470 	return rc;
471 }
472 
473 
474