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 /*
12  *	parse_qc.c: Process the APRS-IS Q construct
13  */
14 
15 #define _GNU_SOURCE
16 #include <string.h>
17 #include <stdio.h>
18 #include <ctype.h>
19 
20 #include "parse_qc.h"
21 #include "incoming.h"
22 #include "clientlist.h"
23 #include "config.h"
24 #include "hlog.h"
25 
26 /*
27  *	Check if a callsign is good for a Q construct
28  *	(valid APRS-IS callsign)
29  */
30 
check_invalid_q_callsign(const char * call,int len)31 int check_invalid_q_callsign(const char *call, int len)
32 {
33 	const char *p = call;
34 	const char *e = call + len;
35 
36 	//hlog(LOG_DEBUG, "check_invalid_q_callsign: '%.*s'", len, call);
37 
38 	/* either length of callsign, or length of IPv6 address in hex (128/4) */
39 	if ((len > CALLSIGNLEN_MAX || len < 1) && len != 32)
40 		return -1;
41 
42 	while (p < e) {
43 		if ((!isalnum(*p)) && *p != '-')
44 			return -1;
45 		p++;
46 	}
47 
48 	return 0;
49 }
50 
51 /*
52  *	q_dropcheck contains the last big block of the Q construct algorithm
53  *	(All packets with q constructs...) and does rejection and duplicate
54  *	dropping.
55  */
56 
57 #define MAX_Q_CALLS 64
58 
q_dropcheck(struct client_t * c,const char * pdata,char * new_q,int new_q_size,char * via_start,int new_q_len,char q_proto,char q_type,char * q_start,char ** q_replace,char * path_end)59 static int q_dropcheck( struct client_t *c, const char *pdata, char *new_q, int new_q_size, char *via_start,
60 			int new_q_len, char q_proto, char q_type, char *q_start,
61 			char **q_replace, char *path_end )
62 {
63 	char *qcallv[MAX_Q_CALLS+1];
64 	int qcallc;
65 	char *p;
66 	int username_len;
67 	int login_in_path = 0;
68 	int i, j, l;
69 
70 	if (q_proto != q_protocol_id && disallow_other_protocol_id) {
71 		hlog(LOG_DEBUG, "q: dropping due to q%c%c (wrong Q protocol ID)", q_proto, q_type);
72 		return INERR_Q_DISALLOW_PROTOCOL; /* drop the packet */
73 	}
74 
75 	/* If disallow_unverified is on, drop packets with qAX (packet from unverified client) */
76 	if (q_type == 'X' && disallow_unverified) {
77 		hlog(LOG_DEBUG, "q: dropping due to q%c%c (unverified client)", q_proto, q_type);
78 		return INERR_Q_QAX; /* drop the packet */
79 	}
80 
81 	/*
82 	 * if ,qAZ, is the q construct:
83 	 * {
84 	 *	Dump to the packet to the reject log
85 	 *	Quit processing the packet
86 	 * }
87 	 */
88 
89 	if (q_type == 'Z') {
90 		/* qAZ is for packets from client to server, to prevent redistribution */
91 		hlog(LOG_DEBUG, "q: dropping due q_type %c%c", q_proto, q_type);
92 		return INERR_Q_QAZ; /* drop the packet */
93 	}
94 
95 	/*
96 	 * Produce an array of pointers pointing to each callsign in the path
97 	 * after the q construct
98 	 */
99 	qcallc = 0;
100 	if (q_start) {
101 		p = q_start + 4;
102 		while (qcallc < MAX_Q_CALLS && p < path_end) {
103 			while (p < path_end && *p == ',')
104 				p++;
105 			if (p == path_end)
106 				break;
107 			qcallv[qcallc++] = p;
108 			while (p < path_end && *p != ',')
109 				p++;
110 		}
111 		qcallv[qcallc] = p+1;
112 	}
113 
114 	/*
115 	 * If ,SERVERLOGIN is found after the q construct:
116 	 * {
117 	 *	Dump to the loop log with the sender's IP address for identification
118 	 *	Quit processing the packet
119 	 * }
120 	 *
121 	 * (note: if serverlogin is 'XYZ-1', it must not match XYZ-12, so we need to
122 	 * match against ,SERVERLOGIN, or ,SERVERLOGIN:)
123 	 */
124 
125 	if (q_start) {
126 		p = memmem(q_start+4, path_end-q_start-4, serverid, serverid_len);
127 		if (p && *(p-1) == ',' && ( *(p+serverid_len) == ',' || p+serverid_len == path_end || *(p+serverid_len) == ':' )) {
128 			/* TODO: The reject log should really log the offending packet */
129 			hlog(LOG_DEBUG, "q: dropping due to my callsign appearing in path");
130 			return INERR_Q_QPATH_MYCALL; /* drop the packet */
131 		}
132 	}
133 
134 	/*
135 	 * 1)
136 	 * If a callsign-SSID is found twice in the q construct:
137 	 * {
138 	 *	Dump to the loop log with the sender's IP address for identification
139 	 *	Quit processing the packet
140 	 * }
141 	 *
142 	 * 2)
143 	 * If a verified login other than this login is found in the q construct
144 	 * and that login is not allowed to have multiple verified connects (the
145 	 * IPADDR of an outbound connection is considered a verified login):
146 	 * {
147 	 *	Dump to the loop log with the sender's IP address for identification
148 	 *	Quit processing the packet
149 	 * }
150 	 *
151 	 * 3)
152 	 * If the packet is from an inbound port and the login is found after the q construct but is not the LAST VIACALL:
153 	 * {
154 	 *	Dump to the loop log with the sender's IP address for identification
155 	 *	Quit processing the packet
156 	 * }
157 	 *
158 	 */
159 	username_len = strlen(c->username);
160 	for (i = 0; i < qcallc; i++) {
161 		l = qcallv[i+1] - qcallv[i] - 1;
162 		/* 1) */
163 		for (j = i + 1; j < qcallc; j++) {
164 			/* this match is case sensitive in javaprssrvr, so that's what we'll do */
165 			if (l == qcallv[j+1] - qcallv[j] - 1 && memcmp(qcallv[i], qcallv[j], l) == 0) {
166 				/* TODO: The reject log should really log the offending packet */
167 				hlog(LOG_DEBUG, "q: dropping due to callsign-SSID '%.*s' found twice after Q construct", l, qcallv[i]);
168 			    	return INERR_Q_QPATH_CALL_TWICE;
169 			}
170 		}
171 		if (l == username_len && strncasecmp(qcallv[i], c->username, username_len) == 0) {
172 			/* ok, login is client's login, handle step 3) */
173 			login_in_path = 1;
174 			if (c->state == CSTATE_CONNECTED &&
175 			    (c->flags & CLFLAGS_INPORT) &&
176 			    (i != qcallc - 1)) {
177 				/* 3) hits: from an inbound connection, client login found in path,
178 				 * but is not the last viacall
179 				 * TODO: should dump...
180 				 */
181 				/* TODO: The reject log should really log the offending packet */
182 				hlog(LOG_DEBUG, "q: dropping due to login callsign %s not being the last viacall after Q construct", c->username);
183 				return INERR_Q_PATH_LOGIN_NOT_LAST;
184 			}
185 		} else if (clientlist_check_if_validated_client(qcallv[i], l) != -1) {
186 			/* 2) hits: TODO: should dump to a loop log */
187 			/* TODO: The reject log should really log the offending packet */
188 			hlog(LOG_DEBUG, "q: dropping due to callsign '%.*s' after Q construct being logged in on another socket, arrived from %s", l, qcallv[i], c->username);
189 			return INERR_Q_PATH_CALL_IS_LOCAL_CLIENT;
190 		} else if (check_invalid_q_callsign(qcallv[i], l) != 0) {
191 			hlog(LOG_DEBUG, "q: dropping due to callsign '%.*s' after Q construct being invalid as an APRS-IS server name, arrived from %s", l, qcallv[i], c->username);
192 			return INERR_Q_PATH_CALL_IS_INVALID;
193 		}
194 
195 		if (q_type == 'U' && memcmp(qcallv[i], pdata, l) == 0 && pdata[l] == '>') {
196 			hlog(LOG_DEBUG, "q: dropping due to callsign '%.*s' after qAU also being srccall", l, qcallv[i]);
197 			return INERR_Q_QAU_PATH_CALL_IS_SRCCALL;
198 		}
199 	}
200 
201 	/*
202 	 * If trace is on, the q construct is qAI, or the FROMCALL is on the server's trace list:
203 	 * {
204 	 *	If the packet is from a verified port where the login is not found after the q construct:
205 	 *		Append ,login
206 	 *	else if the packet is from an outbound connection
207 	 *		Append ,IPADDR
208 	 *
209 	 *	Append ,SERVERLOGIN
210 	 * }
211 	 */
212 
213 	if (q_type == 'I') {
214 		/* we replace the existing Q construct with a regenerated one */
215 		*q_replace = q_start+1;
216 
217 		//hlog(LOG_DEBUG, "qAI: not INPORT, appending ,username %s", c->username);
218 		/* copy over existing qAI trace */
219 		new_q_len = path_end - q_start - 1;
220 		//hlog(LOG_DEBUG, "qAI replacing, new_q_len %d", new_q_len);
221 		if (new_q_len > new_q_size) {
222 			/* ouch, memcpy would run over the buffer */
223 			/* TODO: The reject log should really log the offending packet */
224 			hlog(LOG_DEBUG, "q: dropping due to buffer being too tight");
225 			return INERR_Q_NEWQ_BUFFER_SMALL;
226 		}
227 		memcpy(new_q, q_start+1, new_q_len);
228 
229 		//hlog(LOG_DEBUG, "qAI first memcpy done, new_q_len %d, q_replace %d, new_q %.*s", new_q_len, *q_replace, new_q_len, new_q);
230 
231 		/* If the packet is from a verified port where the login is not found after the q construct,
232 		 * append ,login
233 		 * - but not if this is an UDP core peer, and we don't know the username */
234 		if (c->validated && !login_in_path && c->state != CSTATE_COREPEER) {
235 			/* Append ,login */
236 			//hlog(LOG_DEBUG, "qAI: from validated client, login not in path, appending ,username %s", c->username);
237 			new_q_len += snprintf(new_q + new_q_len, new_q_size - new_q_len, ",%s", c->username);
238 		}
239 		//hlog(LOG_DEBUG, "qAI append done, new_q_len %d, new_q_size %d, q_replace %d, going to append %d more", new_q_len, new_q_size, *q_replace, strlen(serverid)+1);
240 
241 		if (new_q_size - new_q_len < 20) {
242 			hlog(LOG_DEBUG, "q dropping due to buffer being too tight when appending my login for qAI");
243 			return INERR_Q_NEWQ_BUFFER_SMALL;
244 		}
245 
246 		/* Append ,SERVERLOGIN */
247 		new_q_len += snprintf(new_q + new_q_len, new_q_size - new_q_len, ",%s", serverid);
248 		//hlog(LOG_DEBUG, "qAI: complete, new_q %s", new_q);
249 	}
250 
251 	return new_q_len;
252 }
253 
254 
255 /*
256  *	Parse, and possibly generate a Q construct.
257  *	http://www.aprs-is.net/q.htm
258  *	http://www.aprs-is.net/qalgorithm.htm
259  *
260  *	Called by incoming.c, runs in the worker thread's context.
261  *	Threadsafe.
262  *
263  *	1) figure out where a (possibly) existing Q construct is
264  *	2) if it exists, we might need to modify it
265  *	3) we might have to append a new Q construct if one does not exist
266  *	4) we might have to append our server ID to the path
267  *	5) we might have to append the hexadecimal IP address of an UDP peer
268  *
269  *	This function is a bit too long, should probably split a bit, and
270  *	reuse some code snippets.
271  */
272 
q_process(struct client_t * c,const char * pdata,char * new_q,int new_q_size,char * via_start,char ** path_end,int pathlen,char ** new_q_start,char ** q_replace,int originated_by_client)273 int q_process(struct client_t *c, const char *pdata, char *new_q, int new_q_size, char *via_start,
274               char **path_end, int pathlen, char **new_q_start, char **q_replace,
275               int originated_by_client)
276 {
277 	char *q_start = NULL; /* points to the , before the Q construct */
278 	char *q_nextcall = NULL; /* points to the , after the Q construct */
279 	char *q_nextcall_end = NULL; /* points to the , after the callsign right after the Q construct */
280 	int new_q_len = 0; /* the length of a newly generated Q construct */
281 	char q_proto = 0; /* parsed Q construct protocol character (A for APRS) */
282 	char q_type = 0; /* parsed Q construct type character */
283 
284 	/*
285 	All packets
286 	{
287 		Place into TNC-2 format
288 		If a q construct is last in the path (no call following the qPT)
289 			delete the qPT
290 	}
291 	*/
292 
293 	// fprintf(stderr, "q_process\n");
294 	q_start = memmem(via_start, *path_end - via_start, ",q", 2);
295 	if (q_start) {
296 		// fprintf(stderr, "\tfound existing q construct\n");
297 		/* there is an existing Q construct, check for a callsign after it */
298 		q_nextcall = memchr(q_start + 1, ',', *path_end - q_start - 1);
299 		if (!q_nextcall) {
300 			// fprintf(stderr, "\tno comma after Q construct, ignoring and overwriting construct\n");
301 			*path_end = q_start;
302 		} else if (q_nextcall - q_start != 4) {
303 			/* does not fit qPT */
304 			// fprintf(stderr, "\tlength of Q construct is not 3 characters\n");
305 			*path_end = q_start;
306 		} else {
307 			/* parse the q construct itself */
308 			q_proto = *(q_start + 2);
309 			q_type = *(q_start + 3);
310 
311 			/* check for callsign following qPT */
312 			q_nextcall++; /* now points to the callsign */
313 			q_nextcall_end = q_nextcall;
314 			while (q_nextcall_end < *path_end && *q_nextcall_end != ',' && *q_nextcall_end != ':')
315 				q_nextcall_end++;
316 			if (q_nextcall == q_nextcall_end) {
317 				// fprintf(stderr, "\tno callsign after Q construct, ignoring and overwriting construct\n");
318 				*path_end = q_start;
319 				q_proto = q_type = 0; /* for the further code: we do not have a Qc */
320 			}
321 			/* it's OK to have more than one callsign after qPT */
322 		}
323 	} else {
324 		/* if the packet's srccall == login, replace digipeater path with
325 		 * TCPIP* and insert Q construct
326 		 */
327 		//hlog(LOG_DEBUG, "no q found");
328 		if (originated_by_client && !(c->flags & CLFLAGS_UDPSUBMIT)) {
329 			// where to replace from
330 			*q_replace = via_start;
331 			//hlog(LOG_DEBUG, "inserting TCPIP,qAC... starting at %s", *q_replace);
332 			if (c->validated)
333 				return snprintf(new_q, new_q_size, ",TCPIP*,q%cC,%s", q_protocol_id, serverid);
334 			else
335 				return snprintf(new_q, new_q_size, ",TCPXX*,q%cX,%s", q_protocol_id, serverid);
336 		}
337 	}
338 
339 	/* If the packet's srccall == login,
340 	 * put in a TCPIP* path element and append Q construct
341 	 */
342 	if (c->validated && originated_by_client && c->flags & CLFLAGS_INPORT && q_type != 'I' && !(c->flags & CLFLAGS_UDPSUBMIT)) {
343 		// where to replace from
344 		*q_replace = via_start;
345 		//hlog(LOG_DEBUG, "inserting TCPIP,qAC... starting at %s", *q_replace);
346 		return snprintf(new_q, new_q_size, ",TCPIP*,q%cC,%s", q_protocol_id, serverid);
347 	}
348 
349 	// fprintf(stderr, "\tstep 2...\n");
350 
351 	/* ok, we now either have found an existing Q construct + the next callsign,
352 	 * or have eliminated an outright invalid Q construct.
353 	 */
354 
355 	/*
356 	 * All packets from an inbound connection that would normally be passed per current validation algorithm:
357 	 */
358 
359 	if (c->state == CSTATE_CONNECTED &&
360 	    (c->flags & CLFLAGS_INPORT)) {
361 		/*
362 		 * If the packet entered the server from an UDP port:
363 		 * {
364 		 *    if a q construct exists in the packet
365 		 *        Replace the q construct with ,qAU,SERVERLOGIN
366 		 *    else
367 		 *        Append ,qAU,SERVERLOGIN
368 		 *    Quit q processing
369 		 * }
370 		 */
371 		if (c->flags & CLFLAGS_UDPSUBMIT) {
372 			//fprintf(stderr, "\tUDP packet\n");
373 			if (q_proto) {
374 				/* a q construct with a exists in the packet,
375 				 * Replace the q construct with ,qAU,SERVERLOGIN
376 				 */
377 				*path_end = q_start;
378 			}
379 			/* Append ,qAU,SERVERLOGIN */
380 			/* Add TCPIP* in the end of the path only if it's not there already */
381 			if (pathlen > 7 && strncmp(*path_end-7, ",TCPIP*", 7) == 0)
382 				return snprintf(new_q, new_q_size, ",q%cU,%s", q_protocol_id, serverid);
383 			else
384 				return snprintf(new_q, new_q_size, ",TCPIP*,q%cU,%s", q_protocol_id, serverid);
385 		}
386 
387 		/*
388 		 * If the packet entered the server from an unverified connection AND
389 		 * the FROMCALL equals the client login AND the header has been
390 		 * successfully converted to TCPXX format (per current validation algorithm):
391 		 * {
392 		 *    (All packets not deemed "OK" from an unverified connection should be dropped.)
393 		 *    if a q construct with a single call exists in the packet
394 		 *        Replace the q construct with ,qAX,SERVERLOGIN
395 		 *    else if more than a single call exists after the q construct
396 		 *        Invalid header, drop packet as error
397 		 *    else
398 		 *        Append ,qAX,SERVERLOGIN
399 		 *    Quit q processing
400 		 * }
401 		 */
402 		if ((!c->validated) && originated_by_client) {
403 			// fprintf(stderr, "\tunvalidated client sends packet originated by itself\n");
404 			// FIXME: how to check if TCPXX conversion is done? Just assume?
405 			if (q_proto && q_nextcall_end == *path_end) {
406 				/* a q construct with a single call exists in the packet,
407 				 * Replace the q construct with ,qAX,SERVERLOGIN
408 				 */
409 				*path_end = via_start;
410 				return snprintf(new_q, new_q_size, ",TCPXX*,q%cX,%s", q_protocol_id, serverid);
411 			} else if (q_proto && q_nextcall_end < *path_end) {
412 				/* more than a single call exists after the q construct,
413 				 * invalid header, drop the packet as error
414 				 */
415 				return INERR_Q_NONVAL_MULTI_Q_CALLS;
416 			} else {
417 				/* Append ,qAX,SERVERLOGIN (well, javaprssrvr replaces the via path too) */
418 				*path_end = via_start;
419 				return snprintf(new_q, new_q_size, ",TCPXX*,q%cX,%s", q_protocol_id, serverid);
420 			}
421 		}
422 
423 		if (c->validated && (c->flags & CLFLAGS_CLIENTONLY) && originated_by_client) {
424 			if (q_start)
425 				*path_end = q_start;
426 			else
427 				*path_end = via_start;
428 			return snprintf(new_q, new_q_size, ",q%cC,%s", q_protocol_id, serverid);
429 		}
430 
431 		/* OLD:
432 		 * If the packet entered the server from a verified client-only connection
433 		 * AND the FROMCALL does not match the login:
434 		 * {
435 		 *	if a q construct exists in the packet
436 		 *		if the q construct is at the end of the path AND it equals ,qAR,login
437 		 *			Replace qAR with qAo
438 		 *      else if the path is terminated with ,login,I
439 		 *      	Replace ,login,I with qAo,login
440 		 *	else
441 		 *		Append ,qAO,login
442 		 *	Skip to "All packets with q constructs"
443 		 * }
444 		 *
445 		 * NOW:
446 		 * If the packet entered the server from a verified client-only connection
447 		 * AND the FROMCALL does not match the login:
448 		 *     {
449 		 *         if a q construct exists in the path
450 		 *             if the q construct equals ,qAR,callsignssid or ,qAr,callsignssid
451 		 *                 Replace qAR or qAr with qAo
452 		 *             else if the q construct equals ,qAS,callsignssid
453 		 *                 Replace qAS with qAO
454 		 *             else if the q construct equals ,qAC,callsignssid and callsignssid is not equal to the servercall or login
455 		 *                 Replace qAC with qAO
456 		 *         else if the path is terminated with ,callsignssid,I
457 		 *             Replace ,callsignssid,I with qAo,callsignssid
458 		 *         else
459 		 *             Append ,qAO,login
460 		 *         Skip to "All packets with q constructs"
461 		 *     }
462 		 */
463 		if (c->validated && (c->flags & CLFLAGS_CLIENTONLY) && !originated_by_client) {
464 			// fprintf(stderr, "\tvalidated client sends sends packet originated by someone else\n");
465 			/* if a q construct exists in the packet */
466 			int add_qAO = 1;
467 			if (q_proto) {
468 				// fprintf(stderr, "\thas q construct\n");
469 				/* if the q construct equals ,qAR,callsignssid or ,qAr,callsignssid */
470 				if (q_type == 'R' || q_type == 'r') {
471 					/* Replace qAR with qAo */
472 					*(q_start + 3) = q_type = 'o';
473 					// fprintf(stderr, "\treplaced qAR with qAo\n");
474 				} else if (q_type == 'S') {
475 					/* Replace qAS with qAO */
476 					*(q_start + 3) = q_type = 'O';
477 					// fprintf(stderr, "\treplaced qAS with qAO\n");
478 				} else if (q_type == 'C') {
479 					// FIXME: should also check callsignssid to servercall & login
480 					/* Replace qAC with qAO */
481 					*(q_start + 3) = q_type = 'O';
482 					// fprintf(stderr, "\treplaced qAC with qAO\n");
483 				} else {
484 					// What? Dunno.
485 				}
486 				/* Not going to modify the construct, update pointer to it */
487 				*new_q_start = q_start + 1;
488 				add_qAO = 0;
489 			} else if (pathlen > 2 && *(*path_end -1) == 'I' && *(*path_end -2) == ',') {
490 				hlog_packet(LOG_DEBUG, pdata, pathlen, "path has ,I in the end: ");
491 				/* the path is terminated with ,I - lookup previous callsign in path */
492 				char *p = *path_end - 3;
493 				while (p > via_start && *p != ',')
494 					p--;
495 				if (*p == ',') {
496 					const char *prevcall = p+1;
497 					const char *prevcall_end = *path_end - 2;
498 					//hlog(LOG_DEBUG, "previous callsign is %.*s", (int)(prevcall_end - prevcall), prevcall);
499 					/* if the path is terminated with ,login,I */
500 					// TODO: Should validate that prevcall is a nice callsign
501 					if (1) {
502 						/* Replace ,login,I with qAo,previouscall */
503 						*path_end = p;
504 						new_q_len = snprintf(new_q, new_q_size, ",q%co,%.*s",
505 							q_protocol_id, (int)(prevcall_end - prevcall), prevcall);
506 						q_proto = q_protocol_id;
507 						q_type = 'o';
508 						add_qAO = 0;
509 					}
510 				}
511 			}
512 
513 			if (add_qAO) {
514 				/* Append ,qAO,login */
515 				new_q_len = snprintf(new_q, new_q_size, ",q%cO,%s", q_protocol_id, c->username);
516 				q_proto = q_protocol_id;
517 				q_type = 'O';
518 			}
519 
520 			/* Skip to "All packets with q constructs" */
521 			return q_dropcheck(c, pdata, new_q, new_q_size, via_start, new_q_len, q_proto, q_type, q_start, q_replace, *path_end);
522 		}
523 
524 		/*
525 		 * If a q construct exists in the header:
526 		 *	Skip to "All packets with q constructs"
527 		 */
528 		if (q_proto) {
529 			// fprintf(stderr, "\texisting q construct\n");
530 			/* Not going to modify the construct, update pointer to it */
531 			*new_q_start = q_start + 1;
532 			/* Skip to "All packets with q constructs" */
533 			return q_dropcheck(c, pdata, new_q, new_q_size, via_start, new_q_len, q_proto, q_type, q_start, q_replace, *path_end);
534 		}
535 
536 		/* At this point we have packets which do not have Q constructs, and
537 		 * are either (from validated clients && originated by client)
538 		 * or (from unvalidated client && not originated by the client)
539 		 */
540 
541 		/*
542 		 * If header is terminated with ,I:
543 		 * {
544 		 *	If the VIACALL preceding the ,I matches the login:
545 		 *		Change from ,VIACALL,I to ,qAR,VIACALL
546 		 *	Else
547 		 *		Change from ,VIACALL,I to ,qAr,VIACALL
548 		 * }
549 		 * Else If the FROMCALL matches the login:
550 		 * {
551 		 *	Append ,qAC,SERVERLOGIN
552 		 *	Quit q processing
553 		 * }
554 		 * Else
555 		 *	Append ,qAS,login
556 		 * Skip to "All packets with q constructs"
557 		 */
558 		if (pathlen > 2 && *(*path_end -1) == 'I' && *(*path_end -2) == ',') {
559 			// fprintf(stderr, "\tpath has ,I in the end\n");
560 			/* the path is terminated with ,I - lookup previous callsign in path */
561 			char *p = *path_end - 3;
562 			while (p > via_start && *p != ',')
563 				p--;
564 			if (*p == ',') {
565 				const char *prevcall = p+1;
566 				const char *prevcall_end = *path_end - 2;
567 				// fprintf(stderr, "\tprevious callsign is %.*s\n", prevcall_end - prevcall, prevcall);
568 				/* if the path is terminated with ,login,I */
569 				if (strlen(c->username) == prevcall_end - prevcall && strncasecmp(c->username, prevcall, prevcall_end - prevcall) == 0) {
570 					/* Replace ,login,I with qAR,login */
571 					*path_end = p;
572 					new_q_len = snprintf(new_q, new_q_size, ",q%cR,%s", q_protocol_id, c->username);
573 					q_proto = q_protocol_id;
574 					q_type = 'R';
575 				} else {
576 					/* Replace ,VIACALL,I with qAr,VIACALL */
577 					*path_end = p;
578 					new_q_len = snprintf(new_q, new_q_size, ",q%cr,%.*s", q_protocol_id, (int)(prevcall_end - prevcall), prevcall);
579 					q_proto = q_protocol_id;
580 					q_type = 'r';
581 				}
582 			} else {
583 				/* Undefined by the algorithm - there was no VIACALL */
584 				return INERR_Q_I_NO_VIACALL;
585 			}
586 		} else if (originated_by_client) {
587 			/* FROMCALL matches the login */
588 			/* Add TCPIP* in the end of the path only if it's not there already */
589 			if (pathlen > 7 && strncmp(*path_end-7, ",TCPIP*", 7) == 0)
590 				return snprintf(new_q, new_q_size, ",qAC,%s", serverid);
591 			else
592 				return snprintf(new_q, new_q_size, ",TCPIP*,qAC,%s", serverid);
593 		} else {
594 			/* Append ,qAS,login */
595 			new_q_len = snprintf(new_q, new_q_size, ",q%cS,%s", q_protocol_id, c->username);
596 			q_proto = q_protocol_id;
597 			q_type = 'S';
598 		}
599 		/* Skip to "All packets with q constructs" */
600 		return q_dropcheck(c, pdata, new_q, new_q_size, via_start, new_q_len, q_proto, q_type, q_start, q_replace, *path_end);
601 	}
602 
603 	/*
604 	 * If packet entered the server from an outbound connection (to
605 	 * another server's port 1313, for instance) and no q construct
606 	 * exists in the header:
607 	 * {
608 	 *	If header is terminated with ,I:
609 	 *		Change from ,VIACALL,I to ,qAr,VIACALL
610 	 *	Else
611 	 *		Append ,qAS,IPADDR (IPADDR is an 8 character hex
612 	 *		representation of the IP address of the remote server)
613 	 * }
614 	 * Untested at the time of implementation (no uplink support yet)
615 	 */
616 
617 	if (!q_proto && (c->flags & CLFLAGS_UPLINKPORT)) {
618 		if (pathlen > 2 && *(*path_end -1) == 'I' && *(*path_end -2) == ',') {
619 			// fprintf(stderr, "\tpath has ,I in the end\n");
620 			/* the path is terminated with ,I - lookup previous callsign in path */
621 			char *p = *path_end - 3;
622 			while (p > via_start && *p != ',')
623 				p--;
624 			if (*p == ',') {
625 				const char *prevcall = p+1;
626 				const char *prevcall_end = *path_end - 2;
627 				// fprintf(stderr, "\tprevious callsign is %.*s\n", prevcall_end - prevcall, prevcall);
628 				/* Replace ,VIACALL,I with qAr,VIACALL */
629 				*path_end = p;
630 				new_q_len = snprintf(new_q, new_q_size, ",q%cr,%.*s", q_protocol_id, (int)(prevcall_end - prevcall), prevcall);
631 				q_proto = q_protocol_id;
632 				q_type = 'r';
633 			} else {
634 				/* Undefined by the algorithm - there was no VIACALL */
635 				return INERR_Q_I_NO_VIACALL;
636 			}
637 		} else {
638 			/* Append ,qAS,IPADDR (IPADDR is an 8 character hex representation
639 			 * of the IP address of the remote server)
640 			 */
641 			new_q_len = snprintf(new_q, new_q_size, ",q%cS,%s", q_protocol_id, c->addr_hex);
642 			q_proto = q_protocol_id;
643 			q_type = 'S';
644 		}
645 	}
646 
647 	/* If we haven't generated a new Q construct, return a pointer to the existing one */
648 	if (!new_q_len) {
649 		if (q_start == NULL)
650 			hlog(LOG_ERR, "q: Did not find or generate a Q construct (from client %s fd %d): %s", c->username, c->fd, pdata);
651 		*new_q_start = q_start + 1;
652 	}
653 
654 	return q_dropcheck(c, pdata, new_q, new_q_size, via_start, new_q_len, q_proto, q_type, q_start, q_replace, *path_end);
655 }
656 
657