Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3b563228f | ||
|
|
4e375b130c | ||
|
|
24d23012c5 | ||
|
|
edef3c3303 | ||
|
|
b3a4c05001 |
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## 1.1.0 - 2026-09-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added optional post-update BIND journal synchronization through `rndc sync -clean`.
|
||||||
|
- Added `DYNDNS_RNDC_SERVER`, `DYNDNS_RNDC_KEY`, and `DYNDNS_RNDC_SYNC_CLEAN` configuration parameters.
|
||||||
|
- Added validation for the RNDC synchronization switch and required RNDC settings when synchronization is enabled.
|
||||||
|
- Added conditional runtime validation for the `rndc` command when post-update journal synchronization is enabled.
|
||||||
|
- Added `nsupdate` diagnostic output to the dynDNS log and preservation of failed update instructions for troubleshooting.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Separated RFC 2136 update settings from BIND RNDC control settings.
|
||||||
|
- Updated dynamic update processing to target the authoritative DNS listener instead of a non-authoritative localhost resolver view.
|
||||||
|
- Changed the post-update workflow to synchronize accepted dynamic updates into the primary zone file and clean BIND journal files when explicitly enabled.
|
||||||
|
- Made RNDC synchronization failures non-fatal after a successful `nsupdate` transaction, while retaining a warning in the dynDNS log.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed unsafe DNSSEC handling that attempted to delete the zone `DNSKEY` RRset before dynamic updates.
|
||||||
|
- Removed the unnecessary `rndc freeze` and `rndc thaw` workflow after RFC 2136 updates.
|
||||||
|
|
||||||
## 1.0.0 - 2026-06-20
|
## 1.0.0 - 2026-06-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ This repository contains only the backend components of the dynDNS service: the
|
|||||||
|
|
||||||
1. Clone the repository.
|
1. Clone the repository.
|
||||||
2. Copy `vars.example` to a site-specific vars file.
|
2. Copy `vars.example` to a site-specific vars file.
|
||||||
3. Adjust paths, database settings, BIND zone settings, and TSIG key references.
|
3. Adjust paths, database settings, BIND zone settings and TSIG key references.
|
||||||
4. Make `dyndns.sh` executable.
|
4. Make `dyndns.sh` executable.
|
||||||
5. Run the script manually once before automation.
|
5. Run the script manually once before automation.
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ GRANT ALL PRIVILEGES ON dyndns.* TO 'dyndnsuser'@'127.0.0.1';
|
|||||||
FLUSH PRIVILEGES;
|
FLUSH PRIVILEGES;
|
||||||
```
|
```
|
||||||
|
|
||||||
This setup allows local access for the dedicated dynDNS user from `localhost`, `127.0.0.1`, and `::1`, and keeps the dynDNS backend user restricted to the `dyndns` database and local connections only.
|
This setup allows local access for the dedicated dynDNS user from `localhost`, `127.0.0.1` and `::1` and keeps the dynDNS backend user restricted to the `dyndns` database and local connections only.
|
||||||
|
|
||||||
After database initialization, adjust the matching settings in your vars file:
|
After database initialization, adjust the matching settings in your vars file:
|
||||||
|
|
||||||
@@ -262,8 +262,8 @@ See `LICENSE` for details.
|
|||||||
|
|
||||||
CB-601 - the open tec Elevator
|
CB-601 - the open tec Elevator
|
||||||
|
|
||||||
- Stephan Düsterhaupt ([XMPP](xmpp:me@jabber.stephanduesterhaupt.de))
|
- [Stephan Düsterhaupt](xmpp:me@jabber.stephanduesterhaupt.de)
|
||||||
- Ivo Noack aka Insonic ([XMPP](xmpp:me@jabber.ivonoack.de))
|
- [Ivo Noack](xmpp:me@jabber.ivonoack.de) aka Insonic
|
||||||
|
|
||||||
## Project Home
|
## Project Home
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,12 @@
|
|||||||
# - Validate database connectivity before command execution
|
# - Validate database connectivity before command execution
|
||||||
# - POSIX-compatible operation for cron and service integration
|
# - POSIX-compatible operation for cron and service integration
|
||||||
|
|
||||||
# Authors: Stephan Düsterhaupt
|
# Authors: Stephan Düsterhaupt, Ivo Noack aka Insonic
|
||||||
# Copyright (c) 2018-2026 CB-601 - the open tec Elevator
|
# Copyright (c) 2018-2026 CB-601 - the open tec Elevator
|
||||||
# License: MIT
|
# License: MIT
|
||||||
|
#
|
||||||
|
# Project Home: https://dev.town-square.de/cb601/dyndns
|
||||||
|
#
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
@@ -238,6 +241,19 @@ _vars_source_check() {
|
|||||||
[ -n "$DYNDNS_BIND_ZONE_DIR" ] || _die "DYNDNS_BIND_ZONE_DIR env-var undefined" 5
|
[ -n "$DYNDNS_BIND_ZONE_DIR" ] || _die "DYNDNS_BIND_ZONE_DIR env-var undefined" 5
|
||||||
[ -n "$DYNDNS_BIND_ZONE_FILE" ] || _die "DYNDNS_BIND_ZONE_FILE env-var undefined" 5
|
[ -n "$DYNDNS_BIND_ZONE_FILE" ] || _die "DYNDNS_BIND_ZONE_FILE env-var undefined" 5
|
||||||
|
|
||||||
|
case "$DYNDNS_RNDC_SYNC_CLEAN" in
|
||||||
|
0|1)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
_die "DYNDNS_RNDC_SYNC_CLEAN must be either 0 or 1" 5
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$DYNDNS_RNDC_SYNC_CLEAN" = "1" ]; then
|
||||||
|
[ -n "$DYNDNS_RNDC_SERVER" ] || _die "DYNDNS_RNDC_SERVER env-var undefined" 5
|
||||||
|
[ -n "$DYNDNS_RNDC_KEY" ] || _die "DYNDNS_RNDC_KEY env-var undefined" 5
|
||||||
|
fi
|
||||||
|
|
||||||
_log "Leave the function '_vars_source_check()'..." 1
|
_log "Leave the function '_vars_source_check()'..." 1
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -262,12 +278,15 @@ _verify_runtime_init() {
|
|||||||
|| command -v "$DYNDNS_DB_PROGRAM" >/dev/null 2>&1 \
|
|| command -v "$DYNDNS_DB_PROGRAM" >/dev/null 2>&1 \
|
||||||
|| _die "Database client '$DYNDNS_DB_PROGRAM' not available. $help_note" 5
|
|| _die "Database client '$DYNDNS_DB_PROGRAM' not available. $help_note" 5
|
||||||
|
|
||||||
command -v rndc >/dev/null 2>&1 || _die "rndc not available. $help_note" 5
|
|
||||||
command -v sed >/dev/null 2>&1 || _die "sed not available. $help_note" 5
|
command -v sed >/dev/null 2>&1 || _die "sed not available. $help_note" 5
|
||||||
command -v grep >/dev/null 2>&1 || _die "grep not available. $help_note" 5
|
command -v grep >/dev/null 2>&1 || _die "grep not available. $help_note" 5
|
||||||
command -v awk >/dev/null 2>&1 || _die "awk not available. $help_note" 5
|
command -v awk >/dev/null 2>&1 || _die "awk not available. $help_note" 5
|
||||||
command -v nsupdate >/dev/null 2>&1 || _die "nsupdate not available. $help_note" 5
|
command -v nsupdate >/dev/null 2>&1 || _die "nsupdate not available. $help_note" 5
|
||||||
|
|
||||||
|
if [ "$DYNDNS_RNDC_SYNC_CLEAN" = "1" ]; then
|
||||||
|
command -v rndc >/dev/null 2>&1 || _die "rndc not available. $help_note" 5
|
||||||
|
fi
|
||||||
|
|
||||||
_log "Leave the function '_verify_runtime_init()'..." 1
|
_log "Leave the function '_verify_runtime_init()'..." 1
|
||||||
return 0
|
return 0
|
||||||
} #=> _verify_runtime_init()
|
} #=> _verify_runtime_init()
|
||||||
@@ -368,6 +387,16 @@ _vars_setup() {
|
|||||||
set_var DYNDNS_BIND_ZONE_FILE_SIGNED "$DYNDNS_BIND_ZONE_DIR/$DYNDNS_BIND_ZONE.zone.signed"
|
set_var DYNDNS_BIND_ZONE_FILE_SIGNED "$DYNDNS_BIND_ZONE_DIR/$DYNDNS_BIND_ZONE.zone.signed"
|
||||||
set_var DYNDNS_BIND_ZONE_TTL 300
|
set_var DYNDNS_BIND_ZONE_TTL 300
|
||||||
|
|
||||||
|
# RNDC post-update zone synchronization defaults
|
||||||
|
#
|
||||||
|
# The RNDC control interface is separate from the authoritative DNS
|
||||||
|
# listener used by nsupdate. Keep the default disabled so existing
|
||||||
|
# installations do not gain a new administrative post-update action
|
||||||
|
# unless it is explicitly enabled in their vars file.
|
||||||
|
set_var DYNDNS_RNDC_SERVER "127.0.0.1"
|
||||||
|
set_var DYNDNS_RNDC_KEY ""
|
||||||
|
set_var DYNDNS_RNDC_SYNC_CLEAN 0
|
||||||
|
|
||||||
# Create a per-run temp directory name. The directory itself may be created
|
# Create a per-run temp directory name. The directory itself may be created
|
||||||
# lazily later by dyndns_mktemp().
|
# lazily later by dyndns_mktemp().
|
||||||
if [ -z "$DYNDNS_TEMP_DIR_session" ]; then
|
if [ -z "$DYNDNS_TEMP_DIR_session" ]; then
|
||||||
@@ -398,7 +427,7 @@ set_var() {
|
|||||||
|
|
||||||
|
|
||||||
# SDuesterhaupt: 2026-06-18 - Write a message to the dynDNS log file.
|
# SDuesterhaupt: 2026-06-18 - Write a message to the dynDNS log file.
|
||||||
|
#
|
||||||
# Severity levels:
|
# Severity levels:
|
||||||
# 0 -> OFF
|
# 0 -> OFF
|
||||||
# 1 -> DEBUG
|
# 1 -> DEBUG
|
||||||
@@ -406,7 +435,7 @@ set_var() {
|
|||||||
# 3 -> WARNING
|
# 3 -> WARNING
|
||||||
# 4 -> ERROR
|
# 4 -> ERROR
|
||||||
# 5 -> CRITICAL
|
# 5 -> CRITICAL
|
||||||
|
#
|
||||||
# @param1: Log message.
|
# @param1: Log message.
|
||||||
# @param2: Severity level.
|
# @param2: Severity level.
|
||||||
_log() {
|
_log() {
|
||||||
@@ -444,7 +473,7 @@ _log() {
|
|||||||
|
|
||||||
|
|
||||||
# SDuesterhaupt: 2026-06-18 - Check whether DYNDNS_MEMBER_HOSTNAME is a valid hostname
|
# SDuesterhaupt: 2026-06-18 - Check whether DYNDNS_MEMBER_HOSTNAME is a valid hostname
|
||||||
|
#
|
||||||
# Returns:
|
# Returns:
|
||||||
# 0 if valid
|
# 0 if valid
|
||||||
# 1 if invalid
|
# 1 if invalid
|
||||||
@@ -1294,26 +1323,11 @@ _zone_updates_apply() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
_log "Check the zone '$DYNDNS_BIND_ZONE' for DNSSEC capabilities..." 1
|
|
||||||
if [ -f "$DYNDNS_BIND_ZONE_FILE_SIGNED" ]; then
|
|
||||||
_log "The zone '$DYNDNS_BIND_ZONE' uses DNSSEC..." 1
|
|
||||||
zone_uses_dnssec=1
|
|
||||||
else
|
|
||||||
_log "The zone '$DYNDNS_BIND_ZONE' does not use DNSSEC..." 1
|
|
||||||
zone_uses_dnssec=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
{
|
{
|
||||||
printf 'debug\n'
|
printf 'debug\n'
|
||||||
printf 'server %s\n' "$DYNDNS_BIND_SERVER"
|
printf 'server %s\n' "$DYNDNS_BIND_SERVER"
|
||||||
printf 'zone %s.\n' "$DYNDNS_BIND_ZONE"
|
printf 'zone %s.\n' "$DYNDNS_BIND_ZONE"
|
||||||
|
|
||||||
if [ "$zone_uses_dnssec" -eq 1 ]; then
|
|
||||||
printf '; Move %s from secure to insecure temporarily\n' "$DYNDNS_BIND_ZONE_FILE"
|
|
||||||
printf 'update delete %s. DNSKEY\n' "$DYNDNS_BIND_ZONE"
|
|
||||||
printf 'send\n'
|
|
||||||
fi
|
|
||||||
|
|
||||||
awk -F '\t' -v zone_file="$DYNDNS_BIND_ZONE_FILE" '
|
awk -F '\t' -v zone_file="$DYNDNS_BIND_ZONE_FILE" '
|
||||||
$1 == "del" {
|
$1 == "del" {
|
||||||
del_owner[++del_count] = $2
|
del_owner[++del_count] = $2
|
||||||
@@ -1372,9 +1386,20 @@ _zone_updates_apply() {
|
|||||||
|
|
||||||
cp "$nsupdate_file" "$DYNDNS_LOG_UPDATE_FILE" 2>/dev/null || true
|
cp "$nsupdate_file" "$DYNDNS_LOG_UPDATE_FILE" 2>/dev/null || true
|
||||||
|
|
||||||
if ! nsupdate -k "$DYNDNS_BIND_ZONE_KEY" "$nsupdate_file" >/dev/null 2>&1; then
|
nsupdate_output=$(nsupdate -d -k "$DYNDNS_BIND_ZONE_KEY" "$nsupdate_file" 2>&1)
|
||||||
|
nsupdate_rc=$?
|
||||||
|
|
||||||
|
if [ "$nsupdate_rc" -ne 0 ]; then
|
||||||
|
{
|
||||||
|
printf '%s\n' "nsupdate output (exit code: $nsupdate_rc):"
|
||||||
|
printf '%s\n' "$nsupdate_output"
|
||||||
|
} >> "$DYNDNS_LOG_FILE"
|
||||||
|
|
||||||
|
cp "$nsupdate_file" \
|
||||||
|
"$DYNDNS_LOG_UPDATE_FILE.failed" 2>/dev/null || true
|
||||||
rm -f "$nsupdate_file"
|
rm -f "$nsupdate_file"
|
||||||
_log "Zone updates could not be applied." 4
|
|
||||||
|
_log "Zone updates could not be applied (nsupdate exit code: $nsupdate_rc)." 4
|
||||||
_log "Leave the function '_zone_updates_apply()'..." 1
|
_log "Leave the function '_zone_updates_apply()'..." 1
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -1394,20 +1419,22 @@ _zone_updates_apply() {
|
|||||||
rm -f "$nsupdate_file"
|
rm -f "$nsupdate_file"
|
||||||
_log "Zone updates were applied successfully." 2
|
_log "Zone updates were applied successfully." 2
|
||||||
|
|
||||||
if command -v rndc >/dev/null 2>&1; then
|
# Synchronize dynamic zone journal changes into the master zone file.
|
||||||
if rndc -s "$DYNDNS_BIND_SERVER" -k "$DYNDNS_BIND_ZONE_KEY" freeze "$DYNDNS_BIND_ZONE" >/dev/null 2>&1; then
|
#
|
||||||
_log "The zone '$DYNDNS_BIND_ZONE' was frozen." 2
|
# BIND keeps successful RFC 2136 updates in the zone journal until a zone dump
|
||||||
if rndc -s "$DYNDNS_BIND_SERVER" -k "$DYNDNS_BIND_ZONE_KEY" thaw "$DYNDNS_BIND_ZONE" >/dev/null 2>&1; then
|
# occurs. sync -clean writes the active journal state to the master zone file
|
||||||
_log "The zone '$DYNDNS_BIND_ZONE' was reloaded." 2
|
# and removes the journal after a successful synchronization.
|
||||||
|
#
|
||||||
|
# A synchronization failure is intentionally non-fatal: nsupdate has already
|
||||||
|
# succeeded and named keeps the authoritative runtime state in the journal.
|
||||||
|
if [ "$DYNDNS_RNDC_SYNC_CLEAN" = "1" ]; then
|
||||||
|
if rndc \
|
||||||
|
-s "$DYNDNS_RNDC_SERVER" \
|
||||||
|
-k "$DYNDNS_RNDC_KEY" \
|
||||||
|
sync -clean "$DYNDNS_BIND_ZONE" >/dev/null 2>&1; then
|
||||||
|
_log "The zone '$DYNDNS_BIND_ZONE' was synchronized and journals were cleaned." 2
|
||||||
else
|
else
|
||||||
_log "Couldn't reload the zone '$DYNDNS_BIND_ZONE'." 4
|
_log "Zone update succeeded, but rndc sync -clean failed for '$DYNDNS_BIND_ZONE'." 3
|
||||||
_log "Leave the function '_zone_updates_apply()'..." 1
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
_log "Couldn't freeze the zone '$DYNDNS_BIND_ZONE'." 4
|
|
||||||
_log "Leave the function '_zone_updates_apply()'..." 1
|
|
||||||
return 1
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+40
-4
@@ -69,7 +69,7 @@
|
|||||||
# Database login credentials.
|
# Database login credentials.
|
||||||
# These are mainly used when DYNDNS_SQL_GROUP is unset/empty.
|
# These are mainly used when DYNDNS_SQL_GROUP is unset/empty.
|
||||||
#set_var DYNDNS_SQL_USER "dyndns_user"
|
#set_var DYNDNS_SQL_USER "dyndns_user"
|
||||||
#set_var DYNDNS_SQL_PASS "strong_passphrase"
|
#set_var DYNDNS_SQL_PASS "<REPLACE_WITH_STRONG_PASSWORD>"
|
||||||
|
|
||||||
# Additional database client options, if needed.
|
# Additional database client options, if needed.
|
||||||
# Example:
|
# Example:
|
||||||
@@ -81,7 +81,12 @@
|
|||||||
# DNS / BIND
|
# DNS / BIND
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
# Authoritative DNS server used for update operations
|
# Authoritative DNS server used for RFC 2136 dynamic update operations.
|
||||||
|
#
|
||||||
|
# This address is used by nsupdate on DNS port 53. It must point to the
|
||||||
|
# authoritative BIND listener and matching view serving DYNDNS_BIND_ZONE.
|
||||||
|
#
|
||||||
|
# Do not use a local recursive-only listener or a non-authoritative view.
|
||||||
#set_var DYNDNS_BIND_SERVER "127.0.0.1"
|
#set_var DYNDNS_BIND_SERVER "127.0.0.1"
|
||||||
|
|
||||||
# Service name used when reloading/reconfiguring the name server
|
# Service name used when reloading/reconfiguring the name server
|
||||||
@@ -90,7 +95,10 @@
|
|||||||
# DNS zone handled by dynDNS
|
# DNS zone handled by dynDNS
|
||||||
#set_var DYNDNS_BIND_ZONE "example24.com"
|
#set_var DYNDNS_BIND_ZONE "example24.com"
|
||||||
|
|
||||||
# TSIG key file or key identifier used for nsupdate access
|
# TSIG key file or key identifier used for nsupdate access.
|
||||||
|
#
|
||||||
|
# This key should be restricted in BIND with update-policy and must not be
|
||||||
|
# confused with the RNDC control key configured below.
|
||||||
#set_var DYNDNS_BIND_ZONE_KEY "keyfile"
|
#set_var DYNDNS_BIND_ZONE_KEY "keyfile"
|
||||||
|
|
||||||
# Directory containing the primary zone files
|
# Directory containing the primary zone files
|
||||||
@@ -105,6 +113,34 @@
|
|||||||
# Default TTL for generated/managed records
|
# Default TTL for generated/managed records
|
||||||
#set_var DYNDNS_BIND_ZONE_TTL 300
|
#set_var DYNDNS_BIND_ZONE_TTL 300
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# RNDC POST-UPDATE ZONE SYNCHRONIZATION
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Local BIND control endpoint used for optional post-update synchronization.
|
||||||
|
#
|
||||||
|
# rndc connects to named's control interface, normally TCP port 953.
|
||||||
|
# This is independent from DYNDNS_BIND_SERVER, which is used by nsupdate
|
||||||
|
# against DNS port 53.
|
||||||
|
#set_var DYNDNS_RNDC_SERVER "127.0.0.1"
|
||||||
|
|
||||||
|
# RNDC control key authorized by the named controls {} configuration.
|
||||||
|
#
|
||||||
|
# This key is intentionally separate from DYNDNS_BIND_ZONE_KEY. The latter is
|
||||||
|
# used by nsupdate for RFC 2136 DNS updates, while this key authorizes BIND
|
||||||
|
# administrative operations such as "rndc sync -clean".
|
||||||
|
#set_var DYNDNS_RNDC_KEY "/etc/named/rndc.key"
|
||||||
|
|
||||||
|
# Synchronize dynamic zone journals after a successful nsupdate request.
|
||||||
|
#
|
||||||
|
# 0 = disabled; named handles zone dumps using its normal maintenance schedule.
|
||||||
|
# 1 = execute "rndc sync -clean <zone>" after a successful DNS update.
|
||||||
|
#
|
||||||
|
# A failed synchronization must be logged as a warning only. The dynamic update
|
||||||
|
# remains successful because named retains the authoritative state in its
|
||||||
|
# journal until the next successful zone dump.
|
||||||
|
#set_var DYNDNS_RNDC_SYNC_CLEAN 0
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# MEMBER DEFAULTS
|
# MEMBER DEFAULTS
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@@ -119,7 +155,7 @@
|
|||||||
#set_var DYNDNS_MEMBER_EMAIL "admin@example24.com"
|
#set_var DYNDNS_MEMBER_EMAIL "admin@example24.com"
|
||||||
|
|
||||||
# Password or shared secret used during member creation/authentication
|
# Password or shared secret used during member creation/authentication
|
||||||
#set_var DYNDNS_MEMBER_PASS "strong_passphrase"
|
#set_var DYNDNS_MEMBER_PASS "<REPLACE_WITH_STRONG_PASSWORD>"
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# OPTIONAL COMMAND HOOKS
|
# OPTIONAL COMMAND HOOKS
|
||||||
|
|||||||
Reference in New Issue
Block a user