Route finger search through the cacheable /finger/<term> path
Build and Push Docker Image / build-and-push (push) Failing after 2m19s
Build and Push Docker Image / build-and-push (push) Failing after 2m19s
The search form POSTed to /finger, an uncacheable URL with no search term in it, so every lookup re-ran the finger command even for repeated searches of the same user. Redirect any query/POST search to the canonical /finger/<username> path (and submit the form via GET, with a JS fast-path straight to that URL) so repeated lookups are served from the nginx response cache. A bare /finger with no user still lists local system users.
This commit is contained in:
@@ -146,36 +146,31 @@ def index():
|
|||||||
@app.route('/finger', methods=['GET', 'POST'])
|
@app.route('/finger', methods=['GET', 'POST'])
|
||||||
@limiter.limit(lambda: app.config['RATELIMIT_FINGER'])
|
@limiter.limit(lambda: app.config['RATELIMIT_FINGER'])
|
||||||
def finger():
|
def finger():
|
||||||
"""Finger command route"""
|
"""Finger search form.
|
||||||
|
|
||||||
|
Any submitted username is redirected to the canonical
|
||||||
|
``/finger/<username>`` path so the lookup happens on a cacheable URL with
|
||||||
|
the search term in it — repeated searches for the same user are served from
|
||||||
|
the nginx response cache instead of re-running the finger command. A bare
|
||||||
|
``/finger`` with no user shows the local system users (no daemon enumeration
|
||||||
|
risk: it never takes external input).
|
||||||
|
"""
|
||||||
|
username = (request.args.get('user', '')
|
||||||
|
or request.args.get('username', '')
|
||||||
|
or request.form.get('username', '')).strip()
|
||||||
|
|
||||||
|
if username:
|
||||||
|
# Send the search to the cacheable canonical path.
|
||||||
|
return redirect(url_for('finger_direct', username=username))
|
||||||
|
|
||||||
|
# No user supplied: list logged-in system users.
|
||||||
result = None
|
result = None
|
||||||
error = None
|
error = None
|
||||||
username = request.args.get('user', '') or request.form.get('username', '')
|
success, output, is_error = run_finger_command(['finger'])
|
||||||
|
|
||||||
if request.method == 'POST' or username:
|
|
||||||
# Sanitize username input
|
|
||||||
if username:
|
|
||||||
# Allow alphanumeric characters, dots, hyphens, underscores, and @ symbol for email-style usernames
|
|
||||||
if not all(c.isalnum() or c in '.-_@' for c in username):
|
|
||||||
error = "Invalid username format. Only alphanumeric characters, dots, hyphens, underscores, and @ symbol are allowed."
|
|
||||||
else:
|
|
||||||
# Execute finger command with specific user
|
|
||||||
cmd = ['finger', username]
|
|
||||||
else:
|
|
||||||
# Execute finger command without user (show all logged in users)
|
|
||||||
cmd = ['finger']
|
|
||||||
|
|
||||||
if not error:
|
|
||||||
# Execute the command with robust encoding handling
|
|
||||||
success, output, is_error = run_finger_command(cmd)
|
|
||||||
if success:
|
if success:
|
||||||
result = output
|
result = output
|
||||||
else:
|
else:
|
||||||
error = output
|
error = output
|
||||||
app.logger.warning("finger lookup failed for %r from %s: %s",
|
|
||||||
username, get_remote_address(), output.strip()[:200])
|
|
||||||
elif username:
|
|
||||||
app.logger.warning("rejected invalid finger username %r from %s",
|
|
||||||
username, get_remote_address())
|
|
||||||
|
|
||||||
return render_template('finger.html', title='Finger', result=result, error=error, username=username)
|
return render_template('finger.html', title='Finger', result=result, error=error, username=username)
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -5,7 +5,10 @@
|
|||||||
<div class="col-lg-10 mx-auto">
|
<div class="col-lg-10 mx-auto">
|
||||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
||||||
<h1 class="h5 mb-0">🔍 Finger Command</h1>
|
<h1 class="h5 mb-0">🔍 Finger Command</h1>
|
||||||
<form method="POST" action="{{ url_for('finger') }}" class="d-flex" role="search">
|
<!-- GET so the search lands on a cacheable URL; JS sends it straight
|
||||||
|
to /finger/<term>, and without JS the server redirects there. -->
|
||||||
|
<form method="GET" action="{{ url_for('finger') }}" class="d-flex" role="search"
|
||||||
|
onsubmit="return gotoFinger(event)">
|
||||||
<input type="text" class="form-control form-control-sm me-2" id="username" name="username"
|
<input type="text" class="form-control form-control-sm me-2" id="username" name="username"
|
||||||
value="{{ username or '' }}"
|
value="{{ username or '' }}"
|
||||||
placeholder="[email protected]"
|
placeholder="[email protected]"
|
||||||
@@ -50,6 +53,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// Navigate straight to the cacheable /finger/<term> path so the nginx response
|
||||||
|
// cache is hit for repeated lookups (avoids a redirect round-trip).
|
||||||
|
function gotoFinger(event) {
|
||||||
|
var term = document.getElementById('username').value.trim();
|
||||||
|
if (!term) { return true; } // empty -> let GET /finger list system users
|
||||||
|
event.preventDefault();
|
||||||
|
window.location.href = "{{ url_for('finger') }}/" + encodeURIComponent(term);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function clearForm() {
|
function clearForm() {
|
||||||
document.getElementById('username').value = '';
|
document.getElementById('username').value = '';
|
||||||
// Optionally reload the page to clear results
|
// Optionally reload the page to clear results
|
||||||
|
|||||||
Reference in New Issue
Block a user