Skip to content

Instantly share code, notes, and snippets.

@fnordomat
Last active September 7, 2021 07:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save fnordomat/40860a59162b2658324dbf666b10538c to your computer and use it in GitHub Desktop.
Save fnordomat/40860a59162b2658324dbf666b10538c to your computer and use it in GitHub Desktop.
Automatic negotiation of a captive portal: connect to m3connect wifi in some Accor group hotels without having to jump through hoops.
#!/bin/bash
# Accor group hotels offer "free wifi" (ESSID: "m3connect") service
# with internet access (with paid options for higher transfer rates).
#
# This takes care of the m3connect captive portal.
#
# Please drop me a note if it stops working, is insecure etc.
#
# very basic, something like this worked for me
curl --stderr - --trace - -v -L -A '' http://google.com | tee step0001
curl --stderr - --trace - -v -L -A '' -c kookie.jar https://portal.m3connect.de/en/login/free | tee step0002
cat kookie.jar
PHPSESSID=$(grep PHPSESSID kookie.jar | perl -ne '/([A-Za-z0-9]*)$/; print $1;')
curl --stderr - --trace - --post301 --post302 --post303 -v -L -A 'Mozilla/5.0' -e 'https://portal.m3connect.de/en/login/free' -b "PHPSESSID=${PHPSESSID}" -F submit=Register -F "registration[tariff]=707" -F "registration[terms]=1" https://portal.m3connect.de/en/register/free | tee step0003
@dbrindl1337
Copy link

Hi,
I have recently begun to automatize captive portal negotion with bash for the portals I am using regularly. The m3connect portal is a really tough one, thus I was very pleased when I saw that you already put some work into it.
Unfortunately, your script doesn't do the job for me.
At my hotel, the behaviour of the redirects is ambigous, sometimes there is a cookie challenge in between, sometimes not.
Additionally, there is (on top of the php session) a 'himalaya session id', and another session-token that are transmitted if you request the portal via a browser.
Did you go into detail with that (and find that you just don't need them), or do the portals just behave differently?

Thank you :)

@fnordomat
Copy link
Author

fnordomat commented Nov 13, 2019 via email

@fnordomat
Copy link
Author

I've met the himalaya one and automated it too, it's in the "misc" repo:
https://github.com/fnordomat/misc
https://notabug.org/fnordomat/misc/src/master/captive-be-gone/m34.py

@dbrindl1337
Copy link

Hi,
thank you too for acknowlegding my question. Unfortunately, I didn't stay in an Accor branded hotel for a few months now.
I will try it your script as soon as there is a chance.
In the meantime I have started my own repo with a little "framework" embedding the portal circumvention scripts.
https://bitbucket.org/renttoodamnhigh/captive-portal-circumvention/
Feel free to adopt some or help me out if you find something that can be improved (there sure is plenty).
Greetings

@fnordomat
Copy link
Author

Excellent! I've cloned your repo now. I'll try your connection methods when I run into those networks.

I like the idea of redistributing the connection over a pocket router. Maybe I can come up with a generic linux script for that, as an alternative (some drivers even support access point and client mode on the same device https://wireless.wiki.kernel.org/en/users/documentation/iw/vif ).

btw you can also contact me in private:
fnordomat posteo.net 0x05E0743AAF6DF8B32B7BDF0FA295F7C4AC1907BF :-)

@humaita-github
Copy link

Hi, thank you for the script. It only works partly for me. Running the script for the first time works well and connects me. I have my router connect to the access point, and other devices (laptop, tablet) connecting to my router.
Sometimes I will get disconnected, then the problems begin. Whatever device behind my router tries to open a web site first will become the only device that can register in the portal. To be more specific, in that case the web page loaded by the script in the last step will say "Invalid IP address. This portal has been called from an unknown IP address. Please connect to a Wi-Fi hotspot operated by m3connect."
Any ideas how to overcome this?

@fnordomat
Copy link
Author

Hi! Unfortunately, I don't have the opportunity to get close to such a hotspot right now.
I can only venture a guess - if it works the first time, then it usually makes sense to change the MAC address of the connecting device before reconnecting, when it stops working. Captive portals usually treat a device they've already seen differently from a "new" one. Maybe it gets recorded along with the IP address, which the error message seems to be hinting at.
Can you describe your setup more precisely: what kind of router is it, do you run the script from one of the devices connected to it? Does it get its IP address assigned by the router?

@humaita-github
Copy link

Hi, thank you for the feedback. I have one "master" router running OpenWrt and connecting to the hotel hotspot. Another "secondary" router also runnning OpenWrt runs your script (not enough space on the first router to install perl). So only the master router gets an IP address from the hotel. However, only one device behind the router will be able to register, likely the one that opens a web site first. All other devices will get the "Invalid IP address" even though from a hotel perspective all comms are coming from the master router... I assume its a cookie related problem, i.e. expecting a cookie from one device but receiving a cookie from a different device ?!

@fnordomat
Copy link
Author

fnordomat commented Nov 3, 2020

A cookie is only required once, so I don't think that's a likely source of the problem.

Couldn't the first router be configured to forward all connections to the second one?
Or if the second router can register successfully, as a workaround maybe you could use it to open a VPN connection and share that with the other devices.

@humaita-github
Copy link

Hi, after the secondary router registers succesfully, all devices have internet -- so no need for a VPN.
The problem is only when re-connecting (you need to register again after 24h, or if the Wifi connection is interrupted). Then whatever device tries to open a website first will be the only device that can register. And since all devices are "behind" the master router, i.e. through masquerading all external traffic goes through the master router to the hotel, the hotel only sees one IP. So I guess the error message "Invalid IP address" is not really related to the IP address but to something else.

@fnordomat
Copy link
Author

Nice to know. Thanks for the feedback!

@humaita-github
Copy link

Hi, it seems that the access point is the "conn4.com" variant. So I tried your m34.py -- however with mixed success.

I get the error

Traceback (most recent call last):
File "./m34.py", line 95, in
f3 = foo.perform_get(url)
File "./m34.py", line 76, in perform_get
f = pool.request(method='GET', url=url, headers=headers, redirect=redirect)
File "/usr/lib/python3/dist-packages/urllib3/request.py", line 75, in request
return self.request_encode_url(
File "/usr/lib/python3/dist-packages/urllib3/request.py", line 97, in request_encode_url
return self.urlopen(method, url, **extra_kw)
File "/usr/lib/python3/dist-packages/urllib3/poolmanager.py", line 319, in urlopen
conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme)
File "/usr/lib/python3/dist-packages/urllib3/poolmanager.py", line 225, in connection_from_host
raise LocationValueError("No host specified.")
urllib3.exceptions.LocationValueError: No host specified.

If I comment out line 93 (i.e. no get('location') but just get the last URL again), then the script runs through the end. However, I get the error "API authentication failed: Empty or illegal token" in most tries. But running the script 10-20 times will suddenly make it work. Not sure why -- potentially my browser tries to open a website in the background to check connectivity?!

Any ideas how to move forward on this one?

@fnordomat
Copy link
Author

Hi!

Maybe this is yet another variant or a different version of the hotspot ... the script worked very reliably for me, but I only ever tried it in one place IIRC. There could well be some minor difference that makes it fail. Would you mind sharing a complete log of the server responses?

If you find out how to make it work wherever you are right now, please share your solution, I'm curious to know what makes it different :-)

fnord

@humaita-github
Copy link

Thank you for you help. Here is the output of the script:

# ./m34.py
let's go
request GET http://detectportal.firefox.com/success.txt
request headers: {'User-Agent': 'Mozilla/5.0', 'Cookie': ''}
response headers: HTTPHeaderDict({'cache-control': 'max-age=0, private, must-revalidate', 'content-length': '442', 'content-type': 'text/html; charset=utf-8', 'date': 'Sun, 22 Nov 2020 22:05:26 GMT', 'location': 'https://210.rdr.conn4.com/ident?client_ip=10.22.56.186&client_mac=0A000A1638BA&site_id=210&signature=172f43c382e39ad743486454784886e7b56aeebf87b4d05d70b6d35298d1c091&loggedin=0&remembered_mac=0', 'server': 'Cowboy'})
redirect: https://210.rdr.conn4.com/ident?client_ip=10.22.56.186&client_mac=0A000A1638BA&site_id=210&signature=172f43c382e39ad743486454784886e7b56aeebf87b4d05d70b6d35298d1c091&loggedin=0&remembered_mac=0
b'<!doctype html>\n<html>\n\n <head>\n <title>Redirecting ....</title>\n <meta name="viewport" content="width=device-width, initial-scale=1">\n <meta http-equiv="refresh" content="1; URL=https://210.rdr.conn4.com/ident?client_ip=10.22.56.186&client_mac=0A000A1638BA&site_id=210&signature=172f43c382e39ad743486454784886e7b56aeebf87b4d05d70b6d35298d1c091&loggedin=0&remembered_mac=0">\n </head>\n <body>\n Redirecting ...\n </body>\n</html>\n'

request GET https://210.rdr.conn4.com/ident?client_ip=10.22.56.186&client_mac=0A000A1638BA&site_id=210&signature=172f43c382e39ad743486454784886e7b56aeebf87b4d05d70b6d35298d1c091&loggedin=0&remembered_mac=0
request headers: {'User-Agent': 'Mozilla/5.0', 'Cookie': ''}
response headers: HTTPHeaderDict({'Server': 'm3connect', 'Date': 'Sun, 22 Nov 2020 22:05:26 GMT', 'Content-Type': 'text/html; charset=UTF-8', 'Content-Length': '0', 'Connection': 'keep-alive', 'Set-Cookie': 'ngx_conn4_portal=efb425315b4935ca4cff2444d4afad94; expires=Sun, 22-Nov-20 22:35:26 GMT; max-age=1800; path=/, himalaya-site-ident=SFNJKk86MzQ6Ik0zXEhpbWFsYXlhXFNoYXJlZFxTaXRlSWRlbnRcVG9rZW4iOjk6e3M6MTM6IgAqAE1BQ0FkZHJlc3MiO3M6MTI6IjBBMDAwQTE2MzhCQSI7czoxOToiACoAZXh0ZW5kZWRMaWZldGltZSI7YjowO3M6MTI6IgAqAElQQWRkcmVzcyI7czoxMjoiMTAuMjIuNTYuMTg2IjtzOjE2OiIAKgByZW1vdGVBZGRyZXNzIjtzOjEzOiIxODUuMjAyLjMyLjI2IjtzOjk6IgAqAG5vZGVJZCI7TjtzOjk6IgAqAHNpdGVJZCI7aToyMTA7czoyMjoiACoAY29udGVudFJlcG9zaXRvcnlJZCI7TjtzOjc6IgAqAHVybHMiO2E6MDp7fXM6MTA6IgAqAGNyZWF0ZWQiO086ODoiRGF0ZVRpbWUiOjM6e3M6NDoiZGF0ZSI7czoyNjoiMjAyMC0xMS0yMiAyMjowNToyNi4wMDAwMDAiO3M6MTM6InRpbWV6b25lX3R5cGUiO2k6MztzOjg6InRpbWV6b25lIjtzOjM6IlVUQyI7fX18N2Y5ZGExZDdjMGM3NzMxNjQyM2Q0NmI5Njc4MGUxMjkwNWZkNWQxMmFmYjVjZWMwMDFkMGUzZjU2ZDVkNThjMQ%3D%3D; expires=Sun, 22-Nov-2020 22:35:26 GMT; Max-Age=1800; path=/', 'Location': 'https://210.rdr.conn4.com/#', 'Strict-Transport-Security': 'max-age=31415926', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-DNS-Prefetch-Control': 'off'})
redirect: https://210.rdr.conn4.com/#
setting crookie ngx_conn4_portal to efb425315b4935ca4cff2444d4afad94
setting crookie himalaya-site-ident to SFNJKk86MzQ6Ik0zXEhpbWFsYXlhXFNoYXJlZFxTaXRlSWRlbnRcVG9rZW4iOjk6e3M6MTM6IgAqAE1BQ0FkZHJlc3MiO3M6MTI6IjBBMDAwQTE2MzhCQSI7czoxOToiACoAZXh0ZW5kZWRMaWZldGltZSI7YjowO3M6MTI6IgAqAElQQWRkcmVzcyI7czoxMjoiMTAuMjIuNTYuMTg2IjtzOjE2OiIAKgByZW1vdGVBZGRyZXNzIjtzOjEzOiIxODUuMjAyLjMyLjI2IjtzOjk6IgAqAG5vZGVJZCI7TjtzOjk6IgAqAHNpdGVJZCI7aToyMTA7czoyMjoiACoAY29udGVudFJlcG9zaXRvcnlJZCI7TjtzOjc6IgAqAHVybHMiO2E6MDp7fXM6MTA6IgAqAGNyZWF0ZWQiO086ODoiRGF0ZVRpbWUiOjM6e3M6NDoiZGF0ZSI7czoyNjoiMjAyMC0xMS0yMiAyMjowNToyNi4wMDAwMDAiO3M6MTM6InRpbWV6b25lX3R5cGUiO2k6MztzOjg6InRpbWV6b25lIjtzOjM6IlVUQyI7fX18N2Y5ZGExZDdjMGM3NzMxNjQyM2Q0NmI5Njc4MGUxMjkwNWZkNWQxMmFmYjVjZWMwMDFkMGUzZjU2ZDVkNThjMQ%3D%3D
himalaya-site-ident: b'HSI*O:34:"M3\\Himalaya\\Shared\\SiteIdent\\Token":9:{s:13:"\x00*\x00MACAddress";s:12:"0A000A1638BA";s:19:"\x00*\x00extendedLifetime";b:0;s:12:"\x00*\x00IPAddress";s:12:"10.22.56.186";s:16:"\x00*\x00remoteAddress";s:13:"185.202.32.26";s:9:"\x00*\x00nodeId";N;s:9:"\x00*\x00siteId";i:210;s:22:"\x00*\x00contentRepositoryId";N;s:7:"\x00*\x00urls";a:0:{}s:10:"\x00*\x00created";O:8:"DateTime":3:{s:4:"date";s:26:"2020-11-22 22:05:26.000000";s:13:"timezone_type";i:3;s:8:"timezone";s:3:"UTC";}}|7f9da1d7c0c77316423d46b96780e12905fd5d12afb5cec001d0e3f56d5d58c1'
b''

request GET https://210.rdr.conn4.com/#
request headers: {'User-Agent': 'Mozilla/5.0', 'Cookie': 'ngx_conn4_portal=efb425315b4935ca4cff2444d4afad94; himalaya-site-ident=SFNJKk86MzQ6Ik0zXEhpbWFsYXlhXFNoYXJlZFxTaXRlSWRlbnRcVG9rZW4iOjk6e3M6MTM6IgAqAE1BQ0FkZHJlc3MiO3M6MTI6IjBBMDAwQTE2MzhCQSI7czoxOToiACoAZXh0ZW5kZWRMaWZldGltZSI7YjowO3M6MTI6IgAqAElQQWRkcmVzcyI7czoxMjoiMTAuMjIuNTYuMTg2IjtzOjE2OiIAKgByZW1vdGVBZGRyZXNzIjtzOjEzOiIxODUuMjAyLjMyLjI2IjtzOjk6IgAqAG5vZGVJZCI7TjtzOjk6IgAqAHNpdGVJZCI7aToyMTA7czoyMjoiACoAY29udGVudFJlcG9zaXRvcnlJZCI7TjtzOjc6IgAqAHVybHMiO2E6MDp7fXM6MTA6IgAqAGNyZWF0ZWQiO086ODoiRGF0ZVRpbWUiOjM6e3M6NDoiZGF0ZSI7czoyNjoiMjAyMC0xMS0yMiAyMjowNToyNi4wMDAwMDAiO3M6MTM6InRpbWV6b25lX3R5cGUiO2k6MztzOjg6InRpbWV6b25lIjtzOjM6IlVUQyI7fX18N2Y5ZGExZDdjMGM3NzMxNjQyM2Q0NmI5Njc4MGUxMjkwNWZkNWQxMmFmYjVjZWMwMDFkMGUzZjU2ZDVkNThjMQ%3D%3D'}
response headers: HTTPHeaderDict({'Server': 'm3connect', 'Date': 'Sun, 22 Nov 2020 22:05:27 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': '7074', 'Connection': 'keep-alive', 'Set-Cookie': 'ngx_conn4_portal=efb425315b4935ca4cff2444d4afad94; expires=Sun, 22-Nov-20 22:35:26 GMT; max-age=1800; path=/', 'Strict-Transport-Security': 'max-age=31415926', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-DNS-Prefetch-Control': 'off'})
replacing crookie ngx_conn4_portal with efb425315b4935ca4cff2444d4afad94
b'<!DOCTYPE html>\n<html xmlns="http://www.w3.org/1999/xhtml" id="no-js">\n <head>\n <script>\n var scenename = \'sceneplayer\';\n if (typeof scenename === \'undefined\') {\n var scenename = \'undefined\';\n}\n\nfunction logError(error) {\n var oReq = new XMLHttpRequest();\n oReq.open("POST", "/admon-assets/log.php?channel=clienterror", true);\n oReq.setRequestHeader(\'Content-type\', \'application/x-www-form-urlencoded\');\n oReq.send("type=" + error.type\n + "&msg=" + error.message\n + " | module=" + scenename\n + " | file=" + error.filename\n + " | line=" + error.lineno\n + " | col=" + error.colno\n );\n}\n\nwindow.addEventListener(\'error\', logError);\n </script>\n\n <script type="text/javascript">with(document.documentElement){id=id.replace(\'no-\',\'\');}</script>\n <style>body{margin:0}object{position:absolute;top:0;left:0;opacity:0;z-index:100}.fullscreen{width:100%;height:100%;position:absolute;border:0}.fade-out{opacity:0}.fade-in{opacity:1;-webkit-transition:opacity 1500ms ease-out;-moz-transition:opacity 1500ms ease-out;-o-transition:opacity 1500ms ease-out;transition:opacity 1500ms ease-out}.message{background:rgba(0,0,0,.8);position:absolute;bottom:0;left:0;right:0;z-index:1000 !important;padding:1em;text-align:center;font-size:32px;color:#fff}.hidden,#no-js #_loading_indicator,#_scene_volume,#_standby_overlay{display:none}.error{background-color:#eee;z-index:200}.error>div{width:500px;text-align:center;margin:-5em auto 0;position:relative;top:50%;font-size:24px;font-family:Helvetica,Arial,sans-serif}#_scene_content>iframe.ios-fix{width:1px;min-width:100%;*width:100%;overflow:auto;height:100%;overflow-y:scroll;-webkit-overflow-scrolling:touch}#_loading_indicator{position:fixed;z-index:10000;bottom:0;top:0;left:0;right:0}#_loading_indicator>.loading--simple{position:absolute;bottom:.6rem;right:.8rem;font-size:.9rem;padding:.5rem;border-radius:.3rem;font-family:sans-serif;color:black;background-color:rgba(255,255,255,.9)}\n</style>\n\n <style id="_scene_custom_style" type="text/css"></style>\n\n <meta http-equiv="X-UA-Compatible" content="IE=edge"/>\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n\n \n </head>\n\n <body>\n <!--\n <?xml version="1.0" encoding="UTF-8"?>\n <WISPAccessGatewayParam xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.acmewisp.com/WISPAccessGatewayParam.xsd">\n <Redirect>\n <AccessProcedure>1.0</AccessProcedure>\n <AccessLocation>12</AccessLocation>\n <LocationName>CONN4</LocationName>\n <LoginURL>https://210.rdr.conn4.com/wbs/de/roaming/return/</LoginURL>\n <MessageType>100</MessageType>\n <ResponseCode>0</ResponseCode>\n </Redirect>\n </WISPAccessGatewayParam>\n -->\n\n <noscript>\n <div class="error fullscreen">\n <div>\n <p>Please enable JavaScript to continue.</p>\n </div>\n </div>\n </noscript>\n\n <div id="_error" class="error hidden fullscreen">\n <div>\n <h1>Error <span class="js-error-code">601</span></h1>\n <p>Need assistance?</p>\n <p>Wi-Fi-Hotline: +49 (0) 241 / 705 39 605<br/>\n <span style="font-size: 50%">(at local tariff throughout Germany)</span></p>\n </div>\n </div>\n\n \n <div id="_scene_content" data-channel="">\n </div>\n\n <div id="_loading_indicator" class="loading--initial">\n <div class="loading--simple">\n Loading ...\n </div>\n </div>\n\n \n <script src="/cache/js-file-6dc28a7efbc7361554b912ca7ba1456ad729108d-j-5fb0f4f22ff91326592287dfa448fd5e.js" type="text/javascript"></script>\n <script type="text/javascript">\n (function() {\n if (!location.search) {\n return;\n }\n\n var url = location.href.replace(location.search, \'\');\n if (history && history.replaceState) {\n history.replaceState(history.state || {}, document.title, url);\n } else {\n location.href = url;\n }\n })();\n </script>\n <script type="text/javascript">\n conn4.deviceConfiguration = false;\n\n conn4.log = $.noop;\n \n// conn4.recorder = new conn4.LogRecorder({log: conn4.log});\n// conn4.record = conn4.recorder.record;\n conn4.record = function() {};\n\n conn4.eventTracker = new conn4.EventTracker({\n logger: conn4.logger\n });\n conn4.trackEvent = conn4.eventTracker.track;\n \n conn4.serverdate = new conn4.ServerDate({\n initializationTs: 1606082727026,\n log: conn4.log\n });\n var ServerDate = function () {\n return new Date(ServerDate.now());\n };\n ServerDate.now = function() {\n return conn4.serverdate.getMilliseconds();\n };\n\n \n \n conn4.hotspot = conn4.hotspot || {};\n conn4.hotspot.wbsToken = {"token":"SFdBKk86MzU6Ik0zXEhpbWFsYXlhXFNoYXJlZFxXQlNBcGlBdXRoXFRva2VuIjo1OntzOjk6IgAqAHNpdGVJZCI7aToyMTA7czoxNjoiACoAcmVtb3RlQWRkcmVzcyI7czoxMjoiMTAuMjIuNTYuMTg2IjtzOjEzOiIAKgBtYWNBZGRyZXNzIjtzOjEyOiIwQTAwMEExNjM4QkEiO3M6MTA6IgAqAGNyZWF0ZWQiO086ODoiRGF0ZVRpbWUiOjM6e3M6NDoiZGF0ZSI7czoyNjoiMjAyMC0xMS0yMiAyMjowNToyNy4wMDAwMDAiO3M6MTM6InRpbWV6b25lX3R5cGUiO2k6MztzOjg6InRpbWV6b25lIjtzOjM6IlVUQyI7fXM6OToiACoAb3JpZ2luIjtzOjI1OiJodHRwczovLzIxMC5yZHIuY29ubjQuY29tIjt9fGJkZDdmZTU5NmRlOGI4MGMzYjI1YzI1ZTgyNzZjNDUzZjE5YzU0YjQwMWJkOGU1N2QzMjBjMjRjMzcxYTYyODg=","urls":{"grant_url":null,"continue_url":null}};\n setTimeout(function() {\n conn4.hotspot.wbsToken = undefined;\n }, 118 * 60 * 1000); \n $(_.partial(conn4.startSceneLoader, {\n log: conn4.log,\n trackEvent: conn4.trackEvent,\n settings: {"sceneLoaderAssertLoaded":true,"sceneLoaderTimeoutLimit":2}\n,\n schedule: {"token":{"site":210,"group":"desktop","mac":"0A000A1638BA"},"site_title":"","url_version":1,"scene_template":"https:\\/\\/210.rdr.conn4.com\\/scenes\\/{id}\\/","fallback":"https:\\/\\/210.rdr.conn4.com\\/admon-assets\\/fallback.html","error_404":"https:\\/\\/210.rdr.conn4.com\\/admon\\/404","initialization_ts":1606082727,"events":[{"time":"1984-11-17 00:00:00","seq":100,"type":"view","payload":{"type":"scene","data":{"id":"5jx9ZrGe72eI0l4n","module":"html-page-scene-wbs-new","variant":"default"}}},{"time":"1984-11-17 00:00:00","seq":200,"type":"campaign","payload":null}],"playlists":[],"navigations":[]}\n }));\n </script>\n </body>\n</html>\n'

request GET None
request headers: {'User-Agent': 'Mozilla/5.0', 'Cookie': 'ngx_conn4_portal=efb425315b4935ca4cff2444d4afad94; himalaya-site-ident=SFNJKk86MzQ6Ik0zXEhpbWFsYXlhXFNoYXJlZFxTaXRlSWRlbnRcVG9rZW4iOjk6e3M6MTM6IgAqAE1BQ0FkZHJlc3MiO3M6MTI6IjBBMDAwQTE2MzhCQSI7czoxOToiACoAZXh0ZW5kZWRMaWZldGltZSI7YjowO3M6MTI6IgAqAElQQWRkcmVzcyI7czoxMjoiMTAuMjIuNTYuMTg2IjtzOjE2OiIAKgByZW1vdGVBZGRyZXNzIjtzOjEzOiIxODUuMjAyLjMyLjI2IjtzOjk6IgAqAG5vZGVJZCI7TjtzOjk6IgAqAHNpdGVJZCI7aToyMTA7czoyMjoiACoAY29udGVudFJlcG9zaXRvcnlJZCI7TjtzOjc6IgAqAHVybHMiO2E6MDp7fXM6MTA6IgAqAGNyZWF0ZWQiO086ODoiRGF0ZVRpbWUiOjM6e3M6NDoiZGF0ZSI7czoyNjoiMjAyMC0xMS0yMiAyMjowNToyNi4wMDAwMDAiO3M6MTM6InRpbWV6b25lX3R5cGUiO2k6MztzOjg6InRpbWV6b25lIjtzOjM6IlVUQyI7fX18N2Y5ZGExZDdjMGM3NzMxNjQyM2Q0NmI5Njc4MGUxMjkwNWZkNWQxMmFmYjVjZWMwMDFkMGUzZjU2ZDVkNThjMQ%3D%3D'}
Traceback (most recent call last):
File "./m34.py", line 94, in <module>
f3 = foo.perform_get(url)
File "./m34.py", line 76, in perform_get
f = pool.request(method='GET', url=url, headers=headers, redirect=redirect)
File "/usr/lib/python3/dist-packages/urllib3/request.py", line 75, in request
return self.request_encode_url(
File "/usr/lib/python3/dist-packages/urllib3/request.py", line 97, in request_encode_url
return self.urlopen(method, url, **extra_kw)
File "/usr/lib/python3/dist-packages/urllib3/poolmanager.py", line 319, in urlopen
conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme)
File "/usr/lib/python3/dist-packages/urllib3/poolmanager.py", line 225, in connection_from_host
raise LocationValueError("No host specified.")
urllib3.exceptions.LocationValueError: No host specified.

@fnordomat
Copy link
Author

fnordomat commented Dec 2, 2020

f2.getheaders().get('location') is empty.

Maybe comment out lines 90, 91 - for some reason, my script is hard-coded to expect a second redirect there. Also, the headers from the request before the second redirect seem to have a 'Location' field, not 'location'. This should be made case-insensitive, in any case.

like this:

replace lines 90..93 (inclusive) with

# url = f2.get_redirect_location()
# f2 = foo.perform_get(url)

f2_headers = {k.lower(): v for k, v in f2.getheaders().items()}
url = f2_headers.get('location')

(or, even better, make it so that it works in either case, whether there is a second redirect or not ...)

@fnordomat
Copy link
Author

Actually, don't comment out 90, 91. Counting the requests in your log, the first three GET requests succeed.
Therefore, I'd just replace

url = f2.getheaders().get('location')

with

f2_headers = {k.lower(): v for k, v in f2.getheaders().items()}
url = f2_headers.get('location')

just updated gist and "official" version in repo https://github.com/fnordomat/misc/blob/master/captive-be-gone/m34.py

@humaita-github
Copy link

Hi, thank you very much for the feedback. I tested your newest script. Unfortunately, it doenst fully work yet, see error detailed output below.

However, if I comment out lines 90 and 91 in the newest script it works a little bit better (I still need to run it a few times until it succesfully authenticates).

let's go
request GET http://detectportal.firefox.com/success.txt
request headers: {'User-Agent': 'Mozilla/5.0', 'Cookie': ''}
response headers: HTTPHeaderDict({'cache-control': 'max-age=0, private, must-revalidate', 'content-length': '441', 'content-type': 'text/html; charset=utf-8', 'date': 'Wed, 02 Dec 2020 21:28:25 GMT', 'location': 'https://210.rdr.conn4.com/ident?client_ip=10.22.57.66&client_mac=0A000A163942&site_id=210&signature=d2edb1b884f2b030c67ee5fe3e4daec068b46d1c90e4e68d07a66e9feb141924&loggedin=0&remembered_mac=0', 'server': 'Cowboy'})
redirect: https://210.rdr.conn4.com/ident?client_ip=10.22.57.66&client_mac=0A000A163942&site_id=210&signature=d2edb1b884f2b030c67ee5fe3e4daec068b46d1c90e4e68d07a66e9feb141924&loggedin=0&remembered_mac=0
b'<!doctype html>\n<html>\n\n <head>\n <title>Redirecting ....</title>\n <meta name="viewport" content="width=device-width, initial-scale=1">\n <meta http-equiv="refresh" content="1; URL=https://210.rdr.conn4.com/ident?client_ip=10.22.57.66&client_mac=0A000A163942&site_id=210&signature=d2edb1b884f2b030c67ee5fe3e4daec068b46d1c90e4e68d07a66e9feb141924&loggedin=0&remembered_mac=0">\n </head>\n <body>\n Redirecting ...\n </body>\n</html>\n'
`request GET https://210.rdr.conn4.com/ident?client_ip=10.22.57.66&client_mac=0A000A163942&site_id=210&signature=d2edb1b884f2b030c67ee5fe3e4daec068b46d1c90e4e68d07a66e9feb141924&loggedin=0&remembered_mac=0` `request headers: {'User-Agent': 'Mozilla/5.0', 'Cookie': ''}` `response headers: HTTPHeaderDict({'Server': 'm3connect', 'Date': 'Wed, 02 Dec 2020 21:28:25 GMT', 'Content-Type': 'text/html; charset=UTF-8', 'Content-Length': '0', 'Connection': 'keep-alive', 'Set-Cookie': 'ngx_conn4_portal=96508d6209a17693f0379bb61abe725b; expires=Wed, 02-Dec-20 21:58:25 GMT; max-age=1800; path=/, himalaya-site-ident=SFNJKk86MzQ6Ik0zXEhpbWFsYXlhXFNoYXJlZFxTaXRlSWRlbnRcVG9rZW4iOjk6e3M6MTM6IgAqAE1BQ0FkZHJlc3MiO3M6MTI6IjBBMDAwQTE2Mzk0MiI7czoxOToiACoAZXh0ZW5kZWRMaWZldGltZSI7YjowO3M6MTI6IgAqAElQQWRkcmVzcyI7czoxMToiMTAuMjIuNTcuNjYiO3M6MTY6IgAqAHJlbW90ZUFkZHJlc3MiO3M6MTM6IjE4NS4yMDIuMzIuMjYiO3M6OToiACoAbm9kZUlkIjtOO3M6OToiACoAc2l0ZUlkIjtpOjIxMDtzOjIyOiIAKgBjb250ZW50UmVwb3NpdG9yeUlkIjtOO3M6NzoiACoAdXJscyI7YTowOnt9czoxMDoiACoAY3JlYXRlZCI7Tzo4OiJEYXRlVGltZSI6Mzp7czo0OiJkYXRlIjtzOjI2OiIyMDIwLTEyLTAyIDIxOjI4OjI1LjAwMDAwMCI7czoxMzoidGltZXpvbmVfdHlwZSI7aTozO3M6ODoidGltZXpvbmUiO3M6MzoiVVRDIjt9fXwyMmMzNmYyOWI5ZDRiYjBiNmI0YTQ2MDU3NTBiMTQwZjM1ZjM5NTFhNmZiNzc2ZGFhNjlkOTFhODJlYjUyY2Qy; expires=Wed, 02-Dec-2020 21:58:25 GMT; Max-Age=1800; path=/', 'Location': 'https://210.rdr.conn4.com/#', 'Strict-Transport-Security': 'max-age=31415926', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-DNS-Prefetch-Control': 'off'})` `redirect: https://210.rdr.conn4.com/#` `setting cookie ngx_conn4_portal to 96508d6209a17693f0379bb61abe725b` `setting cookie himalaya-site-ident to SFNJKk86MzQ6Ik0zXEhpbWFsYXlhXFNoYXJlZFxTaXRlSWRlbnRcVG9rZW4iOjk6e3M6MTM6IgAqAE1BQ0FkZHJlc3MiO3M6MTI6IjBBMDAwQTE2Mzk0MiI7czoxOToiACoAZXh0ZW5kZWRMaWZldGltZSI7YjowO3M6MTI6IgAqAElQQWRkcmVzcyI7czoxMToiMTAuMjIuNTcuNjYiO3M6MTY6IgAqAHJlbW90ZUFkZHJlc3MiO3M6MTM6IjE4NS4yMDIuMzIuMjYiO3M6OToiACoAbm9kZUlkIjtOO3M6OToiACoAc2l0ZUlkIjtpOjIxMDtzOjIyOiIAKgBjb250ZW50UmVwb3NpdG9yeUlkIjtOO3M6NzoiACoAdXJscyI7YTowOnt9czoxMDoiACoAY3JlYXRlZCI7Tzo4OiJEYXRlVGltZSI6Mzp7czo0OiJkYXRlIjtzOjI2OiIyMDIwLTEyLTAyIDIxOjI4OjI1LjAwMDAwMCI7czoxMzoidGltZXpvbmVfdHlwZSI7aTozO3M6ODoidGltZXpvbmUiO3M6MzoiVVRDIjt9fXwyMmMzNmYyOWI5ZDRiYjBiNmI0YTQ2MDU3NTBiMTQwZjM1ZjM5NTFhNmZiNzc2ZGFhNjlkOTFhODJlYjUyY2Qy` `himalaya-site-ident: b'HSI*O:34:"M3\\Himalaya\\Shared\\SiteIdent\\Token":9:{s:13:"\x00*\x00MACAddress";s:12:"0A000A163942";s:19:"\x00*\x00extendedLifetime";b:0;s:12:"\x00*\x00IPAddress";s:11:"10.22.57.66";s:16:"\x00*\x00remoteAddress";s:13:"185.202.32.26";s:9:"\x00*\x00nodeId";N;s:9:"\x00*\x00siteId";i:210;s:22:"\x00*\x00contentRepositoryId";N;s:7:"\x00*\x00urls";a:0:{}s:10:"\x00*\x00created";O:8:"DateTime":3:{s:4:"date";s:26:"2020-12-02 21:28:25.000000";s:13:"timezone_type";i:3;s:8:"timezone";s:3:"UTC";}}|22c36f29b9d4bb0b6b4a4605750b140f35f3951a6fb776daa69d91a82eb52cd2'` `b''`
request GET https://210.rdr.conn4.com/#
request headers: {'User-Agent': 'Mozilla/5.0', 'Cookie': 'ngx_conn4_portal=96508d6209a17693f0379bb61abe725b; himalaya-site-ident=SFNJKk86MzQ6Ik0zXEhpbWFsYXlhXFNoYXJlZFxTaXRlSWRlbnRcVG9rZW4iOjk6e3M6MTM6IgAqAE1BQ0FkZHJlc3MiO3M6MTI6IjBBMDAwQTE2Mzk0MiI7czoxOToiACoAZXh0ZW5kZWRMaWZldGltZSI7YjowO3M6MTI6IgAqAElQQWRkcmVzcyI7czoxMToiMTAuMjIuNTcuNjYiO3M6MTY6IgAqAHJlbW90ZUFkZHJlc3MiO3M6MTM6IjE4NS4yMDIuMzIuMjYiO3M6OToiACoAbm9kZUlkIjtOO3M6OToiACoAc2l0ZUlkIjtpOjIxMDtzOjIyOiIAKgBjb250ZW50UmVwb3NpdG9yeUlkIjtOO3M6NzoiACoAdXJscyI7YTowOnt9czoxMDoiACoAY3JlYXRlZCI7Tzo4OiJEYXRlVGltZSI6Mzp7czo0OiJkYXRlIjtzOjI2OiIyMDIwLTEyLTAyIDIxOjI4OjI1LjAwMDAwMCI7czoxMzoidGltZXpvbmVfdHlwZSI7aTozO3M6ODoidGltZXpvbmUiO3M6MzoiVVRDIjt9fXwyMmMzNmYyOWI5ZDRiYjBiNmI0YTQ2MDU3NTBiMTQwZjM1ZjM5NTFhNmZiNzc2ZGFhNjlkOTFhODJlYjUyY2Qy'}
response headers: HTTPHeaderDict({'Server': 'm3connect', 'Date': 'Wed, 02 Dec 2020 21:28:25 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': '7074', 'Connection': 'keep-alive', 'Set-Cookie': 'ngx_conn4_portal=96508d6209a17693f0379bb61abe725b; expires=Wed, 02-Dec-20 21:58:25 GMT; max-age=1800; path=/', 'Strict-Transport-Security': 'max-age=31415926', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-DNS-Prefetch-Control': 'off'})
replacing cookie ngx_conn4_portal with 96508d6209a17693f0379bb61abe725b
b'<!DOCTYPE html>\n<html xmlns="http://www.w3.org/1999/xhtml" id="no-js">\n <head>\n <script>\n var scenename = \'sceneplayer\';\n if (typeof scenename === \'undefined\') {\n var scenename = \'undefined\';\n}\n\nfunction logError(error) {\n var oReq = new XMLHttpRequest();\n oReq.open("POST", "/admon-assets/log.php?channel=clienterror", true);\n oReq.setRequestHeader(\'Content-type\', \'application/x-www-form-urlencoded\');\n oReq.send("type=" + error.type\n + "&msg=" + error.message\n + " | module=" + scenename\n + " | file=" + error.filename\n + " | line=" + error.lineno\n + " | col=" + error.colno\n );\n}\n\nwindow.addEventListener(\'error\', logError);\n </script>\n\n <script type="text/javascript">with(document.documentElement){id=id.replace(\'no-\',\'\');}</script>\n <style>body{margin:0}object{position:absolute;top:0;left:0;opacity:0;z-index:100}.fullscreen{width:100%;height:100%;position:absolute;border:0}.fade-out{opacity:0}.fade-in{opacity:1;-webkit-transition:opacity 1500ms ease-out;-moz-transition:opacity 1500ms ease-out;-o-transition:opacity 1500ms ease-out;transition:opacity 1500ms ease-out}.message{background:rgba(0,0,0,.8);position:absolute;bottom:0;left:0;right:0;z-index:1000 !important;padding:1em;text-align:center;font-size:32px;color:#fff}.hidden,#no-js #_loading_indicator,#_scene_volume,#_standby_overlay{display:none}.error{background-color:#eee;z-index:200}.error>div{width:500px;text-align:center;margin:-5em auto 0;position:relative;top:50%;font-size:24px;font-family:Helvetica,Arial,sans-serif}#_scene_content>iframe.ios-fix{width:1px;min-width:100%;*width:100%;overflow:auto;height:100%;overflow-y:scroll;-webkit-overflow-scrolling:touch}#_loading_indicator{position:fixed;z-index:10000;bottom:0;top:0;left:0;right:0}#_loading_indicator>.loading--simple{position:absolute;bottom:.6rem;right:.8rem;font-size:.9rem;padding:.5rem;border-radius:.3rem;font-family:sans-serif;color:black;background-color:rgba(255,255,255,.9)}\n</style>\n\n <style id="_scene_custom_style" type="text/css"></style>\n\n <meta http-equiv="X-UA-Compatible" content="IE=edge"/>\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n\n \n </head>\n\n <body>\n <!--\n <?xml version="1.0" encoding="UTF-8"?>\n <WISPAccessGatewayParam xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.acmewisp.com/WISPAccessGatewayParam.xsd">\n <Redirect>\n <AccessProcedure>1.0</AccessProcedure>\n <AccessLocation>12</AccessLocation>\n <LocationName>CONN4</LocationName>\n <LoginURL>https://210.rdr.conn4.com/wbs/de/roaming/return/</LoginURL>\n <MessageType>100</MessageType>\n <ResponseCode>0</ResponseCode>\n </Redirect>\n </WISPAccessGatewayParam>\n -->\n\n <noscript>\n <div class="error fullscreen">\n <div>\n <p>Please enable JavaScript to continue.</p>\n </div>\n </div>\n </noscript>\n\n <div id="_error" class="error hidden fullscreen">\n <div>\n <h1>Error <span class="js-error-code">601</span></h1>\n <p>Need assistance?</p>\n <p>Wi-Fi-Hotline: +49 (0) 241 / 705 39 605<br/>\n <span style="font-size: 50%">(at local tariff throughout Germany)</span></p>\n </div>\n </div>\n\n \n <div id="_scene_content" data-channel="">\n </div>\n\n <div id="_loading_indicator" class="loading--initial">\n <div class="loading--simple">\n Loading ...\n </div>\n </div>\n\n \n <script src="/cache/js-file-e552c72faa03fb9c5ea859701ce5304e8feb8e0f-j-a578d89915ec98c5314bf279b2b6766c.js" type="text/javascript"></script>\n <script type="text/javascript">\n (function() {\n if (!location.search) {\n return;\n }\n\n var url = location.href.replace(location.search, \'\');\n if (history && history.replaceState) {\n history.replaceState(history.state || {}, document.title, url);\n } else {\n location.href = url;\n }\n })();\n </script>\n <script type="text/javascript">\n conn4.deviceConfiguration = false;\n\n conn4.log = $.noop;\n \n// conn4.recorder = new conn4.LogRecorder({log: conn4.log});\n// conn4.record = conn4.recorder.record;\n conn4.record = function() {};\n\n conn4.eventTracker = new conn4.EventTracker({\n logger: conn4.logger\n });\n conn4.trackEvent = conn4.eventTracker.track;\n \n conn4.serverdate = new conn4.ServerDate({\n initializationTs: 1606944505621,\n log: conn4.log\n });\n var ServerDate = function () {\n return new Date(ServerDate.now());\n };\n ServerDate.now = function() {\n return conn4.serverdate.getMilliseconds();\n };\n\n \n \n conn4.hotspot = conn4.hotspot || {};\n conn4.hotspot.wbsToken = {"token":"SFdBKk86MzU6Ik0zXEhpbWFsYXlhXFNoYXJlZFxXQlNBcGlBdXRoXFRva2VuIjo1OntzOjk6IgAqAHNpdGVJZCI7aToyMTA7czoxNjoiACoAcmVtb3RlQWRkcmVzcyI7czoxMToiMTAuMjIuNTcuNjYiO3M6MTM6IgAqAG1hY0FkZHJlc3MiO3M6MTI6IjBBMDAwQTE2Mzk0MiI7czoxMDoiACoAY3JlYXRlZCI7Tzo4OiJEYXRlVGltZSI6Mzp7czo0OiJkYXRlIjtzOjI2OiIyMDIwLTEyLTAyIDIxOjI4OjI1LjAwMDAwMCI7czoxMzoidGltZXpvbmVfdHlwZSI7aTozO3M6ODoidGltZXpvbmUiO3M6MzoiVVRDIjt9czo5OiIAKgBvcmlnaW4iO3M6MjU6Imh0dHBzOi8vMjEwLnJkci5jb25uNC5jb20iO318NmRmZDk4OWU0OGVmYjA3ZGU3MWVkZmYxYWUzMzdkYjRlOTQ4OWFkZWZjOTUyODU0MDE5NWE0MjJmYTYxMTJiZA==","urls":{"grant_url":null,"continue_url":null}};\n setTimeout(function() {\n conn4.hotspot.wbsToken = undefined;\n }, 118 * 60 * 1000); \n $(_.partial(conn4.startSceneLoader, {\n log: conn4.log,\n trackEvent: conn4.trackEvent,\n settings: {"sceneLoaderAssertLoaded":true,"sceneLoaderTimeoutLimit":2}\n,\n schedule: {"token":{"site":210,"group":"desktop","mac":"0A000A163942"},"site_title":"","url_version":1,"scene_template":"https:\\/\\/210.rdr.conn4.com\\/scenes\\/{id}\\/","fallback":"https:\\/\\/210.rdr.conn4.com\\/admon-assets\\/fallback.html","error_404":"https:\\/\\/210.rdr.conn4.com\\/admon\\/404","initialization_ts":1606944505,"events":[{"time":"1984-11-17 00:00:00","seq":100,"type":"view","payload":{"type":"scene","data":{"id":"5jx9ZrGe72eI0l4n","module":"html-page-scene-wbs-new","variant":"default"}}},{"time":"1984-11-17 00:00:00","seq":200,"type":"campaign","payload":null}],"playlists":[],"navigations":[]}\n }));\n </script>\n </body>\n</html>\n'
``
request GET None
`request headers: {'User-Agent': 'Mozilla/5.0', 'Cookie': 'ngx_conn4_portal=96508d6209a17693f0379bb61abe725b; himalaya-site-ident=SFNJKk86MzQ6Ik0zXEhpbWFsYXlhXFNoYXJlZFxTaXRlSWRlbnRcVG9rZW4iOjk6e3M6MTM6IgAqAE1BQ0FkZHJlc3MiO3M6MTI6IjBBMDAwQTE2Mzk0MiI7czoxOToiACoAZXh0ZW5kZWRMaWZldGltZSI7YjowO3M6MTI6IgAqAElQQWRkcmVzcyI7czoxMToiMTAuMjIuNTcuNjYiO3M6MTY6IgAqAHJlbW90ZUFkZHJlc3MiO3M6MTM6IjE4NS4yMDIuMzIuMjYiO3M6OToiACoAbm9kZUlkIjtOO3M6OToiACoAc2l0ZUlkIjtpOjIxMDtzOjIyOiIAKgBjb250ZW50UmVwb3NpdG9yeUlkIjtOO3M6NzoiACoAdXJscyI7YTowOnt9czoxMDoiACoAY3JlYXRlZCI7Tzo4OiJEYXRlVGltZSI6Mzp7czo0OiJkYXRlIjtzOjI2OiIyMDIwLTEyLTAyIDIxOjI4OjI1LjAwMDAwMCI7czoxMzoidGltZXpvbmVfdHlwZSI7aTozO3M6ODoidGltZXpvbmUiO3M6MzoiVVRDIjt9fXwyMmMzNmYyOWI5ZDRiYjBiNmI0YTQ2MDU3NTBiMTQwZjM1ZjM5NTFhNmZiNzc2ZGFhNjlkOTFhODJlYjUyY2Qy'}`
`Traceback (most recent call last):`
` File "./m34.py", line 95, in `
` f3 = foo.perform_get(url)`
` File "./m34.py", line 76, in perform_get`
` f = pool.request(method='GET', url=url, headers=headers, redirect=redirect)`
` File "/usr/lib/python3/dist-packages/urllib3/request.py", line 75, in request`
` return self.request_encode_url(`
` File "/usr/lib/python3/dist-packages/urllib3/request.py", line 97, in request_encode_url`
` return self.urlopen(method, url, **extra_kw)`
` File "/usr/lib/python3/dist-packages/urllib3/poolmanager.py", line 319, in urlopen`
` conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme)`
` File "/usr/lib/python3/dist-packages/urllib3/poolmanager.py", line 225, in connection_from_host`
` raise LocationValueError("No host specified.")`
`urllib3.exceptions.LocationValueError: No host specified.`

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