diff --git a/src/Makefile.in b/src/Makefile.in index 44d44b1..61b779a 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -102,6 +102,7 @@ WEBFILES = \ src/web/excanvas.min.js src/web/jquery.flot.min.js testinstall: + mkdir -p ../tests/web cp -p $(subst src/,,$(WEBFILES)) ../tests/web/ # -------------------------------------------------------------------- # diff --git a/src/VERSION b/src/VERSION index 3eefcb9..ee90284 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.0.0 +1.0.4 diff --git a/src/incoming.c b/src/incoming.c index 7ce861d..1de005d 100644 --- a/src/incoming.c +++ b/src/incoming.c @@ -36,6 +36,9 @@ #include "cellmalloc.h" #include "messaging.h" +/* When adding labels here, remember to add the description strings in + * web/aprsc.js rx_err_strings + */ const char *inerr_labels[] = { "unknown", "no_colon", @@ -47,6 +50,7 @@ const char *inerr_labels[] = { "disallow_unverified", "path_nogate", "3rd_party", + "general_query", "aprsc_oom_pbuf", "aprsc_class_fail", "aprsc_q_bug", @@ -733,6 +737,12 @@ int incoming_parse(struct worker_t *self, struct client_t *c, char *s, int len) return incoming_server_message(self, c, pb); } + /* check for general queries - those cause reply floods and need + * to be dropped + */ + if (pb->packettype & T_QUERY) + return INERR_GENERAL_QUERY; + /* Filter preprocessing before sending this to dupefilter.. */ filter_preprocess_dupefilter(pb); diff --git a/src/login.c b/src/login.c index ec01998..d05613b 100644 --- a/src/login.c +++ b/src/login.c @@ -22,6 +22,12 @@ #include "clientlist.h" #include "parse_qc.h" +/* a static list of usernames which are not allowed to log in */ +static const char *disallow_login_usernames[] = { + "pass", /* a sign of "user pass -1" login with no configured username */ + NULL +}; + /* * Parse the login string in a HTTP POST or UDP submit packet * Argh, why are these not in standard POST parameters in HTTP? @@ -32,6 +38,7 @@ int http_udp_upload_login(const char *addr_rem, char *s, char **username) int argc; char *argv[256]; int i; + int username_len; /* parse to arguments */ if ((argc = parse_args_noshell(argv, s)) == 0) @@ -48,8 +55,26 @@ int http_udp_upload_login(const char *addr_rem, char *s, char **username) } *username = argv[1]; - if (strlen(*username) > 9) /* limit length */ - *username[9] = 0; + username_len = strlen(*username); + /* limit username length */ + if (username_len > CALLSIGNLEN_MAX) { + hlog(LOG_WARNING, "%s: HTTP POST: Invalid login string, too long 'user' username: '%s'", addr_rem, *username); + return -1; + } + + /* check the username against a static list of disallowed usernames */ + for (i = 0; (disallow_login_usernames[i]); i++) { + if (strcasecmp(*username, disallow_login_usernames[i]) == 0) { + hlog(LOG_WARNING, "%s: HTTP POST: Login by user '%s' not allowed", addr_rem, *username); + return -1; + } + } + + /* make sure the callsign is OK on the APRS-IS */ + if (check_invalid_q_callsign(*username, username_len)) { + hlog(LOG_WARNING, "%s: HTTP POST: Invalid login string, invalid 'user': '%s'", addr_rem, *username); + return -1; + } int given_passcode = -1; int validated = 0; @@ -133,6 +158,15 @@ int login_handler(struct worker_t *self, struct client_t *c, int l4proto, char * #endif c->username_len = strlen(c->username); + /* check the username against a static list of disallowed usernames */ + for (i = 0; (disallow_login_usernames[i]); i++) { + if (strcasecmp(c->username, disallow_login_usernames[i]) == 0) { + hlog(LOG_WARNING, "%s: Login by user '%s' not allowed", c->addr_rem, c->username); + rc = client_printf(self, c, "# Login by user not allowed\r\n"); + goto failed_login; + } + } + /* make sure the callsign is OK on the APRS-IS */ if (check_invalid_q_callsign(c->username, c->username_len)) { hlog(LOG_WARNING, "%s: Invalid login string, invalid 'user': '%s'", c->addr_rem, c->username); diff --git a/src/parse_aprs.c b/src/parse_aprs.c index 273351a..e38a42f 100644 --- a/src/parse_aprs.c +++ b/src/parse_aprs.c @@ -1127,6 +1127,8 @@ int parse_aprs(struct pbuf_t *pb) /* It might not be a bright idea to mark all messages starting with ? * queries instead of messages and making them NOT match the * filter message. + * ALSO: General (non-directed) queries are DROPPED by aprsc. + * Do not mark DIRECTED QUERIES as queries - we don't want to drop them. if (body[9] == ':' && body[10] == '?') { pb->packettype &= ~T_MESSAGE; pb->packettype |= T_QUERY; diff --git a/src/uplink.c b/src/uplink.c index 48c913f..50299d7 100644 --- a/src/uplink.c +++ b/src/uplink.c @@ -149,37 +149,37 @@ int uplink_logresp_handler(struct worker_t *self, struct client_t *c, int l4prot char *e = s + len; *e = 0; if ((argc = parse_args_noshell(argv, s)) == 0 || *argv[0] != '#') { - hlog(LOG_ERR, "%s: Uplink's logresp message is not recognized: no # in beginning", c->addr_rem); + hlog(LOG_ERR, "%s: Uplink's logresp message is not recognized: no # in beginning (protocol incompatibility)", c->addr_rem); client_close(self, c, -3); return 0; } if (argc < 6) { - hlog(LOG_ERR, "%s: Uplink's logresp message does not have enough arguments", c->addr_rem); + hlog(LOG_ERR, "%s: Uplink's logresp message does not have enough arguments (protocol incompatibility)", c->addr_rem); client_close(self, c, -3); return 0; } if (strcmp(argv[1], "logresp") != 0) { - hlog(LOG_ERR, "%s: Uplink's logresp message does not say 'logresp'", c->addr_rem); + hlog(LOG_ERR, "%s: Uplink's logresp message does not say 'logresp' (protocol incompatibility)", c->addr_rem); client_close(self, c, -3); return 0; } if (strcmp(argv[2], serverid) != 0) { - hlog(LOG_ERR, "%s: Uplink's logresp message does not have my callsign '%s' on it", c->addr_rem, serverid); + hlog(LOG_ERR, "%s: Uplink's logresp message does not have my callsign '%s' on it (protocol incompatibility)", c->addr_rem, serverid); client_close(self, c, -3); return 0; } if (strcmp(argv[3], "verified,") != 0) { - hlog(LOG_ERR, "%s: Uplink's logresp message does not say I'm verified", c->addr_rem); + hlog(LOG_ERR, "%s: Uplink's logresp message does not say I'm verified (wrong passcode in my configuration?)", c->addr_rem); client_close(self, c, -3); return 0; } if (strcmp(argv[4], "server") != 0) { - hlog(LOG_ERR, "%s: Uplink's logresp message does not contain 'server'", c->addr_rem); + hlog(LOG_ERR, "%s: Uplink's logresp message does not contain 'server' (protocol incompatibility)", c->addr_rem); client_close(self, c, -3); return 0; } diff --git a/src/version.h b/src/version.h index ec40658..fb7e2d5 100644 --- a/src/version.h +++ b/src/version.h @@ -12,7 +12,7 @@ * If you're making modifications, put your own variant version * identification in version_branch.h. Thanks! */ -#define VERSION "1.0.0" +#define VERSION "1.0.4" #define APRSC_TOCALL "APSC10" diff --git a/src/web/aprsc.css b/src/web/aprsc.css index f0cbf8a..6221586 100644 --- a/src/web/aprsc.css +++ b/src/web/aprsc.css @@ -7,6 +7,8 @@ body { .grey { color: #666; } .bold { font-weight: bold; } .red { color: #ff5370; } +.rxerr { color: #0000ff; cursor: pointer; } +.rxerr_red { color: #ff5370; cursor: pointer; } div.page_title { color: #000; @@ -65,3 +67,20 @@ div.msg_e, div.msg_i, div.msg_s { width: 450px; } +/* tooltip */ + +div.ttip { + position: absolute; + left: 100px; + top: 100px; + background: #ffffff; + font-size: 11px; + width: 300px; + z-index: 80; + border: 1px solid #ff0000; + padding: 7px; + border-radius: 5px; + box-shadow: 8px 8px 3px rgba(0,0,0,0.5); + display: none; +} + diff --git a/src/web/aprsc.js b/src/web/aprsc.js index d03968f..1144fcf 100644 --- a/src/web/aprsc.js +++ b/src/web/aprsc.js @@ -16,10 +16,27 @@ function isUndefined(v) return v === undef; } +function cancel_events(e) +{ + if (!e) + if (window.event) e = window.event; + else + return; + + if (e.cancelBubble != null) e.cancelBubble = true; + if (e.stopPropagation) e.stopPropagation(); + if (e.preventDefault) e.preventDefault(); + if (window.event) e.returnValue = false; + if (e.cancel != null) e.cancel = true; +} + function server_status_host(s) { var h = s['addr_rem']; - return h.substr(0, h.lastIndexOf(':')) + ':14501'; + var p = h.lastIndexOf(':'); + if (h.lastIndexOf(']') > p || p == -1) + return h + ':14501'; + return h.substr(0, p) + ':14501'; } function addr_loc_port(s) @@ -70,8 +87,10 @@ function client_pkts_rx(c, k) var s = c['pkts_rx'] + '/' + c['pkts_ign']; - if (c['pkts_ign'] / c['pkts_rx'] > 0.1) - return '' + s + ''; + if (c['pkts_ign'] > 0) + return '' + s + ''; return s; } @@ -141,6 +160,26 @@ function conv_none(s) var listeners_table, uplinks_table, peers_table, clients_table, memory_table, dupecheck_table, totals_table; +var rx_err_strings = { + "unknown": 'Unknown error', + "no_colon": 'No colon (":") in packet', + "no_dst": 'No ">" in packet to mark beginning of destination callsign', + "no_path": 'No path found between source callsign and ":"', + "long_srccall": 'Source callsign too long', + "no_body": 'No packet body/data after ":"', + "long_dstcall": 'Destination callsign too long', + "disallow_unverified": 'Packet from unverified client', + "path_nogate": 'Packet with NOGATE/RFONLY in path', + "3rd_party": '3rd-party packet', + "general_query": 'General query', + "aprsc_oom_pbuf": 'aprsc out of packet buffers', + "aprsc_class_fail": 'aprsc failed to classify packet', + "aprsc_q_bug": 'aprsc Q construct processing failed', + "q_drop": 'Q construct algorithm dropped packet', + "short_packet": 'Packet too short', + "long_packet": 'Packet too long' +}; + var key_translate = { // server block 'server_id': 'Server ID', @@ -197,7 +236,7 @@ var val_convert = { 't_connect': timestr, 'since_connect': dur_str, 'since_last_read': dur_str, - 'addr_rem': conv_none, + 'addr_rem_shown': conv_none, 'username': username_link, 'addr_loc': addr_loc_port, 'verified': conv_verified @@ -221,7 +260,7 @@ var listener_cols = { var uplink_cols = { 'username': 'Server ID', - 'addr_rem': 'Address', + 'addr_rem_shown': 'Address', 'mode': 'Mode', 't_connect': 'Connected', 'since_connect': 'Up', @@ -236,7 +275,7 @@ var uplink_cols = { }; var peer_cols = { - 'addr_rem': 'Address', + 'addr_rem_shown': 'Address', 'since_last_read': 'Last in', 'pkts_tx': 'Packets Tx', 'pkts_rx': 'Packets Rx', @@ -249,7 +288,7 @@ var peer_cols = { var client_cols = { 'addr_loc': 'Port', 'username': 'Callsign', - 'addr_rem': 'Address', + 'addr_rem_shown': 'Address', 'verified': 'Verified', 'since_connect': 'Up', 'since_last_read': 'Last in', @@ -264,12 +303,114 @@ var client_cols = { 'filter': 'Filter' }; +/* applications which typically have a port 14501 status port - can be linked */ var linkable = { 'aprsc': 1, 'aprsd': 1, 'javAPRSSrvr': 1 }; +/* clients per fd, to support onclick actions within client/uplink/peer tables */ +var fd_clients = {}; +var rx_err_codes = []; /* an array of rx err field codes */ + +/* tooltip action for rx errors counters */ +function event_click_coordinates(e) +{ + var posx = 0; + var posy = 0; + + if (e.pageX || e.pageY) { + posx = e.pageX; + posy = e.pageY; + } else if (e.clientX || e.clientY) { + posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; + }/* else { + alert("event_click_coordinates failed!"); + }*/ + + return [ posx, posy ]; +} + +var ttip_inside_element; +function ttip(e, elem, fd) +{ + var co = event_click_coordinates(e); + + // make note of element we're in + ttip_inside_element = elem; + + //jsjam-keep: event_attach + //$(elem).on('mouseout', ttip_hide); + setTimeout(function() { ttip_show_maybe(elem, function() { return "contents"; }, co); }, 300); + //deb(" added listener"); +} + +function rx_err_contents(fd) +{ + if (isUndefined(fd_clients[fd])) + return 'No client on fd ' + fd; + + var er = fd_clients[fd]['rx_errs']; + + if (isUndefined(er)) + return 'No rx errors for client on fd ' + fd; + + var s = 'Received packets dropped: ' + fd_clients[fd]['pkts_ign'] + ' out of ' + fd_clients[fd]['pkts_rx'] + '
'; + + for (var i = 0; i < rx_err_codes.length; i++) { + if (er[i] < 1) + continue; + s += ((rx_err_strings[rx_err_codes[i]]) ? rx_err_strings[rx_err_codes[i]] : rx_err_codes[i]) + + ': ' + er[i] + '
'; + } + + return s; +} + +function rx_err_popup(e, fd) +{ + cancel_events(e); + + if (isUndefined(fd_clients[fd])) + return; + + var co = event_click_coordinates(e); + + ttip_show(function() { return rx_err_contents(fd); }, co); + + return false; +} + +function ttip_show_maybe(elem, cb, co) +{ + if (ttip_inside_element == elem) + ttip_show(cb, co); +} + +function ttip_show(cb, co) +{ + var element = $('#ttip'); + element.hide(); + + var ttip_size = 300; + if (co[0] > ttip_size + 40) + co[0] -= ttip_size + 40; // position on left of event + else + co[0] += 40; // position on right of event + + element.html("" + cb() + ""); + element.css({ 'left': co[0] + 'px', 'top': co[1] + 'px'}).show('fast'); +} + +function ttip_hide() +{ + $('#ttip').hide('fast'); + ttip_inside_element = null; +} + +/* render a clients array (also peers, uplinks, and listeners) */ function render_clients(element, d, cols) { var s = ''; @@ -282,15 +423,21 @@ function render_clients(element, d, cols) s += ''; var c = d[ci]; + if (isUndefined(c['fd']) || c['fd'] < 0) + c['fd'] = Math.random() * -1000000; + + fd_clients[c['fd']] = c; + c['addr_rem_shown'] = c['addr_rem']; + if (c['udp_downstream']) { if (c['mode'] == 'peer') - c['addr_rem'] += ' UDP'; + c['addr_rem_shown'] += ' UDP'; else - c['addr_rem'] += ' +UDP'; + c['addr_rem_shown'] += ' +UDP'; } - if (linkable[c['app_name']]) - c['addr_rem'] = '' + htmlent(c['addr_rem']) + ''; + if (linkable[c['app_name']] || c['mode'] == 'peer') + c['addr_rem_shown'] = '' + htmlent(c['addr_rem_shown']) + ''; if (c['app_name'] && c['app_version']) c['show_app_name'] = c['app_name'] + ' ' + c['app_version']; @@ -471,6 +618,7 @@ function render(d) } t_now = d['server']['t_now']; + rx_err_codes = d['rx_errs']; if (d['dupecheck']) { var u = d['dupecheck']; @@ -487,9 +635,11 @@ function render(d) render_block('totals', totals_table, u); } + fd_clients = {}; + if (d['listeners']) render_clients(listeners_table, d['listeners'], listener_cols); - + if (d['uplinks'] && d['uplinks'].length > 0) { render_clients(uplinks_table, d['uplinks'], uplink_cols); $('#uplinks_d').show(); diff --git a/src/web/index.html b/src/web/index.html index fbc209c..46d6105 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -9,7 +9,7 @@ - +
aprsc status
@@ -44,12 +44,14 @@

Memory

- - - - - - + + + + + + + + diff --git a/src/worker.c b/src/worker.c index 149170e..40f360e 100644 --- a/src/worker.c +++ b/src/worker.c @@ -1762,6 +1762,8 @@ int worker_client_list(cJSON *workers, cJSON *clients, cJSON *uplinks, cJSON *pe "peer" }; char *mode; + char addr_s[80]; + char *s; int client_heard_count = 0; int client_courtesy_count = 0; @@ -1785,11 +1787,27 @@ int worker_client_list(cJSON *workers, cJSON *clients, cJSON *uplinks, cJSON *pe cJSON *jc = cJSON_CreateObject(); cJSON_AddNumberToObject(jc, "fd", c->fd); - cJSON_AddStringToObject(jc, "addr_rem", c->addr_rem); - cJSON_AddStringToObject(jc, "addr_loc", c->addr_loc); + + if (c->state == CSTATE_COREPEER) { + /* cut out ports in the name of security by obscurity */ + strncpy(addr_s, c->addr_rem, sizeof(addr_s)); + if ((s = strrchr(addr_s, ':'))) + *s = 0; + cJSON_AddStringToObject(jc, "addr_rem", addr_s); + strncpy(addr_s, c->addr_loc, sizeof(addr_s)); + if ((s = strrchr(addr_s, ':'))) + *s = 0; + cJSON_AddStringToObject(jc, "addr_loc", addr_s); + } else { + cJSON_AddStringToObject(jc, "addr_rem", c->addr_rem); + cJSON_AddStringToObject(jc, "addr_loc", c->addr_loc); + } + cJSON_AddStringToObject(jc, "addr_q", c->addr_hex); + if (c->udp_port && c->udpclient) cJSON_AddNumberToObject(jc, "udp_downstream", 1); + cJSON_AddNumberToObject(jc, "t_connect", c->connect_time); cJSON_AddNumberToObject(jc, "since_connect", tick - c->connect_time); cJSON_AddNumberToObject(jc, "since_last_read", tick - c->last_read); diff --git a/src/worker.h b/src/worker.h index 6d817d4..aeecbae 100644 --- a/src/worker.h +++ b/src/worker.h @@ -179,7 +179,7 @@ struct client_heard_t { /* error codes for incoming packet drop reasons */ #define INERR_UNKNOWN 0 #define INERR_NO_COLON -1 /* no : in packet */ -#define INERR_NO_DST -2 /* no > in packet to mark end of dstcall */ +#define INERR_NO_DST -2 /* no > in packet to mark beginning of dstcall */ #define INERR_NO_PATH -3 /* no path found between srccall and : */ #define INERR_LONG_SRCCALL -4 /* too long srccall */ #define INERR_NO_BODY -5 /* no packet body/data after : */ @@ -187,13 +187,14 @@ struct client_heard_t { #define INERR_DISALLOW_UNVERIFIED -7 /* disallow_unverified = 1, unverified client */ #define INERR_NOGATE -8 /* packet path has NOGATE/RFONLY */ #define INERR_3RD_PARTY -9 /* 3rd-party packet dropped */ -#define INERR_OUT_OF_PBUFS -10 /* aprsc ran out of packet buffers */ -#define INERR_CLASS_FAIL -11 /* aprsc failed to classify packet */ -#define INERR_Q_BUG -12 /* aprsc q construct code bugging */ -#define INERR_Q_DROP -13 /* q construct drop */ -#define INERR_SHORT_PACKET -14 /* too short packet */ -#define INERR_LONG_PACKET -15 /* too long packet */ -#define INERR_MIN -15 /* MINIMUM VALUE FOR INERR, GROW WHEN NEEDED! */ +#define INERR_GENERAL_QUERY -10 /* general query ?APRS? dropped */ +#define INERR_OUT_OF_PBUFS -11 /* aprsc ran out of packet buffers */ +#define INERR_CLASS_FAIL -12 /* aprsc failed to classify packet */ +#define INERR_Q_BUG -13 /* aprsc q construct code bugging */ +#define INERR_Q_DROP -14 /* q construct drop */ +#define INERR_SHORT_PACKET -15 /* too short packet */ +#define INERR_LONG_PACKET -16 /* too long packet */ +#define INERR_MIN -16 /* MINIMUM VALUE FOR INERR, GROW WHEN NEEDED! */ /* WHEN ADDING STUFF HERE, REMEMBER TO UPDATE inerr_labels IN incoming.c. Thanks! */ #define INERR_BUCKETS (INERR_MIN*-1 + 1) diff --git a/tests/t/10dupecheck.t b/tests/t/10dupecheck.t index 6a72fe9..051459a 100644 --- a/tests/t/10dupecheck.t +++ b/tests/t/10dupecheck.t @@ -3,7 +3,7 @@ # use Test; -BEGIN { plan tests => 17 }; +BEGIN { plan tests => 18 }; use runproduct; use istest; use Ham::APRS::IS; @@ -70,12 +70,17 @@ istest::should_drop(\&ok, $i_tx, $i_rx, "SRC>DST,$login,I:$data", # should drop "SRC>DST:dummy", 1); # will pass (helper packet) -# 13: send the same packet with additional whitespace in the end, should pass umodified +# 13: send the same packet with a different dstcall SSID and see that it is dropped +istest::should_drop(\&ok, $i_tx, $i_rx, + "SRC>DST-2,$login,I:$data", # should drop + "SRC>DST:dummy2", 1); # will pass (helper packet) + +# 14: send the same packet with additional whitespace in the end, should pass umodified istest::txrx(\&ok, $i_tx, $i_rx, "SRC>DST,$login,I:$data ", "SRC>DST,qAR,$login:$data "); -# 14: send the same packet with a different destination call, should pass +# 15: send the same packet with a different destination call, should pass istest::txrx(\&ok, $i_tx, $i_rx, "SRC>DST2,DIGI1*,qAR,$login:$data", "SRC>DST2,DIGI1*,qAR,$login:$data"); diff --git a/tests/t/11misc-drops.t b/tests/t/11misc-drops.t index 8645224..e02aff4 100644 --- a/tests/t/11misc-drops.t +++ b/tests/t/11misc-drops.t @@ -43,7 +43,10 @@ my @pkts = ( "SRC>APRS,NOGATE,qAR,$login:>should drop, NOGATE", "SRC>APRS,RFONLY,qAR,$login:>should drop, RFONLY", "SRC>DST,DIGI,qAR,$login:}SRC2>DST,DIGI,TCPIP*:>should drop, 3rd party", - "SRC>DST,DIGI,qAR,$login:}blah, 3rd party ID only" + "SRC>DST,DIGI,qAR,$login:}blah, 3rd party ID only", + "SRC>DST,DIGI,qAR,$login:?APRS? general query", + "SRC>DST,DIGI,qAR,$login:?WX? general query", + "SRC>DST,DIGI,qAR,$login:?FOOBAR? general query" ); # send the packets