Skip to content

Instantly share code, notes, and snippets.

@sport4minus
Created February 21, 2023 19:37
Show Gist options
  • Select an option

  • Save sport4minus/32f2b31acaa34f1a7687de57b1cc73d7 to your computer and use it in GitHub Desktop.

Select an option

Save sport4minus/32f2b31acaa34f1a7687de57b1cc73d7 to your computer and use it in GitHub Desktop.
python script to control a Brennenstuhl Premium-Web-Line V3
# Brennenstuhl Premium-Web-Line V3 is a socket strip with an embedded web server.
# It can be switched via a web interface or using http get requests. The latter is a bit under-documented,
# so here's what i found out:
# The built-in server accepts only authenticated http get requests (using digest auth).
# each command has its own url, like so: http://{IP-OF-THE-SOCKETSTRIP}/cgi/{commandname}?params
# full list can be found in a PDF called "Premium-Web-Line V3 CGI specification.pdf" which is included in the download package of the official software:
# download link -> https://www.brennenstuhl.com/index.php?module=explorer&index[explorer][action]=download&index[explorer][file]=firmware/premium-web-line-v3-software-3_3_6.zip
# here's an example on how to toggle one of the two relais (nr. 0)
# modified a general digest auth example from stackoverflow: https://stackoverflow.com/questions/23254013/http-digest-basic-auth-with-python-requests-module
import logging
import requests
from requests.auth import HTTPDigestAuth
try:
import httplib
except ImportError:
import http.client as httplib
httplib.HTTPConnection.debuglevel = 1
# you need to initialize logging,
# otherwise you will not see anything from requests
logging.basicConfig(level=logging.DEBUG)
# make request
url = 'http://10.0.1.118/cgi/toggleRelay?Rel=0' #Rel=1 would toggle the other relay
# user / password is factory set to admin/admin - you better change this quick.
r = requests.get(url, auth=HTTPDigestAuth('admin', 'admin'),
timeout=10)
print(r.status_code)
print(r.headers)
@lukas-hofstaetter

Copy link
Copy Markdown

Great! Thankyou! I always tried the same via curl but that does not work. Foreverboy searching a one-liner:

python3 -c "import requests; r = requests.get('http://192.168.0.12/cgi/toggleRelay?Rel=0', auth=requests.auth.HTTPDigestAuth('admin', 'admin'), timeout=10); print(r.status_code)"

@sport4minus

Copy link
Copy Markdown
Author

Thank you, very useful 👍
i also tried curl but never got the parameters right..
then python.
I would really like to how it's done though...

@lukas-hofstaetter

Copy link
Copy Markdown

Yeah, curl can also do a getand a digest authentication.. But I always get a permission denied" error. Maybe I should do a Wireshark debug session to understand the difference between the python and the curl command. Would be a nice learning.

@sport4minus

Copy link
Copy Markdown
Author

same here – if you find the time to do that, i'd be curious to hear why it fails.

@martinlauer

martinlauer commented Nov 24, 2025

Copy link
Copy Markdown

I've compared the auth calls of requests and curl. I'm guessing that the http server in the BS bar has a bad implementation of the auth workflow. When using requests, it will cover for the first 401 reply and just continue. Curl on the other hand is very strict and stops properly after the 401 reply.

send: b'GET /cgi/getIpSet HTTP/1.1\r\nHost: 192.168.1.12\r\nUser-Agent: python-requests/2.31.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\n\r\n'
reply: 'HTTP/1.1 401 Unauthorized\r\n'
header: Server: nbsoftware/1.1
header: Access-Control-Allow-Origin: *
header: WWW-Authenticate: Digest realm="Enter User/Password",qop="auth",nonce="1ad2cb12ba47519b888b2e20aeffd76c",opaque="Opaque_Test_ddd123987fe20_finish",algorithm=MD5,stale=false
header: Connection: keep-alive
header: Content-type: text/html
header: Content-Length: 755
send: b'GET /cgi/getIpSet HTTP/1.1\r\nHost: 192.168.1.12\r\nUser-Agent: python-requests/2.31.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nAuthorization: Digest username="admin", realm="Enter User/Password", nonce="1ad2cb12ba47519b888b2e20aeffd76c", uri="/cgi/getIpSet", response="fc157353a8d56506adb2ae8bc999e15c", opaque="Opaque_Test_ddd123987fe20_finish", algorithm="MD5", qop="auth", nc=00000001, cnonce="18778a09be6f16d4"\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Server: nbsoftware/1.1
header: Access-Control-Allow-Origin: *
header: Connection: keep-alive
header: Content-type: text/plain
header: Content-Length: 47

Request successful! Status: 200

Most of the time was wasted on trial and error with curl and assuming that something was broken with curl...

@sport4minus

Copy link
Copy Markdown
Author

@martinlauer
thank you very much for the investigation - good to know the bad implementation is at the other end in this case :)

@chwdt

chwdt commented Jan 23, 2026

Copy link
Copy Markdown

Using curl with --digest works for me:

$ curl -v --insecure --digest --user admin:$PASS "https://10.4.2.24/cgi/toggleRelay?Rel=1"
*   Trying 10.4.2.24...
* TCP_NODELAY set
* Connected to 10.4.2.24 (10.4.2.24) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=ws_brs; O=brennenstuhl; C=DE
*  start date: Feb  1 00:00:00 2021 GMT
*  expire date: Dec 31 00:00:00 2024 GMT
*  issuer: CN=ws_brs; O=brennenstuhl; C=DE
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* Server auth using Digest with user 'admin'
> GET /cgi/toggleRelay?Rel=1 HTTP/1.1
> Host: 10.4.2.24
> User-Agent: curl/7.52.1
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Server: nbsoftware/1.1
< Access-Control-Allow-Origin: *
< WWW-Authenticate: Digest realm="Enter User/Password",qop="auth",nonce="65a1c5a32484549eff860456e58bd940",opaque="Opaque_Test_ddd123987fe20_finish",algorithm=MD5,stale=false
< Content-type: text/html
< Content-Length: 730
<
* Ignoring the response-body
* Curl_http_done: called premature == 0
* Connection #0 to host 10.4.2.24 left intact
* Issue another request to this URL: 'https://10.4.2.24/cgi/toggleRelay?Rel=1'
* Found bundle for host 10.4.2.24: 0x55fc973a8820 [can pipeline]
* Re-using existing connection! (#0) with host 10.4.2.24
* Connected to 10.4.2.24 (10.4.2.24) port 443 (#0)
* Server auth using Digest with user 'admin'
> GET /cgi/toggleRelay?Rel=1 HTTP/1.1
> Host: 10.4.2.24
> Authorization: Digest username="admin", realm="Enter User/Password", nonce="65a1c5a32484549eff860456e58bd940", uri="/cgi/toggleRelay?Rel=1", cnonce="OGM3OGExMjVkNTZlMzg0ZGMwMjUxNjliMGMxM2ExN2E=", nc=00000001, qop=auth, response="429989a05c5f10b96ae400da19be4118", opaque="Opaque_Test_ddd123987fe20_finish", algorithm="MD5"
> User-Agent: curl/7.52.1
> Accept: */*
>
* SSL read: error:00000000:lib(0):func(0):reason(0), errno 104
* Connection died, retrying a fresh connect
* Curl_http_done: called premature == 0
* Closing connection 0
* Issue another request to this URL: 'https://10.4.2.24/cgi/toggleRelay?Rel=1'
* Hostname 10.4.2.24 was found in DNS cache
*   Trying 10.4.2.24...
* TCP_NODELAY set
* Connected to 10.4.2.24 (10.4.2.24) port 443 (#1)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* SSL re-using session ID
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=ws_brs; O=brennenstuhl; C=DE
*  start date: Feb  1 00:00:00 2021 GMT
*  expire date: Dec 31 00:00:00 2024 GMT
*  issuer: CN=ws_brs; O=brennenstuhl; C=DE
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* Server auth using Digest with user 'admin'
> GET /cgi/toggleRelay?Rel=1 HTTP/1.1
> Host: 10.4.2.24
> Authorization: Digest username="admin", realm="Enter User/Password", nonce="65a1c5a32484549eff860456e58bd940", uri="/cgi/toggleRelay?Rel=1", cnonce="OGM3OGExMjVkNTZlMzg0ZGMwMjUxNjliMGMxM2ExN2E=", nc=00000002, qop=auth, response="86ad0087d5c0411ea80c9f24b67d08ce", opaque="Opaque_Test_ddd123987fe20_finish", algorithm="MD5"
> User-Agent: curl/7.52.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nbsoftware/1.1
< Access-Control-Allow-Origin: *
< Content-type: text/plain
< Content-Length: 0
<
* Curl_http_done: called premature == 0
* Connection #1 to host 10.4.2.24 left intact

@sport4minus

Copy link
Copy Markdown
Author

@chwdt ooooh interesting.
might the --insecure flag be the key to success?
i'll try next time i am near the device.

@chwdt

chwdt commented Jan 23, 2026

Copy link
Copy Markdown

@sport4minus No, I need --insecure because I use https, and the device uses a self-signed certificate.

@sport4minus

Copy link
Copy Markdown
Author

i see, missed the 's' :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment